Skip to content

Implement parameterized types without reifying type arguments #296

@raner

Description

@raner

Consider the following scenario where a class implements a parameterized interface and binds its type parameter to a specific type:

interface Operation<TYPE extends Number>
{
  TYPE perform(TYPE parameter);
}
class Increment implements Operation<Integer>
{
  @Override
  public Integer perform(Integer parameter)
  {
    return parameter+1;
  }
}

Under the hood, the Java compiler will generate the following:

public class Increment extends java.lang.Object implements Operation<java.lang.Integer>
{
  public Increment();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC

  public java.lang.Integer perform(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
    flags: (0x0001) ACC_PUBLIC

  public java.lang.Number perform(java.lang.Number);
    descriptor: (Ljava/lang/Number;)Ljava/lang/Number;
    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
}

The implemented method is rendered with the expected return type and parameter type (Integer), but this method will not actually implement the method that is specified by the interface because it has a different signature.
To really implement the interface, the compiler generates a synthetic bridge method (which uses Number as parameter and return type). The bridge method verifies the argument type and redirects to the other method.

Having both methods is necessary for being able to call the method directly with its actual argument type and indirectly as a generic implementation of the interface. If methods are only ever invoked through the the generic interface (i.e., not using concrete type arguments), then the method that uses the concrete type argument type is technically not necessary (and the implementation code could be directly added to the bridge method).

There is one scenario where the method with the concrete type argument actually gets in the way. This scenario will typically not happen with "regular" Java code, but it can be a regular headache when solving certain compiler bootstrapping problems. The example used the Integer type, which is a well known API type that is guaranteed to exist. However, what if instead of using Integer we would like to use a user-defined type... and that type has not been translated yet (i.e., there is no class file containing the corresponding bytecode yet). Let's also assume that we cannot simply change the compilation order, e.g., due to other constraints.

When such a scenario is implemented with Projo, this will currently produce an exception:

java.lang.TypeNotPresentException: Type lang.Real not present
	at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:117)
	at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:125)
	at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
	at java.base/sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68)
	at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138)
	at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
	at java.base/sun.reflect.generics.repository.ClassRepository.computeSuperInterfaces(ClassRepository.java:117)
	at java.base/sun.reflect.generics.repository.ClassRepository.getSuperInterfaces(ClassRepository.java:95)
	at java.base/java.lang.Class.getGenericInterfaces(Class.java:1211)
	at net.bytebuddy.description.type.TypeList$Generic$OfLoadedInterfaceTypes$TypeProjection.resolve(TypeList.java:823)
	at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection.accept(TypeDescription.java:6297)
	at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection$WithResolvedErasure.resolve(TypeDescription.java:6949)
	at net.bytebuddy.description.type.TypeDescription$Generic$LazyProjection.accept(TypeDescription.java:6297)
	at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.doAnalyze(MethodGraph.java:746)
	at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.analyze(MethodGraph.java:710)
	at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.doAnalyze(MethodGraph.java:746)
	at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$Default.compile(MethodGraph.java:668)
	at net.bytebuddy.dynamic.scaffold.MethodGraph$Compiler$AbstractBase.compile(MethodGraph.java:519)
	at net.bytebuddy.dynamic.scaffold.MethodRegistry$Default.prepare(MethodRegistry.java:472)
	at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.toTypeWriter(SubclassDynamicTypeBuilder.java:212)
	at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.toTypeWriter(SubclassDynamicTypeBuilder.java:203)
	at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$UsingTypeWriter.make(DynamicType.java:4055)
	at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3739)
	at pro.projo.internal.rcg.RuntimeCodeGenerationHandler.generateImplementation(RuntimeCodeGenerationHandler.java:208)
	at pro.projo.internal.rcg.RuntimeCodeGenerationHandler.lambda$getImplementationOf$0(RuntimeCodeGenerationHandler.java:183)
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1740)
	at pro.projo.internal.rcg.RuntimeCodeGenerationHandler.getImplementationOf(RuntimeCodeGenerationHandler.java:183)
	at pro.projo.Projo.getImplementationClass(Projo.java:496)
	at pro.projo.Projo.getImplementationClass(Projo.java:468)
	at natives.bootstrap.Bootstrap.bindNativeImplementation(Bootstrap.java:139)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions