You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Inconsistent handling of self-references (ResolvedRecursiveType) in caching and equality #128
Follow-up from review of the #125 fix: handling of self-references (ResolvedRecursiveType) is inconsistent in two ways.
1. Cached types may contain self-references from another resolution context
A type without type parameters is cached under a single key, but when it is first resolved as a supertype of a type that refers back to it, its members contain self-references to that type (with its parameterization). A later direct resolution returns the cached instance, so results depend on resolution order:
staticabstractclassBarimplementsComparable<Foo> { } // raw FoostaticabstractclassFoo<T> extendsBar { }
TypeResolverr = newTypeResolver();
// with fresh resolver: Comparable<Foo<Object>> (ResolvedObjectType)r.resolve(Foo.class, String.class);
r.resolve(Bar.class).typeParametersFor(Comparable.class).get(0);
// -> ResolvedRecursiveType that refers to Foo<String>
Before #125 this was hidden for generic subtypes (supertypes were cached per enclosing parameterization), but the same already happens on 1.7 for non-generic subtypes. As of 1.8, types that have self-references in their type parameters are no longer cached, but self-references in supertypes are not checked.
2. Self-reference vs resolved type equality is only relaxed for array elements
As of 1.8, ResolvedArrayType.equals() treats a self-referential element type as equal to the fully resolved type (by erased type and bindings), so that f.ex Node[] within class Node extends Base<Node[]> equals resolve(Node[].class) (needed to detect method overrides). But the same is not done for type parameters: Base<Node> reached via Node's parent is still not equal to a directly resolved Base<Node>, nor is List<Node>[]. Also, two equal arrays may have element types that are not equal to each other.
Possible approach
Make ResolvedRecursiveType equality (and hash code) consistent with the type it refers to, in general rather than only for array elements; this needs care to avoid infinite recursion (see #117). That would also allow simplifying the array special case.
More cases and a suggested design, from further review of the #125 fix.
More cases
Self-references via supertypes are still cached (part 1 above, also without raw types):
staticclassAextendsBase<B> { }
staticclassBextendsBase<A> { }
r.resolve(A.class); // caches B, with parent Base<A> where A is a ResolvedRecursiveTyper.resolve(B.class).getParentClass().getTypeParameters().get(0);
// -> ResolvedRecursiveType (getParentClass() == null); fresh resolver gives full ResolvedObjectType A
Same for Mid extends Base<Outer> / Outer extends Mid, and for arrays of such types. (1.8 only avoids caching types with self-references in their type parameters.)
Equality only relaxed for array elements (part 2 above), still not equal:
class LNode extends Base<List<LNode>>: resolve(LNode).getParentClass() vs resolve(Base.class, List<LNode>)
class N<T> extends Base<N<N<T>>[]>: parent's N<N<String>>[] vs arrayType(N<N<String>>) (self-reference nested within bindings of the self-reference)
class GNode<T> extends Base<GNode[]>: raw self-reference has no bindings, so it is not equal to GNode<Object>[] (relaxing it to "matches any parameterization" was tried and rejected, since it breaks transitivity and with it HashSet/HashMap)
Suggested design
Caching: instead of scanning TypeBindings for self-references (current 1.8 approach, TypeBindings.hasContextualTypes()), mark ClassStack frames as incomplete when context.find() creates a self-reference to them, and skip caching for types constructed while any such frame is open. That covers bindings, supertypes and arrays with one mechanism; placeholder handling could stay as is.
Equality: handle self-reference vs. resolved type in TypeBindings.equals() (a ResolvedRecursiveType matches a non-recursive type with the same erased type and equal bindings), instead of the special case in ResolvedArrayType.equals(); raw self-references could be given bindings resolved to bounds at creation. Needs care to avoid infinite recursion (see Regression in 1.7.2 (wrt #53) causes StackOverflowError for some recursive types #117).
Activity
cowtowncoder commentedon Oct 8, 2026
More cases and a suggested design, from further review of the #125 fix.
More cases
Self-references via supertypes are still cached (part 1 above, also without raw types):
Same for
Mid extends Base<Outer>/Outer extends Mid, and for arrays of such types. (1.8 only avoids caching types with self-references in their type parameters.)Equality only relaxed for array elements (part 2 above), still not equal:
class LNode extends Base<List<LNode>>:resolve(LNode).getParentClass()vsresolve(Base.class, List<LNode>)class N<T> extends Base<N<N<T>>[]>: parent'sN<N<String>>[]vsarrayType(N<N<String>>)(self-reference nested within bindings of the self-reference)class GNode<T> extends Base<GNode[]>: raw self-reference has no bindings, so it is not equal toGNode<Object>[](relaxing it to "matches any parameterization" was tried and rejected, since it breaks transitivity and with itHashSet/HashMap)Suggested design
TypeBindingsfor self-references (current 1.8 approach,TypeBindings.hasContextualTypes()), markClassStackframes as incomplete whencontext.find()creates a self-reference to them, and skip caching for types constructed while any such frame is open. That covers bindings, supertypes and arrays with one mechanism; placeholder handling could stay as is.TypeBindings.equals()(aResolvedRecursiveTypematches a non-recursive type with the same erased type and equal bindings), instead of the special case inResolvedArrayType.equals(); raw self-references could be given bindings resolved to bounds at creation. Needs care to avoid infinite recursion (see Regression in 1.7.2 (wrt #53) causesStackOverflowErrorfor some recursive types #117).