Fix TypeUtils.toString() recursion and bound handling on recursive generic types - #1789
Conversation
…neric types - Format embedded TypeVariables as type references (name only) in ParameterizedType type arguments, WildcardType bounds, and GenericArrayType component types, preventing recursion and StackOverflowError - Preserve valid interface bounds on TypeVariables instead of stripping them - Remove fragile heuristic methods findRecursiveTypes, appendRecursiveTypes, and containsVariableTypeSameParametrizedTypeBound that corrupted formatting to <T><U><U> - Add recursion guard with VISITING ThreadLocal cleanup on unwind - Fix TypeUtils.containsTypeVariables(WildcardType) to check all upper and lower bounds instead of only index 0
|
Hi @garydgregory, could you please review.. |
There was a problem hiding this comment.
🟡 Changes recommended
The recursion guard does not cover all cyclic Type graphs, leaving a critical StackOverflowError path.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This pull request fixes recursive generic type formatting and preserves valid bounds in TypeUtils.
Changes:
- Separates type declarations from references.
- Preserves interface and multi-bounded wildcard bounds.
- Replaces recursive-formatting workarounds with cycle protection.
- Adds regression coverage.
File summaries
| File | Summary |
|---|---|
src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java |
Adds tests for recursive, multi-bound, interface, and wildcard cases. |
src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java |
Updates type formatting, bound handling, wildcard inspection, and recursion protection. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ely copy bounds in WildcardTypeImpl
There was a problem hiding this comment.
Hello @Alwaysgaurav1
Thank you for the update.
I'm not sure the new tests do not cover every change:
- Bounded T[], List, and <T extends Number, S extends T> formatting.
- A bounded variable passed to toLongString().
- A parameterized owner with a non-generic inner class, exercising removal of <>.
- Defensive copying of lower bounds; only upper bounds are tested.
- Owner cycles, repeated sibling references, and cleanup after an exception.
- Add tests for bounded T[], List<T>, and <T extends Number, S extends T> formatting - Add tests for bounded TypeVariable passed to toLongString() - Add test for parameterized owner with non-generic inner class exercising removal of <> - Add test for defensive copying of lower bounds in WildcardTypeImpl - Add tests for owner cycles, repeated sibling references, and ThreadLocal cleanup after exception - Route non-Class owner types and toLongString TypeVariables through toString(Type) for cycle tracking
|
Hello @garydgregory, Thank you for the thorough review! I have addressed all five points in the latest push (commit df5c62c):
All 442 tests in |
generic types (#1789). Sort members.
Problem
TypeUtils.toString(Type)suffered from several interrelated issues when handling recursive generic types and type bounds:class MySuperClass<T>,class MyClass<U extends MySuperClass<? super U>>, orclass MultiBoundClass<U extends Number & Comparable<? super U>>resulted inStackOverflowError.LANG-1698unconditionally skipped bounds if the raw type was an interface. This stripped all valid interface bounds (e.g.<T extends List<String>>formatted as"T"instead of"T extends java.util.List<java.lang.String>"), while still failing with aStackOverflowErroron class bounds.findRecursiveTypesandappendRecursiveTypescorrupted type parameter formatting on multi-parameter recursive types (such asTwoParams<T extends TwoParams<T, U>, U>), outputting<T><U><U>.TypeUtils.containsTypeVariables(WildcardType)only inspected the bound at index0, ignoring subsequent bounds for multi-bounded wildcards.Root Cause
In Java grammar (JLS Chapter 4), a
TypeVariableis a declaration only when declaring a type parameter (e.g.<U extends Number>). Everywhere else—such as inside type arguments (List<U>), wildcard bounds (? extends U,? super U), generic arrays (U[]), or other type variable bounds (<S extends T>)—it is a type reference (ReferenceType) and should be formatted only by its identifier nameU. Treating embedded type variables as declarations led to recursive expansion of bounds.Solution
toReferenceString(Type)which formatsTypeVariableinstances by name only and delegates other types totoString(Type). ConfiguredAMP_JOINER,TYPE_ARG_JOINER, andgenericArrayTypeToStringto format embedded types as references.typeVariableToString, properly preserving all interface bounds without recursion.findRecursiveTypes,appendRecursiveTypes, andcontainsVariableTypeSameParametrizedTypeBound.containsTypeVariables(WildcardType)to check all implicit upper and lower bounds.VISITINGThreadLocal<Set<TypeVariable<?>>>with guaranteedVISITING.remove()infinallyto guard against arbitrary cyclical type graphs without leaking memory.Verification
TypeUtilsTestpassed (including 6 new tests covering recursive class bounds, multi-bounds, interface preservation, and multi-bound wildcards).org.apache.commons.lang3.reflect.*passed without regressions.