编译

时间:2015-07-13 05:42:16

标签: scala methods macros annotations inject

我的目标是建立一个注释宏,将方法和变量注入特征。虽然这似乎为特征生成了正确的代码,但我无法让编译器认识到现在存在的方法。仅供参考,在尝试访问该值时,真正的实现会起作用。

我希望在这样的情况下使用它来清理一些代码(并更好地理解宏):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中)访问方法时,我甚至没有看到使用反射的方法(虽然宏的输出仍然出现)。

0 个答案:

没有答案