groovy
  1. groovy
  2. GROOVY-2582

cannot extend java.util.Properties unless getProperty() is overridden

    Details

    • Type: Bug Bug
    • Status: Closed Closed
    • Priority: Minor Minor
    • Resolution: Fixed
    • Affects Version/s: 1.5.4
    • Fix Version/s: 1.6-rc-1, 1.5.8, 1.7-beta-1
    • Component/s: ast builder
    • Labels:
      None
    • Environment:
      Windows 2000, JDK 1.6, Groovy 1.5.4
    • Number of attachments :
      0

      Description

      Running this in groovyconsole

      public class Configuration extends java.util.Properties 
      {  
          
      }  
      1
      

      yields the following error:

      Exception thrown: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, 
      Script9: -1: the return type is incompatible with java.lang.String getProperty(java.lang.String) in 
      java.util.Properties. Node: org.codehaus.groovy.ast.MethodNode. At [-1:-1]  @ line -1, column -1.
      

      But when the getProperty() method is overridden the code runs fine:

      public class Configuration extends java.util.Properties 
      {  
          String getProperty(String s) {
          }
      } 
      1
      

        Activity

        Hide
        Roshan Dawrani added a comment -

        Adding some details to describe the following conditions in which this problem occurs :-

        1) Base class and interface are implemented in java. The base class does not implement this interface. The base class and interface define a method that have incompatible return types but otherwise matching signatures.

        2) There is a class in groovy that extends that above mentioned base class and implements the interface. Compiling the groovy class throws the compilation error mentioned in this JIRA item. (groovy does not consider method implementation in Base class as implementation of interface method because of incompatible return type and hence marks considers the interface method as unimplemented and groovy class as abstract)

        The code that demonstrates the above conditions is:

        Interface and Base class in java:

        public interface IParent {
        	public Object invokeMe();
        }
        
        public class Parent {
        	public String invokeMe(){return null;} // return type incompatible to method defined in IParent
        }
        

        Subclass in groovy:

        class Child extends Parent implements IParent {}
        

        "public class Configuration extends java.util.Properties" fails because java.util.Properties has "public String getProperty(String)" and GroovyObject has "public Object getProperty(String)" - same conditions as mentioned above.

        Hope it helps.
        Roshan

        Show
        Roshan Dawrani added a comment - Adding some details to describe the following conditions in which this problem occurs :- 1) Base class and interface are implemented in java. The base class does not implement this interface. The base class and interface define a method that have incompatible return types but otherwise matching signatures. 2) There is a class in groovy that extends that above mentioned base class and implements the interface. Compiling the groovy class throws the compilation error mentioned in this JIRA item. (groovy does not consider method implementation in Base class as implementation of interface method because of incompatible return type and hence marks considers the interface method as unimplemented and groovy class as abstract) The code that demonstrates the above conditions is: Interface and Base class in java: public interface IParent { public Object invokeMe(); } public class Parent { public String invokeMe(){ return null ;} // return type incompatible to method defined in IParent } Subclass in groovy: class Child extends Parent implements IParent {} "public class Configuration extends java.util.Properties" fails because java.util.Properties has "public String getProperty(String)" and GroovyObject has "public Object getProperty(String)" - same conditions as mentioned above. Hope it helps. Roshan
        Hide
        blackdrag blackdrag added a comment -

        I guess I should extend that a bit.... afaik it does not matter if it is in Groovy or not, important is that there is a parent class method that is covariant to a method in a n interface I implement in the subclass. The Java compiler seems not to have a problem with this, so it should be allowed in Groovy too. The current algorithm for covariants is looking at methods covariant to methods from the super class and interfaces only. That means a method of the current class "overrides" the method of the super class or implements an interface method. Now here we have a parent class method "overriding" the method from an interface implemented in the current class. This is out of the scope of the algorithm, so the algorithm has to be adapted to consider these methods too.

        Show
        blackdrag blackdrag added a comment - I guess I should extend that a bit.... afaik it does not matter if it is in Groovy or not, important is that there is a parent class method that is covariant to a method in a n interface I implement in the subclass. The Java compiler seems not to have a problem with this, so it should be allowed in Groovy too. The current algorithm for covariants is looking at methods covariant to methods from the super class and interfaces only. That means a method of the current class "overrides" the method of the super class or implements an interface method. Now here we have a parent class method "overriding" the method from an interface implemented in the current class. This is out of the scope of the algorithm, so the algorithm has to be adapted to consider these methods too.
        Hide
        Roshan Dawrani added a comment -

        But even Java has the same problem:

        interface IParent {
        	public Object invokeMe();
        }
        class Parent {
        	public String invokeMe(){return null;}	
        }
        
        	/*
        	 * The following line results in compilation error because the return type is
        	 * incompatible.
        	 */
        class Child extends Parent implements IParent {
        	public Object invokeMe();
        }
        
        	/*
        	 * Child class' code as shown above is what following Configuration groovy class's generated code 
        will come down to if groovy does somehow implement GroovyObject's "public Object getProperty(String)" method
        	 */
        public class Configuration extends java.util.Properties 
        {  
            String getProperty(String s) {
            }
        }
        
        

        I think what groovy compiler is doing is correct - in the sense that the compilation in this scenario should fail.

        Probably what can be done is make some checks in groovy compiler to identify this scenario (java parent class providing the implementation for an interface that groovy subclass implements) and throw a different message with some suggested solution.

        However, additional checks will affect the compilation and runtime performance both. Or, it can be documented well but then it is not as useful as a good message from compiler.

        Show
        Roshan Dawrani added a comment - But even Java has the same problem: interface IParent { public Object invokeMe(); } class Parent { public String invokeMe(){ return null ;} } /* * The following line results in compilation error because the return type is * incompatible. */ class Child extends Parent implements IParent { public Object invokeMe(); } /* * Child class' code as shown above is what following Configuration groovy class's generated code will come down to if groovy does somehow implement GroovyObject's " public Object getProperty( String )" method */ public class Configuration extends java.util.Properties { String getProperty( String s) { } } I think what groovy compiler is doing is correct - in the sense that the compilation in this scenario should fail. Probably what can be done is make some checks in groovy compiler to identify this scenario (java parent class providing the implementation for an interface that groovy subclass implements) and throw a different message with some suggested solution. However, additional checks will affect the compilation and runtime performance both. Or, it can be documented well but then it is not as useful as a good message from compiler.
        Hide
        blackdrag blackdrag added a comment -

        to override "String invokeMe" with "Object invokeMe" is of course wrong. But we are talking about a method added by the compiler automatically. AFAIK the compiler does no longer add the method as of 1.5.7 and 1.6-beta2 if the parent class has already a method like that. But this does not solve the problem here. Since IParent requires a Object returning method one method like that has to be added as bridge, which calls the String returning method. This part is missing and will probably lead to a compilation error talking about not having implemented an abstract method. Or another problem...

        If your Child class above would not contain any method declaration, then it should compile in Java.

        Show
        blackdrag blackdrag added a comment - to override "String invokeMe" with "Object invokeMe" is of course wrong. But we are talking about a method added by the compiler automatically. AFAIK the compiler does no longer add the method as of 1.5.7 and 1.6-beta2 if the parent class has already a method like that. But this does not solve the problem here. Since IParent requires a Object returning method one method like that has to be added as bridge, which calls the String returning method. This part is missing and will probably lead to a compilation error talking about not having implemented an abstract method. Or another problem... If your Child class above would not contain any method declaration, then it should compile in Java.
        Hide
        Roshan Dawrani added a comment -

        I see. So, you are saying that for a method that is added automatically by the compiler, it is ok to have "Object invokeMe" in sub-class bytecode [one that adapts the call to String invokeMe] when its parent is having "String invokeMe" - something that both java/groovy compilers both don't allow for user written code?

        I didn't understand that compiler can use this extra power. I thought rules needed to be same whether methods were being added by compiler or the methods were being overridden in the code.

        The JVM won't have any issue executing that code? I am just curious.

        Show
        Roshan Dawrani added a comment - I see. So, you are saying that for a method that is added automatically by the compiler, it is ok to have "Object invokeMe" in sub-class bytecode [one that adapts the call to String invokeMe] when its parent is having "String invokeMe" - something that both java/groovy compilers both don't allow for user written code? I didn't understand that compiler can use this extra power. I thought rules needed to be same whether methods were being added by compiler or the methods were being overridden in the code. The JVM won't have any issue executing that code? I am just curious.
        Hide
        blackdrag blackdrag added a comment -

        yes, the compiler should reuse the existing method. A Object version will still exist, it only is a bridge to the real implementation, the covariant method. The JVM only cares about an existing Object method, because there is an interface with that method and the method must be implemented. But the JVM allows multiple methods of the same name with the same parameter types and different return types. When you for example compile a class with a method String invokeMet(), and the parent class contains a method Object invokeMe, then we have the covariant case here and the compiler will not only add the method String invokeMe() as specified by the user, the compiler will also add a bridge method for Object invokeMe() that will call the String returning method. From this logic the constraints of covariants do also become clear... the overriding method must return a class, that is a subclass of the other method return type. String is ok for Object, but obviously not for Integer.

        Now our policy for methods from GroovyObject is, that they are only added if needed. If the super class is already implementing them, then don't override. If the user has specified them, then don't replace. According to this, the compiler must not add another getProperty method, because Properties has already one. If that is not doing the right thing, then the method needs to be overridden by user code.

        So I compare this case more to your Child class without any user methods... which should compile and cause the compiler to add the bridge method.

        Show
        blackdrag blackdrag added a comment - yes, the compiler should reuse the existing method. A Object version will still exist, it only is a bridge to the real implementation, the covariant method. The JVM only cares about an existing Object method, because there is an interface with that method and the method must be implemented. But the JVM allows multiple methods of the same name with the same parameter types and different return types. When you for example compile a class with a method String invokeMet(), and the parent class contains a method Object invokeMe, then we have the covariant case here and the compiler will not only add the method String invokeMe() as specified by the user, the compiler will also add a bridge method for Object invokeMe() that will call the String returning method. From this logic the constraints of covariants do also become clear... the overriding method must return a class, that is a subclass of the other method return type. String is ok for Object, but obviously not for Integer. Now our policy for methods from GroovyObject is, that they are only added if needed. If the super class is already implementing them, then don't override. If the user has specified them, then don't replace. According to this, the compiler must not add another getProperty method, because Properties has already one. If that is not doing the right thing, then the method needs to be overridden by user code. So I compare this case more to your Child class without any user methods... which should compile and cause the compiler to add the bridge method.
        Hide
        Roshan Dawrani added a comment -

        Thanks for explaining. I had no idea about covariant methods. Your suggested approach is very clear now.

        Can you please point me to where in the compiler code this covariant algorithm is implemented - say in 1.5.8?

        Thanks again.

        rgds,
        Roshan

        Show
        Roshan Dawrani added a comment - Thanks for explaining. I had no idea about covariant methods. Your suggested approach is very clear now. Can you please point me to where in the compiler code this covariant algorithm is implemented - say in 1.5.8? Thanks again. rgds, Roshan
        Hide
        Erik Brenn added a comment -

        > So I compare this case more to your Child class without any user methods... which should compile and cause the compiler to add the bridge method.

        Agreed. Your compiler discussion is a bit beyond me but with Groovy's promise of Java integration I certainly think the code should work...

        Show
        Erik Brenn added a comment - > So I compare this case more to your Child class without any user methods... which should compile and cause the compiler to add the bridge method. Agreed. Your compiler discussion is a bit beyond me but with Groovy's promise of Java integration I certainly think the code should work...
        Hide
        blackdrag blackdrag added a comment -

        fixed

        Show
        blackdrag blackdrag added a comment - fixed

          People

          • Assignee:
            blackdrag blackdrag
            Reporter:
            Erik Brenn
          • Votes:
            1 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: