-
Notifications
You must be signed in to change notification settings - Fork 1.1k
WIP: Constraint-based GADT reasoning #5258
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
Conversation
161693e
to
5195cab
Compare
So, turns out that remembering to substitute TypeParams with TypeVars in the bounds is quite important for correctness.
Continuing to poke at #4471, it produces the most complex constraints so far. Some additional changes are required to make the example compile, but it still fails to infer one final constraint necessary to typecheck the code. |
test performance please |
performance test scheduled: 5 job(s) in queue, 1 running. |
Performance test finished successfully: Visit http://dotty-bench.epfl.ch/5258/ to see the changes. Benchmarks is based on merging with master (45eb6aa) |
test performance please |
performance test scheduled: 1 job(s) in queue, 0 running. |
Performance test finished successfully: Visit http://dotty-bench.epfl.ch/5258/ to see the changes. Benchmarks is based on merging with master (c0941f3) |
Performance test finished successfully: Visit http://dotty-bench.epfl.ch/5258/ to see the changes. Benchmarks is based on merging with master (180c8df) |
Answering on #5300 (comment), as a summary of what was discussed today. Since opaque types behave like abstract types, the code below requires opaque type Pos = Int
object Pos {
def mkPos(i: Int): Pos = {
require(i > 0)
i
}
def coerce[F[_]](fa: F[Int]): F[Pos] = fa
}
sealed trait Expr[T]
final case class PosExpr(p: Pos) extends Expr[Pos]
final case class IntExpr(i: Int) extends Expr[Int]
final case class StringExpr() extends Expr[String]
def eval(e: Expr[Pos]): Pos = e match {
case PosExpr(p) => p
case IntExpr(_) => Pos.mkPos(1)
case StringExpr() => Pos.mkPos(2)
} For abstract types that's already implemented, after a discussion in #3645 where people initially thought roles would be necessary. The solution to that problem appears noncontroversial. What's more, the original question was already on the (manual) encoding of opaque types! |
Also for the record:
|
@Blaisorblade I think this is not the PR you want 😄 EDIT - ok, I think I see why you commented here. I think it would be best to limit this PR to discussion about adding unification to GADT solving, not to GADTs in general. I'll open a separate issue for exhaustivity warnings, and a PR to add the test. |
fac4066
to
20dc62d
Compare
Note for anyone curious: the performance comparison is between:
I had to revert 756cd0a - due to some black magic this breaks tests when combined with weak conformance changes, but not on its own. Will need to investigate further. |
Performance test finished successfully: Visit http://dotty-bench.epfl.ch/5258/ to see the changes. Benchmarks is based on merging with master (2d23cba) |
20dc62d
to
bee8173
Compare
This reverts commit 756cd0a.
Turns out this is quite important for correctness. Tests now pass with -Y-no-deep-subtypes, so GadtTests was simplified accordingly. GADTMap.setBound accepts a single type bound instead of TypeBounds to simplify the implementation. NatsVects tests was rewritten, as class parameters are known to not work with GADTs.
bee8173
to
a80a2ca
Compare
a80a2ca
to
c015381
Compare
TypeComparer.explain is easier to just drop where necessary. Also, debugging an ExplainingTypeComparer calls .toString, which destroyed the stack trace previously.
Since GADTMap is re-initialised each time it is used, it's not necessary to care about avoiding instantiations for shared TypeVars.
Instead, record semi-instantiations in the constraint. Otherwise, since the TypeVars live long enough, the _second_ case of a nested pattern match would see bounds recorded by the _first_ one.
Apparently it was fixed along the way.
Parameterised ConstraintHandling on what context it needs. TypeComparer works by being manually cloned in Context#typeComparer whenever Context notices that TypeComparer#ctx is different from itself. Unlike TypeComparer, SmartGADTMap keeps its own internal variables, so we cannot apply the same trick.
Makes the code cleaner and does not allocate for trivial cases.
c015381
to
bc92ce8
Compare
@odersky @smarter FWIW I think this is best reviewed as a whole (Github shows commits in a non-existant order anyway due to rebases). One commit which is worth seeing alone is f008bbc - I'm not entirely certain this is the best fix and I'll revert it if you think it's bad. The code for HKTs remains unsound (see 3084bf9 ), but this will be tracked as a separate issue. That's also the reason for keeping 756cd0a reverted. |
test performance please |
performance test scheduled: 1 job(s) in queue, 1 running. |
Performance test finished successfully: Visit http://dotty-bench.epfl.ch/5258/ to see the changes. Benchmarks is based on merging with master (052c3b1) |
Working on #5405 made me consider why
Overall, we might still want to do it, but for the time being it seems best to me to just merge this PR as-is, work with it for a bit and only consider unifying GADT & Typer state afterwards. |
Closing in favour of #5611. |
This is the current state of the "smart" GADT Map, written by re-using
ConstraintHandling
. An example of code that did not compile previously and now does:All old tests still pass. Test suite as a whole fails, since no new code compiles with no-deep-subtypes flag. The implementation is a bit hacky as well.
Since all the new examples fail deep-subtypes, it's likely this approach will ultimately not work. We will probably need after all to keep only a single
Constraint
and allow freezing only a part of it.