-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Implement AssumeInfo #15928
Implement AssumeInfo #15928
Conversation
7eb84be
to
b9eba5b
Compare
I think I need to redo this because I need the GADT constraints installed for the pattern (and the guard, actually) as well - i.e. I can't just wrap the body I need to wrap the whole case... |
@nicolasstucki Are there requirements around the test that uses Extractor? I'm pretty sure it was the BootstrappedStdLibTASYyTest test. Can I leave some internal details out? |
8627e01
to
9ab1b05
Compare
768f067
to
0d77e13
Compare
7a43fd0
to
e7efd68
Compare
e7efd68
to
3af49b9
Compare
e1a934b
to
6a7ee06
Compare
6a7ee06
to
dbe1c3c
Compare
dbe1c3c
to
0acaa36
Compare
test performance please |
performance test scheduled: 1 job(s) in queue, 0 running. |
Performance test finished successfully: Visit https://dotty-bench.epfl.ch/15928/ to see the changes. Benchmarks is based on merging with main (a6a3385) |
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've been assigned so here are some comments. Overall it looks reasonable, although I really can't judge the changes in Typer
and TypeComparer
.
inContext(ctx.withGadt(ctx.gadt.withAssumeInfos(assumeInfos))) { | ||
val pat1 = transform(pat) | ||
val guard1 = transform(guard) |
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 seems fishy to me. IIUC, that means that top-level AssumeInfo
s in the body of a CaseDef
actually also apply to their patten
and guard
? That's a pretty weird combo yielding semantics that cannot be understood for one separately from the the other. Have you considered:
- Surrounding the
CaseDef
withAssumeInfo
(also a bit weird, because now aMatch
needs to deal withAssumeInfo
in its cases; but at least theMatch
/CaseDef
is already a very solid combo), or - Repeating the required
AssumeInfo
in the pattern and guard, if necessary
?
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 necessary to satisfy the bounds check that happens in post typer. On the opposite side it needs to especially not be done in the TypeTestCasts checks, which are run during Erasure, in order to give correct results on whether any type test has any uncheckable parts.
It comes down to the fact that the assumed info may only be assume provided the pattern matches. So in PostTyper we want to check the bounds, which only matters if the pattern matches, while in TypeTestCasts we want to check uncheckable, which may not assume the pattern matches.
Wrapping the CaseDef is double hard. Firstly, because undoing the assumption for TypeTestCasts isn't easy. Secondly, because Match is already reused for inline matches and match types, so having all those code paths deal with this change is painful. There's a lot of code that expects matches to be of cases, so it's quite hairy to make it all deal with AssumeInfo vs CaseDef. In my original where I was holding onto a GadtConstraint I tried splitting CaseDef into {GadtCaseDef, CaseDef} <: GenericCaseDef, but it was very painful. I also tried having all CaseDef have a GadtConstraint, but it was still awkward.
Repeating the required AssumeInfo still runs into the TypeTestCasts issue. And would that be repeating as in copy or same reference? I could reuse the same reference, and do something awful for TypeTestCasts.
Btw, if you look at the before state (earlier in the file) it was set up in the opposite direction: the gadt as an attachment on the pattern was installed to be present when transforming the tree as a whole. So this is different but the same, as I see it.
inContext(ctx.withGadt(ctx.gadt.withAssumeInfos(assumeInfos))) { | ||
assumeInfos.foldRight(readTerm()) { case (AssumeInfo(sym, nestingLevel, info, _), body) => |
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.
inContext(ctx.withGadt(ctx.gadt.withAssumeInfos(assumeInfos))) { | |
assumeInfos.foldRight(readTerm()) { case (AssumeInfo(sym, nestingLevel, info, _), body) => | |
inContext(ctx.withGadt(ctx.gadt.withAssumeInfos(assumeInfos))) { | |
assumeInfos.foldRight(readTerm()) { case (AssumeInfo(sym, nestingLevel, info, _), body) => |
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.
Do we actually need this gadt.withAssumeInfos
while unpickling? If yes, why?
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 guess I don't have a canonical example that would fail without this. I haven't tried (today) to take it out and test - but I can. But my general desire is to have these known facts to always be present whenever there's a chance there's some type comparisons potentially happening. As I understand it the design is that there's only a very light, simple amount of type composition that happens during unpickling (e.g. List + Int = List[Int]
). But does none of it require any type comparisons?
What's your concern here? Performance?
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.
My concern is mostly whether we can replicate this logic in tasty-query. In tasty-query, the unpickling step is not allowed to look at any information coming from outside the current .tasty
file. If dotty requires contextual type information like this to unpickle, there's a good chance that tasty-query would need it as well, and that would be a problem.
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'd prefer not to enrich the context. First. it costs time. Second, I don't see a case where we would need it. Which means we should start without and see whether we are proven wrong. If we are proven wrong it might be that TreeUnpickler does more than we think it should do, which would require a fix there. Or we conclude that, indeed, adding assumed infos is necessary. But then in either case we have learned something. By contrast, adding stuff "just in case" tells us nothing.
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 that's a deal breaker then. I believe that TastyQuery (or any of the other tools) should not have to know about Gadt constraints. So, maybe we should abandon the effort and stick with casts.
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 my problem is that GadtConstraint itself is too complicated. To start with, it sits in the wrong place of the type comparer system: it should be a constraint, but it is really a constraint handler (i.e. a mutable abstraction that encapsulates a constraint field). Then there is all this complication of creating type variables for a real constraint that sits behind it. For type inference, all of this is a bit awkward, and should really be redesigned before we go further. For after type inference, it should have no place whatsoever.
So if assumeInfo should work, it should be decoupled from GadtConstraint.
My recommendation would be to stop all work for now on extending GadtConstraints until we have a better core design that we can actually understand.
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.
What we could do (maybe, eventually):
- Change the context so that instead of a GadtConstraint it just contains a map from symbols to infos.
- TypeComparer would consult that map and, if a symbol has an entry, try that entry for comparisons.
- The entry could be a type variable linked to a GadtConstraint, but that's the business of Typer.
Context
knows nothing about this. - We might also need to refactor GadtConstraint to split it into a real constraint and a handler (I know less about this part, just feel uncomfortable with the way it is done now).
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.
- Change the context so that instead of a GadtConstraint it just contains a map from symbols to infos.
- TypeComparer would consult that map and, if a symbol has an entry, try that entry for comparisons.
FWIW, this is also how I imagine tasty-query would deal with AssumeInfo
, and with opaque type
aliases. A map of symbols to infos for AssumeInfo
handling, and a set of opaque type alias symbols that must be treated as translucent.
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.
That all sounds good to me. I'll see what I can do and we can chat about it.
0acaa36
to
6cb777f
Compare
Interestingly, it does not work for opaque type aliases. My first attempt was to model them with Gadt constraints, but it turned out that was too weak. Gadt constraints are too weak in the sense that they come into play only for subtyping. On the other hand, member selection or basetype computation does not take Gadt constraints into account. I tried to change that so that Gadt constraints would be consulted in more situations but that ran into a lot of problems. That's why we based opaque type aliases on self types, following an idea of Adriaan. The idea is that an opaque type alias should have essentially the same power as a combo of an abstract type in an interface and a type alias in the implementation. Just without having to split it into interface and implementation. Refining the self type can achieve that, but Gadt constraints do not. |
Right, but nothing says we have to follow the model of Gadt constraints in tasty-query. We can implement member lookup with the knowledge of which opaque type aliases are translucent and which aren't. |
Yes, and baseType computation, that was the harder part. I think it could work. Maybe there will be some subtle differences where the compiler can resolve something but TastyQuery cannot (or vice versa), but maybe that does not matter. |
6cb777f
to
7cadc36
Compare
7cadc36
to
c6342c4
Compare
@@ -1340,6 +1340,14 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |||
end extension | |||
end BlockMethods | |||
|
|||
type AssumeInfo <: Term |
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.
The documentation of trait reflectModule
must be updated
No description provided.