Fix incomplete type traversals#4155
Conversation
|
@kl0tl @natefaubion I've gotten myself into a bit of a pickle here, and it's at the intersection of Coercible and polykinds. There's one golden test that fails to match as a consequence of adding a skolem's kind to the type traversals. In that test, a Is it true, then, that the original error message is the correct one to show? If so, how should we go about ignoring the kind unknowns? (If all kind information is irrelevant in |
The interaction solver only unifies kinds in order to emit KindsDoNotUnify errors instead of NoInstanceFound errors when solving heterogeneously kinded Coercible contraints, so annotating kinds to instantiate unknowns in Coercible constraints should never resolve NoInstanceFound errors.
I don’t know if skolem kinds were purposefuly ignored, but I think we can erase skolems kinds and kinds applications when computing whether insoluble Coercible constraints contain unknowns. That would get rid of the “The instance head contains unknown type variables. Consider adding a type annotation.” part of the error message.
We could avoid listing “invisible” unknowns by erasing skolem kinds in insoluble Coercible constraints arguments and in ErrorSolvingConstraint hints, while stripping redundants hints, and since we erase kind applications when pretty printing types unless --verbose-errors is enabled I think we should erase them when simplifying error messages. Also, do we hide kinds arguments of constraints on purpose? Taking the failing CoercibleRepresentational test as example, should we display No type class instance was found for
- Prim.Coerce.Coercible a2
+ Prim.Coerce.Coercible @t4 a2
b3with --verbose-errors, and while solving type class constraint
- Prim.Coerce.Coercible (Phantom @t0 a1)
+ Prim.Coerce.Coercible @Type (Phantom @t0 a1)
(Phantom @t2 b3)in hints, regardless of the flag? |
purescript/src/Language/PureScript/Errors.hs Line 1495 in 2b9c7a6 Edit: The first behavior is similarly connected to the second wildcard in purescript/src/Language/PureScript/Errors.hs Line 884 in 2b9c7a6 |
cff6e8c to
6aaee0e
Compare
rhendric
left a comment
There was a problem hiding this comment.
This is now in a state where everything passes and I'm mostly happy with what I had to do to get there. The three unexpected consequences of fixing these traversals are noted in review comments (as well as in code comments, where appropriate).
| Skolem ann name _ i sc -> | ||
| Skolem ann name Nothing i sc |
There was a problem hiding this comment.
Unexpected consequence #1: Removing kinds from skolems when erasing kind applications. This field is really just another form of kind annotation, and so it seems of a piece with the rest of what this function does—I'm 100% not conflicted about this.
| -- We intentionally remove the kinds from skolems, because they are never | ||
| -- presented when pretty-printing. Any unknowns in those kinds shouldn't | ||
| -- appear in the list of unknowns unless used somewhere else. | ||
| replaceTypes (Skolem ann name _ s sko) = do | ||
| m <- get | ||
| case M.lookup s (umSkolemMap m) of | ||
| Nothing -> do | ||
| let s' = umNextIndex m | ||
| put $ m { umSkolemMap = M.insert s (T.unpack name, s', Just (fst ann)) (umSkolemMap m), umNextIndex = s' + 1 } | ||
| return (Skolem ann name mbK s' sko) | ||
| Just (_, s', _) -> return (Skolem ann name mbK s' sko) | ||
| return (Skolem ann name Nothing s' sko) | ||
| Just (_, s', _) -> return (Skolem ann name Nothing s' sko) |
There was a problem hiding this comment.
Unexpected consequence #2: Also removing kinds from skolems when traversing a type looking for unknowns to report in an error message. As the comment says, this is because we never print the kinds from skolems; we drop them on the ground when converting them to PrettyPrintType.
This seems slightly less principled but still fairly practical; I feel 90% good about this. Only caveat is we might need to change this back if we do decide to print the kinds from skolems under some conditions (like when --verbose-errors is in use).
47bf081 to
bceab35
Compare
| -- | Check if a type contains unknowns in a position that is relevant to | ||
| -- constraint solving. (Kinds are not.) | ||
| containsUnknowns :: Type a -> Bool | ||
| containsUnknowns = everythingOnTypes (||) go where | ||
| containsUnknowns = everythingOnTypes (||) go . eraseKindApps where |
There was a problem hiding this comment.
Unexpected consequence #3 (take two): ignoring unknowns in kinds when deciding whether to show the unknown types hint in NoInstanceFound errors. Previously, I thought this would only apply to Coercible, but after merging the apartness checker fixes in #4149, it seems that unknowns need to be stripped from kinds when solving all constraints. I'm no expert on polykinds, but it would make sense to me that kind applications and annotations in a constraint can't affect how the constraint is solved. My confidence here is down to something like 60%—it's marginally my best guess at the right thing to do, but I think we need some confirmation from additional brains; I don't claim to fully understand how polymorphic class kinds can interact with entailment. (@natefaubion?)
There was a problem hiding this comment.
My take is that non-ambiguous kind polymorphism on classes are equivalent to adding extra arguments to the class with fundeps pointing from the type to the kind. That is:
class Wat :: forall k. k -> Constraint
class Wat okWould be kind of like saying:
class Wat k ok | ok -> kfor the purposes of dispatch.
In the case of ambiguous kind variables, it's not really any different from a normal parameter, it would just be provided through explicit kind applications.
I'm not sure how this affects your interpretation of "kind applications and kind annotations in a constraint can't affect how the constraint is solved".
There was a problem hiding this comment.
The kind-of-like-fundeps are the thing I'm asking about here; so if ok is known but k is unknown, and no instance of Wat ok is found, it's not for lack of type annotations, because k is determined by ok. And from that, I conclude that it's correct to strip out kinds from types before checking those types for unknowns when deciding whether to show the ‘consider adding type annotations’ hint.
There was a problem hiding this comment.
So... is this a safe change to make?
There was a problem hiding this comment.
I stand by my above comments. More to the point of safety, though, this is a change to a function that is only ever used to determine whether or not to display a particular hint in an error message; the harm of getting this wrong is minimal.
bceab35 to
bba194f
Compare
bba194f to
2c71381
Compare
|
@rhendric I just made an attempt at #1647 and the approach I am taking appears to work, although I have stumbled due to missing cases in |
|
Mind sharing your branch? |
|
👍 I'll make a PR |
|
See #4337. Come to think of it, maybe |
|
I didn't see any new property-based tests in #4337, so I'm still not 100% sure I'm following you, but I think
|
|
Oh, sorry, I don't yet have anything that tries to verify that |
Description of the change
Our type traversals were ignoring the optional kind in
Skolemtypes; assuming that wasn't intentional, this fixes it. It also fixes a double application of the traversing function on non-recursive constructors ineverywhereOnTypesTopDownM. I'm not aware of any user-facing errors caused by these issues.Both of these issues are caught by the new tests, which involve an amount of QuickCheck machinery rather disproportionate to the fixes themselves, but our traversals have had other issues in the past and this seems like a prudent precaution.
Checklist: