我的目标是建立一个注释宏,将方法和变量注入特征。虽然这似乎为特征生成了正确的代码,但我无法让编译器认识到现在存在的方法。仅供参考,在尝试访问该值时,真正的实现会起作用。
我希望在这样的情况下使用它来清理一些代码(并更好地理解宏):https://github.com/ibm-et/spark-kernel/blob/b5b0dc1995ab010d0f0570fbb9fe0f4f19817e03/kernel-api/src/main/scala/com/ibm/spark/magic/dependencies/IncludeInterpreter.scala
我正在关注Scala宏文档中指向的示例项目中的代码。 https://github.com/scalamacros/sbt-example-paradise
所以,下面是我的宏的实际代码(使用Scala 2.10.4与宏天堂插件2.1.0-M5编译:
import scala.reflect.internal.annotations.compileTimeOnly
import scala.reflect.macros.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
/**
* Represents a macro that injects appropriate getters and setters into the
* annotated trait such that it can be treated as a dependency.
*
* @param variableName The name of the variable to provide getter/setter
* @param className The class name of the variable
*/
@compileTimeOnly("Enable macro paradise to expand macro annotations")
class Dependency(
variableName: String,
className: String
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro Dependency.impl
}
object Dependency {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
println("Starting macro")
println("Prefix: " + c.prefix)
val q"new Dependency(..$params)" = c.prefix.tree
println("Params: " + params)
val (variableName: String, className: String) = params match {
case p: List[_] =>
val stringParams = p.map(_.toString.replace("\"", ""))
(stringParams(0), stringParams(1))
case _ => c.abort(
c.enclosingPosition,
"Required arguments are (variable name: String) (class name: String)"
)
}
println("Variable name: " + variableName)
println("Class name: " + className)
def modifiedTrait(traitDecl: ClassDef) = {
val (name) = try {
val q"trait $name" = traitDecl
(name)
} catch {
case _: MatchError => c.abort(
c.enclosingPosition,
"Annotation is only supported on trait"
)
}
println("Trait name: " + name)
val actualVariableName = variableName.toLowerCase
println("Actual variable name: " + actualVariableName)
val actualVariableClass = newTypeName(className)
println("Actual class name: " + actualVariableClass)
val internalVariableName = newTermName("_" + actualVariableName)
println("Internal variable name: " + internalVariableName)
val getterName = newTermName(actualVariableName)
println("Getter name: " + getterName)
val setterName = newTermName(actualVariableName + "_=")
println("Setter name: " + setterName)
val setterVariableName = newTermName("new" + actualVariableName.capitalize)
println("Setter variable name: " + setterVariableName)
val generatedTrait = q"""
trait $name {
private var $internalVariableName: $actualVariableClass = _
def $setterName($setterVariableName: $actualVariableClass) =
$internalVariableName = $setterVariableName
def $getterName: $actualVariableClass = $internalVariableName
}
"""
println("Generated trait: " + generatedTrait)
c.Expr[Any](generatedTrait)
}
annottees.map(_.tree) match {
case (traitDecl: ClassDef) :: Nil => modifiedTrait(traitDecl)
case _ => c.abort(c.enclosingPosition, "Invalid annottee")
}
}
}
以下是我在ScalaTest中使用注释的尝试:
@Dependency("someFakeField", "Int") trait TestTrait
class A extends TestTrait
val a = new A
println("TestTrait")
classOf[TestTrait].getDeclaredMethods.foreach(println)
println("A")
classOf[A].getDeclaredMethods.foreach(println)
// This line fails
//a.someFakeField = 3
以下是运行我的宏的输出:
Warning:scalac: Starting macro
Warning:scalac:
Warning:scalac: Prefix: Expr[Nothing](new Dependency("someFakeField", "Int"))
Warning:scalac: Params: List("someFakeField", "Int")
Warning:scalac: Variable name: someFakeField
Warning:scalac: Class name: Int
Warning:scalac: Trait name: TestTrait
Warning:scalac: Actual variable name: somefakefield
Warning:scalac: Actual class name: Int
Warning:scalac: Internal variable name: _somefakefield
Warning:scalac: Getter name: somefakefield
Warning:scalac: Setter name: somefakefield_=
Warning:scalac: Setter variable name: newSomefakefield
Warning:scalac: Generated trait: abstract trait TestTrait extends scala.AnyRef {
def $init$() = {
()
};
private var _somefakefield: Int = _;
def somefakefield_=(newSomefakefield: Int) = _somefakefield = newSomefakefield;
def somefakefield: Int = _somefakefield
}
以下是运行反射以使用特征访问特征和类的方法的输出(注意方法的外观):
TestTrait
public abstract void com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$TestTrait$1.somefakefield_=(int)
public abstract com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1 com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$TestTrait$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$$outer()
public abstract int com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$TestTrait$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$_somefakefield()
public abstract void com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$TestTrait$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$_somefakefield_$eq(int)
public abstract int com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$TestTrait$1.somefakefield()
A
public void com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.somefakefield_=(int)
public com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1 com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$A$$$outer()
public com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1 com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$$outer()
public int com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$_somefakefield()
public void com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$_somefakefield_$eq(int)
public int com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.somefakefield()
这是一个有效的手写特性:
trait IncludeInterpreter {
private var _interpreter: Interpreter = _
def interpreter: Interpreter = _interpreter
def interpreter_=(newInterpreter: Interpreter) =
_interpreter = newInterpreter
}
关于我做错了什么的任何想法?如果我不能用宏做这样的事情,那么我想我需要继续手工编写。仅供参考,我也尝试在一个模块中编译宏,使用宏在另一个模块中生成特征的方法,并在第三个模块中实际使用特征。不确定需要分开什么,什么不分开。
当我运行宏然后尝试从另一个模块(不在ScalaTest中)访问方法时,我甚至没有看到使用反射的方法(虽然宏的输出仍然出现)。