From 7b69d3304972b94fecd01788784def2db00ba269 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 15 Mar 2024 12:29:10 +0100 Subject: [PATCH 1/7] Add regression tests --- tests/neg/i19949.scala | 9 +++++++++ tests/pos/i19950.scala | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/neg/i19949.scala create mode 100644 tests/pos/i19950.scala 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/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 From 7460ab3ca001eafaa544dca152b751a328b4be06 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 15 Mar 2024 12:46:16 +0100 Subject: [PATCH 2/7] Revert "ErrorType instead of throwing in match type "no cases"" This reverts commit 9ae1598e Note that the changes in Typer: ``` val unsimplifiedType = result.tpe simplify(result, pt, locked) result.tpe.stripTypeVar match case e: ErrorType if !unsimplifiedType.isErroneous => errorTree(xtree, e.msg, xtree.srcPos) case _ => result ``` cannot be reverted yet since the MatchReducer now also reduces to an `ErrorType` for MatchTypeLegacyPatterns, introduced after 9ae1598e. --- .../tools/dotc/core/MatchTypeTrace.scala | 12 +++++ .../dotty/tools/dotc/core/TypeComparer.scala | 19 ++----- .../dotty/tools/dotc/core/TypeErrors.scala | 3 ++ .../dotty/tools/dotc/typer/Implicits.scala | 2 +- tests/neg-macros/toexproftuple.scala | 49 +++++++++++++++++-- tests/neg/i12049.check | 20 ++++---- tests/neg/i13757-match-type-anykind.scala | 8 +-- tests/neg/matchtype-seq.check | 8 +-- 8 files changed, 82 insertions(+), 39 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala index fb278ab92dc9..145c7cf5e9eb 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. @@ -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/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 302ad7987889..c26512232c6b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3595,22 +3595,9 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { 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) - } + throw MatchTypeReductionError(em"Match type reduction $casesText") + + inFrozenConstraint(recur(cases)) } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 240bc4eebd84..eda3910f44fc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -53,6 +53,9 @@ object TypeError: def toMessage(using Context) = msg end TypeError +class MatchTypeReductionError(msg: Message)(using Context) extends TypeError: + def toMessage(using Context) = msg + class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name])(using Context) extends TypeError: def toMessage(using Context) = em"malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}" diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 5162b3fed1b9..f3abe87ed765 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -663,7 +663,7 @@ trait ImplicitRunInfo: traverseChildren(t) case t => traverseChildren(t) - traverse(t.normalized) + traverse(try t.normalized catch case _: MatchTypeReductionError => t) catch case ex: Throwable => handleRecursive("collectParts of", t.show, ex) def apply(tp: Type): collection.Set[Type] = diff --git a/tests/neg-macros/toexproftuple.scala b/tests/neg-macros/toexproftuple.scala index 7b69c578be70..20ae2f08ff8d 100644 --- a/tests/neg-macros/toexproftuple.scala +++ b/tests/neg-macros/toexproftuple.scala @@ -1,8 +1,33 @@ -import scala.quoted._, scala.deriving.* +import scala.quoted._, scala.deriving.* // error +// ^ +// Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) +// matches none of the cases +// +// case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] +// case EmptyTuple => EmptyTuple -inline def mcr: Any = ${mcrImpl} +inline def mcr: Any = ${mcrImpl} // error +// ^ +// Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) +// matches none of the cases +// +// case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] +// case EmptyTuple => EmptyTuple -def mcrImpl(using ctx: Quotes): Expr[Any] = { +def mcrImpl(using ctx: Quotes): Expr[Any] = { // error // error + //^ + // Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) + // matches none of the cases + // + // case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] + // case EmptyTuple => EmptyTuple + + // ^ + // Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) + // matches none of the cases + // + // case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] + // case EmptyTuple => EmptyTuple val tpl: (Expr[1], Expr[2], Expr[3]) = ('{1}, '{2}, '{3}) '{val res: (1, 3, 3) = ${Expr.ofTuple(tpl)}; res} // error @@ -11,7 +36,7 @@ def mcrImpl(using ctx: Quotes): Expr[Any] = { // 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 + '{val res = ${Expr.ofTuple(tpl2)}; res} // error // error // error // error // ^ // Cannot prove that (quoted.Expr[(1 : Int)], (2 : Int), quoted.Expr[(3 : Int)]) =:= scala.Tuple.Map[ // scala.Tuple.InverseMap[ @@ -19,4 +44,20 @@ def mcrImpl(using ctx: Quotes): Expr[Any] = { // , quoted.Expr] // , quoted.Expr]. + // ^ + // Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) + // matches none of the cases + // + // case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] + // case EmptyTuple => EmptyTuple + + // ^ + // Cyclic reference involving val res + + // ^ + // Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) + // matches none of the cases + // + // case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] + // case EmptyTuple => EmptyTuple } diff --git a/tests/neg/i12049.check b/tests/neg/i12049.check index b44eb612f627..11c648e35a57 100644 --- a/tests/neg/i12049.check +++ b/tests/neg/i12049.check @@ -15,17 +15,17 @@ | case B => String | | longer explanation available when compiling with `-explain` --- [E184] Type Error: tests/neg/i12049.scala:14:23 --------------------------------------------------------------------- +-- Error: tests/neg/i12049.scala:14:23 --------------------------------------------------------------------------------- 14 |val y3: String = ??? : Last[Int *: Int *: Boolean *: String *: EmptyTuple] // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Match type reduction failed since selector EmptyTuple + | ^ + | Match type reduction failed since selector EmptyTuple.type | matches none of the cases | | case _ *: _ *: t => Last[t] | case t *: EmptyTuple => t --- [E184] Type Error: tests/neg/i12049.scala:22:26 --------------------------------------------------------------------- +-- Error: tests/neg/i12049.scala:22:26 --------------------------------------------------------------------------------- 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 | @@ -45,17 +45,17 @@ | Therefore, reduction cannot advance to the remaining case | | case B => String --- [E184] Type Error: tests/neg/i12049.scala:25:26 --------------------------------------------------------------------- +-- Error: tests/neg/i12049.scala:25:26 --------------------------------------------------------------------------------- 25 |val _ = summon[String =:= Last[Int *: Int *: Boolean *: String *: EmptyTuple]] // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Match type reduction failed since selector EmptyTuple + | ^ + | Match type reduction failed since selector EmptyTuple.type | matches none of the cases | | case _ *: _ *: t => Last[t] | case t *: EmptyTuple => t --- [E184] Type Error: tests/neg/i12049.scala:26:29 --------------------------------------------------------------------- +-- Error: tests/neg/i12049.scala:26:29 --------------------------------------------------------------------------------- 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 | diff --git a/tests/neg/i13757-match-type-anykind.scala b/tests/neg/i13757-match-type-anykind.scala index a80e8b2b289b..d8273e546dab 100644 --- a/tests/neg/i13757-match-type-anykind.scala +++ b/tests/neg/i13757-match-type-anykind.scala @@ -1,16 +1,16 @@ object Test: - type AnyKindMatchType1[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded + type AnyKindMatchType1[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded // error case Option[a] => Int type AnyKindMatchType2[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded case Option => Int // error: Missing type parameter for Option - type AnyKindMatchType3[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded + type AnyKindMatchType3[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded // error case _ => Int - type AnyKindMatchType4[X <: Option] = X match // error // error: the scrutinee of a match type cannot be higher-kinded + type AnyKindMatchType4[X <: Option] = X match // error // error: the scrutinee of a match type cannot be higher-kinded // error case _ => Int - type AnyKindMatchType5[X[_]] = X match // error: the scrutinee of a match type cannot be higher-kinded + type AnyKindMatchType5[X[_]] = X match // error: the scrutinee of a match type cannot be higher-kinded // error case _ => Int end Test diff --git a/tests/neg/matchtype-seq.check b/tests/neg/matchtype-seq.check index 980329d585dc..b72200868d81 100644 --- a/tests/neg/matchtype-seq.check +++ b/tests/neg/matchtype-seq.check @@ -1,14 +1,14 @@ --- [E184] Type Error: tests/neg/matchtype-seq.scala:9:11 --------------------------------------------------------------- +-- Error: tests/neg/matchtype-seq.scala:9:11 --------------------------------------------------------------------------- 9 | identity[T1[3]]("") // error - | ^^^^^ + | ^ | Match type reduction failed since selector (3 : Int) | matches none of the cases | | case (1 : Int) => Int | case (2 : Int) => String --- [E184] Type Error: tests/neg/matchtype-seq.scala:10:11 -------------------------------------------------------------- +-- Error: tests/neg/matchtype-seq.scala:10:11 -------------------------------------------------------------------------- 10 | identity[T1[3]](1) // error - | ^^^^^ + | ^ | Match type reduction failed since selector (3 : Int) | matches none of the cases | From 5becaace6495a8e1af69951d9ffef54406f722d1 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 15 Mar 2024 13:17:42 +0100 Subject: [PATCH 3/7] Update check-files and remove i18488.scala i18488.scala was only passing because of the bug in the MatchReducer, as we can see in the subtyping trace: ``` ==> isSubType TableQuery[BaseCrudRepository.this.EntityTable] <:< Query[BaseCrudRepository.this.EntityTable, E[Option]]? ==> isSubType Query[BaseCrudRepository.this.EntityTable, Extract[BaseCrudRepository.this.EntityTable]] <:< Query[BaseCrudRepository.this.EntityTable, E[Option]] (left is approximated)? ==> isSubType E[Option] <:< Extract[BaseCrudRepository.this.EntityTable]? ==> isSubType [T[_$1]] =>> Any <:< Extract? ==> isSubType Any <:< Extract[T]? ==> isSubType Any <:< T match { case AbstractTable[t] => t } <: t (right is approximated)? ==> isSubType Any <:< t> (right is approximated)? <== isSubType Any <:< t> (right is approximated) = true <== isSubType Any <:< T match { case AbstractTable[t] => t } <: t (right is approximated) = true <== isSubType Any <:< Extract[T] = true <== isSubType [T[_$1]] =>> Any <:< Extract = true ... <== isSubType Extract[BaseCrudRepository.this.EntityTable] <:< E[Option] = true <== isSubType Query[BaseCrudRepository.this.EntityTable, Extract[BaseCrudRepository.this.EntityTable]] <:< Query[BaseCrudRepository.this.EntityTable, E[Option]] (left is approximated) = true <== isSubType TableQuery[BaseCrudRepository.this.EntityTable] <:< Query[BaseCrudRepository.this.EntityTable, E[Option]] = true ``` --- tests/neg/i12049.check | 4 ++-- tests/neg/i13757-match-type-anykind.scala | 4 ++-- tests/pos/i18488.scala | 15 --------------- 3 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 tests/pos/i18488.scala diff --git a/tests/neg/i12049.check b/tests/neg/i12049.check index 11c648e35a57..4977b8d8c591 100644 --- a/tests/neg/i12049.check +++ b/tests/neg/i12049.check @@ -18,7 +18,7 @@ -- Error: tests/neg/i12049.scala:14:23 --------------------------------------------------------------------------------- 14 |val y3: String = ??? : Last[Int *: Int *: Boolean *: String *: EmptyTuple] // error | ^ - | Match type reduction failed since selector EmptyTuple.type + | Match type reduction failed since selector EmptyTuple | matches none of the cases | | case _ *: _ *: t => Last[t] @@ -48,7 +48,7 @@ -- Error: tests/neg/i12049.scala:25:26 --------------------------------------------------------------------------------- 25 |val _ = summon[String =:= Last[Int *: Int *: Boolean *: String *: EmptyTuple]] // error | ^ - | Match type reduction failed since selector EmptyTuple.type + | Match type reduction failed since selector EmptyTuple | matches none of the cases | | case _ *: _ *: t => Last[t] diff --git a/tests/neg/i13757-match-type-anykind.scala b/tests/neg/i13757-match-type-anykind.scala index d8273e546dab..3feb9907fb69 100644 --- a/tests/neg/i13757-match-type-anykind.scala +++ b/tests/neg/i13757-match-type-anykind.scala @@ -1,11 +1,11 @@ object Test: - type AnyKindMatchType1[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded // error + type AnyKindMatchType1[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded case Option[a] => Int type AnyKindMatchType2[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded case Option => Int // error: Missing type parameter for Option - type AnyKindMatchType3[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded // error + type AnyKindMatchType3[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded case _ => Int type AnyKindMatchType4[X <: Option] = X match // error // error: the scrutinee of a match type cannot be higher-kinded // 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] From d7946bf928bef748ee509cc709570b308e94fba7 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Sat, 16 Mar 2024 18:28:26 +0100 Subject: [PATCH 4/7] Make match types with no matching cases not an error Modify the MatchReducer to return NoType in the case of no matches, rather than throwing a MatchTypeReductionError. This makes it consistent with the other match type reduction failures, where being stuck does not result in an error, but simply in an unreduced match type. We still get the explanations of the underlying error in the MatchTypeTrace, but in positions which need the reduction for conformance, rather than at application site of the match type. --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 4 ++-- compiler/src/dotty/tools/dotc/core/TypeErrors.scala | 3 --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index c26512232c6b..b07ec01aa75b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3594,8 +3594,8 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { MatchTypeTrace.emptyScrutinee(scrut) NoType case Nil => - val casesText = MatchTypeTrace.noMatchesText(scrut, cases) - throw MatchTypeReductionError(em"Match type reduction $casesText") + MatchTypeTrace.noMatches(scrut, cases) + NoType inFrozenConstraint(recur(cases)) } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index eda3910f44fc..240bc4eebd84 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -53,9 +53,6 @@ object TypeError: def toMessage(using Context) = msg end TypeError -class MatchTypeReductionError(msg: Message)(using Context) extends TypeError: - def toMessage(using Context) = msg - class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name])(using Context) extends TypeError: def toMessage(using Context) = em"malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}" diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index f3abe87ed765..5162b3fed1b9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -663,7 +663,7 @@ trait ImplicitRunInfo: traverseChildren(t) case t => traverseChildren(t) - traverse(try t.normalized catch case _: MatchTypeReductionError => t) + traverse(t.normalized) catch case ex: Throwable => handleRecursive("collectParts of", t.show, ex) def apply(tp: Type): collection.Set[Type] = From 61c28329e4850ea7f0a0e93e354ea10deccc727b Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Sat, 16 Mar 2024 18:30:57 +0100 Subject: [PATCH 5/7] Update check-files and error annotations The diff in neg/10349.scala is quite interesting. With a few intermediate values: ```scala type First[X] = X match case Map[_, v] => First[Option[v]] def first[X](x: X): First[X] = x match case x: Map[k, v] => val hdOpt: Option[v] = x.values.headOption first(hdOpt): First[Option[v]] // error only before changes ``` This now type-checks but will fail at runtime because of the in-exhaustivity of the match expression. Perhaps we should add some additional condition in `isMatchTypeShaped` to account for this, or at least emit a warning ? --- .../dotty/tools/dotc/core/TypeComparer.scala | 4 + tests/neg-macros/toexproftuple.scala | 61 +------------ tests/neg/10349.scala | 2 +- tests/neg/10747.scala | 3 +- tests/neg/i12049.check | 88 ++++++++++++++----- tests/neg/i13757-match-type-anykind.scala | 4 +- tests/neg/matchtype-seq.check | 40 ++++++--- tests/{neg => warn}/12974.scala | 2 +- 8 files changed, 106 insertions(+), 98 deletions(-) rename tests/{neg => warn}/12974.scala (94%) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index b07ec01aa75b..73b45117cc2d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3594,6 +3594,10 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { MatchTypeTrace.emptyScrutinee(scrut) NoType case Nil => + /* 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 diff --git a/tests/neg-macros/toexproftuple.scala b/tests/neg-macros/toexproftuple.scala index 20ae2f08ff8d..f33bfd5f6dfb 100644 --- a/tests/neg-macros/toexproftuple.scala +++ b/tests/neg-macros/toexproftuple.scala @@ -1,63 +1,10 @@ -import scala.quoted._, scala.deriving.* // error -// ^ -// Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) -// matches none of the cases -// -// case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] -// case EmptyTuple => EmptyTuple - -inline def mcr: Any = ${mcrImpl} // error -// ^ -// Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) -// matches none of the cases -// -// case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] -// case EmptyTuple => EmptyTuple - -def mcrImpl(using ctx: Quotes): Expr[Any] = { // error // error - //^ - // Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) - // matches none of the cases - // - // case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] - // case EmptyTuple => EmptyTuple - - // ^ - // Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) - // matches none of the cases - // - // case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] - // case EmptyTuple => EmptyTuple +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 // error // error // 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]. - - // ^ - // Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) - // matches none of the cases - // - // case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] - // case EmptyTuple => EmptyTuple - - // ^ - // Cyclic reference involving val res - - // ^ - // Match type reduction failed since selector ((2 : Int), quoted.Expr[(3 : Int)]) - // matches none of the cases - // - // case quoted.Expr[x] *: t => x *: scala.Tuple.InverseMap[t, quoted.Expr] - // case EmptyTuple => EmptyTuple + '{val res = ${Expr.ofTuple(tpl2)}; res} // error } 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 4977b8d8c591..b9d3a8434015 100644 --- a/tests/neg/i12049.check +++ b/tests/neg/i12049.check @@ -15,22 +15,45 @@ | case B => String | | longer explanation available when compiling with `-explain` --- 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 --- 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 + | 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 +68,39 @@ | Therefore, reduction cannot advance to the remaining case | | case B => String --- 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 --- 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 + | 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/i13757-match-type-anykind.scala b/tests/neg/i13757-match-type-anykind.scala index 3feb9907fb69..a80e8b2b289b 100644 --- a/tests/neg/i13757-match-type-anykind.scala +++ b/tests/neg/i13757-match-type-anykind.scala @@ -8,9 +8,9 @@ object Test: type AnyKindMatchType3[X <: AnyKind] = X match // error: the scrutinee of a match type cannot be higher-kinded case _ => Int - type AnyKindMatchType4[X <: Option] = X match // error // error: the scrutinee of a match type cannot be higher-kinded // error + type AnyKindMatchType4[X <: Option] = X match // error // error: the scrutinee of a match type cannot be higher-kinded case _ => Int - type AnyKindMatchType5[X[_]] = X match // error: the scrutinee of a match type cannot be higher-kinded // error + type AnyKindMatchType5[X[_]] = X match // error: the scrutinee of a match type cannot be higher-kinded case _ => Int end Test diff --git a/tests/neg/matchtype-seq.check b/tests/neg/matchtype-seq.check index b72200868d81..1e786b6714c6 100644 --- a/tests/neg/matchtype-seq.check +++ b/tests/neg/matchtype-seq.check @@ -1,19 +1,35 @@ --- 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 --- 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/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 From 232923668274adf1b8902fa05dc5686a090463d4 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Wed, 20 Mar 2024 14:59:55 +0100 Subject: [PATCH 6/7] Only record in MatchTypeTrace if not previously attempted `recurseWith` can be called with the same scrutinee (even if match type reduction is cached) if it is an applied match alias For example, `Tuple.Head[Tuple.Tail[T]]` will attempt to reduce `Tuple.Tail[T]` twice: - once as an argument of the match alias `Head`, and - once as a scrutinee in body of `Head` (after the substitution). --- .../tools/dotc/core/MatchTypeTrace.scala | 2 +- tests/neg/i12049.check | 12 -------- tests/neg/i17944.check | 30 ------------------- 3 files changed, 1 insertion(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala index 145c7cf5e9eb..e16a950aa32a 100644 --- a/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala +++ b/compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala @@ -78,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 diff --git a/tests/neg/i12049.check b/tests/neg/i12049.check index b9d3a8434015..e0c2d498f119 100644 --- a/tests/neg/i12049.check +++ b/tests/neg/i12049.check @@ -46,12 +46,6 @@ | | case t1 *: t2 *: ts => Tuple.Concat[Reverse[ts], (t2, t1)] | case EmptyTuple => EmptyTuple - | 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 | | longer explanation available when compiling with `-explain` -- [E172] Type Error: tests/neg/i12049.scala:24:20 --------------------------------------------------------------------- @@ -95,12 +89,6 @@ | | case t1 *: t2 *: ts => Tuple.Concat[Reverse[ts], (t2, t1)] | case EmptyTuple => EmptyTuple - | 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 -- [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)]] From 2beb67e908941ff71aafce163c3c0df766cb1622 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Tue, 26 Mar 2024 12:45:23 +0100 Subject: [PATCH 7/7] Do match type reduction atPhaseNoLater than ElimOpaque If a match type pattern is an opaque type, we use its bounds when checking the validity of the pattern. Following the ElimOpaque phase however, the pattern is beta-reduced (as normal applied type aliases), which may result in an illegal pattern. --- compiler/src/dotty/tools/dotc/core/Phases.scala | 4 ++++ compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- tests/pos/i19434.scala | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i19434.scala 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/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/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