From 429496e5fab32011e0eb26b4a32d54d0b5dbc107 Mon Sep 17 00:00:00 2001 From: Mirko Raner Date: Sun, 3 Nov 2024 10:34:01 -0800 Subject: [PATCH] Issue #296: avoided reifying type arguments --- .../rcg/RuntimeCodeGenerationHandler.java | 39 +++++- .../rcg/utilities/GenericVisitor.java | 128 ++++++++++++++++++ .../rcg/utilities/RawGenericLoadedType.java | 44 ++++++ .../integration/tests/ProjoDecimalsIT.java | 90 ++++++++++++ .../projo/test/implementations/Decimal.java | 64 +++++++++ .../projo/test/implementations/Decimals.java | 35 +++++ .../projo/test/implementations/Utilities.java | 10 +- .../pro/projo/test/interfaces/Decimal.java | 39 ++++++ .../pro/projo/test/interfaces/Decimals.java | 23 ++++ .../pro/projo/annotations/RawInterfaces.java | 55 ++++++++ 10 files changed, 520 insertions(+), 7 deletions(-) create mode 100644 projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/utilities/GenericVisitor.java create mode 100644 projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/utilities/RawGenericLoadedType.java create mode 100644 projo-runtime-code-generation/src/test/java/pro/projo/integration/tests/ProjoDecimalsIT.java create mode 100644 projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Decimal.java create mode 100644 projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Decimals.java create mode 100644 projo-runtime-code-generation/src/test/java/pro/projo/test/interfaces/Decimal.java create mode 100644 projo-runtime-code-generation/src/test/java/pro/projo/test/interfaces/Decimals.java create mode 100644 projo/src/main/java/pro/projo/annotations/RawInterfaces.java diff --git a/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandler.java b/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandler.java index 9e07635..e1cae11 100644 --- a/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandler.java +++ b/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandler.java @@ -51,12 +51,17 @@ import net.bytebuddy.dynamic.DynamicType.Builder.FieldDefinition.Valuable; import net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ImplementationDefinition; import net.bytebuddy.dynamic.DynamicType.Loaded; +import net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler; +import net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default; +import net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Harmonizer; +import net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Merger; import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.implementation.DefaultMethodCall; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.Implementation.Composable; import net.bytebuddy.implementation.MethodCall; +import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.utility.JavaConstant; import pro.projo.Projo; import pro.projo.annotations.Cached; @@ -76,6 +81,8 @@ import pro.projo.internal.rcg.runtime.ToStringValueObject; import pro.projo.internal.rcg.runtime.ValueObject; import pro.projo.internal.rcg.utilities.GenericTypeResolver; +import pro.projo.internal.rcg.utilities.GenericVisitor; +import pro.projo.internal.rcg.utilities.RawGenericLoadedType; import pro.projo.internal.rcg.utilities.UncheckedMethodDescription; import pro.projo.utilities.AnnotationList; import pro.projo.utilities.MethodInfo; @@ -496,7 +503,21 @@ private MethodDescription.Latent latent(TypeDescription declaringType, Generic r private ByteBuddy codeGenerator() { - return new ByteBuddy(JAVA_V8).with(TypeValidation.DISABLED); + return codeGenerator(false); + } + + private ByteBuddy codeGenerator(boolean requiresGenericVisitor) + { + Compiler compiler = !requiresGenericVisitor? Default.forJavaHierarchy():new Default<> + ( + Harmonizer.ForJavaMethod.INSTANCE, + Merger.Directional.LEFT, + new GenericVisitor(), + ElementMatchers.any() + ); + return new ByteBuddy(JAVA_V8) + .with(TypeValidation.DISABLED) + .with(compiler); } /** @@ -607,9 +628,15 @@ private Class baseclass(Class<_Artifact_> type) private Builder<_Artifact_> create(Class<_Artifact_> type, List additionalImplements, ClassLoader classLoader) { Stream baseTypes = Stream.of(type(type), type(ProjoObject.class)); - TypeDefinition[] interfaces = Stream.concat(baseTypes, additionalImplements.stream().map(it -> type(it, classLoader))).toArray(TypeDefinition[]::new); + Generic[] interfaces = Stream + .concat(baseTypes, additionalImplements.stream().map(it -> type(it, classLoader))) + .toArray(Generic[]::new); + Stream annotationNames = Stream.of(interfaces) + .flatMap(it -> it.getDeclaredAnnotations().asTypeNames().stream()); + boolean requiresGenericVisitor = GenericVisitor.isNecessaryFor(annotationNames); @SuppressWarnings("unchecked") - Builder<_Artifact_> builder = (Builder<_Artifact_>)codeGenerator().subclass(baseclass(type)).implement(interfaces); + Builder<_Artifact_> builder = (Builder<_Artifact_>)codeGenerator(requiresGenericVisitor) + .subclass(baseclass(type)).implement(interfaces); return builder; } @@ -633,12 +660,12 @@ private Class classOf(Type type) throw new UnsupportedOperationException(type + " (" + type.getClass() + ")"); } - private TypeDefinition type(Class type) + private Generic type(Class type) { - return new TypeDescription.ForLoadedType(type); + return new RawGenericLoadedType(type); } - private TypeDefinition type(String typeName, ClassLoader classLoader) + private Generic type(String typeName, ClassLoader classLoader) { Matcher matcher = typeNamePattern.matcher(typeName); matcher.matches(); diff --git a/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/utilities/GenericVisitor.java b/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/utilities/GenericVisitor.java new file mode 100644 index 0000000..a6f9ce6 --- /dev/null +++ b/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/utilities/GenericVisitor.java @@ -0,0 +1,128 @@ +// // +// Copyright 2024 Mirko Raner // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +package pro.projo.internal.rcg.utilities; + +import java.util.stream.Stream; +import net.bytebuddy.description.annotation.AnnotationList; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeDescription.Generic; +import net.bytebuddy.description.type.TypeDescription.Generic.Visitor; +import net.bytebuddy.description.type.TypeList; +import pro.projo.annotations.RawInterfaces; +import static java.util.stream.Collectors.toList; + +/** +* {@link GenericVisitor} implements a variation of {@link Visitor.Reifying#INITIATING} +* that will strip type arguments from all interfaces extended by an interface that is +* annotated with {@link RawInterfaces} (effectively causing the interface to extend the +* corresponding raw types). +* +* @author Mirko Raner +**/ +public class GenericVisitor implements Visitor +{ + private final Visitor delegate = Generic.Visitor.Reifying.INITIATING; + + /** + * Determines if use of the {@link GenericVisitor} is necessary. + * + * @param annotationNames the type names of the annotations present + * @return {@code true} if the annotation names include {@link RawInterfaces}, + * otherwise {@code false} + **/ + public static boolean isNecessaryFor(Stream annotationNames) + { + return annotationNames.anyMatch(RawInterfaces.class.getName()::equals); + } + + @Override + public Generic onGenericArray(Generic genericArray) + { + return delegate.onGenericArray(genericArray); + } + + @Override + public Generic onWildcard(Generic wildcard) + { + return delegate.onWildcard(wildcard); + } + + @Override + public Generic onParameterizedType(Generic parameterizedType) + { + return delegate.onParameterizedType(parameterizedType); + } + + @Override + public Generic onTypeVariable(Generic typeVariable) + { + return delegate.onTypeVariable(typeVariable); + } + + @Override + public Generic onNonGenericType(Generic typeDescription) + { + // NOTE: typeDescription.getDeclaredAnnotations() will typically not return any + // annotations for Generics corresponding to loaded types because + // Generic.OfNonGenericType.ForLoadedType uses AnnotationReader.NoOp.INSTANCE. + // This method will only work with custom Generics that retain annotations. + // + AnnotationList annotations = typeDescription.getDeclaredAnnotations(); + if (typeDescription instanceof RawGenericLoadedType + && (isNecessaryFor(annotations.asTypeNames().stream()))) + { + // Even though the type name is known from typeDescription.getActualName() + // simply regenerating the Class via Class.forName(...) is tricky and prone + // to failure because the correct class loader is not known. Unless, the + // current class loader was also used to originally load the Class in question + // the class may not be found in the current class loader, or, even worse, + // a duplicate class is loaded. Therefore, the only way to obtain the Class + // reliably is when the Generic type provided is a RawGenericLoadedType that + // provides access to the encapsulated Class object: + // + Class type = ((RawGenericLoadedType)typeDescription).getType(); + return new Generic.OfNonGenericType.ForLoadedType(type) + { + @Override + public TypeList.Generic getInterfaces() + { + Stream> interfaces = Stream.of(type.getInterfaces()); + Stream rawInterfaces = interfaces.map + ( + it -> new TypeDescription.ForLoadedType(it) + { + private final static long serialVersionUID = 1L; + + @Override + public TypeList.Generic getTypeVariables() + { + return new TypeList.Generic.Empty(); + } + + @Override + public boolean isGenerified() + { + return false; + } + } + ); + return new TypeList.Generic.Explicit(rawInterfaces.collect(toList())); + } + }; + } + return delegate.onNonGenericType(typeDescription); + } +} diff --git a/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/utilities/RawGenericLoadedType.java b/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/utilities/RawGenericLoadedType.java new file mode 100644 index 0000000..34e3ac5 --- /dev/null +++ b/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/utilities/RawGenericLoadedType.java @@ -0,0 +1,44 @@ +// // +// Copyright 2024 Mirko Raner // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +package pro.projo.internal.rcg.utilities; + +import net.bytebuddy.description.type.TypeDescription.Generic; + +/** +* {@link RawGenericLoadedType} is a customization of +* {@link Generic.OfNonGenericType.ForLoadedType} that provides the following +* additional features: +*
    +*
  • it preserves annotations declared by the wrapped {@link Class}
  • +*
  • it provides access to the original wrapped {@link Class} object
  • +*
