From 5cf8978d22451006624af9a3e3388055e4b558f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Wed, 19 Jun 2024 17:21:18 +0200 Subject: [PATCH] Restore proper code generation for types with nested generics 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 --- .../generate/ValueCodeGeneratorDelegates.java | 2 +- .../core/GenericTypeResolver.java | 2 +- .../springframework/core/ResolvableType.java | 13 ++++---- .../aot/generate/ValueCodeGeneratorTests.java | 30 ++++++++++++++++++- .../core/ResolvableTypeTests.java | 26 ++++++++++++++++ 5 files changed, 65 insertions(+), 8 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/generate/ValueCodeGeneratorDelegates.java b/spring-core/src/main/java/org/springframework/aot/generate/ValueCodeGeneratorDelegates.java index caa806a4463d..86904a0c30d3 100644 --- a/spring-core/src/main/java/org/springframework/aot/generate/ValueCodeGeneratorDelegates.java +++ b/spring-core/src/main/java/org/springframework/aot/generate/ValueCodeGeneratorDelegates.java @@ -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) { diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index 055b41dc0104..a5e0d74c65bd 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -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); diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index 66b5b9509b77..ed541ebbda63 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -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; } /** diff --git a/spring-core/src/test/java/org/springframework/aot/generate/ValueCodeGeneratorTests.java b/spring-core/src/test/java/org/springframework/aot/generate/ValueCodeGeneratorTests.java index dced0ed7bbf1..dc755467ec1c 100644 --- a/spring-core/src/test/java/org/springframework/aot/generate/ValueCodeGeneratorTests.java +++ b/spring-core/src/test/java/org/springframework/aot/generate/ValueCodeGeneratorTests.java @@ -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. @@ -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 { + + public List genericList; + + public Map> mapWithNestedGenericInValueType; + + } + } @Nested diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 8e8882b7b7f5..e2f397528acf 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -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")); @@ -1466,6 +1490,8 @@ static class Fields { public List[][][] genericMultiArrayType; + public List anyListElement; + public List wildcardType; public List wildcardSuperType = new ArrayList();