From c79c6644d5739c3376e5eec0a22649360cb9b1b4 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 15 Mar 2024 12:29:10 +0100 Subject: [PATCH 1/4] 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 b2c544ab80786f864b7dbb147a29a1982e063524 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 15 Mar 2024 12:46:16 +0100 Subject: [PATCH 2/4] 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. --- .../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 +-- 7 files changed, 70 insertions(+), 39 deletions(-) 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 ed5ddfe85c86b8d7c34e0c6a1192f2c3e4ec1ae9 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Fri, 15 Mar 2024 13:17:42 +0100 Subject: [PATCH 3/4] 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 be4065ea9c57c043ec2fd776ad324f45e6f300aa Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Sun, 17 Mar 2024 09:47:30 +0100 Subject: [PATCH 4/4] Only throw TypeErrors for match types with no cases if ctx.isTyper --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index c26512232c6b..56be943292fa 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3594,8 +3594,14 @@ 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") + if ctx.isTyper then + val casesText = MatchTypeTrace.noMatchesText(scrut, cases) + throw MatchTypeReductionError(em"Match type reduction $casesText") + else + NoType + /* The match type is left unreduced if an error can not be reported + * See pos/constvalue-of-failed-match-type.scala for an example + */ inFrozenConstraint(recur(cases)) }