groovy
  1. groovy
  2. GROOVY-2643

Delegate property is ignored by closure

    Details

    • Type: Bug Bug
    • Status: Closed Closed
    • Priority: Blocker Blocker
    • Resolution: Fixed
    • Affects Version/s: 1.5.4, 1.6-beta-1
    • Fix Version/s: 1.5.5, 1.6-beta-1
    • Component/s: None
    • Labels:
      None
    • Testcase included:
      yes
    • Number of attachments :
      3

      Description

      I have attached the following test case. It is interesting to see that the testDelegateFileMethodAccess fails, whereas the other test where delegate methods are accessed succeed. All tests with property access fail. This is a serious bug which for example prevents me to offer important DSL functionality for my build tool.

      import org.apache.tools.ant.Project
      
      class ClosureBug extends GroovyTestCase {
          String expectedFileResult
      
          void setUp() {
              expectedFileResult = 'path'
          }
      
          void testDelegateAntPropertyAccess() {
              assert script(antScriptText('antProject')).run() instanceof Project
          }
      
          void testDelegateAntMethodAccess() {
              assert script(antScriptText('getAntProject()')).run() instanceof Project
          }
      
          void testDelegateFilePropertyAccess() {
              assert script(fileScriptText('path')).run() == expectedFileResult
          }
      
          void testDelegateFileMethodAccess() {
              assert script(fileScriptText('getPath()')).run() == expectedFileResult
          }
      
          void testDelegateCircleAccessGetterWithoutField() {
              assert script(circleScriptText('getProp3()')).run() == 'value3'
          }
      
          void testDelegateCircleAccessGetterWithField() {
              assert script(circleScriptText('getProp2()')).run() == 'value2'
          }
      
          void testDelegateCircleAccessDynamicGetter() {
              assert script(circleScriptText('getProp1()')).run() == 'value1'
          }
      
          void testDelegateCircleAccessPropertyWithoutGetter() {
              assert script(circleScriptText('prop1')).run() == 'value1'
          }
      
          void testDelegateCircleAccessPropertyWithGetter() {
              assert script(circleScriptText('prop2')).run() == 'value2'
          }
      
          void testDelegateCircleAccessPropertyWithoutField() {
              assert script(circleScriptText('prop3')).run() == 'value3'
          }
      
      
          def replaceMetaclass(Script script) {
              ExpandoMetaClass emc = new ExpandoMetaClass(script.class, false)
              emc.methodMissing = {String name, args ->
                  throw new MissingMethodException(name, Script, args)
              }
              emc.propertyMissing = {String name ->
                  throw new MissingPropertyException(name, Script)
              }
              emc.initialize()
              script.metaClass = emc
          }
      
          Script script(String text) {
              GroovyShell groovyShell = new GroovyShell()
              Script script = groovyShell.parse(text)
              replaceMetaclass(script)
              script
          }
      
          String antScriptText(String accessDirective) {
              """          
      AntBuilder ant = new AntBuilder()
      Closure cl = {
           $accessDirective
      }
      cl.delegate = ant
      cl.call()"""
          }
      
          String fileScriptText(String accessDirective) {
              """
      File file = new File('$expectedFileResult')
      Closure cl = {
           $accessDirective
      }
      cl.delegate = file
      cl.call()"""
          }
      
          String circleScriptText(String accessDirective) {
              """
      Circle circle = new Circle()
      Closure cl = {
           $accessDirective
      }
      cl.delegate = circle
      cl.call()"""
          }
      }
      
      class Circle {
          String prop1 = 'value1'
          String prop2 = 'value2'
      
          String getProp2() {
              prop2
          }
      
          String getProp3() {
              'value3'
          }
      
      }
      
      1. groovy2643.groovy
        3 kB
        Paul King
      2. groovy2643-passing.groovy
        3 kB
        Paul King
      3. Groovy2643ShortTest.groovy
        2 kB
        Hans Dockter

        Issue Links

          Activity

          Hide
          Hans Dockter added a comment -

          Before EMC was introduces this problem was probably more hidden. What I do is nothing exotic. Things you need to do for many DSL scenarios.

          Show
          Hans Dockter added a comment - Before EMC was introduces this problem was probably more hidden. What I do is nothing exotic. Things you need to do for many DSL scenarios.
          Hide
          blackdrag blackdrag added a comment -

          without EMC this problem does happen, because it is special to closures... even if missing method is used from the current class this will not happen. But a fix is to be expected in a matter of hours.

          Show
          blackdrag blackdrag added a comment - without EMC this problem does happen, because it is special to closures... even if missing method is used from the current class this will not happen. But a fix is to be expected in a matter of hours.
          Hide
          blackdrag blackdrag added a comment -

          ok, both 1.5.x and 1.6 now have fixes for this issue. It would be nice of you to test this to be sure it fixes your original issue

          Show
          blackdrag blackdrag added a comment - ok, both 1.5.x and 1.6 now have fixes for this issue. It would be nice of you to test this to be sure it fixes your original issue
          Hide
          Hans Dockter added a comment -

          The tests run now, but in my real code I still have a problem. I've used the GROOVY_1_5_X branch from this morning. I could not build HEAD, as I got test failures.

          Here is a script to reproduce my problem. It gives the following error:

          [echo] closureCall
          testprop
          testMeth
               [echo] directCall
          Caught: groovy.lang.MissingMethodException: No signature of method: Parent.echo() is applicable for argument types: (java.util.LinkedHashMap) values: {["message":"closureCall"]}
          groovy.lang.MissingMethodException: No signature of method: Parent.echo() is applicable for argument types: (java.util.LinkedHashMap) values: {["message":"closureCall"]}
          

          What is also confusing! When I uncomment: cl.resolveStrategy(Closure.DELEGATE_ONLY), I get the exception:

          Caught: groovy.lang.MissingMethodException: No signature of method: MyScript.resolveStrategy() is applicable for argument types: (java.lang.Integer) values: {3}
          groovy.lang.MissingMethodException: No signature of method: MyScript.resolveStrategy() is applicable for argument types: (java.lang.Integer) values: {3}
          
          class BuildScriptProcessor {
              void evaluate(def parent, String text) {
                  GroovyShell groovyShell = new GroovyShell()
                  Script script = groovyShell.parse(text)
                  replaceMetaclass(script, parent)
                  script.run()
              }
          
              private void replaceMetaclass(Script script, def parent) {
                  ExpandoMetaClass projectScriptExpandoMetaclass = new ExpandoMetaClass(script.class, false)
                  projectScriptExpandoMetaclass.methodMissing = {String name, args ->
                      parent.invokeMethod(name, args)
                  }
                  projectScriptExpandoMetaclass.propertyMissing = {String name ->
                      if (name == 'out') {
                          return System.out
                      }
                      parent."$name"
                  }
                  projectScriptExpandoMetaclass.initialize()
                  script.metaClass = projectScriptExpandoMetaclass
              }
          }
          
          class Parent {
              AntBuilder ant = new AntBuilder()
              String testProp = 'testprop'
          
              String testMeth() {
                  'testMeth'
              }
          
              void ant(Closure cl) {
          //        cl.resolveStrategy(Closure.DELEGATE_ONLY)
                  cl.delegate = ant
                  cl.call()
              }
          }
          
          Parent parent = new Parent()
          
          parent.ant {
             echo(message: 'closureCall')  
          }
          
          String script = """
          println testProp
          println testMeth()
          ant.echo(message: 'directCall')
          ant {
              echo(message: 'closureCall') 
          }
          """
          
          new BuildScriptProcessor().evaluate(parent, script)
          
          Show
          Hans Dockter added a comment - The tests run now, but in my real code I still have a problem. I've used the GROOVY_1_5_X branch from this morning. I could not build HEAD, as I got test failures. Here is a script to reproduce my problem. It gives the following error: [echo] closureCall testprop testMeth [echo] directCall Caught: groovy.lang.MissingMethodException: No signature of method: Parent.echo() is applicable for argument types: (java.util.LinkedHashMap) values: {["message":"closureCall"]} groovy.lang.MissingMethodException: No signature of method: Parent.echo() is applicable for argument types: (java.util.LinkedHashMap) values: {["message":"closureCall"]} What is also confusing! When I uncomment: cl.resolveStrategy(Closure.DELEGATE_ONLY), I get the exception: Caught: groovy.lang.MissingMethodException: No signature of method: MyScript.resolveStrategy() is applicable for argument types: (java.lang.Integer) values: {3} groovy.lang.MissingMethodException: No signature of method: MyScript.resolveStrategy() is applicable for argument types: (java.lang.Integer) values: {3} class BuildScriptProcessor { void evaluate(def parent, String text) { GroovyShell groovyShell = new GroovyShell() Script script = groovyShell.parse(text) replaceMetaclass(script, parent) script.run() } private void replaceMetaclass(Script script, def parent) { ExpandoMetaClass projectScriptExpandoMetaclass = new ExpandoMetaClass(script.class, false ) projectScriptExpandoMetaclass.methodMissing = { String name, args -> parent.invokeMethod(name, args) } projectScriptExpandoMetaclass.propertyMissing = { String name -> if (name == 'out') { return System .out } parent. "$name" } projectScriptExpandoMetaclass.initialize() script.metaClass = projectScriptExpandoMetaclass } } class Parent { AntBuilder ant = new AntBuilder() String testProp = 'testprop' String testMeth() { 'testMeth' } void ant(Closure cl) { // cl.resolveStrategy(Closure.DELEGATE_ONLY) cl.delegate = ant cl.call() } } Parent parent = new Parent() parent.ant { echo(message: 'closureCall') } String script = """ println testProp println testMeth() ant.echo(message: 'directCall') ant { echo(message: 'closureCall') } """ new BuildScriptProcessor().evaluate(parent, script)
          Hide
          blackdrag blackdrag added a comment -

          the issue was resolved the day after, but I let it open to see if there are more problems. I merged the change now into 1.6 and since it looks like there are no problems left I close it

          Show
          blackdrag blackdrag added a comment - the issue was resolved the day after, but I let it open to see if there are more problems. I merged the change now into 1.6 and since it looks like there are no problems left I close it

            People

            • Assignee:
              blackdrag blackdrag
              Reporter:
              Hans Dockter
            • Votes:
              1 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: