GeekFactory

int128.hatenablog.com

trait内のクロージャからプライベートメソッドが見えない

Groovy 2.3から導入されたtraitを使っていて、妙な事象に遭遇したのでまとめてみます。具体的には、traitの中でクロージャを使う場合に、クロージャからプライベートメソッドが見えない仕様があるようです。

例えば、下記のような trait T があるとします。

trait T {
  private a(x) { "--$x--" }

  // プライベートメソッドを直接呼んでいる
  def b(x)  { a(x) }

  // クロージャの中からプライベートメソッドを呼んでいる
  def c(xs) { xs.collect { x -> a(x) } }
}

下記のように、この trait を適用した class P および class Q を定義します。

// implementsでtraitを適用する
class P implements T {}

// withTraitsでtraitを適用する
class Q {}

P, Q に対してそれぞれメソッド b, c を呼び出します。メソッド b, c が期待通りに動くか検証してみます。

def p = new P()
println p.b('hoge')
println p.c(['hoge'])
def q = new Q().withTraits T
println q.b('hoge')
println q.c(['hoge'])

Groovy 2.3.6の仕様

実行結果は下記のようになりました。withTraits で適用すると、trait 内のメソッドクロージャを使うと、プライベートメソッドが見えないことが分かります。

--hoge--
[--hoge--]
--hoge--
groovy.lang.MissingMethodException: No signature of method: Q1_groovyProxy.a() is applicable for argument types: (java.lang.String) values: [hoge]
Possible solutions: b(java.lang.Object), c(java.lang.Object), any(), is(java.lang.Object), any(groovy.lang.Closure), wait()

Groovy 2.4.0の仕様

実行結果は下記のようになりました。いずれの適用方法でも、trait 内のクロージャからプライベートメソッドが見えないことが分かります。見えない仕様に統一されたのかもしれません。

--hoge--
groovy.lang.MissingMethodException: No signature of method: P.a() is applicable for argument types: (java.lang.String) values: [hoge]
Possible solutions: b(java.lang.Object), c(java.lang.Object), any(), is(java.lang.Object), any(groovy.lang.Closure), wait()
--hoge--
Caught: groovy.lang.MissingMethodException: No signature of method: Q1_groovyProxy.a() is applicable for argument types: (java.lang.String) values: [hoge]
Possible solutions: b(java.lang.Object), c(java.lang.Object), any(), is(java.lang.Object), any(groovy.lang.Closure), wait()

詳しい情報をお持ちでしたらぜひ教えてください。traitで拡張可能なDSLを作ろうとするとこの仕様はつらいですね。