Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arc - allow @All injection points for parameterizes types with wildcards #32158

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@
import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.MethodParameterInfo;
import org.jboss.jandex.ParameterizedType;
import org.jboss.jandex.Type;
import org.jboss.jandex.WildcardType;
import org.jboss.logging.Logger;

import io.quarkus.arc.ArcContainer;
Expand Down Expand Up @@ -923,11 +926,18 @@ public void registerField(FieldInfo fieldInfo) {
: elementType,
qualifiers));

// by default, we keep the type of the injection point
// we only need to inspect and change this in case of param. type with a wildcard
Type syntheticBeanType = injectionPoint.getRequiredType();
if (syntheticBeanType.kind() == Type.Kind.PARAMETERIZED_TYPE) {
ParameterizedType paramType = syntheticBeanType.asParameterizedType();
syntheticBeanType = removeWildcardFromParamType(paramType.name(), paramType.arguments(), paramType.owner());
}
BeanConfigurator<?> configurator = beanRegistrationPhase.getContext()
.configure(List.class)
.forceApplicationClass()
.scope(BuiltinScope.DEPENDENT.getInfo())
.addType(injectionPoint.getRequiredType());
.addType(syntheticBeanType);

injectionPoint.getRequiredQualifiers().forEach(configurator::addQualifier);

Expand Down Expand Up @@ -996,6 +1006,33 @@ public boolean test(BeanInfo bean) {
}
}

private ParameterizedType removeWildcardFromParamType(DotName name, List<Type> typeArgs, Type owner) {
Type[] typeArray = new Type[typeArgs.size()];
for (int i = 0; i < typeArgs.size(); i++) {
Type t = typeArgs.get(i);
if (t.kind() == Type.Kind.WILDCARD_TYPE) {
// detect wildcard and replace with upper/lower bound or Object type
WildcardType wildcard = t.asWildcardType();
if (wildcard.extendsBound() != null && !wildcard.extendsBound().equals(ClassType.OBJECT_TYPE)) {
typeArray[i] = wildcard.extendsBound();
} else if (wildcard.superBound() != null && !wildcard.superBound().equals(ClassType.OBJECT_TYPE)) {
typeArray[i] = wildcard.superBound();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically wrong because it will effectively reduce List<Some<? extends Integer>> to List<Some<Integer>> which in turn means that such list cannot contain items like Some<Number>.
Using upper bounds instead (i.e. List<Some<Object>>) has similar problem because you can then insert items such as Some<String> which is incorrect.

We need to look into how to implement this differently, possibly via a built-in bean.

} else {
typeArray[i] = ClassType.OBJECT_TYPE;
}
} else if (t.kind() == Type.Kind.PARAMETERIZED_TYPE) {
// recursively search parameterized types
ParameterizedType parameterizedArg = t.asParameterizedType();
typeArray[i] = removeWildcardFromParamType(parameterizedArg.name(), parameterizedArg.arguments(),
parameterizedArg.owner());
} else {
// all else stays the same
typeArray[i] = t;
}
}
return ParameterizedType.create(name, typeArray, owner);
}

private boolean isListAllInjectionPoint(InjectionPointInfo injectionPoint) {
return DotNames.LIST.equals(injectionPoint.getRequiredType().name())
&& Annotations.contains(injectionPoint.getRequiredQualifiers(), DotNames.ALL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.arc.Priority;
import io.quarkus.arc.Unremovable;
import io.quarkus.test.QuarkusUnitTest;

public class ListInjectionTest {
Expand All @@ -31,7 +32,8 @@ public class ListInjectionTest {
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Foo.class, ServiceAlpha.class, ServiceBravo.class, ServiceCharlie.class, Service.class,
Counter.class, Converter.class, ConverterAlpha.class, ConverterBravo.class, MyQualifier.class));
Counter.class, Converter.class, ConverterAlpha.class, ConverterBravo.class, MyQualifier.class,
Some.class, SomeInt.class, SomeStringA.class, SomeStringB.class, BeanA.class, BeanB.class));

@Inject
Foo foo;
Expand Down Expand Up @@ -89,6 +91,18 @@ public void testListAll() {
assertEquals(Object.class, bravo.getInjectionPoint().get().getType());
}

@Test
public void testWildcardInBeanType() {
BeanA beanA = Arc.container().instance(BeanA.class).get();
assertEquals(3, beanA.somes.size());
assertEquals(2, beanA.somesExtends.size());
assertEquals(2, beanA.somesSuper.size());
BeanB beanB = Arc.container().instance(BeanB.class).get();
assertEquals(3, beanB.somes.size());
assertEquals(2, beanB.somesExtends.size());
assertEquals(2, beanB.somesSuper.size());
}

@Singleton
static class Foo {

Expand Down Expand Up @@ -217,4 +231,61 @@ void destroy() {

}

@Singleton
@Unremovable
public static class BeanA {

final List<Some<?>> somes;

@Inject
@All
List<Some<? extends String>> somesExtends;

@Inject
@All
List<Some<? super String>> somesSuper;

public BeanA(@All List<Some<?>> somes) {
this.somes = somes;
}

}

@Singleton
@Unremovable
// this bean is, in its functionality, copy of BeanA but it was required to reproduce the problem
// see https://github.com/quarkusio/quarkus/issues/32080 for details
public static class BeanB {

final List<Some<?>> somes;

@Inject
@All
List<Some<? extends String>> somesExtends;

@Inject
@All
List<Some<? super String>> somesSuper;

public BeanB(@All List<Some<?>> somes) {
this.somes = somes;
}

}

public interface Some<K> {
}

@Singleton
public static class SomeStringA implements Some<String> {
}

@Singleton
public static class SomeStringB implements Some<String> {
}

@Singleton
public static class SomeInt implements Some<Integer> {
}

}