🐾
Scala3のマクロでメソッド名を取得する
目的
あるメソッド内で生成する文字列にメソッド名を入れたいということがあり、ハードコードするとメソッド名の変更に際し変更漏れが発生することがあり得るのでマクロで自動取得することを目的とする。
下記のコードの文字列リテラル中の"process"を自動で取得したいというイメージである。
def process: Unit = {
println("process: xx中")
}
実装
マクロ呼出自体の情報はSymbol.spliceOwner
によって取得できる。マクロ呼出のコードが含まれているメソッドの情報を取りたいので.ower
によって呼出元情報を取得する。また.name
によって名前が取得できるので名前を取得したいメソッドの直下で呼び出すことを前提とするのであればこのように書ける。
inline def methodName: String = ${ methodNameImpl }
private def methodNameImpl(using Quotes): Expr[String] = {
import quotes.reflect.*
Expr(Symbol.spliceOwner.owner.name)
}
上記の実装の場合、無名関数の中で呼び出されると意図したように機能しない。
PlayframeworkのControllerのActionを書くときは下記のようにActionBuilderのblockとして無名関数を書かざるを得ない。このとき、取得したいのはアクションを実行するindex
メソッドの名前なのでこのようなケースにも対応するためにはもう少し変更を加える必要がある。
def index: Action[AnyContent] = Action { implicit request => ??? }
名前を取得したいのはメソッド定義かつ無名関数ではないものなので、それが見つかるまで呼出元を辿る必要がある。isDefDef
とisAnonymousFunction
を使って判定し、条件に合致しない場合は呼出元に対して適用するということを再帰的に行う。(エントリポイントのmainメソッドがあるので無限呼び出しにはならないはず......)
@tailrec
def callerMethod(symbol: Symbol): Symbol = {
if (symbol.isDefDef && !symbol.isAnonymousFunction) { symbol }
else callerMethod(symbol.owner)
}
上記のメソッドを利用すると最終形は下記のようになる。
inline def methodName: String = ${ methodNameImpl }
private def methodNameImpl(using Quotes): Expr[String] = {
import quotes.reflect.*
@tailrec
def callerMethod(symbol: Symbol): Symbol = {
if (symbol.isDefDef && !symbol.isAnonymousFunction) { symbol }
else { callerMethod(symbol.owner) }
}
Expr(callerMethod(Symbol.spliceOwner.owner).name)
}
参考資料
- Scala3マクロ入門
Discussion