Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
278 views
in Technique[技术] by (71.8m points)

scala 3 macros: returning Map[String, Class[Any]] from a Macro

I am trying to write a macro against scala 3.0.0-M3. I want the macro to return the inner types of the Option[_] fields of a Type. For example, given:

class Professor(val lastName: String, val id: Option[Int], val bossId: Option[Long])

I want to associate id with Int, and bossId with Long.

I have some code that does that for primitive types and compiles ok:

import scala.quoted._
import scala.quoted.staging._
import scala.quoted.{Quotes, Type}

object TypeInfo {


  inline def fieldsInfo[T <: AnyKind]: Map[String, Class[Any]] = ${ fieldsInfo[T] }

  def fieldsInfo[T <: AnyKind: Type](using qctx0: Quotes): Expr[Map[String, Class[Any]]] = {
    given qctx0.type = qctx0
    import qctx0.reflect.{given, _}

    val uns = TypeTree.of[T]
    val symbol = uns.symbol
    val innerClassOfOptionFields: Map[String, Class[Any]] = symbol.memberFields.flatMap { m =>
      // we only support val fields for now
      if(m.isValDef){
        val tpe = ValDef(m, None).tpt.tpe
        // only if the field is an Option[_]
        if(tpe.typeSymbol == TypeRepr.of[Option[Any]].typeSymbol){
          val containedClass: Option[Class[Any]] =
            if(tpe =:= TypeRepr.of[Option[Int]]) Some(classOf[Int].asInstanceOf[Class[Any]])
            else if(tpe =:= TypeRepr.of[Option[Short]])  Some(classOf[Short].asInstanceOf[Class[Any]])
            else if(tpe =:= TypeRepr.of[Option[Long]])  Some(classOf[Long].asInstanceOf[Class[Any]])
            else if(tpe =:= TypeRepr.of[Option[Double]])  Some(classOf[Double].asInstanceOf[Class[Any]])
            else if(tpe =:= TypeRepr.of[Option[Float]])  Some(classOf[Float].asInstanceOf[Class[Any]])
            else if(tpe =:= TypeRepr.of[Option[Boolean]])  Some(classOf[Boolean].asInstanceOf[Class[Any]])
            else if(tpe =:= TypeRepr.of[Option[Byte]])  Some(classOf[Byte].asInstanceOf[Class[Any]])
            else if(tpe =:= TypeRepr.of[Option[Char]])  Some(classOf[Char].asInstanceOf[Class[Any]])
            else None

          containedClass.map(clazz => (m.name -> clazz))
        } else None
      } else None
    }.toMap

    println(innerClassOfOptionFields)

    Expr(innerClassOfOptionFields)
  }

But if I try to use it, like this:

class Professor(val lastName: String, val id: Option[Int], val bossId: Option[Long])

object Main extends App {

  val fields = TypeInfo.fieldsInfo[Professor]

}

the compiler first prints Map(id -> int, bossId -> long) because of the println in the macro code which looks alright, but then fails with:

[error] 16 |  val fields = TypeInfo.fieldsInfo[Professor]
[error]    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]    |               Found:    (classOf[Int] : Class[Int])
[error]    |               Required: Class[Any]
[error]    | This location contains code that was inlined from Main.scala:34

What I am doing wrong? Am I not supposed to be able to return a Map from a macro, or maybe not this way?

Note that the if/else logic in my macro doesn't really matter, the problem can be reduced to (everything else being equal):

    val result: Map[String, Class[Any]] = Map(
      "bossId" -> classOf[scala.Long].asInstanceOf[Class[Any]],
      "id" -> classOf[scala.Int].asInstanceOf[Class[Any]]
    )
    Expr(result)
question from:https://stackoverflow.com/questions/65869784/scala-3-macros-returning-mapstring-classany-from-a-macro

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

You can define this missing given, based on the one from the standard library.

import scala.quoted._

given ToExpr[Class[?]] with {                               
  def apply(x: Class[?])(using Quotes) = {
    import quotes.reflect._
    Ref(defn.Predef_classOf).appliedToType(TypeRepr.typeConstructorOf(x)).asExpr.asInstanceOf[Expr[Class[?]]]
  }
}

In the next release of Scala 3, this should no longer be necessary. The given instance of the standard library has been adapted to work for Class[?] too.

Then you can return a well typed Map[String, Class[?]].

inline def fieldsInfo: Map[String, Class[?]] = ${ fieldsInfoMacro }

def fieldsInfoMacro(using Quotes): Expr[Map[String, Class[?]]] = {
  val result: Map[String, Class[?]] = Map(
    "bossId" -> classOf[scala.Long],
    "id" -> classOf[scala.Int]
  )
  Expr(result)
}

And everything works:

scala> fieldsInfo                                                               
val res1: Map[String, Class[?]] = Map(bossId -> long, id -> int)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...