Uploaded image for project: 'Groovy'
  1. Groovy
  2. GROOVY-10763

ClassNode setSuperClass and setInterfaces should update usingGenerics

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Closed
    • Major
    • Resolution: Fixed
    • 4.0.5
    • 5.0.0-alpha-1
    • Compiler
    • None

    Description

      I have an annotation that, if an annotated class has no extends, should make it extend Resource<A>, where A is determined by Logic. I attempted to create the ClassNode representing the intended base class and assign it as the derived class's superClass:

      println resource.superClass  // java.lang.Object
      
      def sc = makeClassSafeWithGenerics(TYPE_RESOURCE, attr)
      println sc  // Resource<com.example.Derived$Attributes> -> Resource<A>
      println sc.genericsTypes  // [com.example.Derived$Attributes]
      cn.superClass = sc
      
      def sc2 = cn.superClass
      println sc2            // Resource<A>
      println cn.@superClass // Resource<com.example.Derived$Attributes> -> Resource<A>
      println sc2 == sc  // true, but compares only the erasure
      println sc2 === sc // false
      

      The compiler then proceeds to write out a .class file extending the raw Resource type. The compiler writes the correct Resource<Attributes> if it is typed out inline in the input file.

      I've confirmed that the ClassNode TYPE_RESOURCE is not a redirect or a genericsPlaceholder, so the superClass assignment appears to be a direct field assignment (once redirect() returns this), and getUnresolvedSuperClass appears to read the field directly (lazyInitDone is already true), but using .@superClass returns the generic copy and .superClass (which is used by the rest of the compilation process) the raw copy.

       

      Update: cn.@superClass.redirectNode is true, and getSuperClass unwraps redirects (ClassNode:1040 in 4.0.5). This appears to be a logic error precisely because of this discard-generics-on-read effect.

      Update 2: I see the same "superclass is a redirect, and getSuperClass returns a raw type" on a class where the generics are specified in Groovy source code, but this class has the correct generics written into the bytecode where the ASTT-modified class does not. I cannot find any way to distinguish between the ClassNodes for the superclasses in these cases, but the output differs. (In case it's relevant at all, constructing a node for an interface type that has generic parameters and adding it to a class writes the type parameters into the bytecode; it's only the superclass that gets erased.)

      Update 3: In the case of a resource subclass that has type parameters defined in Groovy source, the field usesGenerics is set to true, presumably via the check at ClassNode:345. I have tried manually setting cn.usingGenerics = true, but that's triggering a (spurious?) "transform used a generics containing... directly" error downstream. It seems like a bug that the constructor sets usesGenerics if the superclass uses generics but that setSuperClass does not.

      Update 4: Adding the explicit cn.usingGenerics = true fixes the missing type parameters in the bytecode, at the cost of having to make spurious calls to .plainNodeReference later. It appears that the compiler is checking usingGenerics on class nodes when writing the extends part of the bytecode but not implements. There seem to be two logical bugs here: If the superclass has generic types, it shouldn't matter what the flag is on the subclass (write the type parameters as for interfaces), and creating a ClassNode that extends a generic superclass but does not itself have type parameters (e.g., StringMap extends HashMap<String, String>) should not trigger the "you must copy this node to refer to it" check.

      Attachments

        Activity

          People

            emilles Eric Milles
            chrylis Christopher Smith
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: