diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java index 1aee1542711da..68b5a4bbec98b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java @@ -432,31 +432,47 @@ static List getResolvedParameters(ClassInfo classInfo, Map r } } - static void detectWildcardAndThrow(Type type, AnnotationTarget producerFieldOrMethod) { + /** + * Detects wildcard for given producer method/field. + * Based on the boolean parameter, it either throws a {@link DefinitionException} of performs logging. + * Returns true if a wildcard is detected, false otherwise. + */ + static boolean isProducerWithWildcard(Type type, AnnotationTarget producerFieldOrMethod, boolean throwIfDetected) { if (producerFieldOrMethod == null) { // not a producer, no further analysis required - return; + return false; } if (type.kind().equals(Kind.WILDCARD_TYPE)) { - throw new DefinitionException("Producer " + - (producerFieldOrMethod.kind().equals(AnnotationTarget.Kind.FIELD) ? "field " : "method ") + - producerFieldOrMethod + - " declared on class " + - (producerFieldOrMethod.kind().equals(AnnotationTarget.Kind.FIELD) - ? producerFieldOrMethod.asField().declaringClass().name() - : producerFieldOrMethod.asMethod().declaringClass().name()) - + - " contains a parameterized type with a wildcard. This type is not a legal bean type" + - " according to CDI specification."); + if (throwIfDetected) { + throw new DefinitionException("Producer " + + (producerFieldOrMethod.kind().equals(AnnotationTarget.Kind.FIELD) ? "field " : "method ") + + producerFieldOrMethod + + " declared on class " + + (producerFieldOrMethod.kind().equals(AnnotationTarget.Kind.FIELD) + ? producerFieldOrMethod.asField().declaringClass().name() + : producerFieldOrMethod.asMethod().declaringClass().name()) + + + " contains a parameterized type with a wildcard. This type is not a legal bean type" + + " according to CDI specification."); + } else { + LOGGER.info("Producer " + + (producerFieldOrMethod.kind().equals(AnnotationTarget.Kind.FIELD) ? "field " : "method ") + + producerFieldOrMethod + + " contains a parameterized typed with a wildcard. This type is not a legal bean type" + + " according to CDI specification and will be ignored during bean resolution."); + return true; + } } else if (type.kind().equals(Kind.PARAMETERIZED_TYPE)) { for (Type t : type.asParameterizedType().arguments()) { // recursive check of all parameterized types - detectWildcardAndThrow(t, producerFieldOrMethod); + return isProducerWithWildcard(t, producerFieldOrMethod, throwIfDetected); } } + return false; } - static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFieldOrMethod, + private static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFieldOrMethod, + boolean throwOnProducerWildcard, Map resolvedTypeParameters, BeanDeployment beanDeployment, BiConsumer> resolvedTypeVariablesConsumer) { Set types = new HashSet<>(); @@ -469,12 +485,13 @@ static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFi } else { // Canonical ParameterizedType with unresolved type variables Type[] typeParams = new Type[typeParameters.size()]; + boolean skipThisType = false; for (int i = 0; i < typeParameters.size(); i++) { typeParams[i] = resolvedTypeParameters.get(typeParameters.get(i).identifier()); // for producers, wildcard is not a legal bean type and results in a definition error // see https://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#legal_bean_types // NOTE: wildcard can be nested, such as List> - detectWildcardAndThrow(typeParams[i], producerFieldOrMethod); + skipThisType = isProducerWithWildcard(typeParams[i], producerFieldOrMethod, throwOnProducerWildcard); } if (resolvedTypeVariablesConsumer != null) { Map resolved = new HashMap<>(); @@ -483,7 +500,9 @@ static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFi } resolvedTypeVariablesConsumer.accept(classInfo, resolved); } - types.add(ParameterizedType.create(classInfo.name(), typeParams, null)); + if (!skipThisType) { + types.add(ParameterizedType.create(classInfo.name(), typeParams, null)); + } } // Interfaces for (Type interfaceType : classInfo.interfaceTypes()) { @@ -497,7 +516,7 @@ static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFi resolved = buildResolvedMap(interfaceType.asParameterizedType().arguments(), interfaceClassInfo.typeParameters(), resolvedTypeParameters, beanDeployment.getBeanArchiveIndex()); } - types.addAll(getTypeClosure(interfaceClassInfo, producerFieldOrMethod, resolved, beanDeployment, + types.addAll(getTypeClosure(interfaceClassInfo, producerFieldOrMethod, false, resolved, beanDeployment, resolvedTypeVariablesConsumer)); } } @@ -511,13 +530,20 @@ static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFi superClassInfo.typeParameters(), resolvedTypeParameters, beanDeployment.getBeanArchiveIndex()); } - types.addAll(getTypeClosure(superClassInfo, producerFieldOrMethod, resolved, beanDeployment, + types.addAll(getTypeClosure(superClassInfo, producerFieldOrMethod, false, resolved, beanDeployment, resolvedTypeVariablesConsumer)); } } return types; } + static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFieldOrMethod, + Map resolvedTypeParameters, + BeanDeployment beanDeployment, BiConsumer> resolvedTypeVariablesConsumer) { + return getTypeClosure(classInfo, producerFieldOrMethod, true, resolvedTypeParameters, beanDeployment, + resolvedTypeVariablesConsumer); + } + static Set getDelegateTypeClosure(InjectionPointInfo delegateInjectionPoint, BeanDeployment beanDeployment) { Set types; Type delegateType = delegateInjectionPoint.getRequiredType(); diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java index eb35c5ff8acb5..1bfeacd0a24d4 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java @@ -1,5 +1,6 @@ package io.quarkus.arc.processor; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -27,7 +28,7 @@ public class TypesTest { @Test public void testGetTypeClosure() throws IOException { IndexView index = Basics.index(Foo.class, Baz.class, Producer.class, Object.class, List.class, Collection.class, - Iterable.class, Set.class); + Iterable.class, Set.class, Eagle.class, Bird.class, Animal.class, AnimalHolder.class); DotName bazName = DotName.createSimple(Baz.class.getName()); DotName fooName = DotName.createSimple(Foo.class.getName()); DotName producerName = DotName.createSimple(Producer.class.getName()); @@ -78,6 +79,13 @@ public void testGetTypeClosure() throws IOException { assertThrows(DefinitionException.class, () -> Types.getProducerFieldTypeClosure(producerClass.field(nestedWildCardProducersName), dummyDeployment)); + // now assert following ones do NOT throw, the wildcard in the hierarchy is just ignored + final String wildcardInTypeHierarchy = "eagleProducer"; + assertDoesNotThrow( + () -> Types.getProducerMethodTypeClosure(producerClass.method(wildcardInTypeHierarchy), dummyDeployment)); + assertDoesNotThrow( + () -> Types.getProducerFieldTypeClosure(producerClass.field(wildcardInTypeHierarchy), dummyDeployment)); + } static class Foo { @@ -90,7 +98,7 @@ static class Baz extends Foo { } - static class Producer { + static class Producer { public List produce() { return null; @@ -103,5 +111,26 @@ public List> produceNested() { List produce; List> produceNested; + + // following producer should NOT throw an exception because the return types doesn't contain wildcard + // but the hierarchy of the return type actually contains one + // taken from CDI TCK setup, see https://github.com/jakartaee/cdi-tck/blob/4.0.7/impl/src/main/java/org/jboss/cdi/tck/tests/definition/bean/types/illegal/BeanTypesWithIllegalTypeTest.java + public Eagle eagleProducer() { + return new Eagle<>(); + } + + public Eagle eagleProducer; + } + + static class Eagle extends Bird { + } + + static class Bird extends AnimalHolder> { + } + + static class Animal { + } + + static class AnimalHolder { } }