I'm writing a Gradle Plugin in Kotlin and am seeing that
`methodMissing` is not being called... but only when a `Closure` (from
the build script) uses the "unspecified receiver" syntax that makes
the DSL/builder stuff so nice. Everything works as expected with an
explicit receiver, which I interpret to mean that this is not a
problem in the bytecode Kotlin is producing. I trimmed the code down
to 80ish lines that neatly capture the behavior. I apologize for
making my first post here so large. I hope that some benefit comes out
of having a succinct sample and that the formatting doesn't make this
post an unreadable mess.
*** File "Kt.kt" defines a class that can be either a
`Closure.delegate` (callee) or can invoke a closure (caller):
=============================================================================
import groovy.lang.Closure
import org.codehaus.groovy.runtime.DefaultGroovyMethods
open class Kt
{
open fun propertyMissing( name:String ):Any {
System.out.print( "${this}.pM($name)" )
return "Kt.pM:$name" }
open fun methodMissing( name:String, args:Any ):Any {
System.out.print( "${this}.mM($name )" )
return "Kt.mM:$name($args)" }
open fun callWith( strategy:Int, delegate:Any?, closure:Closure<*> ):Any? {
val hashCode = System.identityHashCode( closure )
System.out.println( "Kt.callWith( $strategy, $delegate, $hashCode )" )
if ( strategy < 0 ) {
return DefaultGroovyMethods.with( delegate, closure ) }
val clone = closure.clone() as Closure<*>
clone.resolveStrategy = strategy
clone.delegate = delegate
return clone.call( delegate ) }
}
=============================================================================
*** File "run.groovy" defines class `Gr` similar to `Kt` (I tried to
use syntax that overlaps), then a closure that can be called, and
executes all four combinations of caller/callee pairs:
=============================================================================
import org.codehaus.groovy.runtime.DefaultGroovyMethods
class Gr
{
def propertyMissing( String name ) {
System.out.print( "${this}.pM($name)" )
return "Gr.pM:$name" }
def methodMissing( String name, args ) {
System.out.print( "${this}.mM($name)" )
return "Gr.mM:$name($args)" }
def callWith( int strategy, delegate, Closure closure ) {
int hashCode = System.identityHashCode( closure )
System.out.println( "Gr.callWith( $strategy, $delegate, $hashCode )" )
if ( strategy < 0 ) {
return DefaultGroovyMethods.with( delegate, closure ) }
Closure clone = closure.clone()
clone.resolveStrategy = strategy
clone.delegate = delegate
return clone.call( delegate ) }
}
Closure ORGTABLE = {
println '|-||'
print '|' ; println '|this | '+this
print '|' ; println '|delegate | '+delegate
print '|' ; try { println '|it | '+it } catch ( ex )
{ println '|it | '+ex.message.readLines().head() }
print '|' ; try { println '|it.prop | '+it.prop } catch ( ex )
{ println '|it.prop | '+ex.message.readLines().head() }
print '|' ; try { println '|it.mth(1)| '+it.mth(1) } catch ( ex )
{ println '|it.mth(1)| '+ex.message.readLines().head() }
print '|' ; try { println '|prop | '+(prop) } catch ( ex )
{ println '|prop | '+ex.message.readLines().head() }
print '|' ; try { println '|mth(2) | '+(mth(2)) } catch ( ex )
{ println '|mth(2) | '+ex.message.readLines().head() }
println '|-||' }
//this.metaClass.getProp << { -> 'p' }
//def mth( arg ) { arg }
//println "props = "+this.metaClass.properties*.name
//println 'direct call' ; ORGTABLE( this )
def both = [ new Gr(), new Kt() ]
int strategy = Closure.DELEGATE_ONLY // comment this line & uncomment
next to see others
//for ( strategy in [ -1, Closure.DELEGATE_FIRST, Closure.TO_SELF,
Closure.DELEGATE_ONLY ] )
for ( callee in both ) for ( caller in both ) caller.callWith(
strategy, callee, ORGTABLE )
=============================================================================
*** File "makefile" (which won't work until you put tabs back in front
of the commands):
=============================================================================
KOTLIN_JAR=/usr/share/kotlin/lib/kotlin-runtime.jar # 1.1.51
GROOVY_JAR=/usr/share/groovy/embeddable/groovy-all.jar # 2.4.12
run : Kt.class
groovy -cp .:${KOTLIN_JAR} run.groovy
Kt.class : Kt.kt
kotlinc -cp ${GROOVY_JAR} Kt.kt
showKt : Kt.class
javap Kt 'Kt$$Companion'
clean :
rm *.class
=============================================================================
The following can be clearly seen from the output of the above:
+ the closure's delegate and "it" are exactly the same `Object` in all cases.
+ the `it.prop`, `it.mth()` and even `prop` syntax work as expected
in all cases.
+ but the `mth()` syntax works for the Groovy delegate (callee) and
fails for the Kotlin one (regardless of caller).
Thanks! --Jeremy