Skip to content

Commit

Permalink
Restore proper code generation for types with nested generics
Browse files Browse the repository at this point in the history
This commit aligns code generation to recent improvement in the core
container regarding type detection. Now that nested types are properly
resolved, our code generation that uses hasResolvableGenerics() is
taking the wrong decision if only a nested type has an unresolved
generics. Previously, this was hidden by the fact that the core
container would not resolve them recursively.

A new hasResolvableGenerics() method allows to verify that at least
one direct generic type is resolved. This restore our intent of checking
at the first level only and let recursive invocations figure out if they
have to write the raw type or the type with generics.

Closes gh-33069
  • Loading branch information
snicoll committed Jun 19, 2024
1 parent 1047e1f commit 5cf8978
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ private static CodeBlock generateCode(ResolvableType resolvableType, boolean all
return CodeBlock.of("$T.NONE", ResolvableType.class);
}
Class<?> type = ClassUtils.getUserClass(resolvableType.toClass());
if (resolvableType.hasGenerics() && !resolvableType.hasUnresolvableGenerics()) {
if (resolvableType.hasGenerics() && resolvableType.hasResolvableGenerics()) {
return generateCodeWithGenerics(resolvableType, type);
}
if (allowClassResult) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ private static Class<?> getSingleGeneric(ResolvableType resolvableType) {
@Nullable
public static Class<?>[] resolveTypeArguments(Class<?> clazz, Class<?> genericType) {
ResolvableType type = ResolvableType.forClass(clazz).as(genericType);
if (!type.hasGenerics() || type.isEntirelyUnresolvable()) {
if (!type.hasGenerics() || !type.hasResolvableGenerics()) {
return null;
}
return type.resolveGenerics(Object.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -568,20 +568,23 @@ public boolean hasGenerics() {
}

/**
* Return {@code true} if this type contains unresolvable generics only,
* that is, no substitute for any of its declared type variables.
* Return {@code true} if this type contains at least a generic type
* that is resolved. In other words, this returns {@code false} if
* the type contains unresolvable generics only, that is, no substitute
* for any of its declared type variables.
* @since 6.2
*/
boolean isEntirelyUnresolvable() {
public boolean hasResolvableGenerics() {
if (this == NONE) {
return false;
}
ResolvableType[] generics = getGenerics();
for (ResolvableType generic : generics) {
if (!generic.isUnresolvableTypeVariable() && !generic.isWildcardWithoutBounds()) {
return false;
return true;
}
}
return true;
return false;
}

/**
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 @@ -290,6 +290,34 @@ void generateWhenNestedGenericResolvableType() {
+ "ResolvableType.forClassWithGenerics(List.class, String.class))");
}

@Test
void generateWhenUnresolvedGenericType() throws NoSuchFieldException {
ResolvableType resolvableType = ResolvableType
.forField(SampleTypes.class.getField("genericList"));
assertThat(resolve(generateCode(resolvableType)))
.hasImport(ResolvableType.class, List.class)
.hasValueCode("ResolvableType.forClass(List.class)");
}

@Test
void generateWhenUnresolvedNestedGenericType() throws NoSuchFieldException {
ResolvableType resolvableType = ResolvableType
.forField(SampleTypes.class.getField("mapWithNestedGenericInValueType"));
assertThat(resolve(generateCode(resolvableType)))
.hasImport(ResolvableType.class, List.class)
.hasValueCode("""
ResolvableType.forClassWithGenerics(Map.class, ResolvableType.forClass(String.class), \
ResolvableType.forClass(List.class))""");
}

static class SampleTypes<A> {

public List<A> genericList;

public Map<String, List<A>> mapWithNestedGenericInValueType;

}

}

@Nested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,30 @@ void narrow() throws Exception {
assertThat(narrow.getGeneric().resolve()).isEqualTo(String.class);
}

@Test
void hasResolvableGenerics() throws Exception {
ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList"));
assertThat(type.hasResolvableGenerics()).isTrue();
}

@Test
void hasResolvableGenericsWithSingleBoundedWildcard() throws Exception {
ResolvableType type = ResolvableType.forField(Fields.class.getField("wildcardType"));
assertThat(type.hasResolvableGenerics()).isTrue();
}

@Test
void hasResolvableGenericsWithSingleParameterizedType() throws Exception {
ResolvableType type = ResolvableType.forField(Fields.class.getField("parameterizedType"));
assertThat(type.hasResolvableGenerics()).isFalse();
}

@Test
void hasResolvableGenericsWithSingleWildcard() throws Exception {
ResolvableType type = ResolvableType.forField(Fields.class.getField("anyListElement"));
assertThat(type.hasResolvableGenerics()).isFalse();
}

@Test
void hasUnresolvableGenerics() throws Exception {
ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList"));
Expand Down Expand Up @@ -1466,6 +1490,8 @@ static class Fields<T> {

public List<String>[][][] genericMultiArrayType;

public List<?> anyListElement;

public List<? extends Number> wildcardType;

public List<? super Number> wildcardSuperType = new ArrayList<Object>();
Expand Down

0 comments on commit 5cf8978

Please sign in to comment.