Scala 3宏中的`tq`等效项

zzwlnbp8  于 2022-11-09  发布在  Scala
关注(0)|答案(1)|浏览(170)

使用Scala2,我可以实现一个宏并使用tq准引号语法生成类型,例如:

q"""        
new Foo {
  type Bar = ${tq"(..$params)"}
}
"""

我可以用这个语法做两件事--
1.可以根据params定义类型Bar
1.能够将params作为元组展开。
我如何使用Scala 3实现这一点?

bf1o4zei

bf1o4zei1#

Scala 3中没有quasiquotes(q"..."tq"..."pq"..."cq"..."fq"..."),感觉就像Scala 2.10:
Scala 3 quotations'{...}(和拼接${...})不仅必须在主代码的编译时(即宏的运行时,宏展开的时间)进行类型检查,而且还必须在宏本身的编译时更早地进行类型检查。这类似于Scala 2中的reify {...}(和.splice)。
new Foo {...}实际上是扩展Foo的匿名类的示例。因此,请参见您之前的问题Macro class name expansion in Scala 3Method Override with Scala 3 Macros
一切都取决于是否静态地知道Fooparams。如果是这样,那么一切都很容易:

import scala.quoted.*

trait Foo

inline def doSmth[A](x: Int): Unit = ${doSmthImpl[A]('x)}

def doSmthImpl[A](x: Expr[Int])(using Quotes, Type[A]): Expr[Unit]= {
//    import quotes.reflect.*

  '{

    new Foo {
      type Bar = (Double, Boolean)
    }

    // doing smth with this instance
  }
}

val params = Type.of[(Double, Boolean)]

'{
  new Foo {
    type Bar = $params
  }
}

'{
  new Foo {
    type Bar = params.Underlying
  }
}

在评论**@Jasper-M**中,建议如何处理静态Foo而非静态params的情况:

type X

given Type[X] = paramsTypeTree.tpe.asType.asInstanceOf[Type[X]]

'{
  new Foo {
    type Bar = X
  }
}

paramsTypeTree.tpe.asType match {
  case '[x] =>
    '{
      new Foo {
        type Bar = x
      }
    }
}

现在假设Foo是静态未知的。因为没有准引号,所以在Scala3中构建树的唯一不同方法是深入到美味的reflection级别并手动构建树。因此,您可以打印静态类型检查的代码树,并尝试手动重新构建它。代码

println('{
  new Foo {
    type Bar = (Double, Boolean)
  }
}.asTerm.underlyingArgument.show

指纹

{
  final class $anon() extends App.Foo {
    type Bar = scala.Tuple2[scala.Double, scala.Boolean]
  }

  (new $anon(): App.Foo)
}

println('{
  new Foo {
     type Bar = (Double, Boolean)
  }
}.asTerm.underlyingArgument.show(using Printer.TreeStructure))

指纹

Block(
  List(ClassDef(
    "$anon",
    DefDef("<init>", List(TermParamClause(Nil)), Inferred(), None),
    List(
      Apply(Select(New(Inferred()), "<init>"), Nil),
      TypeIdent("Foo")
    ),
    None,
    List(TypeDef(
      "Bar",
      Applied(
        Inferred(),
        List(TypeIdent("Double"), TypeIdent("Boolean")) // this should be params
      )
    ))
  )),
  Typed(
    Apply(Select(New(TypeIdent("$anon")), "<init>"), Nil),
    Inferred()
  )
)

这里的另一个复杂之处在于,Scala3宏接受类型化的树,并且必须返回类型化的树。因此,我们也必须处理符号。
实际上,在反射API中,我可以看到Symbol.newMethodSymbol.newClassSymbol.newValSymbol.newBind,但没有Symbol.newType。(事实证明,新类型成员的方法不向反射API公开,因此我们必须使用内部dotty.tools.dotc.core.Symbols.newSymbol。)
我可以想象像这样的事情

val fooTypeTree = TypeTree.ref(Symbol.classSymbol("mypackage.App.Foo"))
val parents = List(TypeTree.of[AnyRef], fooTypeTree)

def decls(cls: Symbol): List[Symbol] = {
  given dotty.tools.dotc.core.Contexts.Context = 
    quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
  import dotty.tools.dotc.core.Decorators.toTypeName
  List(dotty.tools.dotc.core.Symbols.newSymbol(
    cls.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol],
    "Bar".toTypeName,
    Flags.EmptyFlags/*Override*/.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet],
    TypeRepr.of[(Double, Boolean)]/*params*/.asInstanceOf[dotty.tools.dotc.core.Types.Type]
  ).asInstanceOf[Symbol])
}

val cls = Symbol.newClass(Symbol.spliceOwner, "FooImpl", parents = parents.map(_.tpe), decls, selfType = None)
val typeSym = cls.declaredType("Bar").head

val typeDef = TypeDef(typeSym)
val clsDef = ClassDef(cls, parents, body = List(typeDef))
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), fooTypeTree)

Block(List(clsDef, newCls), '{()}.asTerm).asExprOf[Unit]

//{
//  class FooImpl extends java.lang.Object with mypackage.App.Foo {
//    type Bar = scala.Tuple2[scala.Double, scala.Boolean]
//  }
//
//  (new FooImpl(): mypackage.App.Foo)
//  ()
//}

package mypackage

object App {
 trait Foo
}

Scala 3宏是def宏,所有生成的定义将仅在宏展开到的块中可见。
也许,如果在预编译时生成代码就足够了,您可以考虑使用Scalameta。有准引号:)q"..."t"..."p"..."param"..."tparam"..."init"..."self"..."template"..."mod"..."enumerator"..."import"..."importer"..."importee"..."source"..."

相关问题