How to refine type dynamically in scala 3 whitebox macro?

scala 3.2.2 code:

import scala.quoted.*
def refined(using Quotes) =
  import quotes.reflect.*
  val cl = '{
    class Cl extends Object:
      val field: String = "value"
    new Cl
  }
  val tree = cl.asTerm
  val typ = Refined.copy(tree)(
    TypeTree.of[Object],
    List(ValDef.copy(tree)("field", TypeIdent.copy(tree)("String"), None))
  )
  Typed(tree, typ).asExpr
transparent inline def m = ${refined}

Compiler gives:

def m: Any

How I could get:

def m: Object { val field: String }

I believe you need to use asExprOf[T] instead of asExpr. x.asExpr is basically a short-hand for x.asExprOf[Any]. In order to materialize the correct T, you’ll need the pattern-matching trick described at
https://docs.scala-lang.org/scala3/guides/macros/reflection.html#relation-with-exprtype

Something like:

val typedTerm = Typed(tree, typ)
typedTerm.tpe.asType match
  case '[t] => typedTerm.asExprOf[t]

If you want the compiler to infer a structural type without explicitly annotating it, I believe it needs to be a subtype of scala.Selectable. And for reflective access to the structural members you need scala.reflect.Selectable.

scala> import scala.quoted.*
     | def refined(using Quotes) =
     |   '{
     |     new scala.reflect.Selectable:
     |       val field: String = "value"
     |   }
     |
     | transparent inline def m = ${refined}
def refined
  (using x$1: quoted.Quotes): quoted.Expr[reflect.Selectable{val field: String}]
def m: reflect.Selectable{val field: String}

scala> m.field
val res0: String = value

And also m might be Any or AnyRef if the return type of refined cannot be more specific than Expr[Any] or Expr[AnyRef] (e.g. if your implementation gets more complex than it currently is), but the result of invoking m will have a more specific type because m is transparent.

scala> import scala.quoted.*
     | def refined(using Quotes): Expr[Any] =
     |   '{
     |     new scala.reflect.Selectable:
     |       val field: String = "value"
     |   }
     |
     | transparent inline def m = ${refined}
def refined(using x$1: quoted.Quotes): quoted.Expr[Any]
def m: Any

scala> val a = m
val a: reflect.Selectable{val field: String} = anon$1@df0ae74
1 Like

Thanks, it helped!

1 Like