Skip to content

Commit

Permalink
Make match types with no matching cases not an error
Browse files Browse the repository at this point in the history
Modify the MatchReducer to return NoType in the case no matches, rather than throwing an 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.

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 emmit a warning ?
  • Loading branch information
EugeneFlesselle committed Mar 15, 2024
1 parent bb3f891 commit 736630d
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 47 deletions.
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3594,8 +3594,12 @@ 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")
/* 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
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 @@ -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(", ")}"

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 @@ -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] =
Expand Down
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
88 changes: 64 additions & 24 deletions tests/neg/i12049.check
Original file line number Diff line number Diff line change
Expand Up @@ -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
| ^
Expand All @@ -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
| ^^^^^^^^^^^^^^^^^^^
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i13757-match-type-anykind.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
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 @@
-- 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
| ^^
Expand Down
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

0 comments on commit 736630d

Please sign in to comment.