Skip to content

Commit

Permalink
ErrorType instead of throwing in match type "no cases"
Browse files Browse the repository at this point in the history
Instead of throwing MatchTypeReductionError, return
ErrorType(MatchTypeNoCases), which is a proper message as well.

This avoids having to catch and ignore it as an exception.  But it does
require discovering it from type simplification and reporting it then -
which replaces its reliance on catching TypeErrors.

It also required handling scrutinees that are error types, which
previously would always match the first case, due to FlexType semantics.
  • Loading branch information
dwijnand committed Jun 23, 2023
1 parent d0f9a51 commit 6b41901
Show file tree
Hide file tree
Showing 11 changed files with 42 additions and 78 deletions.
13 changes: 1 addition & 12 deletions compiler/src/dotty/tools/dotc/core/MatchTypeTrace.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ object MatchTypeTrace:

private enum TraceEntry:
case TryReduce(scrut: Type)
case NoMatches(scrut: Type, cases: List[Type])
case Stuck(scrut: Type, stuckCase: Type, otherCases: List[Type])
case NoInstance(scrut: Type, stuckCase: Type, fails: List[(Name, TypeBounds)])
case EmptyScrutinee(scrut: Type)
Expand Down Expand Up @@ -51,12 +50,6 @@ 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[Type])(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 Down Expand Up @@ -99,11 +92,6 @@ 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 All @@ -127,6 +115,7 @@ object MatchTypeTrace:
| The computed bounds for the $params are:
| ${fails.map((name, bounds) => i"$name$bounds")}%\n %"""

/** The failure message when the scrutinee `scrut` does not match any case in `cases`. */
def noMatchesText(scrut: Type, cases: List[Type])(using Context): String =
i"""failed since selector $scrut
|matches none of the cases
Expand Down
12 changes: 11 additions & 1 deletion compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3223,7 +3223,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
tp
case Nil =>
val casesText = MatchTypeTrace.noMatchesText(scrut, cases)
throw MatchTypeReductionError(em"Match type reduction $casesText")
ErrorType(reporting.MatchTypeNoCases(casesText))

inFrozenConstraint {
// Empty types break the basic assumption that if a scrutinee and a
Expand All @@ -3242,6 +3242,16 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
if (provablyEmpty(scrut))
MatchTypeTrace.emptyScrutinee(scrut)
NoType
else 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)
}
Expand Down
3 changes: 0 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,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(", ")}"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case UnqualifiedCallToAnyRefMethodID // errorNumber: 181
case NotConstantID // errorNumber: 182
case ClosureCannotHaveInternalParameterDependenciesID // errorNumber: 183
case MatchTypeNoCasesID // errorNumber: 184

def errorNumber = ordinal - 1

Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2916,6 +2916,10 @@ class UnusedNonUnitValue(tp: Type)(using Context)
def msg(using Context) = i"unused value of type $tp"
def explain(using Context) = ""

class MatchTypeNoCases(casesText: String)(using Context) extends TypeMsg(MatchTypeNoCasesID):
def msg(using Context) = i"Match type reduction $casesText"
def explain(using Context) = ""

class MatchTypeScrutineeCannotBeHigherKinded(tp: Type)(using Context)
extends TypeMsg(MatchTypeScrutineeCannotBeHigherKindedID) :
def msg(using Context) = i"the scrutinee of a match type cannot be higher-kinded"
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ trait ImplicitRunInfo:
traverseChildren(t)
case t: MatchType =>
traverseChildren(t)
traverse(try t.normalized catch case _: MatchTypeReductionError => t)
traverse(t.normalized)
case MatchType.InDisguise(mt)
if !t.isInstanceOf[LazyRef] // skip recursive applications (eg. Tuple.Map)
=>
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3138,7 +3138,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case xtree: untpd.NameTree => typedNamed(xtree, pt)
case xtree => typedUnnamed(xtree)

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
catch case ex: TypeError => errorTree(xtree, ex, xtree.srcPos.focus)
// use focussed sourcePos since tree might be a large definition
// and a large error span would hide all errors in interior.
Expand Down
49 changes: 4 additions & 45 deletions tests/neg-macros/toexproftuple.scala
Original file line number Diff line number Diff line change
@@ -1,33 +1,8 @@
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
import scala.quoted._, scala.deriving.*

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
inline def mcr: Any = ${mcrImpl}

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
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
Expand All @@ -36,28 +11,12 @@ def mcrImpl(using ctx: Quotes): Expr[Any] = { // error // error
// 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
'{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].

// ^
// 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
}
16 changes: 8 additions & 8 deletions tests/neg/i12049.check
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
| case B => String
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg/i12049.scala:14:23 ---------------------------------------------------------------------------------
-- [E183] Type 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
| matches none of the cases
|
| case _ *: _ *: t => Last[t]
| case t *: EmptyTuple => t
-- Error: tests/neg/i12049.scala:22:26 ---------------------------------------------------------------------------------
-- [E183] Type 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
|
Expand All @@ -45,17 +45,17 @@
| Therefore, reduction cannot advance to the remaining case
|
| case B => String
-- Error: tests/neg/i12049.scala:25:26 ---------------------------------------------------------------------------------
-- [E183] Type 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
| matches none of the cases
|
| case _ *: _ *: t => Last[t]
| case t *: EmptyTuple => t
-- Error: tests/neg/i12049.scala:26:29 ---------------------------------------------------------------------------------
-- [E183] Type 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
|
Expand Down
8 changes: 4 additions & 4 deletions tests/neg/i13757-match-type-anykind.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
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
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
8 changes: 4 additions & 4 deletions tests/neg/matchtype-seq.check
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
-- Error: tests/neg/matchtype-seq.scala:9:11 ---------------------------------------------------------------------------
-- [E183] Type 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
-- Error: tests/neg/matchtype-seq.scala:10:11 --------------------------------------------------------------------------
-- [E183] Type 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
|
Expand Down

0 comments on commit 6b41901

Please sign in to comment.