diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index a0922c1f0574..3aa0528cf982 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1007,7 +1007,18 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case tp1: MatchType => def compareMatch = tp2 match { case tp2: MatchType => - isSameType(tp1.scrutinee, tp2.scrutinee) && + // we allow a small number of scrutinee types to be widened: + // * skolems, which may appear from type avoidance, but are widened in the inferred result type + // * inline proxies, which is inlining's solution to the same problem + def widenScrutinee(scrutinee1: Type) = scrutinee1 match + case tp: TermRef if tp.symbol.is(InlineProxy) => tp.info + case tp => tp.widenSkolem + def checkScrutinee(scrutinee1: Type): Boolean = + isSameType(scrutinee1, tp2.scrutinee) || { + val widenScrutinee1 = widenScrutinee(scrutinee1) + (widenScrutinee1 ne scrutinee1) && checkScrutinee(widenScrutinee1) + } + checkScrutinee(tp1.scrutinee) && tp1.cases.corresponds(tp2.cases)(isSubType) case _ => false } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 04dfbbb26ef7..965ef4ecfd9c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1373,7 +1373,7 @@ object Types { Atoms.Range(set, set) else Atoms.Unknown - dealias match + dealias.normalized match case tp: SingletonType => tp.underlying.atoms match case as @ Atoms.Range(lo, hi) => diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index fcb20ea920e9..c1239b382498 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -744,23 +744,16 @@ object TreeChecker { override def adapt(tree: Tree, pt: Type, locked: TypeVars)(using Context): Tree = { def isPrimaryConstructorReturn = ctx.owner.isPrimaryConstructor && pt.isRef(ctx.owner.owner) && tree.tpe.isRef(defn.UnitClass) - def infoStr(tp: Type) = tp match { - case tp: TypeRef => - val sym = tp.symbol - i"${sym.showLocated} with ${tp.designator}, flags = ${sym.flagsString}, underlying = ${tp.underlyingIterator.toList}%, %" - case _ => - "??" - } - if (ctx.mode.isExpr && - !tree.isEmpty && - !isPrimaryConstructorReturn && - !pt.isInstanceOf[FunOrPolyProto]) + if ctx.mode.isExpr + && !tree.isEmpty + && !isPrimaryConstructorReturn + && !pt.isInstanceOf[FunOrPolyProto] + then assert(tree.tpe <:< pt, { val mismatch = TypeMismatch(tree.tpe, pt, Some(tree)) - i"""|${mismatch.msg} - |found: ${infoStr(tree.tpe)} - |expected: ${infoStr(pt)} - |tree = $tree""".stripMargin + i"""|Type Mismatch: + |${mismatch.message} + |tree = $tree ${tree.className}""".stripMargin }) tree } diff --git a/tests/neg/mt-scrutinee-widen.scala b/tests/neg/mt-scrutinee-widen.scala new file mode 100644 index 000000000000..b9cb5c8769f4 --- /dev/null +++ b/tests/neg/mt-scrutinee-widen.scala @@ -0,0 +1,24 @@ +// We widen scrutinee's that are inline proxies +// But make sure that term refs in scrutinees are not widened in general + +val x: Int = 42 +val y: Int = 43 +val z: Int = 44 + +type IsX[T] = + T match + case x.type => true + case _ => false +def test = summon[IsX[y.type] =:= IsX[z.type]] // error + +def test2 = summon[ + ( + y.type match + case x.type => true + case _ => false + ) =:= ( + z.type match + case x.type => true + case _ => false + ) +] // error diff --git a/tests/neg/mt-scrutinee-widen2.scala b/tests/neg/mt-scrutinee-widen2.scala new file mode 100644 index 000000000000..8e89f6ab7122 --- /dev/null +++ b/tests/neg/mt-scrutinee-widen2.scala @@ -0,0 +1,13 @@ +// A test case showing how we shouldn't widen +// both IsX scrutinees and make "def test" typecheck +import scala.util.Random +val x = 42 + +type IsX[T] = + T match + case x.type => true + case _ => false + +def bothXOrNot(a: Int, b: Int)(using IsX[a.type] =:= IsX[b.type]) = ??? + +def test = bothXOrNot(Random.nextInt(), Random.nextInt()) // error diff --git a/tests/neg/mt-subtyping-transitivity.scala b/tests/neg/mt-subtyping-transitivity.scala new file mode 100644 index 000000000000..d654f2a45258 --- /dev/null +++ b/tests/neg/mt-subtyping-transitivity.scala @@ -0,0 +1,14 @@ +final class A +final class B + +type MT[X] = X match + case A => String + case B => Int + +def test: MT[A | B] = ??? : MT[A] // error +// testing that +// MT[A] !<: MT[A | B] +// otherwise +// String <: MT[A] <: MT[A | B] +// but +// String !<: MT[A | B] diff --git a/tests/pos/16583.scala b/tests/pos/16583.scala new file mode 100644 index 000000000000..06f9f01b1772 --- /dev/null +++ b/tests/pos/16583.scala @@ -0,0 +1,19 @@ +import scala.compiletime.constValueTuple + +val ll0: Tuple3["one", "two", "three"] = constValueTuple[("one", "two", "three")] +val ll1 = constValueTuple[("one", "two", "three")].toList +val ll3: List["one" | ("two" | ("three" | Nothing))] = constValueTuple[("one", "two", "three")].toList +val ll4: List["one" | ("two" | "three")] = constValueTuple[("one", "two", "three")].toList + +inline def labels[Labels <: Tuple](using ev: Tuple.Union[Labels] <:< String): List[String] = + val tmp = constValueTuple[Labels].toList + ev.substituteCo(tmp) + +def test = labels[("one", "two", "three")] + +def toList(x: Tuple): List[Tuple.Union[x.type]] = ??? +def test2[Labels <: Tuple] = toList((???): Labels) + +def i16654 = + def t1: Tuple = EmptyTuple + val t2 = t1.toList diff --git a/tests/pos/16654.scala b/tests/pos/16654.scala new file mode 100644 index 000000000000..9234c309de88 --- /dev/null +++ b/tests/pos/16654.scala @@ -0,0 +1,7 @@ +def toCsvFlat[A <: Product](a: A)(using m: scala.deriving.Mirror.ProductOf[A]) = { + def flatTuple(any: Any): Tuple = any match + case p: Product => p.productIterator.map(flatTuple).foldLeft(EmptyTuple: Tuple)(_ ++ _) + case a => Tuple1(a) + + val tuple = flatTuple(Tuple.fromProductTyped(a)).toList +} diff --git a/tests/pos/mt-scrutinee-widen3.scala b/tests/pos/mt-scrutinee-widen3.scala new file mode 100644 index 000000000000..6e06cb4e1d4c --- /dev/null +++ b/tests/pos/mt-scrutinee-widen3.scala @@ -0,0 +1,12 @@ +// Like widen2, but using a.type only, meaning it should typecheck +import scala.util.Random +val x = 42 + +type IsX[T] = + T match + case x.type => true + case _ => false + +def bothXOrNot(a: Int, b: Int)(using IsX[a.type] =:= IsX[a.type]) = ??? + +def test = bothXOrNot(Random.nextInt(), Random.nextInt())