diff --git a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala index fb278ab92dc9..e16a950aa32a 100644 --- a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala +++ b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala @@ -12,6 +12,7 @@ object MatchTypeTrace: private enum TraceEntry: case TryReduce(scrut: Type) + case NoMatches(scrut: Type, cases: List[MatchTypeCaseSpec]) case Stuck(scrut: Type, stuckCase: MatchTypeCaseSpec, otherCases: List[MatchTypeCaseSpec]) case NoInstance(scrut: Type, stuckCase: MatchTypeCaseSpec, fails: List[(Name, TypeBounds)]) case EmptyScrutinee(scrut: Type) @@ -50,6 +51,12 @@ object MatchTypeTrace: case _ => case _ => + /** Record a failure that scrutinee `scrut` does not match any case in `cases`. + * Only the first failure is recorded. + */ + def noMatches(scrut: Type, cases: List[MatchTypeCaseSpec])(using Context) = + matchTypeFail(NoMatches(scrut, cases)) + /** Record a failure that scrutinee `scrut` does not match `stuckCase` but is * not disjoint from it either, which means that the remaining cases `otherCases` * cannot be visited. Only the first failure is recorded. @@ -71,7 +78,7 @@ object MatchTypeTrace: */ def recurseWith(scrut: Type)(op: => Type)(using Context): Type = ctx.property(MatchTrace) match - case Some(trace) => + case Some(trace) if !trace.entries.contains(TryReduce(scrut)) => val prev = trace.entries trace.entries = TryReduce(scrut) :: prev val res = op @@ -95,6 +102,11 @@ object MatchTypeTrace: private def explainEntry(entry: TraceEntry)(using Context): String = entry match case TryReduce(scrut: Type) => i" trying to reduce $scrut" + case NoMatches(scrut, cases) => + i""" failed since selector $scrut + | matches none of the cases + | + | ${casesText(cases)}""" case EmptyScrutinee(scrut) => i""" failed since selector $scrut | is uninhabited (there are no values of that type).""" diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index c704846a82da..043c5beb9076 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -220,6 +220,7 @@ object Phases { private var myPatmatPhase: Phase = uninitialized private var myElimRepeatedPhase: Phase = uninitialized private var myElimByNamePhase: Phase = uninitialized + private var myElimOpaquePhase: Phase = uninitialized private var myExtensionMethodsPhase: Phase = uninitialized private var myExplicitOuterPhase: Phase = uninitialized private var myGettersPhase: Phase = uninitialized @@ -245,6 +246,7 @@ object Phases { final def patmatPhase: Phase = myPatmatPhase final def elimRepeatedPhase: Phase = myElimRepeatedPhase final def elimByNamePhase: Phase = myElimByNamePhase + final def elimOpaquePhase: Phase = myElimOpaquePhase final def extensionMethodsPhase: Phase = myExtensionMethodsPhase final def explicitOuterPhase: Phase = myExplicitOuterPhase final def gettersPhase: Phase = myGettersPhase @@ -272,6 +274,7 @@ object Phases { myRefChecksPhase = phaseOfClass(classOf[RefChecks]) myElimRepeatedPhase = phaseOfClass(classOf[ElimRepeated]) myElimByNamePhase = phaseOfClass(classOf[ElimByName]) + myElimOpaquePhase = phaseOfClass(classOf[ElimOpaque]) myExtensionMethodsPhase = phaseOfClass(classOf[ExtensionMethods]) myErasurePhase = phaseOfClass(classOf[Erasure]) myElimErasedValueTypePhase = phaseOfClass(classOf[ElimErasedValueType]) @@ -511,6 +514,7 @@ object Phases { def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase def elimRepeatedPhase(using Context): Phase = ctx.base.elimRepeatedPhase def elimByNamePhase(using Context): Phase = ctx.base.elimByNamePhase + def elimOpaquePhase(using Context): Phase = ctx.base.elimOpaquePhase def extensionMethodsPhase(using Context): Phase = ctx.base.extensionMethodsPhase def explicitOuterPhase(using Context): Phase = ctx.base.explicitOuterPhase def gettersPhase(using Context): Phase = ctx.base.gettersPhase diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 302ad7987889..73b45117cc2d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3594,23 +3594,14 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { MatchTypeTrace.emptyScrutinee(scrut) NoType case Nil => - val casesText = MatchTypeTrace.noMatchesText(scrut, cases) - ErrorType(reporting.MatchTypeNoCases(casesText)) - - inFrozenConstraint { - if scrut.isError then - // if the scrutinee is an error type - // then just return that as the result - // not doing so will result in the first type case matching - // because ErrorType (as a FlexType) is <:< any type case - // this situation can arise from any kind of nesting of match types, - // e.g. neg/i12049 `Tuple.Concat[Reverse[ts], (t2, t1)]` - // if Reverse[ts] fails with no matches, - // the error type should be the reduction of the Concat too - scrut - else - recur(cases) - } + /* TODO warn ? then re-enable warn/12974.scala:26 + val noCasesText = MatchTypeTrace.noMatchesText(scrut, cases) + report.warning(reporting.MatchTypeNoCases(noCasesText), pos = ???) + */ + MatchTypeTrace.noMatches(scrut, cases) + NoType + + inFrozenConstraint(recur(cases)) } } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e38fbbb4b355..f6210b3a9883 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5026,7 +5026,7 @@ object Types extends TypeUtils { private def thisMatchType = this - def reduced(using Context): Type = { + def reduced(using Context): Type = atPhaseNoLater(elimOpaquePhase) { def contextInfo(tp: Type): Type = tp match { case tp: TypeParamRef => diff --git a/tests/neg-macros/toexproftuple.scala b/tests/neg-macros/toexproftuple.scala index 7b69c578be70..f33bfd5f6dfb 100644 --- a/tests/neg-macros/toexproftuple.scala +++ b/tests/neg-macros/toexproftuple.scala @@ -1,22 +1,10 @@ import scala.quoted._, scala.deriving.* inline def mcr: Any = ${mcrImpl} - def mcrImpl(using ctx: Quotes): Expr[Any] = { - val tpl: (Expr[1], Expr[2], Expr[3]) = ('{1}, '{2}, '{3}) '{val res: (1, 3, 3) = ${Expr.ofTuple(tpl)}; res} // error - // ^^^^^^^^^^^^^^^^^ - // Found: quoted.Expr[(1 : Int) *: (2 : Int) *: (3 : Int) *: EmptyTuple] - // Required: quoted.Expr[((1 : Int), (3 : Int), (3 : Int))] val tpl2: (Expr[1], 2, Expr[3]) = ('{1}, 2, '{3}) '{val res = ${Expr.ofTuple(tpl2)}; res} // error - // ^ - // Cannot prove that (quoted.Expr[(1 : Int)], (2 : Int), quoted.Expr[(3 : Int)]) =:= scala.Tuple.Map[ - // scala.Tuple.InverseMap[ - // (quoted.Expr[(1 : Int)], (2 : Int), quoted.Expr[(3 : Int)]) - // , quoted.Expr] - // , quoted.Expr]. - } diff --git a/tests/neg/10349.scala b/tests/neg/10349.scala index 4ea683f6a8fb..b591c1a79abb 100644 --- a/tests/neg/10349.scala +++ b/tests/neg/10349.scala @@ -4,7 +4,7 @@ object Firsts: case Map[_, v] => First[Option[v]] def first[X](x: X): First[X] = x match - case x: Map[_, _] => first(x.values.headOption) // error + case x: Map[_, _] => first(x.values.headOption) @main def runFirsts2(): Unit = diff --git a/tests/neg/10747.scala b/tests/neg/10747.scala index a299f2a6590c..5275ebc84121 100644 --- a/tests/neg/10747.scala +++ b/tests/neg/10747.scala @@ -2,4 +2,5 @@ type Foo[A] = A match { case Int => String } -type B = Foo[Boolean] // error +type B = Foo[Boolean] +val _: B = "hello" // error diff --git a/tests/neg/i12049.check b/tests/neg/i12049.check index b44eb612f627..e0c2d498f119 100644 --- a/tests/neg/i12049.check +++ b/tests/neg/i12049.check @@ -15,22 +15,39 @@ | case B => String | | longer explanation available when compiling with `-explain` --- [E184] Type Error: tests/neg/i12049.scala:14:23 --------------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg/i12049.scala:14:17 ------------------------------------------------------------ 14 |val y3: String = ??? : Last[Int *: Int *: Boolean *: String *: EmptyTuple] // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Match type reduction failed since selector EmptyTuple - | matches none of the cases + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: Last[EmptyTuple] + | Required: String | - | case _ *: _ *: t => Last[t] - | case t *: EmptyTuple => t --- [E184] Type Error: tests/neg/i12049.scala:22:26 --------------------------------------------------------------------- + | Note: a match type could not be fully reduced: + | + | trying to reduce Last[EmptyTuple] + | failed since selector EmptyTuple + | matches none of the cases + | + | case _ *: _ *: t => Last[t] + | case t *: EmptyTuple => t + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/i12049.scala:22:20 ------------------------------------------------------------ 22 |val z3: (A, B, A) = ??? : Reverse[(A, B, A)] // error - | ^^^^^^^^^^^^^^^^^^ - | Match type reduction failed since selector A *: EmptyTuple.type - | matches none of the cases + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: Tuple.Concat[Reverse[A *: EmptyTuple.type], (B, A)] + | Required: (A, B, A) + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Tuple.Concat[Reverse[A *: EmptyTuple.type], (B, A)] + | trying to reduce Reverse[A *: EmptyTuple.type] + | failed since selector A *: EmptyTuple.type + | matches none of the cases | - | case t1 *: t2 *: ts => Tuple.Concat[Reverse[ts], (t2, t1)] - | case EmptyTuple => EmptyTuple + | case t1 *: t2 *: ts => Tuple.Concat[Reverse[ts], (t2, t1)] + | case EmptyTuple => EmptyTuple + | + | longer explanation available when compiling with `-explain` -- [E172] Type Error: tests/neg/i12049.scala:24:20 --------------------------------------------------------------------- 24 |val _ = summon[M[B]] // error | ^ @@ -45,22 +62,33 @@ | Therefore, reduction cannot advance to the remaining case | | case B => String --- [E184] Type Error: tests/neg/i12049.scala:25:26 --------------------------------------------------------------------- +-- [E172] Type Error: tests/neg/i12049.scala:25:78 --------------------------------------------------------------------- 25 |val _ = summon[String =:= Last[Int *: Int *: Boolean *: String *: EmptyTuple]] // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Match type reduction failed since selector EmptyTuple - | matches none of the cases + | ^ + | Cannot prove that String =:= Last[EmptyTuple]. + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Last[EmptyTuple] + | failed since selector EmptyTuple + | matches none of the cases | - | case _ *: _ *: t => Last[t] - | case t *: EmptyTuple => t --- [E184] Type Error: tests/neg/i12049.scala:26:29 --------------------------------------------------------------------- + | case _ *: _ *: t => Last[t] + | case t *: EmptyTuple => t +-- [E172] Type Error: tests/neg/i12049.scala:26:48 --------------------------------------------------------------------- 26 |val _ = summon[(A, B, A) =:= Reverse[(A, B, A)]] // error - | ^^^^^^^^^^^^^^^^^^ - | Match type reduction failed since selector A *: EmptyTuple.type - | matches none of the cases + | ^ + | Cannot prove that (A, B, A) =:= Tuple.Concat[Reverse[A *: EmptyTuple.type], (B, A)]. + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Tuple.Concat[Reverse[A *: EmptyTuple.type], (B, A)] + | trying to reduce Reverse[A *: EmptyTuple.type] + | failed since selector A *: EmptyTuple.type + | matches none of the cases | - | case t1 *: t2 *: ts => Tuple.Concat[Reverse[ts], (t2, t1)] - | case EmptyTuple => EmptyTuple + | case t1 *: t2 *: ts => Tuple.Concat[Reverse[ts], (t2, t1)] + | case EmptyTuple => EmptyTuple -- [E008] Not Found Error: tests/neg/i12049.scala:28:21 ---------------------------------------------------------------- 28 |val _ = (??? : M[B]).length // error | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/neg/i17944.check b/tests/neg/i17944.check index 80dfaac8c4c8..c969edccb46b 100644 --- a/tests/neg/i17944.check +++ b/tests/neg/i17944.check @@ -14,33 +14,3 @@ | Therefore, reduction cannot advance to the remaining case | | case _ *: t => test.FindField0[t, ("i" : String), scala.compiletime.ops.int.S[(0 : Int)]] - | trying to reduce test.FindField[(("s" : String) ->> String, ("i" : String) ->> Int), ("i" : String)] - | trying to reduce test.FindField0[(("s" : String) ->> String, ("i" : String) ->> Int), ("i" : String), (0 : Int)] - | failed since selector (("s" : String) ->> String, ("i" : String) ->> Int) - | does not match case (("i" : String) ->> f) *: _ => (f, (0 : Int)) - | and cannot be shown to be disjoint from it either. - | Therefore, reduction cannot advance to the remaining case - | - | case _ *: t => test.FindField0[t, ("i" : String), scala.compiletime.ops.int.S[(0 : Int)]] - | trying to reduce test.FindField0[(("s" : String) ->> String, ("i" : String) ->> Int), ("i" : String), (0 : Int)] - | failed since selector (("s" : String) ->> String, ("i" : String) ->> Int) - | does not match case (("i" : String) ->> f) *: _ => (f, (0 : Int)) - | and cannot be shown to be disjoint from it either. - | Therefore, reduction cannot advance to the remaining case - | - | case _ *: t => test.FindField0[t, ("i" : String), scala.compiletime.ops.int.S[(0 : Int)]] - | trying to reduce test.FindField[(("s" : String) ->> String, ("i" : String) ->> Int), ("i" : String)] - | trying to reduce test.FindField0[(("s" : String) ->> String, ("i" : String) ->> Int), ("i" : String), (0 : Int)] - | failed since selector (("s" : String) ->> String, ("i" : String) ->> Int) - | does not match case (("i" : String) ->> f) *: _ => (f, (0 : Int)) - | and cannot be shown to be disjoint from it either. - | Therefore, reduction cannot advance to the remaining case - | - | case _ *: t => test.FindField0[t, ("i" : String), scala.compiletime.ops.int.S[(0 : Int)]] - | trying to reduce test.FindField0[(("s" : String) ->> String, ("i" : String) ->> Int), ("i" : String), (0 : Int)] - | failed since selector (("s" : String) ->> String, ("i" : String) ->> Int) - | does not match case (("i" : String) ->> f) *: _ => (f, (0 : Int)) - | and cannot be shown to be disjoint from it either. - | Therefore, reduction cannot advance to the remaining case - | - | case _ *: t => test.FindField0[t, ("i" : String), scala.compiletime.ops.int.S[(0 : Int)]] diff --git a/tests/neg/i19949.scala b/tests/neg/i19949.scala new file mode 100644 index 000000000000..96a22e42e079 --- /dev/null +++ b/tests/neg/i19949.scala @@ -0,0 +1,9 @@ + +trait T[N]: + type M = N match + case 0 => Any + +val t: T[Double] = new T[Double] {} +val x: t.M = "hello" // error + +val z: T[Double]#M = "hello" // error diff --git a/tests/neg/matchtype-seq.check b/tests/neg/matchtype-seq.check index 980329d585dc..1e786b6714c6 100644 --- a/tests/neg/matchtype-seq.check +++ b/tests/neg/matchtype-seq.check @@ -1,19 +1,35 @@ --- [E184] Type Error: tests/neg/matchtype-seq.scala:9:11 --------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg/matchtype-seq.scala:9:18 ------------------------------------------------------ 9 | identity[T1[3]]("") // error - | ^^^^^ - | Match type reduction failed since selector (3 : Int) - | matches none of the cases + | ^^ + | Found: ("" : String) + | Required: Test.T1[(3 : Int)] | - | case (1 : Int) => Int - | case (2 : Int) => String --- [E184] Type Error: tests/neg/matchtype-seq.scala:10:11 -------------------------------------------------------------- + | Note: a match type could not be fully reduced: + | + | trying to reduce Test.T1[(3 : Int)] + | failed since selector (3 : Int) + | matches none of the cases + | + | case (1 : Int) => Int + | case (2 : Int) => String + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/matchtype-seq.scala:10:18 ----------------------------------------------------- 10 | identity[T1[3]](1) // error - | ^^^^^ - | Match type reduction failed since selector (3 : Int) - | matches none of the cases + | ^ + | Found: (1 : Int) + | Required: Test.T1[(3 : Int)] | - | case (1 : Int) => Int - | case (2 : Int) => String + | Note: a match type could not be fully reduced: + | + | trying to reduce Test.T1[(3 : Int)] + | failed since selector (3 : Int) + | matches none of the cases + | + | case (1 : Int) => Int + | case (2 : Int) => String + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg/matchtype-seq.scala:11:20 ----------------------------------------------------- 11 | identity[T1[Int]]("") // error | ^^ diff --git a/tests/pos/i18488.scala b/tests/pos/i18488.scala deleted file mode 100644 index c225a2c20711..000000000000 --- a/tests/pos/i18488.scala +++ /dev/null @@ -1,15 +0,0 @@ -trait AbstractTable[T] - -trait Query[E, U] - -class TableQuery[E <: AbstractTable[?]] extends Query[E, Extract[E]] - -type Extract[E] = E match - case AbstractTable[t] => t - -trait BaseCrudRepository[E[T[_]]]: - - type EntityTable <: AbstractTable[E[Option]] - - def filterById: Query[EntityTable, Extract[EntityTable]] = - new TableQuery[EntityTable] diff --git a/tests/pos/i19434.scala b/tests/pos/i19434.scala new file mode 100644 index 000000000000..e8595fa252d0 --- /dev/null +++ b/tests/pos/i19434.scala @@ -0,0 +1,11 @@ + +object Test: + + object Named: + opaque type Named[name <: String & Singleton, A] >: A = A + + type DropNames[T <: Tuple] = T match + case Named.Named[_, x] *: xs => x *: DropNames[xs] + case _ => T + + def f[T <: Tuple]: DropNames[T] = ??? \ No newline at end of file diff --git a/tests/pos/i19950.scala b/tests/pos/i19950.scala new file mode 100644 index 000000000000..349140f43ff5 --- /dev/null +++ b/tests/pos/i19950.scala @@ -0,0 +1,10 @@ + +trait Apply[F[_]]: + extension [T <: NonEmptyTuple](tuple: T)(using toMap: Tuple.IsMappedBy[F][T]) + def mapN[B](f: Tuple.InverseMap[T, F] => B): F[B] = ??? + +given Apply[Option] = ??? +given Apply[List] = ??? +given Apply[util.Try] = ??? + +@main def Repro = (Option(1), Option(2), Option(3)).mapN(_ + _ + _) \ No newline at end of file diff --git a/tests/neg/12974.scala b/tests/warn/12974.scala similarity index 94% rename from tests/neg/12974.scala rename to tests/warn/12974.scala index 90edcc916471..45029602296f 100644 --- a/tests/neg/12974.scala +++ b/tests/warn/12974.scala @@ -23,7 +23,7 @@ object RecMap { def main(args: Array[String]) = import Record._ - val foo: Any = Rec.empty.fetch("foo") // error + val foo: Any = Rec.empty.fetch("foo") // TODO // ^ // Match type reduction failed since selector EmptyTuple.type // matches none of the cases