Open Atry opened 1 year ago
It fails for all abstract types
import scala.quoted.*
object X:
opaque type Opaque[T] = Array[T]
type Invariant[T]
type Covariant[+T]
class InvariantClass[T]
class CovariantClass[+T]
import X.*
inline def testTypeMatch(): Unit =
${testTypeMatchExpr}
def testTypeMatchExpr(using Quotes) =
Type.of[Opaque[Int]] match
case '[Opaque[t]] => // fails
Type.of[Invariant[Int]] match
case '[Invariant[t]] => // fails
Type.of[Covariant[Int]] match
case '[Covariant[t]] => // fails
Type.of[CovariantClass[Int]] match
case '[CovariantClass[t]] =>
Type.of[InvariantClass[Int]] match
case '[InvariantClass[t]] =>
Type.of[CovariantClass[Int]] match
case '[CovariantClass[t]] =>
'{}
def test = testTypeMatch()
The issue is that we have a scrutinee.tpe <:< pattern.tpe
where we compare F[Int] <:< F[t]
with no constraints of on t
we get false
if F
is a generic type. Otherwise, if F
is a class we get true
, and the constraint is added.
@abgruszecki do you have some insight on why this is the case when we use gadt constraints?
It's because classes are known to be injective. It's justifiable to return true
in both cases, but if F
is abstract, we can't reconstruct the bound Int <: t
, since we could have type F[X] = Unit
for instance.
What behaviour would you expect?
Looking at the code, it feels to me like pattern matching on quoted types should be based on the structure of the types. The actual type information can be kept in GadtConstraint
, if that's useful for type checking. I don't quite know why we involve GADT reasoning here.
Same issue in #15470
Note that this issue also affects something like:
type Abs[T]
inline def test =
inline erasedValue[Abs[1]] match
case _: Abs[a] => valueOf[a]
which is also incorrectly relying on the GADT logic currently: https://github.com/scala/scala3/blob/d2bb85dc6c197a8fd5dea198cf6b7e20a97f63a9/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala#L279-L283 The same fix should apply to both form of type matching.
We could either use regular subtype checking instead of GADT checking, or reuse the match type algorithm (https://docs.scala-lang.org/sips/match-types-spec.html) without the disjointness checks, but this might be constraining in some situations (/cc @sjrd).
As a workaround (for the inline erasedValue
usecase at least, but should also work for the quoted type match), it's possible to define a macro that extracts the type constructor and type arguments and wrap them in a dummy trait for the sole purpose of type matching (just wrote this, no idea if it's robust yet): https://gist.github.com/smarter/7211132efd78b7191fe393800e366835
Compiler version
3.2.2
Minimized code
https://scastie.scala-lang.org/rDUMGIiBQ5Km5vxE7TEl1Q
Output
Expectation