使用Scala2,我可以实现一个宏并使用tq准引号语法生成类型,例如:
tq
q""" new Foo { type Bar = ${tq"(..$params)"} } """
我可以用这个语法做两件事--1.可以根据params定义类型Bar。1.能够将params作为元组展开。我如何使用Scala 3实现这一点?
params
Bar
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 3、Method Override with Scala 3 Macros一切都取决于是否静态地知道Foo和params。如果是这样,那么一切都很容易:
q"..."
tq"..."
pq"..."
cq"..."
fq"..."
'{...}
${...}
reify {...}
.splice
new Foo {...}
Foo
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.newMethod、Symbol.newClass、Symbol.newVal、Symbol.newBind,但没有Symbol.newType。(事实证明,新类型成员的方法不向反射API公开,因此我们必须使用内部dotty.tools.dotc.core.Symbols.newSymbol。)我可以想象像这样的事情
Symbol.newMethod
Symbol.newClass
Symbol.newVal
Symbol.newBind
Symbol.newType
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"..."。
t"..."
p"..."
param"..."
tparam"..."
init"..."
self"..."
template"..."
mod"..."
enumerator"..."
import"..."
importer"..."
importee"..."
source"..."
1条答案
按热度按时间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 3、Method Override with Scala 3 Macros一切都取决于是否静态地知道
Foo
和params
。如果是这样,那么一切都很容易:或
或
在评论**@Jasper-M**中,建议如何处理静态
Foo
而非静态params
的情况:或
现在假设
Foo
是静态未知的。因为没有准引号,所以在Scala3中构建树的唯一不同方法是深入到美味的reflection级别并手动构建树。因此,您可以打印静态类型检查的代码树,并尝试手动重新构建它。代码指纹
和
指纹
这里的另一个复杂之处在于,Scala3宏接受类型化的树,并且必须返回类型化的树。因此,我们也必须处理符号。
实际上,在反射API中,我可以看到
Symbol.newMethod
、Symbol.newClass
、Symbol.newVal
、Symbol.newBind
,但没有Symbol.newType
。(事实证明,新类型成员的方法不向反射API公开,因此我们必须使用内部dotty.tools.dotc.core.Symbols.newSymbol
。)我可以想象像这样的事情
Scala 3宏是def宏,所有生成的定义将仅在宏展开到的块中可见。
也许,如果在预编译时生成代码就足够了,您可以考虑使用Scalameta。有准引号:)
q"..."
、t"..."
、p"..."
、param"..."
、tparam"..."
、init"..."
、self"..."
、template"..."
、mod"..."
、enumerator"..."
、import"..."
、importer"..."
、importee"..."
、source"..."
。