Skip to content

Scala 3 macro cannot match abstract types #16782

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

Open
Atry opened this issue Jan 28, 2023 · 6 comments
Open

Scala 3 macro cannot match abstract types #16782

Atry opened this issue Jan 28, 2023 · 6 comments
Labels
area:gadt area:metaprogramming:quotes Issues related to quotes and splices itype:bug

Comments

@Atry
Copy link
Contributor

Atry commented Jan 28, 2023

Compiler version

3.2.2

Minimized code

import scala.quoted.*

given staging.Compiler = staging.Compiler.make(getClass.getClassLoader)

def matchIArray = staging.run {
  Type.of[IArray[Int]] match
    case '[IArray[t]] =>
      '{"IArray"}
    case _ =>
      '{"not IArray"}
  end match
}
println(matchIArray)

def matchArray = staging.run {
  Type.of[Array[Int]] match
    case '[Array[t]] =>
      '{"Array"}
    case _ =>
      '{"not Array"}
  end match
}
println(matchArray)

https://scastie.scala-lang.org/rDUMGIiBQ5Km5vxE7TEl1Q

Output

not IArray
Array

Expectation

IArray
Array
@Atry Atry added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Jan 28, 2023
@nicolasstucki nicolasstucki added area:metaprogramming:quotes Issues related to quotes and splices and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Jan 30, 2023
@nicolasstucki
Copy link
Contributor

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()

@nicolasstucki nicolasstucki changed the title Scala 3 macro cannot match opaque types Scala 3 macro cannot match abstract types Jan 31, 2023
@nicolasstucki
Copy link
Contributor

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.

Here: https://github.com/lampepfl/dotty/blob/main/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala#L345

@abgruszecki do you have some insight on why this is the case when we use gadt constraints?

@abgruszecki
Copy link
Contributor

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?

@abgruszecki
Copy link
Contributor

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.

@nicolasstucki
Copy link
Contributor

Same issue in #15470

@smarter
Copy link
Member

smarter commented Sep 13, 2024

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:

pat match {
case Typed(pat1, tpt) =>
val typeBinds = getTypeBindsMap(pat1, tpt)
registerAsGadtSyms(typeBinds)
scrut <:< tpt.tpe && {

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:gadt area:metaprogramming:quotes Issues related to quotes and splices itype:bug
Projects
None yet
Development

No branches or pull requests

4 participants