Skip to content
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

Avoid bidirectional GADT typebounds from fullBounds #15683

Merged
merged 5 commits into from
Feb 16, 2023

Conversation

dwijnand
Copy link
Member

@dwijnand dwijnand commented Jul 15, 2022

No description provided.

@SethTisue

This comment was marked as resolved.

@odersky
Copy link
Contributor

odersky commented Jul 20, 2022

I'd prefer if @smarter could take a look when he has time

@dwijnand dwijnand removed the request for review from odersky July 27, 2022 07:54
@dwijnand dwijnand closed this Oct 10, 2022
@dwijnand dwijnand deleted the gadt/cycles branch October 10, 2022 15:46
@dwijnand dwijnand restored the gadt/cycles branch January 27, 2023 19:23
@dwijnand dwijnand reopened this Jan 27, 2023
@dwijnand dwijnand linked an issue Jan 27, 2023 that may be closed by this pull request
@dwijnand dwijnand requested review from odersky and removed request for smarter January 27, 2023 19:26
@dwijnand dwijnand assigned odersky and unassigned smarter Jan 27, 2023
@dwijnand
Copy link
Member Author

This now fixes 3 bugs. Can we merge it?

@smarter
Copy link
Member

smarter commented Jan 30, 2023

I can review it this week.

@smarter smarter assigned smarter and unassigned odersky Jan 30, 2023
@dwijnand
Copy link
Member Author

@smarter Sorry, I slightly misrepresented what I said. I said that I knew, loosely, how reproduce the problem with regular type variables. But what I had found is that if you change maximizeType to return the full bounds before instantiating the type variables, then you'll create the same bidirectional type bounds. IsFullyDefinedAccumulator is also set up to delay maximising type variables, at least the "difficult" ones, while minimising is done eagerly, so that avoids the problem (by being lossy).

@smarter
Copy link
Member

smarter commented Feb 1, 2023

Sorry, I'm not sure I understand what changes you have in mind that would lead to the same issue with non-gadt type variables. To me, it still seems like this is purely an issue with gadt handling. The whole point of having an ordering between type variables in OrderingConstraint is to avoid cyclic bounds, in fact if I print ctx.gadt.constraint.show in tests/pos/i15523.avoid.scala I see:

uninstantiated variables: A$1(param)1, B$1(param)1
 constrained types: [A$1(param)1 <: B$1(param)1, B$1(param)1]: Any
 bounds:
     A$1(param)1
     B$1(param)1
 ordering:
     A$1(param)1 <: B$1(param)1
 co-deps:
 contra-deps:

But then, we end up setting the bounds of GADT symbols using ctx.gadt.fullBounds, so A is upper-bounded by B and B is upper-bounded by A. Even if we fix this particular TypeMap to detect this, this could blow up in any TypeMap in the compiler (and possible other places like TypeComparer, although TypeComparer already has logic to deal with infinite subtype checks via pendingSubTypes).

We could change TypeMap itself to handle this, but to me the real issue is the way we set the info of GADT symbols. It should be morally equivalent to something the user could write if we don't want to end up with more puzzling bugs. For example I can write:

def foo =
  type A
  type B >: A
  val a: A = ???
  val b: B = a

or:

def foo =
  type B
  type A <: B
  val a: A = ???
  val b: B = a

and both will be handled gracefully by the compiler and compile as expected, because TypeComparer knows it needs to check the bounds of both abstract types involved to determine whether they're in a subtyping relationship (fun fact: this means it's possible for subtype checking to take an amount of time exponential in the number of abstract types in your program [Abel 2017, p.4]).

I think we could achieve something similar if we replaced fullBounds in https://github.com/lampepfl/dotty/blob/main/compiler/src/dotty/tools/dotc/typer/Typer.scala#L1767 by something like "fullBounds, but drop ordering constraints for type variables corresponding to symbols we already set the info of" (which might be achievable by instantiating a variable in ctx.gadt after it's been used to set the info of a symbol?), but I haven't tried it.

@dwijnand dwijnand changed the title Handle bidirectional typebounds in AvoidMap Avoid bidirectional typebounds in GADT fullBounds Feb 12, 2023
@dwijnand
Copy link
Member Author

I think we could achieve something similar if we replaced fullBounds in main/compiler/src/dotty/tools/dotc/typer/Typer.scala#L1767 by something like "fullBounds, but drop ordering constraints for type variables corresponding to symbols we already set the info of" (which might be achievable by instantiating a variable in ctx.gadt after it's been used to set the info of a symbol?), but I haven't tried it.

I wasn't able to find a solution based on that. I also know that removing GADT information about symbols changes type comparing, because we trust GADT info, but not abstract symbol info, because we can't distinguish it from unsound, user-defined, info.

But I found a solution based on using and tweaking checkNonCyclic. WDYT?

@dwijnand dwijnand changed the title Avoid bidirectional typebounds in GADT fullBounds Avoid bidirectional GADT typebounds with checkNonCyclic Feb 13, 2023
@dwijnand dwijnand changed the title Avoid bidirectional GADT typebounds with checkNonCyclic Avoid bidirectional GADT typebounds from fullBounds Feb 13, 2023
@dwijnand
Copy link
Member Author

I wasn't able to find a solution based on that.

Nevermind, found a solution! (finally)

Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

Very nice fix! Thanks a lot for doing all the hard work on this!

compiler/src/dotty/tools/dotc/core/GadtConstraint.scala Outdated Show resolved Hide resolved
@smarter smarter assigned dwijnand and unassigned smarter Feb 15, 2023
// B$2 had info <: B$1 and fullBounds <: B$1
// We can use the info of B$2 to drop the lower-bound of B$1
// and return non-bidirectional bounds B$1 <: X and B$2 <: B$1.
if tp.name.is(UniqueName) && !tp.info.hiBound.isExactlyAny && self <:< tp.info.hiBound => acc
Copy link
Member

Choose a reason for hiding this comment

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

Can we use tp.symbol.isPatternBound instead of relying on the name kind?

Copy link
Member

Choose a reason for hiding this comment

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

Also, would checking self =:= tp.info.hiBound be enough for all our test cases? That would remove the need for isExactlyAny which I'm a bit wary of (what if the symbols involved have another common upper-bound like AnyRef?)

@dwijnand dwijnand merged commit 5817920 into scala:main Feb 16, 2023
@dwijnand dwijnand deleted the gadt/cycles branch February 16, 2023 10:23
@Kordyjan Kordyjan added the backport:accepted This PR needs to be backported, once it's been backported replace this tag by "backport:done" label Feb 16, 2023
constraint.minLower(param).foldLeft(nonParamBounds(param).lo) { (acc, p) =>
externalize(p) match
// drop any lower param that is a GADT symbol
// and is upper-bounded by a non-Any super-type of the original parameter
Copy link
Member

Choose a reason for hiding this comment

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

hmm looks like we still need to update the comment after changing <:< to =:=

@Kordyjan Kordyjan added backport:done This PR was successfully backported. and removed backport:accepted This PR needs to be backported, once it's been backported replace this tag by "backport:done" labels Feb 17, 2023
@Kordyjan Kordyjan added this to the 3.3.0 backports milestone Feb 17, 2023
@Kordyjan Kordyjan modified the milestones: 3.3.0 backports, 3.3.1, 3.3.0 Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:done This PR was successfully backported.
Projects
None yet
5 participants