Repository: quarkusio/quarkus
Issue: Build failed due to limit bean types to types outside of the transitive closure of bean types

### Describe the bug

Our Quarkus QE project does not compile when run against `999-SNAPSHOT`. It must be some recent change as it was fine last week.

caused by https://github.com/quarkusio/quarkus/pull/30447

### Expected behavior

Build succeeds.

### Actual behavior

Build failes with

```bash
[ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:999-SNAPSHOT:build (build) on project http-advanced-reactive: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[ERROR] 	[error]: Build step io.quarkus.arc.deployment.ArcProcessor#registerBeans threw an exception: javax.enterprise.inject.spi.DefinitionException: Cannot limit bean types to types outside of the transitive closure of bean types. Bean: org.jboss.resteasy.plugins.providers.multipart.MapMultipartFormDataReader illegal bean types: [javax.ws.rs.ext.MessageBodyReader]
[ERROR] 	at io.quarkus.arc.processor.Types.restrictBeanTypes(Types.java:644)
[ERROR] 	at io.quarkus.arc.processor.Types.getClassBeanTypeClosure(Types.java:403)
[ERROR] 	at io.quarkus.arc.processor.Beans$ClassBeanFactory.create(Beans.java:1121)
[ERROR] 	at io.quarkus.arc.processor.Beans.createClassBean(Beans.java:49)
[ERROR] 	at io.quarkus.arc.processor.BeanDeployment.findBeans(BeanDeployment.java:1034)
[ERROR] 	at io.quarkus.arc.processor.BeanDeployment.registerBeans(BeanDeployment.java:256)
[ERROR] 	at io.quarkus.arc.processor.BeanProcessor.registerBeans(BeanProcessor.java:134)
[ERROR] 	at io.quarkus.arc.deployment.ArcProcessor.registerBeans(ArcProcessor.java:444)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
[ERROR] 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR] 	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
[ERROR] 	at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:909)
[ERROR] 	at io.quarkus.builder.BuildContext.run(BuildContext.java:281)
[ERROR] 	at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
[ERROR] 	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
[ERROR] 	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
[ERROR] 	at java.base/java.lang.Thread.run(Thread.java:833)
[ERROR] 	at org.jboss.threads.JBossThread.run(JBossThread.java:501)
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
[ERROR] 
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR]   mvn <args> -rf :http-advanced-reactive

```

### How to Reproduce?

Reproducer:

Steps to reproduce teh behavior:
1. `git clone https://github.com/quarkus-qe/quarkus-test-suite.git`
2. `mvn -T C1 -DskipDocs -DskipTests -DskipITs -DskipExtensionValidation -Dskip.gradle.tests -Dcheckstyle.skip clean install`

### Output of `uname -a` or `ver`

Linux fedora 6.1.5-200.fc37.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Jan 12 15:52:00 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

### Output of `java -version`

openjdk version "17.0.5" 2022-10-18

### GraalVM version (if different from Java)

22.3

### Quarkus version or git rev

999-SNAPSHOT

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)

### Additional information

_No response_

Changed files:
independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java
independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java

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 ce05ad8207a..f6a97250338 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
@@ -331,6 +331,7 @@ static Type getProviderType(ClassInfo classInfo) {
 
     static Set<Type> getProducerMethodTypeClosure(MethodInfo producerMethod, BeanDeployment beanDeployment) {
         Set<Type> types;
+        Set<Type> unrestrictedBeanTypes = new HashSet<>();
         Type returnType = producerMethod.returnType();
         if (returnType.kind() == Kind.TYPE_VARIABLE) {
             throw new DefinitionException("A type variable is not a legal bean type: " + producerMethod);
@@ -347,22 +348,25 @@ static Set<Type> getProducerMethodTypeClosure(MethodInfo producerMethod, BeanDep
                         "Producer method return type not found in index: " + producerMethod.returnType().name());
             }
             if (Kind.CLASS.equals(returnType.kind())) {
-                types = getTypeClosure(returnTypeClassInfo, producerMethod, Collections.emptyMap(), beanDeployment, null);
+                types = getTypeClosure(returnTypeClassInfo, producerMethod, Collections.emptyMap(), beanDeployment, null,
+                        unrestrictedBeanTypes);
             } else if (Kind.PARAMETERIZED_TYPE.equals(returnType.kind())) {
                 types = getTypeClosure(returnTypeClassInfo, producerMethod,
                         buildResolvedMap(returnType.asParameterizedType().arguments(), returnTypeClassInfo.typeParameters(),
                                 Collections.emptyMap(), beanDeployment.getBeanArchiveIndex()),
-                        beanDeployment, null);
+                        beanDeployment, null, unrestrictedBeanTypes);
             } else {
                 throw new IllegalArgumentException("Unsupported return type");
             }
         }
