Skip to content

Failing GORM domain inheritance with Groovy 4: The interface XXX cannot be implemented more than once with different arguments 9.0.0-SNAPSHOT #1811

@jamesfredley

Description

@jamesfredley

Domain inheritance can be solved in at least one of three ways.

  1. Change 1 exception to a warning in org.codehaus.groovy.classgen.Verifier.checkForDuplicateInterfaces(final ClassNode cn)
    The change on Groovy would be lines 367-368, going from exception to a warning output to console. After that change, the code with domain inheritance compiles, as it did in Groovy 3 and runs without issue. https://github.com/apache/groovy/blob/ab5a811f5bffecf439714bbc9b073c3f66acf42c/src/main/java/org/codehaus/groovy/classgen/Verifier.java#L350-L377
    this has also come up on several other Grails modules and the changes to work around it could be undone if this happens.
  2. Rewrite all of the Interfaces with Generic Type Parameters in GORM (grails data mapping) to AST instead, this is massive change, see below mock code to understand the scope better and attached decompiled domain classes in comment. This impacts not only compile time, but also runtime dynamic missing method interception for methods like findByNameAndAge("james", 56)
  3. Use Gradle Artifact Transforms to replace org.codehaus.groovy.classgen.Verifier in the Groovy distribution jar with the change from option 1, before it is used to compile domain classes. This can be done as a Gradle Plugin and could live in grails-gradle-plugin. See rough example in comment. This is only required at compile time. The compiled classes run without issue on standard Groovy 4.

Groovy-5106 (domain inheritance) issues: https://issues.apache.org/jira/browse/GROOVY-5106

The interface GormEntity cannot be implemented more than once with different arguments: org.grails.datastore.gorm.GormEntity<grails.gorm.tests.XXX> and org.grails.datastore.gorm.GormEntity<grails.gorm.tests.XXX>

This also applies to other projects with inheritance tests
https://github.com/search?q=org%3Agrails%20%2F*extends&type=code

https://github.com/search?q=org%3Agrails+%2F%2F%40Entity&type=code

I dug a bit more into a generated domain with a parent domain and each will have two generic traits with one trait implementing a generic interface and then the generic type parameter is used in many methods in GormEntity, which then forks out in a number of directions. Here is an very simplified view of what that looks like.
At least the first two traits and first interface will generate "The interface XXX cannot be implemented more than once with different arguments" error when the child extends the parent. The compiler stops at the first one it bumps into.

package com.example

import groovy.transform.CompileStatic

static void main(String[] args) {
    List<Child> c = Child.getAll() 
    List<Foo> f = Child.getAll()
    List<Parent> p = Parent.getAll()
}

@CompileStatic
class Parent implements GormEntity<Parent>, Entity<Parent> {
    Parent save(){
    }
}

@CompileStatic
class Child extends Parent implements GormEntity<Child>, Entity<Child> {

}

@CompileStatic
class Foo implements GormEntity<Foo>, Entity<Foo> {
    Foo save(){
    }
}

@CompileStatic
// org.grails.datastore.gorm.GormEntityApi
// API for instance methods defined by a GORM entity
interface GormEntityApi<D> {
    D save()
}

@CompileStatic
// grails.gorm.Entity
// Trait added to all domain classes
trait Entity<D> {

}

@CompileStatic
// org.grails.datastore.gorm.GormEntity
// A trait that turns any class into a GORM entity
trait GormEntity<D> implements GormEntityApi<D> {

    static List<D> getAll() {
        []
    }

    private GormInstanceApi<D> currentGormInstanceApi() {
        // more complex in reality, but same effect
        new GormInstanceApi<D>()
    }

    static GormQueryOperations<D> getNamedQuery(String queryName, Object...args) {
        new GormQueryOperations<D>()
    }

    private static GormStaticApi<D> currentGormStaticApi() {
        new GormStaticApi<D>()
    }
}

class GormInstanceApi<D> extends AbstractGormApi<D> implements GormInstanceOperations<D> {

}

abstract class AbstractGormApi<D> extends AbstractDatastoreApi {

}

abstract class AbstractDatastoreApi {

}

interface GormInstanceOperations<D> {

}

// interface in reality
class GormQueryOperations<T> {

}

class GormStaticApi<D> extends AbstractGormApi<D> implements GormAllOperations<D> {

}

interface GormAllOperations<D> extends GormStaticOperations<D>, GormInstanceOperations<D> {

}

interface GormStaticOperations<D> {

}

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions