help understanding methodMissing on non-Groovy classes

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

help understanding methodMissing on non-Groovy classes

Jeremy Heiner
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
Reply | Threaded
Open this post in threaded view
|

Re: help understanding methodMissing on non-Groovy classes

Jeremy Heiner
Addendum... oops, looks like I forgot to include the output! Here it
is (after Emacs has aligned the org-mode tables - best viewed in
monospace font):

=============================================================================
make clean run
rm *.class
kotlinc -cp /usr/share/groovy/embeddable/groovy-all.jar  Kt.kt
groovy -cp .:/usr/share/kotlin/lib/kotlin-runtime.jar     run.groovy
Gr.callWith( 3, Gr@4802796d, 692331943 )
|----------------------+-----------+----------------|
|                      | this      | run@206a70ef   |
|                      | delegate  | Gr@4802796d    |
|                      | it        | Gr@4802796d    |
| [hidden email](prop) | it.prop   | Gr.pM:prop     |
| [hidden email](mth)  | it.mth(1) | Gr.mM:mth([1]) |
| [hidden email](prop) | prop      | Gr.pM:prop     |
| [hidden email](mth)  | mth(2)    | Gr.mM:mth([2]) |
|----------------------+-----------+----------------|
Kt.callWith( 3, Gr@4802796d, 692331943 )
|----------------------+-----------+----------------|
|                      | this      | run@206a70ef   |
|                      | delegate  | Gr@4802796d    |
|                      | it        | Gr@4802796d    |
| [hidden email](prop) | it.prop   | Gr.pM:prop     |
| [hidden email](mth)  | it.mth(1) | Gr.mM:mth([1]) |
| [hidden email](prop) | prop      | Gr.pM:prop     |
| [hidden email](mth)  | mth(2)    | Gr.mM:mth([2]) |
|----------------------+-----------+----------------|
Gr.callWith( 3, Kt@1573f9fc, 692331943 )
|----------------------+-----------+-------------------------------------------------------------------------------------------------------------------|
|                      | this      | run@206a70ef

         |
|                      | delegate  | Kt@1573f9fc

         |
|                      | it        | Kt@1573f9fc

         |
| [hidden email](prop) | it.prop   | Kt.pM:prop

         |
| [hidden email](mth ) | it.mth(1) |
Kt.mM:mth([Ljava.lang.Object;@1f2586d6)
                                           |
| [hidden email](prop) | prop      | Kt.pM:prop

         |
|                      | mth(2)    | No signature of method:
run$_run_closure1.mth() is applicable for argument types:
(java.lang.Integer) values: [2] |
|----------------------+-----------+-------------------------------------------------------------------------------------------------------------------|
Kt.callWith( 3, Kt@1573f9fc, 692331943 )
|----------------------+-----------+-------------------------------------------------------------------------------------------------------------------|
|                      | this      | run@206a70ef

         |
|                      | delegate  | Kt@1573f9fc

         |
|                      | it        | Kt@1573f9fc

         |
| [hidden email](prop) | it.prop   | Kt.pM:prop

         |
| [hidden email](mth ) | it.mth(1) |
Kt.mM:mth([Ljava.lang.Object;@4116aac9)
                                           |
| [hidden email](prop) | prop      | Kt.pM:prop

         |
|                      | mth(2)    | No signature of method:
run$_run_closure1.mth() is applicable for argument types:
(java.lang.Integer) values: [2] |
|----------------------+-----------+-------------------------------------------------------------------------------------------------------------------|

So, to repeat, the mystery is why the closure is searched for the
missing method call instead of the delegate.
Reply | Threaded
Open this post in threaded view
|

Re: help understanding methodMissing on non-Groovy classes

Jochen Theodorou
In reply to this post by Jeremy Heiner


Am 09.11.2017 um 21:07 schrieb Jeremy Heiner:
[...]
> 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)" }
[...]
> 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)" }
[...]

does the behaviour change if you make Kt implement GroovyObject?

bye Jochen
Reply | Threaded
Open this post in threaded view
|

Re: help understanding methodMissing on non-Groovy classes

Jeremy Heiner
Thank you, Jochen! Changing

> open class Kt

to

> import groovy.lang.GroovyObjectSupport
> open class Kt : GroovyObjectSupport()

makes everything work as expected. -- Jeremy


On Fri, Nov 10, 2017 at 3:50 AM, Jochen Theodorou <[hidden email]> wrote:
> [...]
>
> does the behaviour change if you make Kt implement GroovyObject?
>
> bye Jochen
Reply | Threaded
Open this post in threaded view
|

Re: help understanding methodMissing on non-Groovy classes

Jochen Theodorou


Am 10.11.2017 um 10:33 schrieb Jeremy Heiner:

> Thank you, Jochen! Changing
>
>> open class Kt
>
> to
>
>> import groovy.lang.GroovyObjectSupport
>> open class Kt : GroovyObjectSupport()
>
> makes everything work as expected. -- Jeremy


ok, I was already guessing this would be the case. There are some cases
in which the delegate mechanism does not work properly if the delegate
is no GroovyObject. In general it works perfectly fine if the method you
delegate to is a static one or if the delegate uses
invokeMethod/getProperty/setProperty. But methodMissing for example
seems not to be tried in some cases.

bye Jochen