-        return restrictBeanTypes(types, beanDeployment.getAnnotations(producerMethod), beanDeployment.getBeanArchiveIndex(),
+        return restrictBeanTypes(types, unrestrictedBeanTypes, beanDeployment.getAnnotations(producerMethod),
+                beanDeployment.getBeanArchiveIndex(),
                 producerMethod);
     }
 
     static Set<Type> getProducerFieldTypeClosure(FieldInfo producerField, BeanDeployment beanDeployment) {
         Set<Type> types;
+        Set<Type> unrestrictedBeanTypes = new HashSet<>();
         Type fieldType = producerField.type();
         if (fieldType.kind() == Kind.TYPE_VARIABLE) {
             throw new DefinitionException("A type variable is not a legal bean type: " + producerField);
@@ -377,30 +381,34 @@ static Set<Type> getProducerFieldTypeClosure(FieldInfo producerField, BeanDeploy
                 throw new IllegalArgumentException("Producer field type not found in index: " + producerField.type().name());
             }
             if (Kind.CLASS.equals(fieldType.kind())) {
-                types = getTypeClosure(fieldClassInfo, producerField, Collections.emptyMap(), beanDeployment, null);
+                types = getTypeClosure(fieldClassInfo, producerField, Collections.emptyMap(), beanDeployment, null,
+                        unrestrictedBeanTypes);
             } else if (Kind.PARAMETERIZED_TYPE.equals(fieldType.kind())) {
                 types = getTypeClosure(fieldClassInfo, producerField,
                         buildResolvedMap(fieldType.asParameterizedType().arguments(), fieldClassInfo.typeParameters(),
                                 Collections.emptyMap(), beanDeployment.getBeanArchiveIndex()),
-                        beanDeployment, null);
+                        beanDeployment, null, unrestrictedBeanTypes);
             } else {
                 throw new IllegalArgumentException("Unsupported return type");
             }
         }
-        return restrictBeanTypes(types, beanDeployment.getAnnotations(producerField), beanDeployment.getBeanArchiveIndex(),
+        return restrictBeanTypes(types, unrestrictedBeanTypes, beanDeployment.getAnnotations(producerField),
+                beanDeployment.getBeanArchiveIndex(),
                 producerField);
     }
 
     static Set<Type> getClassBeanTypeClosure(ClassInfo classInfo, BeanDeployment beanDeployment) {
         Set<Type> types;
+        Set<Type> unrestrictedBeanTypes = new HashSet<>();
         List<TypeVariable> typeParameters = classInfo.typeParameters();
         if (typeParameters.isEmpty()) {
-            types = getTypeClosure(classInfo, null, Collections.emptyMap(), beanDeployment, null);
+            types = getTypeClosure(classInfo, null, Collections.emptyMap(), beanDeployment, null, unrestrictedBeanTypes);
         } else {
             types = getTypeClosure(classInfo, null, buildResolvedMap(typeParameters, typeParameters,
-                    Collections.emptyMap(), beanDeployment.getBeanArchiveIndex()), beanDeployment, null);
+                    Collections.emptyMap(), beanDeployment.getBeanArchiveIndex()), beanDeployment, null, unrestrictedBeanTypes);
         }
-        return restrictBeanTypes(types, beanDeployment.getAnnotations(classInfo), beanDeployment.getBeanArchiveIndex(),
+        return restrictBeanTypes(types, unrestrictedBeanTypes, beanDeployment.getAnnotations(classInfo),
+                beanDeployment.getBeanArchiveIndex(),
                 classInfo);
     }
 
@@ -485,9 +493,13 @@ static boolean containsWildcard(Type type, AnnotationTarget producerFieldOrMetho
                 return true;
             }
         } else if (type.kind().equals(Kind.PARAMETERIZED_TYPE)) {
+            boolean wildcardFound = false;
             for (Type t : type.asParameterizedType().arguments()) {
                 // recursive check of all parameterized types
-                return containsWildcard(t, producerFieldOrMethod, throwIfDetected);
+                wildcardFound = containsWildcard(t, producerFieldOrMethod, throwIfDetected);
+                if (wildcardFound) {
+                    return true;
+                }
             }
         }
         return false;
@@ -496,7 +508,8 @@ static boolean containsWildcard(Type type, AnnotationTarget producerFieldOrMetho
     private static Set<Type> getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFieldOrMethod,
             boolean throwOnProducerWildcard,
             Map<String, Type> resolvedTypeParameters,
-            BeanDeployment beanDeployment, BiConsumer<ClassInfo, Map<String, Type>> resolvedTypeVariablesConsumer) {
+            BeanDeployment beanDeployment, BiConsumer<ClassInfo, Map<String, Type>> resolvedTypeVariablesConsumer,
+            Set<Type> unrestrictedBeanTypes) {
         Set<Type> types = new HashSet<>();
         List<TypeVariable> typeParameters = classInfo.typeParameters();
 
@@ -524,6 +537,8 @@ private static Set<Type> getTypeClosure(ClassInfo classInfo, AnnotationTarget pr
             }
             if (!skipThisType) {
                 types.add(ParameterizedType.create(classInfo.name(), typeParams, null));
+            } else {
+                unrestrictedBeanTypes.add(ParameterizedType.create(classInfo.name(), typeParams, null));
             }
         }
         // Interfaces
@@ -539,7 +554,7 @@ private static Set<Type> getTypeClosure(ClassInfo classInfo, AnnotationTarget pr
                             interfaceClassInfo.typeParameters(), resolvedTypeParameters, beanDeployment.getBeanArchiveIndex());
                 }
                 types.addAll(getTypeClosure(interfaceClassInfo, producerFieldOrMethod, false, resolved, beanDeployment,
-                        resolvedTypeVariablesConsumer));
+                        resolvedTypeVariablesConsumer, unrestrictedBeanTypes));
             }
         }
         // Superclass
@@ -553,21 +568,24 @@ private static Set<Type> getTypeClosure(ClassInfo classInfo, AnnotationTarget pr
                             resolvedTypeParameters, beanDeployment.getBeanArchiveIndex());
                 }
                 types.addAll(getTypeClosure(superClassInfo, producerFieldOrMethod, false, resolved, beanDeployment,
-                        resolvedTypeVariablesConsumer));
+                        resolvedTypeVariablesConsumer, unrestrictedBeanTypes));
             }
         }
+        unrestrictedBeanTypes.addAll(types);
         return types;
     }
 
     static Set<Type> getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFieldOrMethod,
             Map<String, Type> resolvedTypeParameters,
-            BeanDeployment beanDeployment, BiConsumer<ClassInfo, Map<String, Type>> resolvedTypeVariablesConsumer) {
+            BeanDeployment beanDeployment, BiConsumer<ClassInfo, Map<String, Type>> resolvedTypeVariablesConsumer,
+            Set<Type> unrestrictedBeanTypes) {
         return getTypeClosure(classInfo, producerFieldOrMethod, true, resolvedTypeParameters, beanDeployment,
-                resolvedTypeVariablesConsumer);
+                resolvedTypeVariablesConsumer, unrestrictedBeanTypes);
     }
 
     static Set<Type> getDelegateTypeClosure(InjectionPointInfo delegateInjectionPoint, BeanDeployment beanDeployment) {
         Set<Type> types;
+        Set<Type> unrestrictedBeanTypes = new HashSet<>();
         Type delegateType = delegateInjectionPoint.getRequiredType();
         if (delegateType.kind() == Kind.TYPE_VARIABLE
                 || delegateType.kind() == Kind.PRIMITIVE
@@ -580,12 +598,12 @@ static Set<Type> getDelegateTypeClosure(InjectionPointInfo delegateInjectionPoin
         }
         if (Kind.CLASS.equals(delegateType.kind())) {
             types = getTypeClosure(delegateTypeClass, delegateInjectionPoint.getTarget(), Collections.emptyMap(),
-                    beanDeployment, null);
+                    beanDeployment, null, unrestrictedBeanTypes);
         } else if (Kind.PARAMETERIZED_TYPE.equals(delegateType.kind())) {
             types = getTypeClosure(delegateTypeClass, delegateInjectionPoint.getTarget(),
                     buildResolvedMap(delegateType.asParameterizedType().arguments(), delegateTypeClass.typeParameters(),
                             Collections.emptyMap(), beanDeployment.getBeanArchiveIndex()),
-                    beanDeployment, null);
+                    beanDeployment, null, unrestrictedBeanTypes);
         } else {
             throw new IllegalArgumentException("Unsupported return type");
         }
