Skip to content

Commit fd38489

Browse files
committed
Defer param replacement when inferring GADT constraints
When we're infering the GADT constraints from comparing the type arguments of pattern type `Foo[C, C]` against the type arguments of scrutinee type `Foo[X, String]` we end up eagerly instantiating `C` to `X` and then recording in the GADT constraints that `X =:= String`. However, later when the SpaceEngine decomposes the scrutinee type `Foo[X, String]` it discovers `Bar[String]`, but that doesn't match the pattern type `Bar[X]`, because the GADT constraints are no longer present. Not replacing, though, seems to require (pos/gadt-TypeSafeLambda.scala) not considering the widening of a type parameter to its equal bounds as an approximation.
1 parent 67f11ff commit fd38489

File tree

3 files changed

+40
-4
lines changed

3 files changed

+40
-4
lines changed

compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ trait ConstraintHandling {
252252
val bound = legalBound(param, rawBound, isUpper)
253253
val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param)
254254
val equalBounds = (if isUpper then lo else hi) eq bound
255-
if equalBounds && !bound.existsPart(_ eq param, StopAt.Static) then
255+
val isGadt = ctx.mode.is(Mode.GadtConstraintInference)
256+
if equalBounds && !isGadt && !bound.existsPart(_ eq param, StopAt.Static) then
256257
// The narrowed bounds are equal and not recursive,
257258
// so we can remove `param` from the constraint.
258259
constraint = constraint.replace(param, bound)
@@ -267,8 +268,12 @@ trait ConstraintHandling {
267268
homogenizeArgs = Config.alignArgsInAnd
268269
try
269270
trustBounds = false
270-
if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound)
271-
else oldBounds.derivedTypeBounds(lo | bound, hi)
271+
if isUpper then
272+
if isGadt && !hi.isAny && hi.isValueTypeOrWildcard && bound.isValueTypeOrWildcard then oldBounds.derivedTypeBounds(lo, AndType(hi, bound))
273+
else oldBounds.derivedTypeBounds(lo, hi & bound)
274+
else
275+
if isGadt && !lo.isNothingType && lo.isValueTypeOrWildcard && bound.isValueTypeOrWildcard then oldBounds.derivedTypeBounds(OrType(lo, bound, soft = true), hi)
276+
else oldBounds.derivedTypeBounds(lo | bound, hi)
272277
finally
273278
homogenizeArgs = savedHomogenizeArgs
274279
trustBounds = savedTrustBounds

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1201,7 +1201,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
12011201
def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean =
12021202
if ((tycon2bounds.lo `eq` tycon2bounds.hi) && !tycon2bounds.isInstanceOf[MatchAlias])
12031203
if (tyconIsTypeRef) recur(tp1, tp2.superTypeNormalized)
1204-
else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2))
1204+
else recur(tp1, tycon2bounds.lo.applyIfParameterized(args2))
12051205
else
12061206
fallback(tycon2bounds.lo)
12071207

tests/pos/i15289.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// scalac: -Werror
2+
sealed abstract class Foo[A, B]
3+
final case class Bar[C](baz: C) extends Foo[C, C]
4+
5+
class Test:
6+
def m1[X](f1: Foo[X, String]): String = f1 match { case Bar(_) => "" } // ""
7+
def m2[X](f2: Foo[X, String]): X = f2 match { case Bar(x) => x } // x: X
8+
def m3[X](f3: Foo[X, String]): String = f3 match { case Bar(x) => x } // x.asInstanceOf[x.type & String]: String
9+
def m4[X](f4: Foo[X, String]): X = f4 match { case Bar(_) => "" } // "".asInstanceOf[("" & String & X) @uncheckedStable]
10+
11+
// ptc: Bar[C] aka Foo[C, C] vs Foo[X, String]
12+
// ptc: C >: X ~> cstr: C >: X
13+
// ptc: C <: X ~> cstr: C := X
14+
// C instantiated with X, so now C shows up as X
15+
// ptc: X >: String ~> gadt: X >: String
16+
// ptc: X <: String ~> gadt: X := String
17+
// ptc: Bar[X]
18+
// max: Bar[X]
19+
20+
// what's more correct?
21+
// * unapp arg tpe = Bar[X] gadt: X =:= String
22+
// * unapp arg tpe = Bar[String] gadt: X =:= String
23+
24+
// after fix:
25+
// ptc: Bar[C] aka Foo[C, C] vs Foo[X, String]
26+
// ptc: C >: X ~> cstr: C >: X
27+
// ptc: C <: X ~> cstr: C =:= X
28+
// ptc: C >: String ~> cstr: C >: X | String <: X gadt: X >: String
29+
// ptc: C <: String ~> cstr: C >: X | String <: X & String gadt: X =:= String
30+
// ptc: Bar[C]
31+
// max: Bar[String]

0 commit comments

Comments
 (0)