4

単一のクラス引数を定義し、引数として提供されるクラスの実装に基づいてアタッチ先のクラスを変更する Scala マクロを作成しようとしています。

//Simple class with a few arguments
class A(a: String, b: String)

//Definition of this class should be modified based on class definition of A
@parameterized(classOf[A])
class B

注釈から引数を抽出できる単純なマクロを作成することができました。その結果、完全なクラス名の文字列表現を含む TypeName オブジェクトが生成されます。

問題は、マクロの実装から A の定義にアクセスする必要があることです (具体的には、コンストラクターの引数が何であるかを確認したい)。

何らかの方法で TypeTag[A] にアクセス/作成する方法はありますか? クラス A の AST にアクセスする方法はありますか?

私が達成しようとしていることを説明するために、これは私が現在マクロ定義として持っているものです:

object parameterizedMacro {
  def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._
    import Flag._      

    //Extract the parameter type which was provided as an argument (rather hacky way of getting this info)
    val parameterType = c.macroApplication match {
      case Apply(Select(Apply(_, List(
          TypeApply(Ident(TermName("classOf")), List(Ident(TypeName(parameterType))))
      )) , _), _) => parameterType
      case _ =>
        sys.error("Could not match @parameterized arguments. Was a class provided?")
    }

   //Should generate method list based on the code of parameterType
   val methods = ???

   val result = {
      annottees.map(_.tree).toList match {
        case q"object $name extends ..$parents { ..$body }" :: Nil =>
          q"""
            object $name extends ..$parents {
              ..${methods}
              ..$body
             }
          """
        case q"class $name (..$args) extends ..$parents { ..$body }" :: Nil =>
           q"""
             class $name (..$args) extends ..$parents {
              ..${methods}
               ..$body
             }
          """
      }
    }

    c.Expr[Any](result)
  }
}
4

1 に答える 1

3

さまざまなクラスに関する多くのドキュメントを見つけることができなかったため、多くの試行錯誤が必要でしたが、最終的にはやりたいことを達成することができました.

パラメータを取得するために、元の の代わりに完全修飾名を注釈に入れる必要がありましたTypeOf[Name]

@parameterized(the.fully.qualified.Name)

それから私はそれを使うことができました

val parameterType = c.macroApplication match {
      case Apply(Select(Apply(_, List(
          parameterType 
      )) , _), _) => parameterType
      case _ =>
        error("Could not match @parameterized arguments. Was a class provided?")
    }
val fullClassName = parameterType.toString()

から正しい完全修飾名に到達できなかったため、元の投稿で述べたように使用TypeOfしてもうまくいきませんでした。TypeApplyすべてがコンパイル時にチェックされるため、これは実際にはすべてを安全ではありませんが、標準の Scala には少し似ていません。

完全修飾名を取得したら、ユニバースのミラー (ミラーに関するドキュメントはこちらを参照) を使用して ClassSymbol を取得でき、そこからクラス コンストラクターの引数を取得できます。

val constructorArguments = {
  val clazz = c.mirror.staticClass(fullClassName)            //Get ClassSymbol
  val clazzInfo = clazz.info                                 //Turn ClassSymbol into Type
  val constructor = clazzInfo.member(termNames.CONSTRUCTOR)  //Get constructor member Symbol
  val constructorMethod = constructor.asMethod               //Turn into MethodSymbol
  val parametersList = constructorMethod.paramLists          //Finally extract list of parameters

  if (parametersList.size != 1)
    error("Expected only a single constructor in " + fullClassName)

  val parameters = parametersList.head

  for (parameter <- parameters) yield {
    val term = parameter.asTerm //parameter is a term
    (term.name.toString, term.typeSignature)
  }
}
于 2016-04-06T10:43:11.650 に答える