-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update unreducible match types error reporting (#19954)
Match type reduction can fail for any of the following reasons: - EmptyScrutinee: would be unsound to reduce - Stuck: selector does not match a case and is not provably disjoint from it either - NoInstance: selector does not uniquely determine params captures in pattern - NoMatches: selector matches none of the cases - LegacyPattern: match type contains an illegal case and sourceVersion >= 3.4 Out of those, only Stuck and NoInstance, *could* get reduced in a refined context. ## Status quo The match reducer returns: - `ErrorType` for NoMatches and LegacyPattern, - `NoType`, which implies the match type is left unreduced, in all other cases. In addition, the implementation has an issue where the `ErrorType`s can be left unreported, then entering the flexible type logic, thereby conforming to anything. ## Proposed changes In addition to fixing the aforementioned bug, this PR proposes to leave all unreducible match types as unreduced. Of course the reduction may be needed at a later point for conformance, in which case the error message will still contain the same explanations from the `MatchTypeTrace`. Fixes #19949 Fixes #19950 ## Discussion All cases of failed match type reductions which we know will never reduce, even with refined scrutinee, should have a consistent behaviour. So NoMatches and EmptyScrutinee should either both be an error or both be left unreduced. The current implementation attempts to do the former approach (but only for NoMatches), which has some limitations as discussed below (I'm not saying I can do better, hence the latter approach). ### Undesirable errors We dont always want an error for a NoMatches failed reduction, for example if we just need `Nothing` to conform to it: ```scala 3 trait TupleWrap[T <: Tuple]: def head: Tuple.Head[T] object EmptyTupleWrap extends TupleWrap[EmptyTuple]: def head = throw NoSuchElementException() // Error: // | ^ // | Match type reduction failed since selector EmptyTuple // | matches none of the cases ``` But we could do `def head: Nothing = ...` to avoid the error here. Generally speaking, places where the bounds of the match type suffice can still get a reduction error, and adding an ascription to avoid an inferred match type doesn't always do the trick. Another refused example could be: ```scala 3 type Default[N <: Int] = N match case 0 => 'a' | 'c' case 1 => 'b' | 'd' def default(n: Int): Option[Default[n.type]] = n match case _: (0 | 1) => Some[Default[n.type]]: n match case _: 0 => 'a' case _: 1 => 'b' case _ => None default(2): Option[Char] // Error // | ^ // | Match type reduction failed since selector (2 : Int) // | matches none of the cases ``` even though the function looks reasonable and type-checking would be sound. ### Missed errors Also note in the `EmptyTupleWrap` example, we get a reduction error from a match type application which does not appear in the source code. A valid question might be when and for what exactly these conditions are checked ? The goal is to report a type error early on for a NoMatches application right, but we are actually only doing so if we happen to do `tryNormalize` and end up in the `MatchReducer`. Here is an example where were a match type with NoMatches is accepted ```scala 3 trait A: type X type R = X match case 0 => 'a' case 1 => 'b' trait B extends A: type S = 2 type R1 = B#R // no error ``` Generally speaking, the NoMatches error can be circumvented with: ```scala 3 type AllowNoMatchesM[X] = { type X1 = X type R = X1 match case 0 => 'a' case 1 => 'b' }#R type R2 = AllowNoMatchesM[2] // no error ``` Also note the projections are used in the examples for simplicity but are not necessary, `R` *can be* used within `B` as unreduced without a reported error. See #19799 for another example of inconsistent errors
- Loading branch information
Showing
12 changed files
with
124 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]. | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(_ + _ + _) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters