Interaction solver for Coercible constraints#3955
Interaction solver for Coercible constraints#3955hdgarrood merged 3 commits intopurescript:masterfrom
Conversation
55c1c76 to
5405332
Compare
|
At first glance this looks great - the code looks good, especially the comments, and the improvements to the error messages are fantastic. Really nice work! I'm not sure if I am going to be able to find the time to review this properly soon, and this does seem like something we'd want to include in 0.14, so I might say that if, say, a week has passed and there are no outstanding issues we're aware of, then I'll approve and we can merge this and cut a new RC? |
|
This sounds good to me! Would you prefer that I integrate #3927 into this pull request or to review it separately once this is merged ? I’ve rebased on top of this locally and the diff much nicer! It brings yet another small improvement to error messages that I’d like to ship with the v0.14.0. Here’s the changes to the Error found:
in module [33mMain[0m
at tests/purs/failing/CoercibleRepresentational6.purs:8:10 - 8:16 (line 8, column 10 - line 8, column 16)
- No type class instance was found for
-
- Prim.Coerce.Coercible (N a0)
- a0
-
+ The newtype constructor [33mN[0m is not in scope, it should be imported to allow coercions to and from its representation.
+
+while solving type class constraint
+[33m [0m
+[33m Prim.Coerce.Coercible (N a0)[0m
+[33m a0 [0m
+[33m [0m
while checking that type [33mforall (a :: Type) (b :: Type). Coercible @Type a b => a -> b[0m
is at least as general as type [33mN a0 -> a0[0m
while checking that expression [33mcoerce[0m
has type [33mN a0 -> a0[0m
in value declaration [33munwrap[0m
where [33ma0[0m is a rigid type variable
bound at (line 8, column 10 - line 8, column 16)
See https://github.com/purescript/documentation/blob/master/errors/CannotUnwrapHiddenNewtypeConstructor.md for more information,
or to contribute content related to this error. Error found:
in module [33mMain[0m
at tests/purs/failing/CoercibleRepresentational7.purs:8:10 - 8:16 (line 8, column 10 - line 8, column 16)
- No type class instance was found for
-
- Prim.Coerce.Coercible (N a0)
- a0
-
+ The newtype constructor [33mN[0m is not in scope, it should be imported to allow coercions to and from its representation.
+
+while solving type class constraint
+[33m [0m
+[33m Prim.Coerce.Coercible (N a0)[0m
+[33m a0 [0m
+[33m [0m
while checking that type [33mforall (a :: Type) (b :: Type). Coercible @Type a b => a -> b[0m
is at least as general as type [33mN a0 -> a0[0m
while checking that expression [33mcoerce[0m
has type [33mN a0 -> a0[0m
in value declaration [33munwrap[0m
where [33ma0[0m is a rigid type variable
bound at (line 8, column 10 - line 8, column 16)
See https://github.com/purescript/documentation/blob/master/errors/CannotUnwrapHiddenNewtypeConstructor.md for more information,
or to contribute content related to this error.Also I discovered a bug while testing this on the WIP v0.14.0 package set, with purescript/purescript-newtype#22 and some compiler support (this is a good way to test a wide variety of coercions ^^): I have thought of three ways to fix this:
Do you have any advice, or can you think of some better alternative? It seems to me that whatever choice we make here we should keep in mind the second point you listed in #3724 (comment). Perhaps if this is straightforward enough we should rather do this? I don’t understand the implications to be honest but I can try with some guidance. |
|
There’s maybe a better way to do this but in the meantime I went with an alternative to 1 and 2: instead of storing the whole names environment in the typechecker monad state or resolving re-exports we can just annotate the current module imports with the types exported from those modules. |
This pull request implements an interaction solver for
Coercibleconstraints based on the section 5.2 An overview of OUTSIDEIN(X) of the Safe Zero-cost Coercions for Haskell paper, the section 7.4 Rewriting constraints of the Modular type inference with local assumptions paper and GHC implementation. This allowsCoercibleto obey transitivity in a much better way than I initially tried with #3930.Instead of solving
Coercibleconstraints by computing subgoals and recursively solving them like ordinary constraints we keep rewriting each constraints then interacting them with givens until they become irreducible or we encounter an error. Solving fails when there’s some irreducible wanted constraints left.We also solve givens beforehand in order to deduce more equalities and solve more wanteds. For instance the declarations:
yield a given
Coercible (D a) (D b)from which can deduce a constraintCoercible a bthat we can use to discharge the wanted.Other improvements
This leads to more precise error messages because we can always mention the original constraint in
ErrorSolvingConstrainthints (see the changes to the golden tests) and enables some other improvements:We can throw
TypesDoNotUnifyerrors on nominal parameters mismatches in the decomposition rule.Given the following example:
we can now report:
instead of:
Throwing
TypesDoNotUnifyerrors before would have report:We can relax the nominal equality constraint between rows tails to a representational one.
Given the following declaration:
we used to only accept closed rows:
or open rows whose tails unify:
but now we can also accept open rows whose tails are coercible to each others:
The new solver also follows the paper more accurately on newtypes:
We unwrap newtypes first.
The paper doesn’t explain why but there’s a note about this in GHC (see “Unwrap newtypes first” in
GHC.Tc.Solver.Canonical). Given the following declaration:decomposing
Coercible (N Maybe a) (N Maybe b)would otherwise fail because the second parameter ofNis nominal. On the other hand, unwraping on both sides yieldsCoercible (Maybe a) (Maybe b)which we can then decompose toCoercible a band eventually solve ifais a newtype overbfor instance.We only unwrap finite chains of newtypes.
Given the following declarations:
trying to solve
Coercible (N a) (N b)would otherwise yield aPossiblyInfiniteCoercibleInstanceerror although we can decompose toCoercible a band discharge that constraint with an appropriate given.Bugfixes
I fixed some bugs I introduced in #3893:
Polymorphic kind variables should be instantiated when saturating higher kinded types, either to the kinds explicitely provided via kind applications or to unknowns.
Given the following declarations:
naively saturating
Dwhile solvingCoercible (D @k1) (D @k2)yieldsCoercible (D (t0 :: k)) (D (t0 :: k))instead ofCoercible (D (t0 :: k1)) (D (t0 :: k2))andkis not bound anywhere.Kind applications can mention unknowns, so we should apply the current substitution to
Coercibleconstraints parameters once we unified their kinds. This is what I mentioned in #3893 (comment) but did not understand how to observe before.Coercible (D D) (D D)actually meansCoercible (D @(k1 -> Type) (D @k1)) (D @(k2 -> Type) (D @k2)), which we decompose toCoercible (D @k1) (D @k2), wherek1andk2are unknowns. This constraint is not reflexive becauseD @k1andD @k2are differents but both arguments kinds unify withk -> Type, wherekis a fresh unknown, so applying the substitution toD @k1andD @k2yields aCoercible (D @k) (D @k)constraint which could be trivially solved by reflexivity instead of having to saturate the type constructors.Future work
Polytypes.
The paper mention polytypes in section 4.2 Coercions but there are omitted from section 5.2 An overview of OUTSIDEIN(X) “due to the complexities they add to the algorithm”.
They seem to be canonicalized with
can_eq_nc_forallinGHC.Tc.Solver.Canonicalbut the comment there would led me to believe that GHC would be able to compile the following:{-# LANGUAGE RankNTypes #-} module Example where data D a = D a example :: (forall a. D a) -> (forall b. D b) example = coerceor if
(forall a. D a) -> (forall b. D b)is interpreted asforall b. (forall a. D a) -> D b:{-# LANGUAGE RankNTypes #-} module Example where data D a = D a newtype N = N (forall a. D a) example :: (forall a. D a) -> N example = coercebut both examples fail with
and
I’m definitively missing something here but our current solver does not support polytypes either so I think we can safely omit them for now.
Nominal equalities.
The interaction solver only works with representational equalities for now but we could extend it to support nominal equalities in the future. We could then compute given nominal equalities from functional dependencies of given constraints when solving representational and nominal equalities, and emit nominal equalities on unification errors!