From 2ba73894370a2d119160f8742b3fe319340fae1e Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 22 Dec 2025 21:23:20 -0800 Subject: [PATCH 1/3] Fix #53 --- VERSION.txt | 3 ++- .../com/fasterxml/classmate/TypeResolver.java | 15 +++++++++++++++ .../{failing => }/TestTypeResolver53.java | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) rename src/test/java/com/fasterxml/classmate/{failing => }/TestTypeResolver53.java (95%) diff --git a/VERSION.txt b/VERSION.txt index 1b71189..4d85e08 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -5,7 +5,8 @@ Release notes: 1.8.0 (not yet released) -- +#53: Allow for subtype resolution with unknown generics + (reported by Carsten W, @CarstenWickner) 1.7.1 (26-Sep-2025) diff --git a/src/main/java/com/fasterxml/classmate/TypeResolver.java b/src/main/java/com/fasterxml/classmate/TypeResolver.java index 48c174a..303e07e 100644 --- a/src/main/java/com/fasterxml/classmate/TypeResolver.java +++ b/src/main/java/com/fasterxml/classmate/TypeResolver.java @@ -427,6 +427,21 @@ private ResolvedType _constructType(ClassStack context, Class rawType, TypeBi if (!typeBindings.isEmpty() && rawType.getTypeParameters().length == 0) { typeBindings = TypeBindings.emptyBindings(); } + // [classmate#53]: Handle raw generic types - resolve type parameters to their bounds + if (typeBindings.isEmpty() && rawType.getTypeParameters().length > 0) { + TypeVariable[] vars = rawType.getTypeParameters(); + ResolvedType[] types = new ResolvedType[vars.length]; + for (int i = 0; i < vars.length; ++i) { + // Resolve each type parameter to its bound (similar to _fromVariable) + TypeVariable var = vars[i]; + String name = var.getName(); + // Avoid self-reference cycles by marking as unbound during resolution + TypeBindings tempBindings = typeBindings.withUnboundVariable(name); + Type[] bounds = var.getBounds(); + types[i] = _fromAny(context, bounds[0], tempBindings); + } + typeBindings = TypeBindings.create(rawType, types); + } // For other types super interfaces are needed... if (rawType.isInterface()) { return new ResolvedInterfaceType(rawType, typeBindings, diff --git a/src/test/java/com/fasterxml/classmate/failing/TestTypeResolver53.java b/src/test/java/com/fasterxml/classmate/TestTypeResolver53.java similarity index 95% rename from src/test/java/com/fasterxml/classmate/failing/TestTypeResolver53.java rename to src/test/java/com/fasterxml/classmate/TestTypeResolver53.java index 01eedd1..f460627 100644 --- a/src/test/java/com/fasterxml/classmate/failing/TestTypeResolver53.java +++ b/src/test/java/com/fasterxml/classmate/TestTypeResolver53.java @@ -1,4 +1,4 @@ -package com.fasterxml.classmate.failing; +package com.fasterxml.classmate; import java.util.Arrays; import java.util.Comparator; From 6616938a13ff755226622d9e60cd100ef94bdde4 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 23 Dec 2025 15:51:47 -0800 Subject: [PATCH 2/3] Streamline a bit --- .../com/fasterxml/classmate/TypeResolver.java | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/fasterxml/classmate/TypeResolver.java b/src/main/java/com/fasterxml/classmate/TypeResolver.java index 303e07e..b37ecab 100644 --- a/src/main/java/com/fasterxml/classmate/TypeResolver.java +++ b/src/main/java/com/fasterxml/classmate/TypeResolver.java @@ -422,25 +422,28 @@ private ResolvedType _constructType(ClassStack context, Class rawType, TypeBi ResolvedType elementType = _fromAny(context, rawType.getComponentType(), typeBindings); return new ResolvedArrayType(rawType, typeBindings, elementType); } - // Work-around/fix for [#33]: if the type has no type parameters, don't include - // typeBindings in the ResolvedType - if (!typeBindings.isEmpty() && rawType.getTypeParameters().length == 0) { - typeBindings = TypeBindings.emptyBindings(); - } + final TypeVariable[] rawTypeParameters = rawType.getTypeParameters(); // [classmate#53]: Handle raw generic types - resolve type parameters to their bounds - if (typeBindings.isEmpty() && rawType.getTypeParameters().length > 0) { - TypeVariable[] vars = rawType.getTypeParameters(); - ResolvedType[] types = new ResolvedType[vars.length]; - for (int i = 0; i < vars.length; ++i) { - // Resolve each type parameter to its bound (similar to _fromVariable) - TypeVariable var = vars[i]; - String name = var.getName(); - // Avoid self-reference cycles by marking as unbound during resolution - TypeBindings tempBindings = typeBindings.withUnboundVariable(name); - Type[] bounds = var.getBounds(); - types[i] = _fromAny(context, bounds[0], tempBindings); + if (typeBindings.isEmpty()) { + if (rawTypeParameters.length > 0) { + ResolvedType[] types = new ResolvedType[rawTypeParameters.length]; + for (int i = 0; i < rawTypeParameters.length; ++i) { + // Resolve each type parameter to its bound (similar to _fromVariable) + TypeVariable var = rawTypeParameters[i]; + String name = var.getName(); + // Avoid self-reference cycles by marking as unbound during resolution + TypeBindings tempBindings = typeBindings.withUnboundVariable(name); + Type[] bounds = var.getBounds(); + types[i] = _fromAny(context, bounds[0], tempBindings); + } + typeBindings = TypeBindings.create(rawType, types); + } + } else { + // Work-around/fix for [classmate#33]: if the type has no type parameters, + // don't include typeBindings in the ResolvedType + if (rawTypeParameters.length == 0) { + typeBindings = TypeBindings.emptyBindings(); } - typeBindings = TypeBindings.create(rawType, types); } // For other types super interfaces are needed... if (rawType.isInterface()) { From a025420ddb6a1e22a5cad4256f360f069267a2b3 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 23 Dec 2025 15:56:35 -0800 Subject: [PATCH 3/3] Add testing --- .../classmate/TestTypeResolver53.java | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/classmate/TestTypeResolver53.java b/src/test/java/com/fasterxml/classmate/TestTypeResolver53.java index f460627..d771ce1 100644 --- a/src/test/java/com/fasterxml/classmate/TestTypeResolver53.java +++ b/src/test/java/com/fasterxml/classmate/TestTypeResolver53.java @@ -3,6 +3,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Map; import com.fasterxml.classmate.BaseTest; import com.fasterxml.classmate.ResolvedType; @@ -14,13 +15,62 @@ public class TestTypeResolver53 extends BaseTest @SuppressWarnings("rawtypes") static abstract class Comparator53 implements Comparator { } + @SuppressWarnings("rawtypes") + static abstract class Map53 implements Map { } + + @SuppressWarnings("rawtypes") + static abstract class BoundedComparable implements Comparable { } + + @SuppressWarnings("rawtypes") + static abstract class BoundedRaw extends BoundedComparable { } + + static abstract class NestedRaw extends java.util.ArrayList { } + protected final TypeResolver RESOLVER = new TypeResolver(); - // [classmate#53] Problem with Raw types + // [classmate#53] Problem with Raw types - single type parameter public void testResolvingRawType() { ResolvedType rt = RESOLVER.resolve(Comparator53.class); List params = rt.typeParametersFor(Comparator.class); assertEquals(Arrays.asList(RESOLVER.resolve(Object.class)), params); } + + // [classmate#53] Raw type with multiple type parameters (Map) + public void testRawTypeMultipleParameters() { + ResolvedType rt = RESOLVER.resolve(Map53.class); + List params = rt.typeParametersFor(Map.class); + assertEquals(Arrays.asList( + RESOLVER.resolve(Object.class), + RESOLVER.resolve(Object.class)), + params); + } + + // [classmate#53] Raw type with bounded type parameter + public void testRawTypeWithBoundedParameter() { + ResolvedType rt = RESOLVER.resolve(BoundedRaw.class); + List params = rt.typeParametersFor(Comparable.class); + // BoundedComparable implements Comparable, used raw + // Type parameter T has bound Number, so should resolve to Number + assertEquals(Arrays.asList(RESOLVER.resolve(Number.class)), + params); + } + + // [classmate#53] Nested raw types (List where Map is raw) + public void testNestedRawType() { + ResolvedType rt = RESOLVER.resolve(NestedRaw.class); + List params = rt.typeParametersFor(java.util.ArrayList.class); + assertEquals(1, params.size()); + + // The type parameter should be Map (raw), which should have its own parameters + ResolvedType mapType = params.get(0); + assertEquals(Map.class, mapType.getErasedType()); + + // The raw Map should have Object,Object as its type parameters + List mapParams = mapType.getTypeParameters(); + assertEquals(Arrays.asList( + RESOLVER.resolve(Object.class), + RESOLVER.resolve(Object.class)), + mapParams); + } }