-
Notifications
You must be signed in to change notification settings - Fork 0
Description
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)