+* @author Mirko Raner +**/ +public class RawGenericLoadedType extends Generic.OfNonGenericType.ForLoadedType +{ + private final Class type; + + public RawGenericLoadedType(Class type) + { + super(type, new AnnotationReader.Delegator.Simple(type)); + this.type = type; + } + + public Class getType() + { + return type; + } +} diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/integration/tests/ProjoDecimalsIT.java b/projo-runtime-code-generation/src/test/java/pro/projo/integration/tests/ProjoDecimalsIT.java new file mode 100644 index 0000000..51f9cfa --- /dev/null +++ b/projo-runtime-code-generation/src/test/java/pro/projo/integration/tests/ProjoDecimalsIT.java @@ -0,0 +1,90 @@ +// // +// Copyright 2024 Mirko Raner // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +package pro.projo.integration.tests; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URISyntaxException; +import java.net.URL; +import org.junit.BeforeClass; +import org.junit.Test; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.TypeLiteral; +import pro.projo.Projo; +import pro.projo.test.implementations.Ranges; +import pro.projo.test.implementations.Utilities; +import pro.projo.test.interfaces.Decimals; +import pro.projo.test.interfaces.Literals; +import pro.projo.test.interfaces.Provider; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ProjoDecimalsIT +{ + @BeforeClass + public static void deleteDecimalClass() throws URISyntaxException + { + ClassLoader classLoader = ProjoDecimalsIT.class.getClassLoader(); + URL url = classLoader.getResource("pro/projo/test/interfaces/Decimal.class"); + if (url != null) + { + new File(url.toURI()).delete(); + } + } + + @Test + public void implementationOfDecimalsIsProperlyBound() throws Exception + { + Literals literals = new Literals() {}; + Injector injector = Guice.createInjector(module(literals)); + Decimals naturals = injector.getInstance(Decimals.class); + Method literalsMethod = Provider.class.getMethod("literals"); + Literals result = (Literals)literalsMethod.invoke(naturals); + assertEquals(literals, result); + } + + @Test + public void interfaceMethodsAreProperlyInvoked() throws Exception + { + Injector injector = Guice.createInjector(module(new Literals() {})); + Decimals decimals = injector.getInstance(Decimals.class); + Method parseMethod = Provider.class.getMethod("parse", Object.class); + parseMethod.invoke(decimals, ""); + assertTrue(Utilities.nullValueCalled); + } + + private Module module(Literals literals) + { + return new AbstractModule() + { + @Override + @SuppressWarnings("unchecked") + protected void configure() + { + Class classDecimals = (Class)Decimals.class; + TypeLiteral interfaceDecimals = TypeLiteral.get((Class)classDecimals); + Class implementationNaturals = Projo.getImplementationClass(pro.projo.test.implementations.Decimals.class); + TypeLiteral implementation = TypeLiteral.get((Class)implementationNaturals); + bind(interfaceDecimals).to(implementation).asEagerSingleton(); + bind(Literals.class).toInstance(literals); + bind(Ranges.class).toInstance(new Ranges() {}); + } + }; + } +} diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Decimal.java b/projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Decimal.java new file mode 100644 index 0000000..f823b95 --- /dev/null +++ b/projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Decimal.java @@ -0,0 +1,64 @@ +// // +// Copyright 2024 Mirko Raner // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +package pro.projo.test.implementations; + +import static pro.projo.annotations.Overrides.toString; + +import java.math.BigInteger; + +import pro.projo.Projo; +import pro.projo.annotations.Expects; +import pro.projo.annotations.Implements; +import pro.projo.annotations.Overrides; +import pro.projo.annotations.Returns; +import pro.projo.singles.Factory; + +@Implements("pro.projo.test.interfaces.Decimal") +public interface Decimal +{ + public static Factory factory = Projo.creates(Decimal.class).with(Decimal::value); + + BigInteger value(); + + @Returns("pro.projo.test.interfaces.Decimal") + default Decimal successor() + { + return factory.create(value().add(BigInteger.ONE)); + } + + @Returns("pro.projo.test.interfaces.Decimal") + default Decimal plus(@Expects("pro.projo.test.interfaces.Decimal") Decimal other) + { + return factory.create(value().add(other.value())); + } + + @Returns("pro.projo.test.interfaces.Decimal") + default Decimal times(@Expects("pro.projo.test.interfaces.Decimal") Decimal other) + { + return factory.create(value().multiply(other.value())); + } + + default boolean equals(@Expects("pro.projo.test.interfaces.Decimal") Decimal other) + { + return value().equals(other.value()); + } + + @Overrides(toString) + default String toNativeString() + { + return value().toString(); + } +} diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Decimals.java b/projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Decimals.java new file mode 100644 index 0000000..d53f030 --- /dev/null +++ b/projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Decimals.java @@ -0,0 +1,35 @@ +// // +// Copyright 2024 Mirko Raner // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +package pro.projo.test.implementations; + +import javax.inject.Inject; +import pro.projo.annotations.Implements; +import pro.projo.annotations.Returns; +import pro.projo.test.interfaces.Provider; + +@Implements("pro.projo.test.interfaces.Decimals") +public interface Decimals extends Provider +{ + @Inject + Utilities utilities(); + + @Override + @Returns("java.lang.Object") // TODO: why is this necessary?? + public default Object parse(Object literal) + { + return utilities().nullValue(); + } +} diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Utilities.java b/projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Utilities.java index ad6a7a0..8f8d6ad 100644 --- a/projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Utilities.java +++ b/projo-runtime-code-generation/src/test/java/pro/projo/test/implementations/Utilities.java @@ -1,5 +1,5 @@ // // -// Copyright 2022 Mirko Raner // +// Copyright 2022 - 2024 Mirko Raner // // // // Licensed under the Apache License, Version 2.0 (the "License"); // // you may not use this file except in compliance with the License. // @@ -19,8 +19,16 @@ public class Utilities { + public static boolean nullValueCalled; + public Natural natural(String value) { return Natural.factory.create(new BigInteger(value)); } + + public Object nullValue() + { + nullValueCalled = true; + return null; + } } diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/test/interfaces/Decimal.java b/projo-runtime-code-generation/src/test/java/pro/projo/test/interfaces/Decimal.java new file mode 100644 index 0000000..f54a522 --- /dev/null +++ b/projo-runtime-code-generation/src/test/java/pro/projo/test/interfaces/Decimal.java @@ -0,0 +1,39 @@ +// // +// Copyright 2024 Mirko Raner // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +package pro.projo.test.interfaces; + +import javax.inject.Inject; + +import pro.projo.annotations.Property; +import pro.projo.test.implementations.Ranges; + +public interface Decimal<$ extends Decimal<$>> +{ + Decimal successor(); + + Decimal plus(Decimal other); + + Decimal times(Decimal other); + + boolean equals(Decimal other); + + @Inject + @Property + default Ranges ranges() + { + throw new NoSuchMethodError("ranges() should have been implemented"); + } +} diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/test/interfaces/Decimals.java b/projo-runtime-code-generation/src/test/java/pro/projo/test/interfaces/Decimals.java new file mode 100644 index 0000000..f6796d0 --- /dev/null +++ b/projo-runtime-code-generation/src/test/java/pro/projo/test/interfaces/Decimals.java @@ -0,0 +1,23 @@ +// // +// Copyright 2024 Mirko Raner // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +package pro.projo.test.interfaces; + +import pro.projo.annotations.RawInterfaces; + +@RawInterfaces +public interface Decimals<$ extends Decimals<$>> extends Provider> +{ +} diff --git a/projo/src/main/java/pro/projo/annotations/RawInterfaces.java b/projo/src/main/java/pro/projo/annotations/RawInterfaces.java new file mode 100644 index 0000000..86ca59a --- /dev/null +++ b/projo/src/main/java/pro/projo/annotations/RawInterfaces.java @@ -0,0 +1,55 @@ +// // +// Copyright 2024 Mirko Raner // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +package pro.projo.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** +* The {@link RawInterfaces} annotation prevents Projo from reifying type arguments of +* extended interfaces during runtime code generation. +* For example, consider the following interface: +*
+*   interface Decimals extends Provider<Decimal>
+*   {
+*     //...
+*   }
+* 
+* When Projo creates an implementation of this interface (for example, due to an +* {@link Implements @Implements} annotation on some other type) this will trigger +* loading of the {@code Decimal} class, as the default behavior is to reify all +* type arguments. In 99% of all cases, this is exactly what should happen anyway. +* However, there are some edge cases where this is a problem, for example if the +* {@code Decimal} type does not exist (yet), or loading it at this point would +* cause some issue (after all, {@link Implements @Implements} is specifically +* geared towards unusual compile-time/runtime bootstrapping scenarios). +* By annotating the type with {@link RawInterfaces @RawInterfaces} the implemented +* interfaces will be treated as if they were raw types. +* This has a few implications, however. The most significant being that the type +* parameter of the extended interface will not be replaced with the actual type +* argument (as raw types will not convey what that type argument is). Therefore, +* parameterized methods can only be invoked through their generic interface. +* +* @author Mirko Raner +**/ +@Target(TYPE) +@Retention(RUNTIME) +public @interface RawInterfaces +{ + // This annotation currently has no fields. +}