-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #7044: Approximate GADT bounds to avoid explicit type ascriptions #8728
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
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.
Hello, and thank you for opening this PR! 🎉
All contributors have signed the CLA, thank you! ❤️
Commit Messages
We want to keep history, but for that to actually be useful we have
some rules on how to format our commit messages (relevant xkcd).
Please stick to these guidelines for commit messages:
- Separate subject from body with a blank line
- When fixing an issue, start your commit message with
Fix #<ISSUE-NBR>:
- Limit the subject line to 72 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line ("Add" instead of "Added")
- Wrap the body at 80 characters
- Use the body to explain what and why vs. how
adapted from https://chris.beams.io/posts/git-commit
Have an awesome day! ☀️
@@ -25,19 +25,6 @@ object test3 { | |||
} | |||
} | |||
|
|||
// Example contributed by Jason. |
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.
This test has been removed as we discussed with @AleksanderBG and we think it is correct for it to actually compile.
My reasoning is following:
we have y: Tree[S, T]
where Nothing <: S
, Option[Nothing] <: Option[S] <: T
we ascribe y: Tree[Nothing, Option[Nothing]]
- we can do that because:
Nothing <: S
,Option[Nothing] <: T
soTree[S, T] <: Tree[Nothing, Option[Nothing]]
(becauseTree
is contravariant in both its parameters)
we want y to conform to Tree[A,B]
where B <: Option[N]
, as Option[Nothing] <: Option[N]
, it typechecks
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.
Shouldn't this be re added as a pos test?
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.
Indeed, I have added it.
321dbb0
to
072a7a0
Compare
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.
Looks good! Thanks for you work, Radek.
Reg. CI failing: could you try rebasing on newest master? I ran dotty-boostrapped
clean
and test
on my machine and everything succeeded, so if the error was something that was on master, as you wrote to me, then rebase should fix it.
Reg. tuples: I took a look at Paolo's code, the following version works:
scala> {
| object GadtAscription {
| enum Var[G, A] {
| case Z[G, A]() extends Var[(A, G), A]
| case S[G, A, B](x: Var[G, A]) extends Var[(B, G), A]
| }
|
| import Var._
| def evalVar[G, A](x: Var[G, A])(rho: G): A = x match {
| case _: Z[g, a] =>
| rho(0)
| case s: S[g, a, b] =>
| evalVar(s.x)(rho(1))
| }
| }
| }
approximated G ~~ (a, g)
[AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class *:),List(TypeRef(NoPrefix,type a), AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class *:),List(TypeRef(NoPrefix,type g), TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Unit)))))]
approximated G ~~ (b, g)
[AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class *:),List(TypeRef(NoPrefix,type b), AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class *:),List(TypeRef(NoPrefix,type g), TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Unit)))))]
// defined object GadtAscription
It'd be good to add it as a test. You can also see that sth weird is going on w/ GADT bounds if you take a closer look at the .toString
-s of approximated types: they contain essentially a *: g *: Unit
, which is our list-like tuple representation. Where these come from, I do not fully know, but it's my understanding that tuple(n)
is going to be the normal way of selecting tuple members going forward, so it's fine if ._1
doesn't work.
res | ||
} | ||
|
||
private class IsFullyDefinedAccumulator2(implicit ctx: Context) extends TypeMap { |
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.
This class should probably be renamed, to something like ApproximateGadtAccumulator
perhaps? Also it would be nice to add a comment stating it's mostly copied from IsFullyDefinedAccumulator
.
val res = | ||
ctx.gadt.approximation(sym, fromBelow = variance < 0) | ||
|
||
debug.println(i"approximated $tp ~~ $res") |
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.
Overally feedback re. debug stmts (I know that most of these are leftovers from me :P):
- My general impression is that using
debug
Printer is to be avoided in master-branch code.debug
is mostly used for, as name suggests, quick debugging stmts and we usually want only the stmts we just added to print, not the ones half a codebase away. For this message, I'd use thegadt
printer. - At a glance, there's too much debugging stmts remaining. As a first approximation, I'd say only this statement should be left, but since you were the one to work on this code most recently, I'll leave it to you to decide if other stmts should also be left, for the convenience of future debugging.
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.
Thanks, I have left this one and another one that displays some information from the GADT context, as I think it was most useful for debugging. I moved both to the gadts
printer.
072a7a0
to
8d8a362
Compare
8d8a362
to
7f262a1
Compare
For some reason there's the following error in test_bootstrapped
|
Regarding the previous comment, it was my mistake - the And it is indeed related to the GADT approximation somehow, because when I disabled it, the test is back to normal. I will try investigating further what is the cause of that failure. An easy way to reproduce the error is to run |
7f262a1
to
9941c2a
Compare
val IsDummyTree = new Property.Key[Unit] | ||
|
||
def apply(tp: Type)(implicit src: SourceFile): Tree = | ||
(untpd.Literal(Constant(null)) withTypeUnchecked tp).withAttachment(IsDummyTree, ()) |
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.
Maybe we should introduce a proper internal construct for these trees. Maybe have a scala.internal.bla.dummyTree[T]
.
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.
Same for the unapply
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.
We can improve on this later.
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.
@nicolasstucki can you elaborate on what scala.internal.bla.dummyTree[T]
should be? Some sort of special fictional value? I think creating a special Tree
subclass for this purpose would work.
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.
@smarter it turned out that it was dummyTreeOfType
that was the source of the null
we talked about. Could you chime in with an opinion on whether it ever makes sense to adapt
dummy trees created by this function?
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.
honestly no clue, I myself only learned about dummyTreeOfType recently.
def adapt(tree: Tree, pt: Type, locked: TypeVars)(using Context): Tree = | ||
trace(i"adapting $tree to $pt", typr, show = true) { | ||
def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean = true)(using Context): Tree = { | ||
val last = Thread.currentThread.getStackTrace()(2).toString; |
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.
@radeusgd one final bit of work: this should 100% be a def
. Getting a stack trace is costly and adapt
is, I'd hazard the guess, called very often. Everything else looks good. Thanks again for your excellent work on this PR!
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.
Now that I look at it, I think it was just a part of my debugging and possibly can be removed altogether.
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 have double checked and this stack trace here wasn't very useful (probably that's why I forgot to remove it, as I wasn't using it much during debugging), I have removed it from this trace and made (tryGadtHealing=???) appear only if it's false (because most of the time it's true and it would just clutter the traces), is that ok?
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.
This is your code and your debugging statements, it sure is :)
9941c2a
to
b98d94b
Compare
Adding GADT approximation mechanism created by @AleksanderBG, updated with a recovery mechanism that preserves good error messages on approximation failure in the recovery stage, in order to avoid having to specify explicitly ascribe type bounds that can be inferred from the context, as described in #7044.