Skip to content

Commit 3e1fbd6

Browse files
oderskydwijnand
authored andcommitted
Refine criterion when to widen types
Refine criterion when to widen types in match type reduction. We now do not widen if the compared-against type contains variant named type parameters. This is intended to fix the soundness problem in #17149. Fixes #17149 Fixes #15926 Todos: - [ ] Check & fix neg test failures - [ ] Add more tests - [ ] Also consider approximating abstract types to lower bounds. This is completely missing so far. There are neither tests nor an implementation. - [ ] Update the docs on match types to explain what goes on here.
1 parent e8c6ebe commit 3e1fbd6

File tree

5 files changed

+74
-3
lines changed

5 files changed

+74
-3
lines changed

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import typer.Applications.productSelectorTypes
2424
import reporting.trace
2525
import annotation.constructorOnly
2626
import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam, isAlwaysPure}
27+
import NameKinds.WildcardParamName
2728

2829
/** Provides methods to compare types.
2930
*/
@@ -865,10 +866,28 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
865866
fourthTry
866867
}
867868

869+
/** Can we widen an abstract type when comparing with `tp`?
870+
* This is NOT the case if one of the following is true:
871+
* - canWidenAbstract is false,
872+
* - or `tp` contains a non-wildcard type parameter of the matched-against
873+
* case lambda that appears in co- or contra-variant position
874+
*/
875+
def widenAbstractOKFor(tp: Type): Boolean =
876+
val acc = new TypeAccumulator[Boolean]:
877+
override def apply(x: Boolean, t: Type) =
878+
x && t.match
879+
case t: TypeParamRef =>
880+
variance == 0 || (t.binder ne caseLambda) || t.paramName.is(WildcardParamName)
881+
case _ =>
882+
foldOver(x, t)
883+
canWidenAbstract && acc(true, tp)
884+
868885
def tryBaseType(cls2: Symbol) = {
869886
val base = nonExprBaseType(tp1, cls2).boxedIfTypeParam(tp1.typeSymbol)
870887
if base.exists && (base ne tp1)
871-
&& (!caseLambda.exists || canWidenAbstract || tp1.widen.underlyingClassRef(refinementOK = true).exists)
888+
&& (!caseLambda.exists
889+
|| widenAbstractOKFor(tp2)
890+
|| tp1.widen.underlyingClassRef(refinementOK = true).exists)
872891
then
873892
isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx.addLow)
874893
&& recordGadtUsageIf { MatchType.thatReducesUsingGadt(tp1) }
@@ -889,8 +908,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
889908
|| narrowGADTBounds(tp1, tp2, approx, isUpper = true))
890909
&& (tp2.isAny || GADTusage(tp1.symbol))
891910

892-
(!caseLambda.exists || canWidenAbstract)
893-
&& isSubType(hi1.boxedIfTypeParam(tp1.symbol), tp2, approx.addLow) && (trustBounds || isSubType(lo1, tp2, approx.addLow))
911+
(!caseLambda.exists || widenAbstractOKFor(tp2))
912+
&& isSubType(hi1.boxedIfTypeParam(tp1.symbol), tp2, approx.addLow) && (trustBounds || isSubType(lo1, tp2, approx.addLow))
894913
|| compareGADT
895914
|| tryLiftedToThis1
896915
case _ =>

compiler/test/dotc/pos-test-pickling.blacklist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ i6505.scala
4848
i15158.scala
4949
i15155.scala
5050
i15827.scala
51+
i17149.scala
5152

5253
# Opaque type
5354
i5720.scala

tests/neg/i17149.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
type Ext1[S] = S match {
2+
case Seq[t] => t
3+
}
4+
type Ext2[S] = S match {
5+
case Seq[_] => Int
6+
}
7+
type Ext3[S] = S match {
8+
case Array[t] => t
9+
}
10+
type Ext4[S] = S match {
11+
case Array[_] => Int
12+
}
13+
def foo[T <: Seq[Any], A <: Array[B], B] =
14+
summon[Ext1[T] =:= T] // error
15+
summon[Ext2[T] =:= Int] // ok
16+
summon[Ext3[A] =:= B] // ok
17+
summon[Ext4[A] =:= Int] // ok

tests/pos/i15926.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
@main def main(): Unit =
3+
println(summon[Sum[Minus[Succ[Zero]], Minus[Succ[Zero]]] =:= Minus[Succ[Succ[Zero]]]])
4+
5+
sealed trait IntT
6+
sealed trait NatT extends IntT
7+
final case class Zero() extends NatT
8+
final case class Succ[+N <: NatT](n: N) extends NatT
9+
final case class Minus[+N <: Succ[NatT]](n: N) extends IntT
10+
11+
type NatSum[X <: NatT, Y <: NatT] <: NatT = Y match
12+
case Zero => X
13+
case Succ[y] => NatSum[Succ[X], y]
14+
15+
type NatDif[X <: NatT, Y <: NatT] <: IntT = Y match
16+
case Zero => X
17+
case Succ[y] => X match
18+
case Zero => Minus[Y]
19+
case Succ[x] => NatDif[x, y]
20+
21+
type Sum[X <: IntT, Y <: IntT] <: IntT = Y match
22+
case Zero => X
23+
case Minus[y] => X match
24+
case Minus[x] => Minus[NatSum[x, y]]
25+
case _ => NatDif[X, y]
26+
case _ => X match
27+
case Minus[x] => NatDif[Y, x]
28+
case _ => NatSum[X, Y]

tests/pos/i17149.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
type Ext[S <: Seq[_]] = S match {
2+
case Seq[t] => t
3+
}
4+
5+
val _ = implicitly[Ext[Seq[Int]] =:= Int] // e.scala: Cannot prove that e.Ext[Seq[Int]] =:= Int
6+
val _ = summon[Ext[Seq[Int]] =:= Int]

0 commit comments

Comments
 (0)