Explicit type conversion in Scala 3 macros
Asked Answered
B

1

6

I defined the following trait in Scala 3:

trait A[T <: Tuple]

Using Scala 3 macros, I then create objects of this trait performing further checks on the actual types of the tuple T; in particular, I want to check that all the types (T_1, …, T_n) of the tuple T are subtypes of another given type, B:

trait B
private def allSubtypesOfB[T <: Tuple: Type](using quotes: Quotes): Boolean = {
    import quotes.reflect.*
    case '[Nothing] => false // I don't want nothing to be in T
    case '[head *: tail] if TypeRepr.of[head] <:< TypeRepr.of[B] => allSubtypesOfB[tail]
    case '[EmptyTuple] => true
    case _ => false
}

inline def createA[T <: Tuple] = ${ createAImpl[T] }
private def createAImpl[T <: Tuple: Type](using quotes: Quotes): Expr[A[T]] = {
    import quotes.reflect.*
    if !allSubtypesOfB[T] then report.error("All types in T must be subtypes of B")
    // ... create instance of A
}

The problem is that later I need to call, for each type in the tuple type T, a method that has the following signature:

def doSomethingWithBSubtype[T <: B] = ??? // Do something with type T

So, the code would look something like this:

private def createAImpl[T <: Tuple: Type](using quotes: Quotes): Expr[A[T]] = {
    import quotes.reflect.*
    if !allSubtypesOfB[T] then report.error("All types in T must be subtypes of B")
    Type.of[T] match {
        case '[head *: tail] => doSomethingWithBSubtype[head]
        case '[EmptyTuple] => ???
    }
    // ... create instance of A
}

This code won't compile as the compiler says that head must be a subtype of B to be used in the method doSomethingWithBSubtype. However, with the if preceding the match case, I already ensured that all types inside T are subtypes of B. Is there a way to force the compiler to recognise head as a subtype of B?

Breakable answered 26/8, 2021 at 15:55 Comment(1)
See the update.Rutland
R
2

There are compile time and runtime of macros. There are compile time and runtime of main code (using the macros). The runtime of macros is the compile time of main code.

Inside createAImpl the line if !allSubtypesOfB[T]... ensures that all types of T are subtypes of B at the runtime of createAImpl. But when you call doSomethingWithBSubtype[head] you need to know that head is a subtype of B at the compile time of createAImpl so you don't have information from the runtime of createAImpl yet (if !allSubtypesOfB[T]...).

You could do

case '[head *: tail] => doSomethingWithBSubtype[head & B]

Also maybe something like the following is possible

Type.of[T] match {
  case '[head *: tail] =>
    '{foo[head]} match {
      case '{
        type h <: B
        foo[`h`]
      } => doSomethingWithBSubtype[h]
    }
  case '[EmptyTuple] => ???
}

// outside def createAImpl, otherwise "access to method foo from wrong staging level"
def foo[A] = ???

https://docs.scala-lang.org/scala3/guides/macros/quotes.html#type-variables-in-quoted-patterns

In some cases we need to define a pattern variable that is referenced several times or has some type bounds. To achieve this, it is possible to create pattern variables at the start of the pattern using type t with a type pattern variable.

fuseMap macro in scala 3

What Scala 3 syntax can match on a Type and its Type parameters in the context of a macro?

Get information of type in Scala3 macro

Turning inlined varargs into a generic tuple

Rutland answered 26/9, 2021 at 1:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.