@@ -595,11 +613,12 @@ static Set<Type> getDelegateTypeClosure(InjectionPointInfo delegateInjectionPoin
     static Map<ClassInfo, Map<String, Type>> resolvedTypeVariables(ClassInfo classInfo,
             BeanDeployment beanDeployment) {
         Map<ClassInfo, Map<String, Type>> resolvedTypeVariables = new HashMap<>();
-        getTypeClosure(classInfo, null, Collections.emptyMap(), beanDeployment, resolvedTypeVariables::put);
+        getTypeClosure(classInfo, null, Collections.emptyMap(), beanDeployment, resolvedTypeVariables::put, new HashSet<>());
         return resolvedTypeVariables;
     }
 
-    static Set<Type> restrictBeanTypes(Set<Type> types, Collection<AnnotationInstance> annotations, IndexView index,
+    static Set<Type> restrictBeanTypes(Set<Type> types, Set<Type> unrestrictedBeanTypes,
+            Collection<AnnotationInstance> annotations, IndexView index,
             AnnotationTarget target) {
         AnnotationInstance typed = null;
         for (AnnotationInstance a : annotations) {
@@ -621,12 +640,20 @@ static Set<Type> restrictBeanTypes(Set<Type> types, Collection<AnnotationInstanc
                 }
             }
         }
+        // check if all classes declared in @Typed match some class in the unrestricted bean type set
+        for (DotName typeName : typedClasses) {
+            if (!unrestrictedBeanTypes.stream().anyMatch(type -> type.name().equals(typeName))) {
+                throw new DefinitionException(
+                        "Cannot limit bean types to types outside of the transitive closure of bean types. Bean: " + target
+                                + " illegal bean types: " + typedClasses);
+            }
+        }
         for (Iterator<Type> it = types.iterator(); it.hasNext();) {
             Type next = it.next();
             if (DotNames.OBJECT.equals(next.name())) {
                 continue;
             }
-            if (typed != null && !typedClasses.remove(next.name())) {
+            if (typed != null && !typedClasses.contains(next.name())) {
                 it.remove();
                 continue;
             }
@@ -639,12 +666,6 @@ static Set<Type> restrictBeanTypes(Set<Type> types, Collection<AnnotationInstanc
                 }
             }
         }
-        // if the set of types we gathered from @Typed annotation isn't now empty, there are some illegal types in it
-        if (!typedClasses.isEmpty()) {
-            throw new DefinitionException(
-                    "Cannot limit bean types to types outside of the transitive closure of bean types. Bean: " + target
-                            + " illegal bean types: " + typedClasses);
-        }
         return types;
     }
 
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 dc2535c0755..86ddf65858e 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
@@ -10,6 +10,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -42,7 +43,7 @@ public void testGetTypeClosure() throws IOException {
         Set<Type> bazTypes = Types.getTypeClosure(index.getClassByName(bazName), null,
                 Collections.emptyMap(),
                 dummyDeployment,
-                resolvedTypeVariables::put);
+                resolvedTypeVariables::put, new HashSet<>());
         assertEquals(3, bazTypes.size());
         assertTrue(bazTypes.contains(Type.create(bazName, Kind.CLASS)));
         assertTrue(bazTypes.contains(ParameterizedType.create(fooName,