-
Notifications
You must be signed in to change notification settings - Fork 571
Suggest to export newtype constructors when the unwrapping rule fails #3927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggest to export newtype constructors when the unwrapping rule fails #3927
Conversation
7371d47 to
f654cb6
Compare
|
This isn't related to this PR, but what happens if a constructor is imported and only used to permit a coercion? Do we still emit an unused import warning? |
|
Newtype unwrapping is possible as long as the newtype constructor is exported, unlike Haskell it doesn‘t have to be in scope, so there’s no need to import a constructor only to allow some coercions. |
|
I'm not sure that approach is safe: it's quite common to define a newtype in a |
f654cb6 to
37030af
Compare
37030af to
0e76a88
Compare
|
I’ve rebased this pull request on top of #3937. There’s now a |
|
@kl0tl do you think we should try to get this into 0.14.0? I think it would be good if we can. Would you mind resolving the conflicts when you get a chance? |
0e76a88 to
158c7ba
Compare
|
I’ve rebased the pull request! The diff should be much nicer. I also added a DataDeclType field to the DataType constructor of TypeKind, so that a newtype constructor doesn’t have to be exported for us to know that it is a newtype. This allows us to get rid of the CannotFindCoercedCtor error and only throw CannotUnwrapHiddenNewtypeConstructor errors when we know that the failure is really due to an out of scope newtype constructor. |
|
What do you think of CannotUnwrapHiddenNewtypeConstructor for the error? The constructor isn’t necessarily hidden after all, it might just not be imported. Perhaps CannotUnwrapOutOfScopeNewtypeConstructor would be more appropriate? |
src/Language/PureScript/Errors.hs
Outdated
| UnsupportedRoleDeclaration {} -> "UnsupportedRoleDeclaration" | ||
| RoleDeclarationArityMismatch {} -> "RoleDeclarationArityMismatch" | ||
| DuplicateRoleDeclaration {} -> "DuplicateRoleDeclaration" | ||
| CannotUnwrapHiddenNewtypeConstructor {} -> "CannotUnwrapHiddenNewtypeConstructor" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you explicitly invited bikeshedding, how about MissingConstructorImportForCoercible? It's an issue for both wrapping and unwrapping, and the error names seem to prefer describing a thing to be corrected over a thing the compiler failed to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that’s better, thank you for the suggestion!
src/Language/PureScript/Errors.hs
Outdated
| line $ "Duplicate role declaration for " <> markCode (runProperName name) <> "." | ||
|
|
||
| renderSimpleErrorMessage (CannotUnwrapHiddenNewtypeConstructor name) = | ||
| line $ "The newtype constructor " <> markCode (showQualified runProperName name) <> " is not in scope, it should be imported to allow coercions to and from its representation." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency with the other error messages, I think there should be a sentence break and line break between ‘... is not in scope’ and ‘it should be imported...’.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I turned “It should be imported to allow coercions to and from its representation” into its own sentence, on a new line.
8b5d427 to
4c6f9d4
Compare
thomashoneyman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 from me, though this deserves a look from someone more familiar with the code as well. Thanks!
|
I actually feel like this should be a NoInstanceFound error rather than a separate code; the error is that the compiler can’t prove that it’s safe to coerce between the two types in question, which in PureScript is represented by the lack of a Coercible instance. I think it could be confusing that this error looks quite different from other NoInstanceFound errors which arise as a result of trying to coerce two types which aren’t Coercible. I also have a slight worry with the wording of the error message, because to me the message sounds quite confident that the correct course of action is to import the constructor. In practice, if you receive this error, the answer may instead be “don’t do that”. For example, if you were trying to coerce an Array to a NonEmptyArray. I think a reminder that having the newtype constructor in scope will silence this error is warranted, but it should be secondary to the main piece of information, which is “I can’t prove that a coercion from type X to type Y is safe.” |
|
Would you prefer that we add the MissingConstructorImportForCoercible constructor to ErrorMessageHint instead and accumulate hints in solveWanteds, so that the following: module Example.N where
newtype N a = N amodule Example where
import Example.N (N)
import Safe.Coerce (coerce)
example :: forall a. a -> N a
example = coerceyields ? |
|
That sounds more appealing to me, yes. I also think it would be good to make the connection between the constructor being in scope and the coercion being possible slightly more clear in the hint; perhaps something along the lines of "Performing this coercion requires the data constructor N to be in scope"? |
Given
newtype N a = N a, the newtype constructor should be exported forCoercible N aandCoercible a Nconstraints to be solvables. This pull request adds a specific error for when we fail to find a newtype constructor in the environment rather than the unhelpfulNoInstanceFounderror currently thrown.The
CannotFindCoercedConstructorerror is thrown for both data and newtype constructors because I didn’t find how to differentiate them when they aren’t exported, did I miss something?