tl;dr
直接、annotation classの型パラメータやコンストラクタにはアクセス出来ない。
その代わりに、this
を用いてパターンマッチで取得することが出来る。
題材
mix-in injectionとかminimal cake patternと呼ばれる、DI対象となるclassをmix-inするmacro annotationを実装した。
-
Uses[MyService]
とするとval myService: MyService
がフィールドに追加される -
MixIn[MyService](new MyServiceImpl)
するとval myService: MyService = new MyServiceImpl
がフィールドに追加される
こんな感じで使用する。
trait MyService { def double(n: Int) = n * 2 }
object MyServiceImpl extends MyService
trait OtherService { def triple(n: Int) = n * 3 }
class OtherServiceImpl extends OtherService
@Uses[MyService]
@Uses[OtherService]
trait UsesFieldTarget {
def showDouble(n: Int) = println(this.myService.double(n))
def showTriple(n: Int) = println(this.otherService.triple(n))
}
@MixIn[MyService](MyServiceImpl)
@MixIn[OtherService](new OtherServiceImpl)
class UsesFieldTargetImpl extends UsesFieldTarget
object UsesFieldApp extends App {
val impl = new UsesFieldTargetImpl
impl.showDouble(100) // 200
impl.showTriple(100) // 300
}
annotationの実装はscalameta-prac/uses.scalaで、
サンプルコードはscalameta-prac/UsesApp.scala。
ちなみに、IntelliJ上ではうまく解決出来ておらずMacro expansion failed
とか表示されるし、補完もされない...。
こんな感じ。。
このような話もあるので期待したい。
IntelliJ IDEA 2016.3 RC: Scala.Js, Scala.Meta and More | IntelliJ Scala plugin blog
macro annotationで型パラメータと引数を処理する
上のような使い方をするには型パラメータと引数を処理しなければならないが、inline macroの場合はちょっと特殊。
型パラメータを取得する
class Uses[T] extends scala.annotation.StaticAnnotation {
inline def apply(defn: Any): Any = meta { ??? }
}
このようなUses[T]
において、T
には直接アクセス出来ない。
this
を用いるとT
がTerm.ApplyType
として取得できる。
class Uses[T] extends scala.annotation.StaticAnnotation {
inline def apply(defn: Any): Any = meta {
val typeParam: String =
this match {
case Term.New(Template(_,
Seq(Term.Apply(
Term.ApplyType(
_,
Seq(Type.Name(name: String))
), _)), _, _)) =>
name
case _ => None
}
???
}
}
これで型パラメータとして渡されたT
のコンパイル時における型名が文字列として手に入る。
つまりUses[MyService]
みたいになっているとMyService
という文字列が取れる。
コンストラクタを処理する
Uses[T]
でねじ込んだT
型のフィールドに実装を与えるMixIn[T](t: T)
を作る。
そのためにはコンストラクタを手に入れる必要がある。
これも型パラメータと同様にthis
に対するパターンマッチで取り出す。
型パラメータとコンストラクタをそれぞれ取り出すパターンマッチは以下のように書ける。
this match {
case Term.New(Template(_, Seq(
Term.Apply(
Term.ApplyType(_, Seq(Type.Name(name: String))),
Seq(implArg: Term.Arg)
)
), _, _)) =>
(name, implArg)
Term.Apply
に与えられる引数のTerm.ApplyType
とSeq[Type.Arg]
として型パラメータとコンストラクタが得られる。
これでMixIn[T](t: T)
のT
とt
が無事に得られる。
ちなみに、引数を渡すから型パラメータは型推論で解決してくれるだろう、と思ってもダメ。
Error:wrong number of type arguments for MixIn, should be 1
みたいな型パラメータが足りませんよ、というエラーが出てしまう。