Skip to content

Commit

Permalink
Support for matching partial generics
Browse files Browse the repository at this point in the history
Closes gh-20727
  • Loading branch information
jhoeller committed Feb 15, 2024
1 parent d4e8daa commit 7e67da8
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -144,12 +144,16 @@ protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, Dependenc
if (cacheType) {
rbd.targetType = targetType;
}
if (descriptor.fallbackMatchAllowed() &&
(targetType.hasUnresolvableGenerics() || targetType.resolve() == Properties.class)) {
if (descriptor.fallbackMatchAllowed()) {
// Fallback matches allow unresolvable generics, e.g. plain HashMap to Map<String,String>;
// and pragmatically also java.util.Properties to any Map (since despite formally being a
// Map<Object,Object>, java.util.Properties is usually perceived as a Map<String,String>).
return true;
if (targetType.hasUnresolvableGenerics()) {
return dependencyType.isAssignableFromResolvedPart(targetType);
}
else if (targetType.resolve() == Properties.class) {
return true;
}
}
// Full check for complex generic type match...
return dependencyType.isAssignableFrom(targetType);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -32,8 +32,8 @@ class Spr16179Tests {
void repro() {
try (AnnotationConfigApplicationContext bf = new AnnotationConfigApplicationContext(AssemblerConfig.class, AssemblerInjection.class)) {
assertThat(bf.getBean(AssemblerInjection.class).assembler0).isSameAs(bf.getBean("someAssembler"));
// assertNull(bf.getBean(AssemblerInjection.class).assembler1); TODO: accidental match
// assertNull(bf.getBean(AssemblerInjection.class).assembler2);
assertThat(bf.getBean(AssemblerInjection.class).assembler1).isNull();
assertThat(bf.getBean(AssemblerInjection.class).assembler2).isSameAs(bf.getBean("pageAssembler"));
assertThat(bf.getBean(AssemblerInjection.class).assembler3).isSameAs(bf.getBean("pageAssembler"));
assertThat(bf.getBean(AssemblerInjection.class).assembler4).isSameAs(bf.getBean("pageAssembler"));
assertThat(bf.getBean(AssemblerInjection.class).assembler5).isSameAs(bf.getBean("pageAssembler"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -265,7 +265,7 @@ public boolean isInstance(@Nullable Object obj) {
public boolean isAssignableFrom(Class<?> other) {
// As of 6.1: shortcut assignability check for top-level Class references
return (this.type instanceof Class<?> clazz ? ClassUtils.isAssignable(clazz, other) :
isAssignableFrom(forClass(other), false, null));
isAssignableFrom(forClass(other), false, null, false));
}

/**
Expand All @@ -280,10 +280,24 @@ public boolean isAssignableFrom(Class<?> other) {
* {@code ResolvableType}; {@code false} otherwise
*/
public boolean isAssignableFrom(ResolvableType other) {
return isAssignableFrom(other, false, null);
return isAssignableFrom(other, false, null, false);
}

private boolean isAssignableFrom(ResolvableType other, boolean strict, @Nullable Map<Type, Type> matchedBefore) {
/**
* Determine whether this {@code ResolvableType} is assignable from the
* specified other type, as far as the other type is actually resolvable.
* @param other the type to be checked against (as a {@code ResolvableType})
* @return {@code true} if the specified other type can be assigned to this
* {@code ResolvableType} as far as it is resolvable; {@code false} otherwise
* @since 6.2
*/
public boolean isAssignableFromResolvedPart(ResolvableType other) {
return isAssignableFrom(other, false, null, true);
}

private boolean isAssignableFrom(ResolvableType other, boolean strict,
@Nullable Map<Type, Type> matchedBefore, boolean upUntilUnresolvable) {

Assert.notNull(other, "ResolvableType must not be null");

// If we cannot resolve types, we are not assignable
Expand All @@ -305,7 +319,12 @@ private boolean isAssignableFrom(ResolvableType other, boolean strict, @Nullable

// Deal with array by delegating to the component type
if (isArray()) {
return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType(), true, matchedBefore));
return (other.isArray() && getComponentType().isAssignableFrom(
other.getComponentType(), true, matchedBefore, upUntilUnresolvable));
}

if (upUntilUnresolvable && other.isUnresolvableTypeVariable()) {
return true;
}

// Deal with wildcard bounds
Expand All @@ -314,8 +333,15 @@ private boolean isAssignableFrom(ResolvableType other, boolean strict, @Nullable

// In the form X is assignable to <? extends Number>
if (typeBounds != null) {
return (ourBounds != null && ourBounds.isSameKind(typeBounds) &&
ourBounds.isAssignableFrom(typeBounds.getBounds()));
if (ourBounds != null) {
return (ourBounds.isSameKind(typeBounds) && ourBounds.isAssignableFrom(typeBounds.getBounds()));
}
else if (upUntilUnresolvable) {
return typeBounds.isAssignableFrom(this);
}
else {
return false;
}
}

// In the form <? extends Number> is assignable to X...
Expand Down Expand Up @@ -376,7 +402,7 @@ private boolean isAssignableFrom(ResolvableType other, boolean strict, @Nullable
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true, matchedBefore)) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true, matchedBefore, upUntilUnresolvable)) {
return false;
}
}
Expand Down

0 comments on commit 7e67da8

Please sign in to comment.