Koo*_*sha 7 scala scala-macros scala-macro-paradise scala-3
以下是三年多前的Macros: The Plan for Scala 3中的一段话:
例如,我们可以定义一个宏注释 @json,将 JSON 序列化器添加到一种类型中。
知道这在 Scala 3 中如何/是否可行吗?
更一般地说,Scala 3 中有什么可以提供“宏注释”功能吗?以下是宏注释 - Scala 2.13的引用:
与以前版本的宏天堂不同,2.0 中的宏注释在以下意义上是正确的:1) 不仅适用于类和对象,而且适用于任意定义,2)允许扩展类来修改甚至创建伴生对象
从Scala 3.3.0-RC2开始,出现了宏注释(由Nicolas Stucki实现)。
宏注释(第 1 部分) https://github.com/lampepfl/dotty/pull/16392
宏注释类修改(第2部分) https://github.com/lampepfl/dotty/pull/16454
启用从 MacroAnnotations 返回类(第 3 部分) https://github.com/lampepfl/dotty/pull/16534
从宏扩展外部看不到新定义。
构建.sbt
scalaVersion := "3.3.0-RC3"
Run Code Online (Sandbox Code Playgroud)
几个例子:
@memoize为方法添加了记忆功能import scala.annotation.{MacroAnnotation, experimental}
import scala.collection.mutable
import scala.quoted.*
object Macros:
@experimental
class memoize extends MacroAnnotation:
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
import quotes.reflect.*
tree match
case DefDef(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(rhsTree)) =>
(Ref(param.symbol).asExpr, rhsTree.asExpr) match
case ('{ $paramRefExpr: t }, '{ $rhsExpr: u }) =>
val cacheTpe = TypeRepr.of[mutable.Map[t, u]]
val cacheSymbol = Symbol.newVal(tree.symbol.owner, name + "Cache", cacheTpe, Flags.Private, Symbol.noSymbol)
val cacheRhs = '{ mutable.Map.empty[t, u] }.asTerm
val cacheVal = ValDef(cacheSymbol, Some(cacheRhs))
val cacheRefExpr = Ref(cacheSymbol).asExprOf[mutable.Map[t, u]]
val newRhs = '{ $cacheRefExpr.getOrElseUpdate($paramRefExpr, $rhsExpr) }.asTerm
val newTree = DefDef.copy(tree)(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(newRhs))
val res = List(cacheVal, newTree)
println(res.map(_.show))
res
case _ =>
report.error("Annotation only supported on `def` with a single argument are supported")
List(tree)
Run Code Online (Sandbox Code Playgroud)
import scala.annotation.experimental
import Macros.memoize
@experimental
object App:
@memoize
def fib(n: Int): Int =
println(s"compute fib of $n")
if n <= 1 then n else fib(n - 1) + fib(n - 2)
def main(args: Array[String]): Unit =
println(fib(5))
//scalac: List(val fibCache: scala.collection.mutable.Map[n, scala.Int] = scala.collection.mutable.Map.empty[n.type, scala.Int],
// @Macros.memoize def fib(n: scala.Int): scala.Int = App.fibCache.getOrElseUpdate(n, {
// scala.Predef.println(_root_.scala.StringContext.apply("compute fib of ", "").s(n))
// if (n.<=(1)) n else App.fib(n.-(1)).+(App.fib(n.-(2)))
//}))
//compute fib of 5
//compute fib of 4
//compute fib of 3
//compute fib of 2
//compute fib of 1
//compute fib of 0
//5
Run Code Online (Sandbox Code Playgroud)
@equals方法equals和hashCodeimport scala.annotation.{MacroAnnotation, experimental}
import scala.quoted.*
object Macros:
@experimental
class equals extends MacroAnnotation:
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
import quotes.reflect.*
tree match
case ClassDef(className, ctr, parents, self, body) =>
val cls = tree.symbol
val constructorParameters = ctr.paramss.collect { case clause: TermParamClause => clause }
if constructorParameters.size != 1 || constructorParameters.head.params.isEmpty then
report.errorAndAbort("@equals class must have a single argument list with at least one argument", ctr.pos)
def checkNotOverridden(sym: Symbol): Unit =
if sym.overridingSymbol(cls).exists then
report.error(s"Cannot override ${sym.name} in a @equals class")
val fields = body.collect {
case vdef: ValDef if vdef.symbol.flags.is(Flags.ParamAccessor) =>
Select(This(cls), vdef.symbol).asExpr
}
val equalsSym = Symbol.requiredMethod("java.lang.Object.equals")
checkNotOverridden(equalsSym)
val equalsOverrideSym = Symbol.newMethod(cls, "equals", equalsSym.info, Flags.Override, Symbol.noSymbol)
def equalsOverrideDefBody(argss: List[List[Tree]]): Option[Term] =
given Quotes = equalsOverrideSym.asQuotes
cls.typeRef.asType match
case '[c] =>
Some(equalsExpr[c](argss.head.head.asExpr, fields).asTerm)
val equalsOverrideDef = DefDef(equalsOverrideSym, equalsOverrideDefBody)
val hashSym = Symbol.newVal(cls, Symbol.freshName("hash"), TypeRepr.of[Int], Flags.Private | Flags.Lazy, Symbol.noSymbol)
val hashVal = ValDef(hashSym, Some(hashCodeExpr(className, fields)(using hashSym.asQuotes).asTerm))
val hashCodeSym = Symbol.requiredMethod("java.lang.Object.hashCode")
checkNotOverridden(hashCodeSym)
val hashCodeOverrideSym = Symbol.newMethod(cls, "hashCode", hashCodeSym.info, Flags.Override, Symbol.noSymbol)
val hashCodeOverrideDef = DefDef(hashCodeOverrideSym, _ => Some(Ref(hashSym)))
val newBody = equalsOverrideDef :: hashVal :: hashCodeOverrideDef :: body
val res = List(ClassDef.copy(tree)(className, ctr, parents, self, newBody))
println(res.map(_.show))
res
case _ =>
report.error("Annotation only supports `class`")
List(tree)
private def equalsExpr[T: Type](that: Expr[Any], thisFields: List[Expr[Any]])(using Quotes): Expr[Boolean] =
'{
$that match
case that: T@unchecked =>
${
val thatFields: List[Expr[Any]] =
import quotes.reflect.*
thisFields.map(field => Select('{ that }.asTerm, field.asTerm.symbol).asExpr)
thisFields.zip(thatFields)
.map { case (thisField, thatField) => '{ $thisField == $thatField } }
.reduce { case (pred1, pred2) => '{ $pred1 && $pred2 } }
}
case _ => false
}
private def hashCodeExpr(className: String, thisFields: List[Expr[Any]])(using Quotes): Expr[Int] =
'{
var acc: Int = ${ Expr(scala.runtime.Statics.mix(-889275714, className.hashCode)) }
${
Expr.block(
thisFields.map {
case '{ $field: Boolean } => '{ if $field then 1231 else 1237 }
case '{ $field: Byte } => '{ $field.toInt }
case '{ $field: Char } => '{ $field.toInt }
case '{ $field: Short } => '{ $field.toInt }
case '{ $field: Int } => field
case '{ $field: Long } => '{ scala.runtime.Statics.longHash($field) }
case '{ $field: Double } => '{ scala.runtime.Statics.doubleHash($field) }
case '{ $field: Float } => '{ scala.runtime.Statics.floatHash($field) }
case '{ $field: Null } => '{ 0 }
case '{ $field: Unit } => '{ 0 }
case field => '{ scala.runtime.Statics.anyHash($field) }
}.map(hash => '{ acc = scala.runtime.Statics.mix(acc, $hash) }),
'{ scala.runtime.Statics.finalizeHash(acc, ${ Expr(thisFields.size) }) }
)
}
}
Run Code Online (Sandbox Code Playgroud)
import scala.annotation.experimental
import Macros.equals
@experimental
object App:
@equals
class User(val name: String, val id: Int)
def main(args: Array[String]): Unit =
println(User("a", 1) == User("a", 1)) // true
//scalac: List(@Macros.equals class User(val name: scala.Predef.String, val id: scala.Int) {
// override def equals(x$0: scala.Any): scala.Boolean = x$0 match {
// case that: App.User @scala.unchecked =>
// User.this.name.==(that.name).&&(User.this.id.==(that.id))
// case _ =>
// false
// }
// lazy val hash$macro$1: scala.Int = {
// var acc: scala.Int = 515782504
// acc = scala.runtime.Statics.mix(acc, scala.runtime.Statics.anyHash(User.this.name))
// acc = scala.runtime.Statics.mix(acc, User.this.id)
// scala.runtime.Statics.finalizeHash(acc, 2)
// }
// override def hashCode(): scala.Int = User.this.hash$macro$1
//})
Run Code Online (Sandbox Code Playgroud)
@addClass在方法附近生成一个类import scala.annotation.{MacroAnnotation, experimental}
import scala.quoted.*
object Macros:
@experimental
class addClass extends MacroAnnotation:
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
import quotes.reflect._
tree match
case DefDef(name, List(TermParamClause(Nil)), tpt, Some(rhs)) =>
val parents = List(TypeTree.of[Object])
def decls(cls: Symbol): List[Symbol] =
List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.EmptyFlags, Symbol.noSymbol))
val newClassName = Symbol.freshName("Bar")
val cls = Symbol.newClass(Symbol.spliceOwner/*.owner*/, newClassName, parents = parents.map(_.tpe), decls, selfType = None)
val runSym = cls.declaredMethod("run").head
val runDef = DefDef(runSym, _ => Some(rhs))
val clsDef = ClassDef(cls, parents, body = List(runDef))
val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil)
val newDef = DefDef.copy(tree)(name, List(TermParamClause(Nil)), tpt, Some(Apply(Select(newCls, runSym), Nil)))
val res = List(clsDef, newDef)
println(res.map(_.show))
res
case _ =>
report.error("Annotation only supports `def` with one argument")
List(tree)
Run Code Online (Sandbox Code Playgroud)
import Macros.addClass
import scala.annotation.experimental
object App:
@addClass @experimental
def bar(): Unit = println("bar")
//List(class Bar$macro$1 extends java.lang.Object {
// def run(): scala.Unit = scala.Predef.println("bar")
//}, @scala.annotation.experimental @Macros.addClass def bar(): scala.Unit = new App.Bar$macro$1().run())
Run Code Online (Sandbox Code Playgroud)
@mainMacro将方法转换为具有可运行方法的对象mainimport scala.annotation.{experimental, MacroAnnotation}
import scala.quoted._
import scala.collection.mutable
object Macros:
@experimental
class mainMacro extends MacroAnnotation:
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
import quotes.reflect._
tree match
case DefDef(name, List(TermParamClause(Nil)), _, _) =>
val parents = List(TypeTree.of[Object])
def decls(cls: Symbol): List[Symbol] =
List(Symbol.newMethod(cls, "main", MethodType(List("args"))(_ => List(TypeRepr.of[Array[String]]), _ => TypeRepr.of[Unit]), Flags.Static, Symbol.noSymbol))
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None)
val mainSym = cls.declaredMethod("main").head
val mainDef = DefDef(mainSym, _ => Some(Apply(Ref(tree.symbol), Nil)))
val clsDef = ClassDef(cls, parents, body = List(mainDef))
val res = List(clsDef, tree)
println(res.map(_.show))
res
case _ =>
report.error("Annotation only supports `def` without arguments")
List(tree)
Run Code Online (Sandbox Code Playgroud)
import Macros.mainMacro
import scala.annotation.experimental
@experimental
object App:
@mainMacro def Test(): Unit = println("macro generated main")
//scalac: List(class Test extends java.lang.Object {
// def main(args: scala.Array[scala.Predef.String]): scala.Unit = App.Test()
//}, @Macros.mainMacro def Test(): scala.Unit = scala.Predef.println("macro generated main"))
Run Code Online (Sandbox Code Playgroud)
https://github.com/lampepfl/dotty/blob/3.3.0-RC3/library/src/scala/annotation/MacroAnnotation.scala
https://users.scala-lang.org/t/macro-annotations-replacement-in-scala-3/7374
https://contributors.scala-lang.org/t/sponsoring-work-on-scala-3-macro-annotations/5658
https://contributors.scala-lang.org/t/scala-3-macro-annotations-and-code- Generation/6035
https://contributors.scala-lang.org/t/scala-3-macros-next-steps/6105
https://contributors.scala-lang.org/t/whitebox-macros-in-scala-3-are-possible-after-all/5014
截至 2021 年 6 月,Scala 3 不支持宏注释,并且文档中的任何地方都没有提及它们。
现在,如果您想生成方法、类或对象,我相信您必须使用 scalameta 或编写编译器插件。
显然,这种情况将来可能会改变,宏注释一开始也不是Scala 2的一部分。
| 归档时间: |
|
| 查看次数: |
1071 次 |
| 最近记录: |