Merge pull request #45011 from nosan
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run
Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions
Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:22], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:22], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run
Details
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run
Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions
Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:22], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:22], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run
Details
* pr/45011: Polish 'Handle generics with identical names in different positions' Handle generics with identical names in different positions Closes gh-45011
This commit is contained in:
commit
92a8d4181a
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2024 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,7 +23,6 @@ import java.util.EnumMap;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -47,6 +46,7 @@ import javax.lang.model.util.Types;
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Pavel Anisimov
|
* @author Pavel Anisimov
|
||||||
|
* @author Dmytro Nosan
|
||||||
*/
|
*/
|
||||||
class TypeUtils {
|
class TypeUtils {
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ class TypeUtils {
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return type.accept(this.typeExtractor, createTypeDescriptor(element));
|
return type.accept(this.typeExtractor, resolveTypeDescriptor(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,7 +218,7 @@ class TypeUtils {
|
||||||
return WRAPPER_TO_PRIMITIVE.get(type.toString());
|
return WRAPPER_TO_PRIMITIVE.get(type.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeDescriptor resolveTypeDescriptor(TypeElement element) {
|
private TypeDescriptor resolveTypeDescriptor(TypeElement element) {
|
||||||
if (this.typeDescriptors.containsKey(element)) {
|
if (this.typeDescriptors.containsKey(element)) {
|
||||||
return this.typeDescriptors.get(element);
|
return this.typeDescriptors.get(element);
|
||||||
}
|
}
|
||||||
|
@ -319,22 +319,22 @@ class TypeUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String visitTypeVariable(TypeVariable t, TypeDescriptor descriptor) {
|
public String visitTypeVariable(TypeVariable typeVariable, TypeDescriptor descriptor) {
|
||||||
TypeMirror typeMirror = descriptor.resolveGeneric(t);
|
TypeMirror resolvedGeneric = descriptor.resolveGeneric(typeVariable);
|
||||||
if (typeMirror != null) {
|
if (resolvedGeneric != null) {
|
||||||
if (typeMirror instanceof TypeVariable typeVariable) {
|
if (resolvedGeneric instanceof TypeVariable resolveTypeVariable) {
|
||||||
// Still unresolved, let's use the upper bound, checking first if
|
// Still unresolved, let's use the upper bound, checking first if
|
||||||
// a cycle may exist
|
// a cycle may exist
|
||||||
if (!hasCycle(typeVariable)) {
|
if (!hasCycle(resolveTypeVariable)) {
|
||||||
return visit(typeVariable.getUpperBound(), descriptor);
|
return visit(resolveTypeVariable.getUpperBound(), descriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return visit(typeMirror, descriptor);
|
return visit(resolvedGeneric, descriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fallback to simple representation of the upper bound
|
// Fallback to simple representation of the upper bound
|
||||||
return defaultAction(t.getUpperBound(), descriptor);
|
return defaultAction(typeVariable.getUpperBound(), descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasCycle(TypeVariable variable) {
|
private boolean hasCycle(TypeVariable variable) {
|
||||||
|
@ -394,37 +394,20 @@ class TypeUtils {
|
||||||
|
|
||||||
private final Map<TypeVariable, TypeMirror> generics = new HashMap<>();
|
private final Map<TypeVariable, TypeMirror> generics = new HashMap<>();
|
||||||
|
|
||||||
Map<TypeVariable, TypeMirror> getGenerics() {
|
|
||||||
return Collections.unmodifiableMap(this.generics);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeMirror resolveGeneric(TypeVariable typeVariable) {
|
TypeMirror resolveGeneric(TypeVariable typeVariable) {
|
||||||
return resolveGeneric(getParameterName(typeVariable));
|
TypeMirror resolved = this.generics.get(typeVariable);
|
||||||
}
|
if (resolved != typeVariable && resolved instanceof TypeVariable resolvedTypeVariable) {
|
||||||
|
return resolveGeneric(resolvedTypeVariable);
|
||||||
TypeMirror resolveGeneric(String parameterName) {
|
}
|
||||||
return this.generics.entrySet()
|
return resolved;
|
||||||
.stream()
|
|
||||||
.filter((e) -> getParameterName(e.getKey()).equals(parameterName))
|
|
||||||
.findFirst()
|
|
||||||
.map(Entry::getValue)
|
|
||||||
.orElse(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerIfNecessary(TypeMirror variable, TypeMirror resolution) {
|
private void registerIfNecessary(TypeMirror variable, TypeMirror resolution) {
|
||||||
if (variable instanceof TypeVariable typeVariable) {
|
if (variable instanceof TypeVariable typeVariable) {
|
||||||
if (this.generics.keySet()
|
this.generics.put(typeVariable, resolution);
|
||||||
.stream()
|
|
||||||
.noneMatch((candidate) -> getParameterName(candidate).equals(getParameterName(typeVariable)))) {
|
|
||||||
this.generics.put(typeVariable, resolution);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getParameterName(TypeVariable typeVariable) {
|
|
||||||
return typeVariable.asElement().getSimpleName().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,16 +17,23 @@
|
||||||
package org.springframework.boot.configurationprocessor;
|
package org.springframework.boot.configurationprocessor;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import javax.lang.model.element.TypeElement;
|
||||||
|
import javax.lang.model.element.VariableElement;
|
||||||
|
import javax.lang.model.type.TypeMirror;
|
||||||
|
import javax.lang.model.util.ElementFilter;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.boot.configurationprocessor.TypeUtils.TypeDescriptor;
|
|
||||||
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
|
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
|
||||||
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor;
|
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor;
|
||||||
import org.springframework.boot.configurationsample.generic.AbstractGenericProperties;
|
import org.springframework.boot.configurationsample.generic.AbstractGenericProperties;
|
||||||
import org.springframework.boot.configurationsample.generic.AbstractIntermediateGenericProperties;
|
import org.springframework.boot.configurationsample.generic.AbstractIntermediateGenericProperties;
|
||||||
|
import org.springframework.boot.configurationsample.generic.MixGenericNameProperties;
|
||||||
import org.springframework.boot.configurationsample.generic.SimpleGenericProperties;
|
import org.springframework.boot.configurationsample.generic.SimpleGenericProperties;
|
||||||
|
import org.springframework.boot.configurationsample.generic.UnresolvedGenericProperties;
|
||||||
import org.springframework.core.test.tools.SourceFile;
|
import org.springframework.core.test.tools.SourceFile;
|
||||||
import org.springframework.core.test.tools.TestCompiler;
|
import org.springframework.core.test.tools.TestCompiler;
|
||||||
|
|
||||||
|
@ -41,40 +48,52 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
class TypeUtilsTests {
|
class TypeUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void resolveTypeDescriptorOnConcreteClass() {
|
void resolveTypeOnConcreteClass() {
|
||||||
process(SimpleGenericProperties.class, (roundEnv, typeUtils) -> {
|
process(SimpleGenericProperties.class, (roundEnv, typeUtils) -> {
|
||||||
TypeDescriptor typeDescriptor = typeUtils
|
TypeElement typeElement = roundEnv.getRootElement(SimpleGenericProperties.class);
|
||||||
.resolveTypeDescriptor(roundEnv.getRootElement(SimpleGenericProperties.class));
|
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName());
|
||||||
assertThat(typeDescriptor.getGenerics().keySet().stream().map(Object::toString)).containsOnly("A", "B",
|
assertThat(getTypeOfField(typeUtils, typeElement, "mappings"))
|
||||||
"C");
|
.hasToString(constructMapType(Integer.class, Duration.class));
|
||||||
assertThat(typeDescriptor.resolveGeneric("A")).hasToString(String.class.getName());
|
|
||||||
assertThat(typeDescriptor.resolveGeneric("B")).hasToString(Integer.class.getName());
|
|
||||||
assertThat(typeDescriptor.resolveGeneric("C")).hasToString(Duration.class.getName());
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void resolveTypeDescriptorOnIntermediateClass() {
|
void resolveTypeOnIntermediateClass() {
|
||||||
process(AbstractIntermediateGenericProperties.class, (roundEnv, typeUtils) -> {
|
process(AbstractIntermediateGenericProperties.class, (roundEnv, typeUtils) -> {
|
||||||
TypeDescriptor typeDescriptor = typeUtils
|
TypeElement typeElement = roundEnv.getRootElement(AbstractIntermediateGenericProperties.class);
|
||||||
.resolveTypeDescriptor(roundEnv.getRootElement(AbstractIntermediateGenericProperties.class));
|
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName());
|
||||||
assertThat(typeDescriptor.getGenerics().keySet().stream().map(Object::toString)).containsOnly("A", "B",
|
assertThat(getTypeOfField(typeUtils, typeElement, "mappings"))
|
||||||
"C");
|
.hasToString(constructMapType(Integer.class, Object.class));
|
||||||
assertThat(typeDescriptor.resolveGeneric("A")).hasToString(String.class.getName());
|
|
||||||
assertThat(typeDescriptor.resolveGeneric("B")).hasToString(Integer.class.getName());
|
|
||||||
assertThat(typeDescriptor.resolveGeneric("C")).hasToString("C");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void resolveTypeDescriptorWithOnlyGenerics() {
|
void resolveTypeWithOnlyGenerics() {
|
||||||
process(AbstractGenericProperties.class, (roundEnv, typeUtils) -> {
|
process(AbstractGenericProperties.class, (roundEnv, typeUtils) -> {
|
||||||
TypeDescriptor typeDescriptor = typeUtils
|
TypeElement typeElement = roundEnv.getRootElement(AbstractGenericProperties.class);
|
||||||
.resolveTypeDescriptor(roundEnv.getRootElement(AbstractGenericProperties.class));
|
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(Object.class.getName());
|
||||||
assertThat(typeDescriptor.getGenerics().keySet().stream().map(Object::toString)).containsOnly("A", "B",
|
assertThat(getTypeOfField(typeUtils, typeElement, "mappings"))
|
||||||
"C");
|
.hasToString(constructMapType(Object.class, Object.class));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveTypeWithUnresolvedGenericProperties() {
|
||||||
|
process(UnresolvedGenericProperties.class, (roundEnv, typeUtils) -> {
|
||||||
|
TypeElement typeElement = roundEnv.getRootElement(UnresolvedGenericProperties.class);
|
||||||
|
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName());
|
||||||
|
assertThat(getTypeOfField(typeUtils, typeElement, "mappings"))
|
||||||
|
.hasToString(constructMapType(Number.class, Object.class));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolvedTypeMixGenericNamePropertiesProperties() {
|
||||||
|
process(MixGenericNameProperties.class, (roundEnv, typeUtils) -> {
|
||||||
|
TypeElement typeElement = roundEnv.getRootElement(MixGenericNameProperties.class);
|
||||||
|
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName());
|
||||||
|
assertThat(getTypeOfField(typeUtils, typeElement, "mappings"))
|
||||||
|
.hasToString(constructMapType(Number.class, Object.class));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,4 +106,29 @@ class TypeUtilsTests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String constructMapType(Class<?> keyType, Class<?> valueType) {
|
||||||
|
return "%s<%s,%s>".formatted(Map.class.getName(), keyType.getName(), valueType.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTypeOfField(TypeUtils typeUtils, TypeElement typeElement, String name) {
|
||||||
|
TypeMirror field = findField(typeUtils, typeElement, name);
|
||||||
|
if (field == null) {
|
||||||
|
throw new IllegalStateException("Unable to find field '" + name + "' in " + typeElement);
|
||||||
|
}
|
||||||
|
return typeUtils.getType(typeElement, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeMirror findField(TypeUtils typeUtils, TypeElement typeElement, String name) {
|
||||||
|
for (VariableElement variableElement : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) {
|
||||||
|
if (variableElement.getSimpleName().contentEquals(name)) {
|
||||||
|
return variableElement.asType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TypeMirror superclass = typeElement.getSuperclass();
|
||||||
|
if (superclass != null && !superclass.toString().equals(Object.class.getName())) {
|
||||||
|
return findField(typeUtils, (TypeElement) typeUtils.asElement(superclass), name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2025 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://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 org.springframework.boot.configurationsample.generic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties with unresolved generic types that use identical generic parameter names but
|
||||||
|
* differ in their positions.
|
||||||
|
*
|
||||||
|
* @param <C> mapping name type
|
||||||
|
* @param <B> mapping value type
|
||||||
|
* @author Dmytro Nosan
|
||||||
|
*/
|
||||||
|
public class MixGenericNameProperties<B, C extends Number> extends AbstractGenericProperties<String, C, B> {
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue