Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update unreducible match types error reporting #19954

Merged
merged 7 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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)."""
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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
Expand Down
25 changes: 8 additions & 17 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
12 changes: 0 additions & 12 deletions tests/neg-macros/toexproftuple.scala
Original file line number Diff line number Diff line change
@@ -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].

}
2 changes: 1 addition & 1 deletion tests/neg/10349.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
3 changes: 2 additions & 1 deletion tests/neg/10747.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
76 changes: 52 additions & 24 deletions tests/neg/i12049.check
Original file line number Diff line number Diff line change
Expand Up @@ -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
| ^
Expand All @@ -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
| ^^^^^^^^^^^^^^^^^^^
Expand Down
30 changes: 0 additions & 30 deletions tests/neg/i17944.check
Original file line number Diff line number Diff line change
Expand Up @@ -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)]]
9 changes: 9 additions & 0 deletions tests/neg/i19949.scala
Original file line number Diff line number Diff line change
@@ -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
40 changes: 28 additions & 12 deletions tests/neg/matchtype-seq.check
Original file line number Diff line number Diff line change
@@ -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
| ^^
Expand Down
15 changes: 0 additions & 15 deletions tests/pos/i18488.scala

This file was deleted.

11 changes: 11 additions & 0 deletions tests/pos/i19434.scala
Original file line number Diff line number Diff line change
@@ -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] = ???
10 changes: 10 additions & 0 deletions tests/pos/i19950.scala
Original file line number Diff line number Diff line change
@@ -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(_ + _ + _)
2 changes: 1 addition & 1 deletion tests/neg/12974.scala → tests/warn/12974.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading