Skip to content

Commit cdc9fe8

Browse files
authored
Fix match type reduction with avoided types (#18043)
2 parents 65c7072 + 91c9afc commit cdc9fe8

9 files changed

+110
-17
lines changed

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

+12-1
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,18 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
10071007
case tp1: MatchType =>
10081008
def compareMatch = tp2 match {
10091009
case tp2: MatchType =>
1010-
isSameType(tp1.scrutinee, tp2.scrutinee) &&
1010+
// we allow a small number of scrutinee types to be widened:
1011+
// * skolems, which may appear from type avoidance, but are widened in the inferred result type
1012+
// * inline proxies, which is inlining's solution to the same problem
1013+
def widenScrutinee(scrutinee1: Type) = scrutinee1 match
1014+
case tp: TermRef if tp.symbol.is(InlineProxy) => tp.info
1015+
case tp => tp.widenSkolem
1016+
def checkScrutinee(scrutinee1: Type): Boolean =
1017+
isSameType(scrutinee1, tp2.scrutinee) || {
1018+
val widenScrutinee1 = widenScrutinee(scrutinee1)
1019+
(widenScrutinee1 ne scrutinee1) && checkScrutinee(widenScrutinee1)
1020+
}
1021+
checkScrutinee(tp1.scrutinee) &&
10111022
tp1.cases.corresponds(tp2.cases)(isSubType)
10121023
case _ => false
10131024
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1373,7 +1373,7 @@ object Types {
13731373
Atoms.Range(set, set)
13741374
else Atoms.Unknown
13751375

1376-
dealias match
1376+
dealias.normalized match
13771377
case tp: SingletonType =>
13781378
tp.underlying.atoms match
13791379
case as @ Atoms.Range(lo, hi) =>

compiler/src/dotty/tools/dotc/transform/TreeChecker.scala

+8-15
Original file line numberDiff line numberDiff line change
@@ -763,23 +763,16 @@ object TreeChecker {
763763
override def adapt(tree: Tree, pt: Type, locked: TypeVars)(using Context): Tree = {
764764
def isPrimaryConstructorReturn =
765765
ctx.owner.isPrimaryConstructor && pt.isRef(ctx.owner.owner) && tree.tpe.isRef(defn.UnitClass)
766-
def infoStr(tp: Type) = tp match {
767-
case tp: TypeRef =>
768-
val sym = tp.symbol
769-
i"${sym.showLocated} with ${tp.designator}, flags = ${sym.flagsString}, underlying = ${tp.underlyingIterator.toList}%, %"
770-
case _ =>
771-
"??"
772-
}
773-
if (ctx.mode.isExpr &&
774-
!tree.isEmpty &&
775-
!isPrimaryConstructorReturn &&
776-
!pt.isInstanceOf[FunOrPolyProto])
766+
if ctx.mode.isExpr
767+
&& !tree.isEmpty
768+
&& !isPrimaryConstructorReturn
769+
&& !pt.isInstanceOf[FunOrPolyProto]
770+
then
777771
assert(tree.tpe <:< pt, {
778772
val mismatch = TypeMismatch(tree.tpe, pt, Some(tree))
779-
i"""|${mismatch.msg}
780-
|found: ${infoStr(tree.tpe)}
781-
|expected: ${infoStr(pt)}
782-
|tree = $tree""".stripMargin
773+
i"""|Type Mismatch:
774+
|${mismatch.message}
775+
|tree = $tree ${tree.className}""".stripMargin
783776
})
784777
tree
785778
}

tests/neg/mt-scrutinee-widen.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// We widen scrutinee's that are inline proxies
2+
// But make sure that term refs in scrutinees are not widened in general
3+
4+
val x: Int = 42
5+
val y: Int = 43
6+
val z: Int = 44
7+
8+
type IsX[T] =
9+
T match
10+
case x.type => true
11+
case _ => false
12+
def test = summon[IsX[y.type] =:= IsX[z.type]] // error
13+
14+
def test2 = summon[
15+
(
16+
y.type match
17+
case x.type => true
18+
case _ => false
19+
) =:= (
20+
z.type match
21+
case x.type => true
22+
case _ => false
23+
)
24+
] // error

tests/neg/mt-scrutinee-widen2.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// A test case showing how we shouldn't widen
2+
// both IsX scrutinees and make "def test" typecheck
3+
import scala.util.Random
4+
val x = 42
5+
6+
type IsX[T] =
7+
T match
8+
case x.type => true
9+
case _ => false
10+
11+
def bothXOrNot(a: Int, b: Int)(using IsX[a.type] =:= IsX[b.type]) = ???
12+
13+
def test = bothXOrNot(Random.nextInt(), Random.nextInt()) // error
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
final class A
2+
final class B
3+
4+
type MT[X] = X match
5+
case A => String
6+
case B => Int
7+
8+
def test: MT[A | B] = ??? : MT[A] // error
9+
// testing that
10+
// MT[A] !<: MT[A | B]
11+
// otherwise
12+
// String <: MT[A] <: MT[A | B]
13+
// but
14+
// String !<: MT[A | B]

tests/pos/16583.scala

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import scala.compiletime.constValueTuple
2+
3+
val ll0: Tuple3["one", "two", "three"] = constValueTuple[("one", "two", "three")]
4+
val ll1 = constValueTuple[("one", "two", "three")].toList
5+
val ll3: List["one" | ("two" | ("three" | Nothing))] = constValueTuple[("one", "two", "three")].toList
6+
val ll4: List["one" | ("two" | "three")] = constValueTuple[("one", "two", "three")].toList
7+
8+
inline def labels[Labels <: Tuple](using ev: Tuple.Union[Labels] <:< String): List[String] =
9+
val tmp = constValueTuple[Labels].toList
10+
ev.substituteCo(tmp)
11+
12+
def test = labels[("one", "two", "three")]
13+
14+
def toList(x: Tuple): List[Tuple.Union[x.type]] = ???
15+
def test2[Labels <: Tuple] = toList((???): Labels)
16+
17+
def i16654 =
18+
def t1: Tuple = EmptyTuple
19+
val t2 = t1.toList

tests/pos/16654.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
def toCsvFlat[A <: Product](a: A)(using m: scala.deriving.Mirror.ProductOf[A]) = {
2+
def flatTuple(any: Any): Tuple = any match
3+
case p: Product => p.productIterator.map(flatTuple).foldLeft(EmptyTuple: Tuple)(_ ++ _)
4+
case a => Tuple1(a)
5+
6+
val tuple = flatTuple(Tuple.fromProductTyped(a)).toList
7+
}

tests/pos/mt-scrutinee-widen3.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Like widen2, but using a.type only, meaning it should typecheck
2+
import scala.util.Random
3+
val x = 42
4+
5+
type IsX[T] =
6+
T match
7+
case x.type => true
8+
case _ => false
9+
10+
def bothXOrNot(a: Int, b: Int)(using IsX[a.type] =:= IsX[a.type]) = ???
11+
12+
def test = bothXOrNot(Random.nextInt(), Random.nextInt())

0 commit comments

Comments
 (0)