Details

    • Type: Sub-task Sub-task
    • Status: Open Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 1.7.4
    • Fix Version/s: None
    • Component/s: groovy-runtime
    • Labels:
      None
    • Number of attachments :
      0

      Description

      If a class with static methods is mixed in another class, the static methods are not available as static methods.
      Furthermore a strange behaviour occurs - they are available as instance methods and when once called as instance methods they also get available as static methods - see the code below.

      class Base {
          def instanceMethod() { println 'instance method' }
          static staticMethod() { println 'static method' }
      }
      
      class T1 {}
      class T2 {}
      
      T1.mixin Base
      T2.mixin Base
      
      t1 = new T1()
      t1.instanceMethod()
      t1.staticMethod()
      T1.staticMethod()   // somehow working, after callling it as an instance method before - thats' strange!!!
      
      t2 = new T2()
      t2.instanceMethod()
      T2.staticMethod()   // not working: Caught: groovy.lang.MissingMethodException: No signature of method: static T2.staticMethod() is applicable for argument types: () values
      

        Activity

        Hide
        James Lang added a comment -

        This is a pretty important feature, what's the status for v1.8? If none, a quick explanation would be nice so we have an idea why it cannot be done.

        Thanks

        Show
        James Lang added a comment - This is a pretty important feature, what's the status for v1.8? If none, a quick explanation would be nice so we have an idea why it cannot be done. Thanks
        Hide
        Chenyang XING added a comment -

        Why not fix this issue? Why not anyone track this issue? Obviously, this is issue.

        Show
        Chenyang XING added a comment - Why not fix this issue? Why not anyone track this issue? Obviously, this is issue.
        Hide
        Paul King added a comment -

        I didn't write the original logic, but my understanding is that when you "mixin" methods from a class, that static methods are treated like category methods, i.e. they expect the first parameter to be some "self" type which then become instance methods on the class. As to why when you leave out the "self" arg and then call them as an instance method, they suddenly show up (as both instance and static!!) methods? I guess that is some kind of bug in the current implementation. I haven't been able to track it down as yet. I suggest just sticking very closely with the original intention of static methods, i.e. category methods. Anything fancier than that doesn't seem to have well-defined behavior or much testing so I would avoid using it.

        Show
        Paul King added a comment - I didn't write the original logic, but my understanding is that when you "mixin" methods from a class, that static methods are treated like category methods, i.e. they expect the first parameter to be some "self" type which then become instance methods on the class. As to why when you leave out the "self" arg and then call them as an instance method, they suddenly show up (as both instance and static!!) methods? I guess that is some kind of bug in the current implementation. I haven't been able to track it down as yet. I suggest just sticking very closely with the original intention of static methods, i.e. category methods. Anything fancier than that doesn't seem to have well-defined behavior or much testing so I would avoid using it.
        Hide
        Paul King added a comment -

        Actually, looking into it a bit more, the following logic seems to hold:

        • instance methods are mixed in as instance methods when mixin is called
        • static category methods are mixed in as instance methods when mixin is called
        • if during execution, neither of the above are found, then as part of internal missing method logic, a second lookup is done for methods from the mixed in class and any found methods (static or instance including ones added through meta-programming) are mixed in as needed as instance methods (but static methods are also remembered as such) - no category processing is done at this stage - and the fact that they then work as static methods is almost by accident

        This seems quite complex to me and trying to repair the edge cases is possible but could be a little complex.

        I suspect we will try to simplify and rationalize the behavior here in a future version of Groovy.

        Show
        Paul King added a comment - Actually, looking into it a bit more, the following logic seems to hold: instance methods are mixed in as instance methods when mixin is called static category methods are mixed in as instance methods when mixin is called if during execution, neither of the above are found, then as part of internal missing method logic, a second lookup is done for methods from the mixed in class and any found methods (static or instance including ones added through meta-programming) are mixed in as needed as instance methods (but static methods are also remembered as such) - no category processing is done at this stage - and the fact that they then work as static methods is almost by accident This seems quite complex to me and trying to repair the edge cases is possible but could be a little complex. I suspect we will try to simplify and rationalize the behavior here in a future version of Groovy.
        Hide
        Paul King added a comment - - edited

        Just a little script to capture some of the current behaviour:

        class Log { static out = '' }
        import static Log.out
        class Base {
          void method1() { out += '|method1' }
          static method2(def self) { out += "|method2 ${self?.class?.name}" }
        }
        class T1 {}
        T1.mixin Base
        Base.metaClass.method3 = { out += '|method3' }
        out += '|' + T1.metaClass.methods*.name.findAll{ it =~ 'method.*' }.sort().join(':')
        new T1().with {
          try { T1.method2(); assert false } catch(MissingMethodException) { }
          method1(); method2(); method3(); method2('foo'); T1.method2()
        }
        out += '|' + T1.metaClass.methods*.name.findAll{ it =~ 'method.*' }.sort().join(':')
        assert out == '|method1:method2|method1|method2 T1|method3|method2 java.lang.String|method2 null|method1:method2:method2:method3'
        

        Explanation (not that I particularly like it):

        • when T1.mixin Base is invoked, "method1" and "method2" are added as no-arg instance methods to T1. Here method2 is treated as a category method, i.e. it is a static method and the self parameter is "absorbed"
        • we then add "method3" into Base using meta-programming but this isn't seen in T1 yet
        • we can see the two added methods by listing the methods: 'method1:method2'
        • when we try to invoke method2() as a static method it fails as missing but we catch that and ignore here
        • we then invoke the earlier mixed-in methods, method1() and method2()
        • we then try to invoke method3() as an instance method; this isn't found so the MOP relooks for any relevant methods in Base and finds that one has been added - upon finding it, a "method3" instance method is added to T1
        • we then try to invoke method2('foo') as an instance method with an argument - this isn't found so the MOP again relooks for any appropriate methods in Base - upon finding the static method, "method2(Object)" is added as an instance method to T1 but because it was static originally, it is also recognised as such from now on by the MOP
        • as a final check we again look at the available methods, this time we see four (remember there is a no-arg and single arg version of 'method2'): 'method1:method2:method2:method3'
        Show
        Paul King added a comment - - edited Just a little script to capture some of the current behaviour: class Log { static out = '' } import static Log.out class Base { void method1() { out += '|method1' } static method2(def self) { out += "|method2 ${self?.class?.name}" } } class T1 {} T1.mixin Base Base.metaClass.method3 = { out += '|method3' } out += '|' + T1.metaClass.methods*.name.findAll{ it =~ 'method.*' }.sort().join(':') new T1().with { try { T1.method2(); assert false } catch (MissingMethodException) { } method1(); method2(); method3(); method2('foo'); T1.method2() } out += '|' + T1.metaClass.methods*.name.findAll{ it =~ 'method.*' }.sort().join(':') assert out == '|method1:method2|method1|method2 T1|method3|method2 java.lang. String |method2 null |method1:method2:method2:method3' Explanation (not that I particularly like it): when T1.mixin Base is invoked, " method1 " and " method2 " are added as no-arg instance methods to T1 . Here method2 is treated as a category method, i.e. it is a static method and the self parameter is "absorbed" we then add " method3 " into Base using meta-programming but this isn't seen in T1 yet we can see the two added methods by listing the methods: ' method1:method2 ' when we try to invoke method2() as a static method it fails as missing but we catch that and ignore here we then invoke the earlier mixed-in methods, method1() and method2() we then try to invoke method3() as an instance method; this isn't found so the MOP relooks for any relevant methods in Base and finds that one has been added - upon finding it, a " method3 " instance method is added to T1 we then try to invoke method2('foo') as an instance method with an argument - this isn't found so the MOP again relooks for any appropriate methods in Base - upon finding the static method, " method2(Object) " is added as an instance method to T1 but because it was static originally, it is also recognised as such from now on by the MOP as a final check we again look at the available methods, this time we see four (remember there is a no-arg and single arg version of 'method2'): ' method1:method2:method2:method3 '
        blackdrag blackdrag made changes -
        Field Original Value New Value
        Parent GROOVY-2503 [ 61571 ]
        Issue Type Bug [ 1 ] Sub-task [ 7 ]
        blackdrag blackdrag made changes -
        Component/s groovy-runtime [ 16250 ]

          People

          • Assignee:
            Unassigned
            Reporter:
            Rene Scheibe
          • Votes:
            9 Vote for this issue
            Watchers:
            9 Start watching this issue

            Dates

            • Created:
              Updated: