Search code examples
groovymultiple-inheritancetypechecking

Groovy: Is there a way to implement multiple inheritance while using type-checking?


@groovy.transform.TypeChecked
abstract class Entity {
    ...
    double getMass() {
        ...
    }
    ...
}

@groovy.transform.TypeChecked
abstract class Location {
    ...
    Entity[] getContent() {
        ...
    }
    ...
}

@groovy.transform.TypeChecked
abstract class Container {...}  //inherits, somehow, from both Location and Entity

@groovy.transform.TypeChecked
class Main {
    void main() {
        double x
        Container c = new Chest() //Chest extends Container
        Entity e = c
        x = e.mass
        Location l = c
        x = l.content  //Programmer error, should throw compile-time error
    }
}

Essentially, is there a way to achieve this, without sacrificing any of the three properties outlines in main():

  • Direct access to fields, even virtual fields
  • Assigning to both super-classes
  • Typechecking (at compile-time)

Solution

  • I don't think you can do that with classes. Maybe you'd wanted traits (under discussion update: available in Groovy 2.3 and already rocking!) or, for a pure dynamic solution, @Mixin, which you'd back up with a good test suite.

    My guess: @Delegate is your best friend here, but, as it stands, you can only store a Chest object in a Container type variable. So you'd need some interfaces.

    Even if the superclass is not under your control, you can use groovy as operator to make it implement an interface.

    First, i rewrote your classes to remove the abstract and add interfaces:

    import groovy.transform.TypeChecked as TC
    
    interface HasMass { double mass }
    interface HasContent { Entity[] getContent() }
    
    @TC class Entity implements HasMass { double mass }
    
    @TC class Location {
        Entity[] getContent() {
            [new Entity(mass: 10.0), new Entity(mass: 20.0)] as Entity[]
        }
    }
    

    Note i didn't added HasContent to Location, to show the usage of as.

    Second, comes the Container and Chest. @Delegate is added and it auto-inherits the interfaces of the delegates:

    @TC 
    abstract class Container {
      @Delegate Location location = new Location()
      @Delegate Entity entity = new Entity()
    }
    
    
    @TC class Chest extends Container { }
    

    Last, it becomes type-checkable, as long as you stick to interfaces:

    @TC class Mult {
        static main(args) {
            def x // use 'def' for flow-typing
            Container c = new Chest() //Chest extends Container
            HasMass e = c
            x = e.mass
            def l = c as HasContent
    
            x = l.content  //Programmer error, should throw compile-time error
    
            assert c.content.collect { Entity it -> it.mass } == [10.0, 20.0]
        }
    }