Why won't this method with circular generics in its signature compile?

72 views Asked by At

I've made the following method that uses circular generic types in its signature.

private <U extends NameTranslation<T> & Autocompletable, T extends Polyonymous<U>> U createNameTranslationInstance(T parent, Language targetLanguage,
                                                                                            String translatedName, String translatedAutoSuggestionText) {
    if (parent instanceof Organism organism) {
        return (U) new OrganismCommonNameTranslation(organism, targetLanguage, translatedName, gardenerPlanet,
                translatedAutoSuggestionText, SubmissionStatus.APPROVED);
    } else if (parent instanceof LifeStage lifeStage) {
        return (U) new LifeStageCommonNameTranslation(lifeStage, targetLanguage, translatedName, gardenerPlanet,
                translatedAutoSuggestionText, SubmissionStatus.APPROVED);
    }
    throw new IllegalArgumentException("Unsupported parent type: " + parent.getClass().getName());
}

This method returns an object of type U which extends a NameTranslation abstract class with generic type T and also implements the interface Autocompletable. Generic type T is of a type that extends interface Polyonymous with a generic type U.

When I try to compile this it will throw a couple of java compiler errors for this method.

java: incompatible types: T cannot be converted to POJOs.TaxonomicPojos.Organism
java: incompatible types: POJOs.TaxonomicPojos.categorytranslations.OrganismCommonNameTranslation cannot be converted to U
java: incompatible types: T cannot be converted to POJOs.TaxonomicPojos.LifeStage
java: incompatible types: POJOs.TaxonomicPojos.categorytranslations.LifeStageCommonNameTranslation cannot be converted to U

But both Organism and LifeStage extend Polyonymous<> and the associated CommonNameTranslation classes each extend NameTranslation and implement AutoCompletable.

When I remove extends Polyonymous<U> and thereby removing the circular reference everything compiles again. I've googled to see if circular generic dependencies aren't allowed in Java. But according to this SO answer circular generics are perfectly allowable.

It gets even weirder when I remove & Autocompletable from the method signature instead of the extends Polyonymous part. Now this method causes an internal java compiler error instead.

java: Compilation failed: internal java compiler error

This type of error can occur due to a variety of reasons, including incorrect syntax in the code, incorrect usage of Java language constructs, memory or disk space issues, or bugs in the Java compiler itself (source).

So what is it about this generic signature that causes compile issues?

Here are all the class definitions

    public class Organism implements Polyonymous<OrganismCommonNameTranslation>{
    
    }
    
    public class LifeStage implements Polyonymous<LifeStageCommonNameTranslation>{
    
    }
    
    public class OrganismCommonNameTranslation extends NameTranslation<Organism> implements Autocompletable {
    
    }
    
    public class LifeStageCommonNameTranslation extends NameTranslation<LifeStage> implements Autocompletable {
    
    }

public abstract class NameTranslation<T>{}

    public interface Polyonymous<T>{}
    
    public interface Autcompletable{}

EDIT: Here's my java version info

openjdk 20.0.2.1 2023-08-22
OpenJDK Runtime Environment Corretto-20.0.2.10.1 (build 20.0.2.1+10-FR)
OpenJDK 64-Bit Server VM Corretto-20.0.2.10.1 (build 20.0.2.1+10-FR, mixed mode, sharing)

EDIT 07/12/2023

The internal java error seems to be caused by the explicit cast to (U) in combination with circular generic references.

1

There are 1 answers

5
Tianhong Yu On

I think your code is ok theoretically, but it seem like compiler is not smart enough.

I modified your code a little to fool compiler and it run well. Here is the code:

private <U extends NameTranslation<T> & Autocompletable, T extends Polyonymous<U>> U createNameTranslationInstance(T parent, Language targetLanguage,
                                                                                            String translatedName, String translatedAutoSuggestionText) {
    if ((Object) parent instanceof Organism organism) {
        return (U) (Object) new OrganismCommonNameTranslation(organism, targetLanguage, translatedName, gardenerPlanet,
                translatedAutoSuggestionText, SubmissionStatus.APPROVED);
    } else if ((Object) parent instanceof LifeStage lifeStage) {
        return (U) (Object) new LifeStageCommonNameTranslation(lifeStage, targetLanguage, translatedName, gardenerPlanet,
                translatedAutoSuggestionText, SubmissionStatus.APPROVED);
    }
    throw new IllegalArgumentException("Unsupported parent type: " + parent.getClass().getName());
}

I found that the compiler errors occur in these two part:

  1. Use instance of to judge a generic variable is specific type
  2. Cast a instance of a specific type to generic type

I think it may be better if you remove extends NameTranslation<T> & Autocompletable in your code, because they seem useless.

I still don't know the exact reason why the compiler throw those errors. But hope my answer could help you a little.