| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
|
@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 NoInstanceFound error is raised due to a Coercible constraint not being solved. The constraint contains an unknown kind in a skolem, but otherwise doesn't contain unknowns. So the test output is now changed in two ways: first, it reports that there are unknown types in the instance head, and second, it lists a new unknown type. But, at least with the invisible kind applications that are the default in error messages, the unknown type never appears in the rest of the message, which is clearly not good. And I'm also not sure that it's good that an unknown in the kind of a skolem should trigger the ‘unknown type in instance head’ hint—can kinds have an effect on Coercible solving? 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 Coercible solving, maybe we should suppress unknowns in KindApps as well? Or was there actually intent behind leaving skolem kinds untraversed?) And how should ignoring those unknowns interact with whether --verbose-errors is in use? |
Sorry, something went wrong.
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? |
Sorry, something went wrong.
The first behavior I think we have already (Edit: nope, see below); the second is due to not doing anything with the second wildcard in 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 |
Sorry, something went wrong.
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).
Sorry, something went wrong.
| 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.
Sorry, something went wrong.
| -- 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).
Sorry, something went wrong.
| -- | 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?)
Sorry, something went wrong.
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".
Sorry, something went wrong.
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.
Sorry, something went wrong.
There was a problem hiding this comment.
So... is this a safe change to make?
Sorry, something went wrong.
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.
Sorry, something went wrong.
|
@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 onTypesInErrorMessage, in a similar vein to this. The subterms trick won't work here unfortunately; do you have any other suggestions? I'm tempted to pull in a syb dependency in the tests and just compare against everywhere. |
Sorry, something went wrong.
|
Mind sharing your branch? subterms is just the shrinking strategy here, so even if that doesn't work (I imagine recursivelyShrink would be better) you could still have a functional test with forAll instead of forAllShrink. |
Sorry, something went wrong.
|
👍 I'll make a PR |
Sorry, something went wrong.
|
See #4337. Come to think of it, maybe everywhere isn't really what I'm after, I think I might just want gmapT, because I don't want to visit every Type node in this case, I just want to visit the Type values that are immediate children of the ErrorMessageHint and SimpleErrorMessage constructors. |
Sorry, something went wrong.
|
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 gmapQ is actually the thing to use here, assuming a test that does roughly this:
|
Sorry, something went wrong.
|
Oh, sorry, I don't yet have anything that tries to verify that onTypesInErrorMessage is hitting all of the cases it should be hitting. Thanks, I'll give that a try! |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Description of the change
Our type traversals were ignoring the optional kind in Skolem types; assuming that wasn't intentional, this fixes it. It also fixes a double application of the traversing function on non-recursive constructors in everywhereOnTypesTopDownM. 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: