Skip to content

Intersection based gadts #6398

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

Merged
merged 9 commits into from
Jun 6, 2019

Conversation

abgruszecki
Copy link
Contributor

@abgruszecki abgruszecki commented Apr 30, 2019

This PR replaces GADT constraint inference algorithm with an intersection-inspired version.

@abgruszecki abgruszecki force-pushed the intersection-based-gadts branch from 97ef5bc to 2845895 Compare May 3, 2019 15:19
@abgruszecki abgruszecki self-assigned this May 6, 2019
@abgruszecki abgruszecki force-pushed the intersection-based-gadts branch from 2845895 to db38c33 Compare May 22, 2019 15:23
@abgruszecki abgruszecki force-pushed the intersection-based-gadts branch from 0b2c003 to 58c1cf6 Compare May 23, 2019 08:09
@abgruszecki abgruszecki marked this pull request as ready for review May 23, 2019 09:07
@abgruszecki
Copy link
Contributor Author

@odersky This one's ready to go! This PR is the implementation of the "intersection-inspired" way of inferring GADT constraints that we talked about previously. It should be easiest to review it commit-by-commit.

@abgruszecki abgruszecki requested a review from odersky May 23, 2019 09:23
@abgruszecki abgruszecki assigned odersky and unassigned abgruszecki May 23, 2019
Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only serious problem I saw was in subsumes. The rest should be easy fixes.

case AndType(scrut1, scrut2) =>
constrainPatternType(pat, scrut1) && constrainPatternType(pat, scrut2)
case scrut: RefinedOrRecType =>
constrainPatternType(pat, stripRefinement(scrut))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need stripRefinement. Can just do

constrainPatternType(pat, scrut.parent)

(in both places where it is used). That does not special-case the natural recursion of constrainPatternType and removes a method.

Or is there something I overlooked?

@@ -141,6 +141,9 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
*/
private [this] var leftRoot: Type = _

/** Are we forbidden from recording GADT constraints? */
private[this] var frozenGadt = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered, can this somehow be combined with the GADTflexible mode? I guess this would mean that instead of setting the GADTflexible mode bit we create a context with a new type comparer where frozenGadt is true.

...

After looking further I see that frozenGadt and GADTflexible are not equivalent. either depends on GADTflexible but not frozenGadt. I assume that's intentional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see how GADTFlexible and frozenGadt can appear to have the same purpose, though I don't really think of them this way. For me, GADTFlexible mode fundamentally alters isSubType(tp, pt) to mean "what type constraints necessarily follow from tp <: pt?", whereas frozenGadt is simply a temporary flag that TypeComparer sets for itself, much like ApproxState is a set of temporary flags that help with recording constraints.

At one point, frozenGadt was a part of ApproxState. Would it help if I called it gadtApprox and added more information to the comment?

isSubType(tycon1.prefix, tycon2.prefix) && {
// check both tycons to deal with the case when they are equal b/c of GADT constraint
val tyconIsInjective = tycon1sym.isClass || tycon2sym.isClass
isSubArgs(args1, args2, tp1, tparams, inferGadtBounds = tyconIsInjective)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not be better if frozenGadt was set around isSubArgs instead of for each individual arg? Fewer state changes and simpler logic since no additional parameter needs to be passed to isSubArgs.

The only thing that changes is that frozenGadt then also affects compareCaptured, but I don't see why it should not do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I agree that the code will be a bit simpler if we just set frozenGadt around isSubArgs.

I believe that frozenGadt already affects compareCaptured, since it calls isSubArg.

@@ -345,7 +345,7 @@ trait ConstraintHandling[AbstractContext] {
else {
val saved = constraint
try
c2.forallParams(p =>
pre.forallParams(p =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now in contradiction to the doc comment. Also, I don't see how it could capture the intended meaning.
If both c1 and c2 constrain different new type parameters that are not in pre, then according to the old meaning neither subsumes the other. But according to the new meaning each subsumes the other. This is clearly wrong, at least if we use the usual meaning of subsumes.

This seems to be a critical problem that requires further thought to be addressed.

@odersky odersky assigned abgruszecki and unassigned odersky May 31, 2019
GADT constraints are now inferred with an intersection-inspired
algorithm.

Inferencing.constrainPatternType was moved to PatternTypeConstrainer
to organize the code better.
These tests were meant to test inferring GADT constraints based on type
members, which we backed out of.

They are still useful to see if we don't do anything unsound when
type members are present.
Fixed soundness problems with abstract types - see test.
When going into arguments of a HKT, we only infer GADT constraints
if we are certain that tycon is injective.
Previously, if c2 had instantiated a variable, it would not be iterated
over and would not be compared with c1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants