groovy-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Jeremy Heiner <jere...@imagemattersllc.com>
Subject help understanding methodMissing on non-Groovy classes
Date Thu, 09 Nov 2017 20:07:55 GMT
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

Mime
View raw message