diff --git a/VERSION.txt b/VERSION.txt index e67d285..aaf6197 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -19,8 +19,7 @@ Release notes: - Array types are cached by `TypeResolver` using element type as part of the key - Types with `TypePlaceHolder`s (from `resolveSubtype()`) or self-references (`ResolvedRecursiveType`) in their type parameters (at any level of nesting, - including array element types) are no longer cached (but see #128 for - self-references via supertypes) + including array element types) are no longer cached #127: `resolveSubtype()` does not verify or resolve array element types - `resolveSubtype()` now rejects type variable bound to conflicting types (like `E` in `Dup extends Pair`; except that `Object`, which may @@ -36,6 +35,24 @@ Release notes: `IllegalArgumentException` if any of type parameters is a primitive type (like `resolve(List.class, int.class)`) (fix by @cowtowncoder w/ Claude code) +#128: Self-references via supertypes still cached; self-references not equal + to fully resolved types +- Types containing self-references to types still being resolved (like `B` in + `B extends Base` when resolving `A extends Base`) are no longer cached, + and are re-resolved when used later (like for member types) +- Self-references (`ResolvedRecursiveType`) nested in type parameters or array + element types are equal to fully resolved types with same type bindings + (so f.ex raw `Enum` is now equal to `Enum>`) +- Raw self-references have type parameters resolved to bounds, like other raw types + (except ones created while resolving bounds of the type itself, like `T` in + `RawBound`; types containing those may also be shared) +- Self-references with bindings different from referenced type (like raw `Mid` + within `Mid`, or `N>` within `N`) now represent type with own + bindings: see new `ResolvedRecursiveType.getActualType()` (also used for + `getParentClass()`, members and `resolveSubtype()`) + (fix by @cowtowncoder w/ Claude code) +#132: `MemberResolver` misses members inherited via self-referential interface + (fix by @cowtowncoder w/ Claude code) 1.7.3 (02-Jan-2026) diff --git a/src/main/java/com/fasterxml/classmate/MemberResolver.java b/src/main/java/com/fasterxml/classmate/MemberResolver.java index d9556f0..f220be6 100644 --- a/src/main/java/com/fasterxml/classmate/MemberResolver.java +++ b/src/main/java/com/fasterxml/classmate/MemberResolver.java @@ -4,6 +4,7 @@ import java.util.*; import com.fasterxml.classmate.members.*; +import com.fasterxml.classmate.types.ResolvedRecursiveType; import com.fasterxml.classmate.util.ClassKey; /** @@ -113,10 +114,21 @@ public MemberResolver setConstructorFilter(Filter f) { * @param annotationConfig Configuration of annotation types; which ones to include, how to inherit * @param annotationOverrides Definitions of annotation overrides to use, if any (may be null) */ - public ResolvedTypeWithMembers resolve(final ResolvedType mainType, + public ResolvedTypeWithMembers resolve(ResolvedType mainType, AnnotationConfiguration annotationConfig, AnnotationOverrides annotationOverrides) { + // [classmate#128]: type may be one only valid within its original resolution context + // (like one obtained via type parameters of another type), with self-references in + // its supertypes: if so, need to resolve it as stand-alone type + // (and self-reference itself has no supertypes, so need to use type it represents) + if (mainType instanceof ResolvedRecursiveType) { + ResolvedType actual = ((ResolvedRecursiveType) mainType).getActualType(); + if (actual != null) { + mainType = actual; + } + } + mainType = _typeResolver.resolve(mainType); List types; HashSet seenTypes = new HashSet(); diff --git a/src/main/java/com/fasterxml/classmate/ResolvedType.java b/src/main/java/com/fasterxml/classmate/ResolvedType.java index 6c2beac..c037576 100644 --- a/src/main/java/com/fasterxml/classmate/ResolvedType.java +++ b/src/main/java/com/fasterxml/classmate/ResolvedType.java @@ -8,6 +8,7 @@ import java.util.*; import com.fasterxml.classmate.members.*; +import com.fasterxml.classmate.types.ResolvedRecursiveType; public abstract class ResolvedType implements Type @@ -25,19 +26,46 @@ public abstract class ResolvedType * constructors) of this type */ protected final TypeBindings _typeBindings; - + + /** + * Whether this type contains (via type parameters, array element type or supertypes) + * self-references to types it does not itself contain (like {@code B} in + * {@code B extends Base}, when resolved as part of {@code A extends Base}); + * or is such a self-reference. + * Such types are only valid within the resolution of the type that contains them, + * and are never cached by {@link TypeResolver} (nor are types containing them). + * Set by {@link TypeResolver} when type is constructed, before it is exposed. + * + * @since 1.8 + */ + private volatile boolean _incomplete; + /* /********************************************************************** /* Life cycle /********************************************************************** */ - + protected ResolvedType(Class cls, TypeBindings bindings) { _erasedType = cls; _typeBindings = (bindings == null) ? TypeBindings.emptyBindings() : bindings; } - + + /** + * @since 1.8 + */ + void _markIncomplete() { + _incomplete = true; + } + + /** + * @since 1.8 + */ + boolean _isIncomplete() { + return _incomplete; + } + /** * Method that can be used to check if call to {@link TypeResolver#resolveSubtype(ResolvedType, Class)} * may ever succeed; if false, it will fail with an exception, if true, it may succeed. @@ -280,6 +308,25 @@ public String getBriefDescription() { // and type bindings must match as well return _typeBindings.equals(other._typeBindings); } + + /** + * Helper method for comparing types contained in other types (as type parameters + * or array element types): differs from {@link #equals} in that a self-reference + * ({@link ResolvedRecursiveType}) matches a fully resolved type with the same erased + * type and type bindings. + * + * @since 1.8 + */ + protected static boolean _equalTypes(ResolvedType t1, ResolvedType t2) + { + if (t1 == t2) return true; + if ((t1 == null) || (t2 == null)) return false; + if ((t1 instanceof ResolvedRecursiveType) != (t2 instanceof ResolvedRecursiveType)) { + return (t1._erasedType == t2._erasedType) + && t1._typeBindings.equals(t2._typeBindings); + } + return t1.equals(t2); + } /* /********************************************************************** diff --git a/src/main/java/com/fasterxml/classmate/TypeBindings.java b/src/main/java/com/fasterxml/classmate/TypeBindings.java index 5e36d07..ff95967 100644 --- a/src/main/java/com/fasterxml/classmate/TypeBindings.java +++ b/src/main/java/com/fasterxml/classmate/TypeBindings.java @@ -295,7 +295,8 @@ public boolean hasUnbound(String name) { } ResolvedType[] otherTypes = other._types; for (int i = 0; i < len; ++i) { - if (!otherTypes[i].equals(_types[i])) { + // [classmate#128]: self-references must match fully resolved types + if (!ResolvedType._equalTypes(otherTypes[i], _types[i])) { return false; } } diff --git a/src/main/java/com/fasterxml/classmate/TypeResolver.java b/src/main/java/com/fasterxml/classmate/TypeResolver.java index a4bd60c..a41e1a0 100644 --- a/src/main/java/com/fasterxml/classmate/TypeResolver.java +++ b/src/main/java/com/fasterxml/classmate/TypeResolver.java @@ -3,7 +3,7 @@ import java.io.Serializable; import java.lang.reflect.*; import java.util.*; -import java.util.function.Supplier; +import java.util.function.UnaryOperator; import com.fasterxml.classmate.types.*; import com.fasterxml.classmate.util.ClassKey; @@ -140,14 +140,15 @@ public ResolvedType resolve(Type type, Type... typeParameters) } else if (type instanceof GenericType) { bindings = TypeBindings.emptyBindings(); if (noParams) { - return _fromGenericType(null, (GenericType) type, bindings); + return _completeType(_fromGenericType(null, (GenericType) type, bindings)); } ResolvedType rt = _fromAny(null, type, bindings); rawBase = rt.getErasedType(); } else if (type instanceof ResolvedType) { ResolvedType rt = (ResolvedType) type; if (noParams) { - return rt; + // [classmate#128]: same as `resolve(TypeBindings, Type)` + return _completeType(rt); } bindings = rt.getTypeBindings(); rawBase = rt.getErasedType(); @@ -165,9 +166,11 @@ public ResolvedType resolve(Type type, Type... typeParameters) int len = typeParameters.length; ResolvedType[] resolvedParams = new ResolvedType[len]; for (int i = 0; i < len; ++i) { - resolvedParams[i] = _fromAny(null, typeParameters[i], bindings); + // [classmate#128]: type parameters may be incomplete types (from earlier resolution) + resolvedParams[i] = _completeType(_fromAny(null, typeParameters[i], bindings)); } - return _fromClass(null, rawBase, TypeBindings.create(rawBase, resolvedParams)); + // and the type itself may be incomplete due to self-references in type parameters + return _completeType(_fromClass(null, rawBase, TypeBindings.create(rawBase, resolvedParams))); } /** @@ -176,7 +179,7 @@ public ResolvedType resolve(Type type, Type... typeParameters) public ResolvedArrayType arrayType(Type elementType) { ResolvedType resolvedElementType = resolve(TypeBindings.emptyBindings(), elementType); - return _arrayOf(_arrayClassFor(resolvedElementType), resolvedElementType); + return _arrayOf(null, _arrayClassFor(resolvedElementType), resolvedElementType); } /** @@ -199,7 +202,9 @@ public ResolvedArrayType arrayType(Type elementType) */ public ResolvedType resolve(TypeBindings typeBindings, Type jdkType) { - return _fromAny(null, jdkType, typeBindings); + // [classmate#128]: bindings may contain incomplete types (like when resolving + // members of a type), which need to be completed where possible + return _completeType(_fromAny(null, jdkType, typeBindings)); } /** @@ -370,22 +375,29 @@ private ResolvedType _fromClass(ClassStack context, Class rawType, TypeBindin // [classmate#125]: Arrays have no type parameters of their own, so bindings // must not be retained (nor used for element type) if (rawType.isArray()) { - return _arrayOf(rawType, _fromClass(context, rawType.getComponentType(), + return _arrayOf(context, rawType, _fromClass(context, rawType.getComponentType(), TypeBindings.emptyBindings())); } // Second: recursive reference? - if (context == null) { - context = new ClassStack(rawType); - } else { + if (context != null) { ClassStack prev = context.find(rawType); if (prev != null) { // Self-reference: needs special handling, then... + // [classmate#128]: raw self-reference gets type parameters resolved to + // their bounds, same as other raw types (unless already resolving bounds + // of the type, to avoid infinite recursion) + if (typeBindings.isEmpty() && (rawType.getTypeParameters().length > 0) + && !prev.isResolvingBounds()) { + typeBindings = TypeBindings.create(rawType, + _resolveBounds(context, prev, rawType)); + } ResolvedRecursiveType selfRef = new ResolvedRecursiveType(rawType, typeBindings); - prev.addSelfReference(selfRef); + // [classmate#128]: also need to keep track of types containing self-references + // (to types still being resolved) to avoid caching them + ((ResolvedType) selfRef)._markIncomplete(); + context.selfReferenceCreated(selfRef, prev); return selfRef; } - // no, can just add - context = context.child(rawType); } // If not, already recently resolved? @@ -395,24 +407,73 @@ private ResolvedType _fromClass(ClassStack context, Class rawType, TypeBindin // within resolution context) ResolvedTypeKey key = typeBindings.hasContextualTypes() ? null : _resolvedTypes.key(rawType, typeBindings.typeParameterArray()); - final ClassStack typeContext = context; - type = _findOrConstruct(key, () -> _constructType(typeContext, rawType, typeBindings)); - context.resolveSelfReferences(type); + type = _findType(context, key); + if (type != null) { + return type; + } + // If not, need to construct + context = (context == null) ? new ClassStack(rawType) : context.child(rawType); + type = _constructType(context, rawType, typeBindings); + // [classmate#128]: self-references with different bindings (like raw `Mid` within + // `Mid`) represent differently parameterized type, resolved lazily. Except + // if within type parameters of the type itself (like `E` in raw `Enum>`) + if (context.hasSelfReferences()) { + final ResolvedType resolved = type; + context.resolveSelfReferences(type, ref -> _representsReferenced(ref, resolved) + ? null : () -> _standaloneSelfReference(ref)); + } + // [classmate#128]: nor can types with self-references to types still being + // resolved (like `B` in `B extends Base`, when resolving `A extends Base`) + // be cached, whether via type parameters, supertypes or array element types + // (note: self-references created within this frame, like ones in bounds of + // raw type, are accounted for by frame; but type parameters are resolved outside) + boolean incomplete = context.hasOuterReferences(); + for (ResolvedType param : typeBindings.typeParameterArray()) { + incomplete |= param._isIncomplete(); + } + if (incomplete) { + type._markIncomplete(); + context.addIncomplete(key, type); + } else if (key != null) { + _resolvedTypes.put(key, type); + } return type; } /** - * Helper method for finding cached type with given key, if any; or if not, - * constructing and caching it. If key is null, type is constructed but not cached. + * Helper method for replacing incomplete type (see {@link ResolvedType}) obtained + * from an earlier resolution with stand-alone type, to avoid it being used outside + * of its resolution context (see {@link #_resolveSelfReferences}). Called by public + * entry points only. + *

+ * NOTE: self-references themselves are retained as-is, as are incomplete types with + * self-references (in type parameters) to enclosing types, since those are only valid + * within the enclosing types: so returned type may still be incomplete. + * + * @since 1.8 */ - private ResolvedType _findOrConstruct(ResolvedTypeKey key, Supplier constructor) + private ResolvedType _completeType(ResolvedType type) { - ResolvedType type = (key == null) ? null : _resolvedTypes.find(key); - if (type == null) { - type = constructor.get(); - if (key != null) { - _resolvedTypes.put(key, type); - } + if (type._isIncomplete() && !isSelfReference(type)) { + return _resolveSelfReferences(type, null); + } + return type; + } + + /** + * Helper method for finding cached type with given key, if any; or, failing that, + * incomplete type constructed earlier during current resolution that may be reused. + * + * @since 1.8 + */ + private ResolvedType _findType(ClassStack context, ResolvedTypeKey key) + { + if (key == null) { + return null; + } + ResolvedType type = _resolvedTypes.find(key); + if ((type == null) && (context != null)) { + type = context.findIncomplete(key); } return type; } @@ -451,17 +512,7 @@ private ResolvedType _constructType(ClassStack context, Class rawType, TypeBi // (note: [classmate#33] work-around for non-empty bindings of non-generic types // no longer needed as of [classmate#125]: such bindings are never passed) if (typeBindings.isEmpty() && (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); + typeBindings = TypeBindings.create(rawType, _resolveBounds(context, context, rawType)); } // For other types super interfaces are needed... if (rawType.isInterface()) { @@ -474,6 +525,33 @@ private ResolvedType _constructType(ClassStack context, Class rawType, TypeBi _resolveSuperInterfaces(context, rawType, typeBindings)); } + /** + * Helper method for resolving type parameters of a raw generic type to their bounds. + * + * @param context Resolution context to use + * @param frame Stack frame of the raw type (to mark it as having its bounds resolved) + */ + private ResolvedType[] _resolveBounds(ClassStack context, ClassStack frame, Class rawType) + { + final TypeVariable[] rawTypeParameters = rawType.getTypeParameters(); + final boolean wasResolving = frame.isResolvingBounds(); + frame.setResolvingBounds(true); + try { + 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]; + // Avoid self-reference cycles by marking as unbound during resolution + TypeBindings tempBindings = TypeBindings.emptyBindings() + .withUnboundVariable(var.getName()); + types[i] = _fromAny(context, var.getBounds()[0], tempBindings); + } + return types; + } finally { + frame.setResolvingBounds(wasResolving); + } + } + private ResolvedType[] _resolveSuperInterfaces(ClassStack context, Class rawType, TypeBindings typeBindings) { Type[] types = rawType.getGenericInterfaces(); @@ -528,7 +606,7 @@ private ResolvedType _fromArrayType(ClassStack context, GenericArrayType arrayTy { // [classmate#125]: bindings only needed for element type, not retained by array ResolvedType elementType = _fromAny(context, arrayType.getGenericComponentType(), typeBindings); - return _arrayOf(_arrayClassFor(elementType), elementType); + return _arrayOf(context, _arrayClassFor(elementType), elementType); } private static Class _arrayClassFor(ResolvedType elementType) { @@ -541,14 +619,29 @@ private static Class _arrayClassFor(ResolvedType elementType) { * element type. Arrays are cached using element type as the "type parameter" * of the key, so that differently parameterized element types do not collide. * Arrays with element types only valid within resolution context (self-references, - * placeholders) are not cached (cache key will be null). + * placeholders, types containing self-references to types still being resolved) + * are not cached. + * + * @param context Resolution context, if any; {@code null} if none */ - private ResolvedArrayType _arrayOf(Class arrayClass, ResolvedType elementType) + private ResolvedArrayType _arrayOf(ClassStack context, Class arrayClass, ResolvedType elementType) { ResolvedTypeKey key = TypeBindings.isContextual(elementType) ? null : _resolvedTypes.key(arrayClass, new ResolvedType[] { elementType }); - return (ResolvedArrayType) _findOrConstruct(key, - () -> new ResolvedArrayType(arrayClass, TypeBindings.emptyBindings(), elementType)); + ResolvedType type = _findType(context, key); + if (type == null) { + type = new ResolvedArrayType(arrayClass, TypeBindings.emptyBindings(), elementType); + // [classmate#128]: element type may contain self-references to types still being resolved + if (elementType._isIncomplete()) { + type._markIncomplete(); + if (context != null) { + context.addIncomplete(key, type); + } + } else if (key != null) { + _resolvedTypes.put(key, type); + } + } + return (ResolvedArrayType) type; } private ResolvedType _fromWildcard(ClassStack context, WildcardType wildType, TypeBindings typeBindings) @@ -664,7 +757,10 @@ private boolean _verifyAndResolve(ResolvedType exp, ResolvedType act) exp = _selfReferenceTarget(exp); } else if (isSelfReference(act) && !isSelfReference(exp)) { // but only raw one: others have bindings (possibly with placeholders) to use - act = _resolveRawSelfReference(act); + if (act.getTypeBindings().isEmpty() + && (act.getErasedType().getTypeParameters().length > 0)) { + act = _standaloneSelfReference(act); + } } // [classmate#127]: Array types have no type parameters, so need to verify // (and resolve) element types instead. Must be done before erased type check @@ -707,8 +803,9 @@ private boolean _verifyAndResolve(ResolvedType exp, ResolvedType act) */ private ResolvedType _mergeBindings(ResolvedType a, ResolvedType b) { - if (a.equals(b)) { - return a; + // [classmate#128]: self-reference equal to fully resolved type, latter preferred + if (ResolvedType._equalTypes(a, b)) { + return isSelfReference(a) ? b : a; } if (_isJavaLangObject(a) && !b.isPrimitive()) { return b; @@ -716,7 +813,17 @@ private ResolvedType _mergeBindings(ResolvedType a, ResolvedType b) if (_isJavaLangObject(b) && !a.isPrimitive()) { return a; } - if (isSelfReference(a) || isSelfReference(b) || (a.isArray() != b.isArray())) { + // [classmate#128]: self-reference (like one in raw `Enum`) can be merged as the + // type it represents; as long as that contains no self-references (to ensure + // termination) + if (isSelfReference(a) != isSelfReference(b)) { + ResolvedType standalone = _standaloneSelfReference(isSelfReference(a) ? a : b); + if (TypeBindings.isContextual(standalone)) { + return null; + } + return isSelfReference(a) ? _mergeBindings(standalone, b) : _mergeBindings(a, standalone); + } + if (isSelfReference(a) || (a.isArray() != b.isArray())) { return null; } if (a.isArray()) { @@ -732,7 +839,7 @@ private ResolvedType _mergeBindings(ResolvedType a, ResolvedType b) if (elem == elemB) { return b; } - return _arrayOf(_arrayClassFor(elem), elem); + return _arrayOf(null, _arrayClassFor(elem), elem); } if (a.getErasedType() != b.getErasedType()) { return null; @@ -767,40 +874,149 @@ private static boolean _isJavaLangObject(ResolvedType type) { } /** - * Helper method for resolving raw self-reference (one without type bindings, for - * generic class) into type with type parameters bound to their bounds; same as - * other raw types are resolved. + * Helper method for finding stand-alone type to use in place of given self-reference: + * the type referenced, if valid outside its resolution context and with matching + * bindings; otherwise resolved using own bindings (raw one to bounds). * * @since 1.8 */ - private ResolvedType _resolveRawSelfReference(ResolvedType type) + private ResolvedType _selfReferenceTarget(ResolvedType selfRef) { - if (isSelfReference(type) - && type.getTypeBindings().isEmpty() - && type.getErasedType().getTypeParameters().length > 0) { - return _fromClass(null, type.getErasedType(), type.getTypeBindings()); + final ResolvedRecursiveType rrt = (ResolvedRecursiveType) selfRef; + final ResolvedType ref = rrt.getSelfReferencedType(); + if ((ref != null) && !ref._isIncomplete()) { + // [classmate#128]: actual type (with own bindings), if differs + ResolvedType actual = rrt.getActualType(); + if (actual != ref) { + return actual; + } + // otherwise referenced type, if self-reference represents it (only not known + // if self-reference was not constructed by `TypeResolver`) + if (_representsReferenced(rrt, ref)) { + return ref; + } } - return type; + return _standaloneSelfReference(selfRef); } /** - * Helper method for finding stand-alone type to use in place of given self-reference: - * raw one is resolved with type parameters bound to their bounds; others to the type - * referenced. + * Helper method for checking whether given type has a self-reference to its own + * class as (direct) type parameter, like raw F-bounded types do. * * @since 1.8 */ - private ResolvedType _selfReferenceTarget(ResolvedType selfRef) + private static boolean _hasSelfReferenceParameter(ResolvedType type) + { + final TypeBindings bindings = type.getTypeBindings(); + for (int i = 0, len = bindings.size(); i < len; ++i) { + ResolvedType t = bindings.getBoundType(i); + if (isSelfReference(t) && (t.getErasedType() == type.getErasedType())) { + return true; + } + } + return false; + } + /** + * @return Stand-alone raw type of given type, if equal to it; otherwise given type + * + * @since 1.8 + */ + private ResolvedType _rawIfEqual(ResolvedType type) + { + ResolvedType rawType = _fromClass(null, type.getErasedType(), TypeBindings.emptyBindings()); + return rawType.equals(type) ? rawType : type; + } + + /** + * Helper method for checking whether given self-reference represents the type it + * references: that is, either has same type bindings, or is the type parameter of + * the referenced type, for type variable bound to the type itself (like {@code E} in + * raw {@code Enum>}). Other self-references (like {@code W} + * in {@code W>}) represent differently parameterized types. + * + * @since 1.8 + */ + private static boolean _representsReferenced(ResolvedType selfRef, ResolvedType ref) { - ResolvedType raw = _resolveRawSelfReference(selfRef); - if (raw != selfRef) { - return raw; + final TypeBindings refBindings = ref.getTypeBindings(); + if (refBindings.equals(selfRef.getTypeBindings())) { + return true; } - ResolvedType ref = selfRef.getSelfReferencedType(); - if (ref != null) { - return ref; + for (int i = 0, len = refBindings.size(); i < len; ++i) { + if ((refBindings.getBoundType(i) == selfRef) + && _isBoundToSelf(ref.getErasedType(), i)) { + return true; + } } - return _fromClass(null, selfRef.getErasedType(), selfRef.getTypeBindings()); + return false; + } + + /** + * Helper method for checking whether type variable with given index of given class is + * bound to the class itself: either raw ({@code T extends Foo}), or parameterized with + * its own type variables, in order ({@code T extends Foo}). + * + * @since 1.8 + */ + private static boolean _isBoundToSelf(Class raw, int index) + { + TypeVariable[] vars = raw.getTypeParameters(); + if (index >= vars.length) { + return false; + } + Type bound = vars[index].getBounds()[0]; + if (bound == raw) { + return true; + } + if (!(bound instanceof ParameterizedType) + || (((ParameterizedType) bound).getRawType() != raw)) { + return false; + } + // must be parameterized with own type variables, in order (like `Foo`) + return Arrays.equals(((ParameterizedType) bound).getActualTypeArguments(), vars); + } + /** + * Helper method for resolving stand-alone type that given self-reference represents, + * using its own type bindings (with self-references in them resolved similarly). + * + * @since 1.8 + */ + private ResolvedType _standaloneSelfReference(ResolvedType selfRef) + { + final Class erased = selfRef.getErasedType(); + final TypeBindings bindings = selfRef.getTypeBindings(); + if (bindings.isEmpty()) { // raw (or non-generic) type + return _fromClass(null, erased, bindings); + } + ResolvedType[] params = _mapTypes(bindings, t -> _resolveSelfReferences(t, null)); + if (params == null) { + params = bindings.typeParameterArray(); + } + return _fromClass(null, erased, TypeBindings.create(erased, params)); + } + + /** + * Helper method for applying given function to types of given bindings. + * + * @return Array of resulting types, if any changed (by identity); {@code null} if none + * + * @since 1.8 + */ + private static ResolvedType[] _mapTypes(TypeBindings bindings, + UnaryOperator mapper) + { + ResolvedType[] types = null; + for (int i = 0, len = bindings.size(); i < len; ++i) { + ResolvedType t = bindings.getBoundType(i); + ResolvedType newT = mapper.apply(t); + if (newT != t) { + if (types == null) { + types = bindings.typeParameterArray().clone(); + } + types[i] = newT; + } + } + return types; } /** @@ -809,7 +1025,8 @@ private ResolvedType _selfReferenceTarget(ResolvedType selfRef) * resolved types. This includes the type itself, as well as ones within type * parameters and array element types, at any level of nesting: except for * self-references to an enclosing type (like {@code E} in {@code Enum>}) - * which are valid as-is. + * which are valid as-is. Types containing such self-references via their supertypes + * (see [classmate#128]) are re-resolved. * * @param enclosing Enclosing types (containing given type as type parameter), used as * a stack (must be restored before returning); {@code null} if none @@ -820,45 +1037,46 @@ private ResolvedType _selfReferenceTarget(ResolvedType selfRef) */ private ResolvedType _resolveSelfReferences(ResolvedType type, List enclosing) { - if (!TypeBindings.isContextual(type)) { + if (!TypeBindings.isContextual(type) && !type._isIncomplete()) { return type; } if (type.isArray()) { ResolvedType elem = type.getArrayElementType(); ResolvedType newElem = _resolveSelfReferences(elem, enclosing); - return (newElem == elem) ? type : _arrayOf(type.getErasedType(), newElem); + return (newElem == elem) ? type : _arrayOf(null, type.getErasedType(), newElem); } if (isSelfReference(type)) { // Self-reference to an enclosing type (by identity) is valid as-is + // [classmate#128]: as long as it represents that type if (enclosing != null) { final ResolvedType ref = type.getSelfReferencedType(); for (ResolvedType t : enclosing) { - if (t == ref) { + if ((t == ref) && (((ResolvedRecursiveType) type).getActualType() == ref)) { return type; } } } return _selfReferenceTarget(type); } - if (enclosing == null) { - enclosing = new ArrayList<>(); - } - enclosing.add(type); + final List encl = (enclosing == null) ? new ArrayList<>() : enclosing; + encl.add(type); final TypeBindings bindings = type.getTypeBindings(); - ResolvedType[] newTypes = null; - for (int i = 0, len = bindings.size(); i < len; ++i) { - ResolvedType t = bindings.getBoundType(i); - ResolvedType newT = _resolveSelfReferences(t, enclosing); - if (newT != t) { - if (newTypes == null) { - newTypes = bindings.getTypeParameters().toArray(new ResolvedType[0]); - } - newTypes[i] = newT; - } - } - enclosing.remove(enclosing.size() - 1); + ResolvedType[] newTypes = _mapTypes(bindings, t -> _resolveSelfReferences(t, encl)); + encl.remove(encl.size() - 1); if (newTypes == null) { - return type; + // [classmate#128]: type with self-references via supertypes needs to be re-resolved; + // but not if it retains self-references to enclosing types (since those are + // valid as-is, and would not be valid after re-resolution) + if (!type._isIncomplete()) { + return type; + } + if (bindings.hasContextualTypes()) { + // ... although incomplete raw type with self-references to itself (like + // `B>`) may be replaced with equal stand-alone raw type + return _hasSelfReferenceParameter(type) + ? _rawIfEqual(type) : type; + } + newTypes = bindings.typeParameterArray(); } final Class raw = type.getErasedType(); return _fromClass(null, raw, TypeBindings.create(raw, newTypes)); diff --git a/src/main/java/com/fasterxml/classmate/types/ResolvedArrayType.java b/src/main/java/com/fasterxml/classmate/types/ResolvedArrayType.java index 19e0711..2faf5de 100644 --- a/src/main/java/com/fasterxml/classmate/types/ResolvedArrayType.java +++ b/src/main/java/com/fasterxml/classmate/types/ResolvedArrayType.java @@ -35,23 +35,12 @@ public ResolvedArrayType(Class erased, TypeBindings bindings, _elementType = elementType; } + // [classmate#128]: self-reference as element type (like `Node[]` within + // `Node extends Base`) must be equal to fully resolved type @Override public boolean equals(Object other) { return super.equals(other) - && _elementsEqual(_elementType, ((ResolvedArrayType) other)._elementType); - } - - // Self-reference to element type (like `Node[]` within `Node extends Base`) - // must be equal to fully resolved type, so compare erased type and bindings for those - private static boolean _elementsEqual(ResolvedType elem1, ResolvedType elem2) { - if ((elem1 == null) || (elem2 == null)) { - return (elem1 == elem2); - } - if ((elem1 instanceof ResolvedRecursiveType) || (elem2 instanceof ResolvedRecursiveType)) { - return (elem1.getErasedType() == elem2.getErasedType()) - && elem1.getTypeBindings().equals(elem2.getTypeBindings()); - } - return Objects.equals(elem1, elem2); + && _equalTypes(_elementType, ((ResolvedArrayType) other)._elementType); } // NOTE: consistent with `equals()` since hash code of all types (including diff --git a/src/main/java/com/fasterxml/classmate/types/ResolvedObjectType.java b/src/main/java/com/fasterxml/classmate/types/ResolvedObjectType.java index 9b354a8..37e8422 100644 --- a/src/main/java/com/fasterxml/classmate/types/ResolvedObjectType.java +++ b/src/main/java/com/fasterxml/classmate/types/ResolvedObjectType.java @@ -114,7 +114,9 @@ public ResolvedObjectType getParentClass() { if (_superClass instanceof ResolvedObjectType) { return (ResolvedObjectType) _superClass; } - ResolvedType rt = ((ResolvedRecursiveType) _superClass).getSelfReferencedType(); + // [classmate#128]: actual type, in case bindings differ (like raw `Mid` for `Outer extends Mid` + // within `Mid`) + ResolvedType rt = ((ResolvedRecursiveType) _superClass).getActualType(); if (!(rt instanceof ResolvedObjectType)) { throw new IllegalStateException("Internal error: self-referential parent type (" +_superClass+") does not resolve into proper ResolvedObjectType, but instead to: " diff --git a/src/main/java/com/fasterxml/classmate/types/ResolvedRecursiveType.java b/src/main/java/com/fasterxml/classmate/types/ResolvedRecursiveType.java index efa9e7d..91cee25 100644 --- a/src/main/java/com/fasterxml/classmate/types/ResolvedRecursiveType.java +++ b/src/main/java/com/fasterxml/classmate/types/ResolvedRecursiveType.java @@ -2,6 +2,7 @@ import java.lang.reflect.Modifier; import java.util.*; +import java.util.function.Supplier; import com.fasterxml.classmate.ResolvedType; import com.fasterxml.classmate.TypeBindings; @@ -24,6 +25,24 @@ public class ResolvedRecursiveType extends ResolvedType */ protected ResolvedType _referencedType; + /** + * For self-references with type bindings different from those of the referenced + * type (like raw {@code Mid} within {@code Mid}, or {@code N>} within + * {@code N}): supplier of the actual type this self-reference represents. + * Resolved lazily since doing so eagerly could lead to infinite recursion. + * + * @since 1.8 + */ + protected Supplier _actualTypeSupplier; + + /** + * Actual type this self-reference represents, if differs from referenced type; + * resolved lazily using {@link #_actualTypeSupplier}. + * + * @since 1.8 + */ + protected volatile ResolvedType _actualType; + /* /********************************************************************** /* Life cycle @@ -37,16 +56,31 @@ public ResolvedRecursiveType(Class erased, TypeBindings bindings) @Override public boolean canCreateSubtypes() { + // only depends on erased type, so no need to resolve actual type return _referencedType.canCreateSubtypes(); } public void setReference(ResolvedType ref) + { + setReference(ref, null); + } + + /** + * Alternative to {@link #setReference(ResolvedType)} used when type bindings of + * this self-reference differ from those of the referenced type: in that case, + * {@link #getActualType()} returns type obtained (lazily) from given supplier + * (or referenced type, if equal). + * + * @since 1.8 + */ + public synchronized void setReference(ResolvedType ref, Supplier actualType) { // sanity check; should not be called multiple times if (_referencedType != null) { throw new IllegalStateException("Trying to re-set self reference; old value = "+_referencedType+", new = "+ref); } _referencedType = ref; + _actualTypeSupplier = actualType; } /* @@ -63,8 +97,61 @@ public ResolvedType getParentClass() { return null; } + /** + * Accessor for the type being resolved that this self-reference points to. + * Note that type bindings of this self-reference may differ from those of the + * referenced type (like for raw {@code Mid} within {@code Mid}): if so, + * {@link #getActualType()} returns the type self-reference actually represents. + */ @Override public ResolvedType getSelfReferencedType() { return _referencedType; } + + /** + * Accessor for the type this self-reference represents: same as + * {@link #getSelfReferencedType()}, unless type bindings of this self-reference + * differ from those of the referenced type (like raw {@code Mid} within + * {@code Mid}, or {@code N>} within {@code N}), in which case + * it is the type with bindings of this self-reference (resolved lazily). + *

+ * NOTE: for expanding types (like {@code N} above), following actual types + * repeatedly yields ever deeper types. + * + * @return Type this self-reference represents; {@code null} if not yet resolved + * + * @since 1.8 + */ + public ResolvedType getActualType() + { + ResolvedType actual = _actualType; + if (actual != null) { + return actual; + } + final Supplier supplier; + synchronized (this) { + if (_actualType != null) { + return _actualType; + } + supplier = _actualTypeSupplier; + if (supplier == null) { + return _referencedType; + } + } + // resolved outside of lock, since resolution may access other self-references; + // concurrent calls may resolve more than once, but only first one is retained + actual = supplier.get(); + // retain identity if equal + if (actual.equals(_referencedType)) { + actual = _referencedType; + } + synchronized (this) { + if (_actualType == null) { + _actualType = actual; + // no longer needed (and may hold on to `TypeResolver`) + _actualTypeSupplier = null; + } + return _actualType; + } + } /** * To avoid infinite loops, will return empty list @@ -107,15 +194,15 @@ public ResolvedType getArrayElementType() { // interfaces are never arrays, so: */ @Override - public List getMemberFields() { return _referencedType.getMemberFields(); } + public List getMemberFields() { return getActualType().getMemberFields(); } @Override - public List getStaticFields() { return _referencedType.getStaticFields(); } + public List getStaticFields() { return getActualType().getStaticFields(); } @Override - public List getStaticMethods() { return _referencedType.getStaticMethods(); } + public List getStaticMethods() { return getActualType().getStaticMethods(); } @Override - public List getMemberMethods() { return _referencedType.getMemberMethods(); } + public List getMemberMethods() { return getActualType().getMemberMethods(); } @Override - public List getConstructors() { return _referencedType.getConstructors(); } + public List getConstructors() { return getActualType().getConstructors(); } /* /********************************************************************** diff --git a/src/main/java/com/fasterxml/classmate/util/ClassStack.java b/src/main/java/com/fasterxml/classmate/util/ClassStack.java index 57bf73b..9a39afe 100644 --- a/src/main/java/com/fasterxml/classmate/util/ClassStack.java +++ b/src/main/java/com/fasterxml/classmate/util/ClassStack.java @@ -1,6 +1,9 @@ package com.fasterxml.classmate.util; import java.util.ArrayList; +import java.util.HashMap; +import java.util.function.Function; +import java.util.function.Supplier; import com.fasterxml.classmate.ResolvedType; import com.fasterxml.classmate.types.ResolvedRecursiveType; @@ -16,6 +19,40 @@ public final class ClassStack private ArrayList _selfRefs; + /** + * Root of the stack, used for storing state shared by all frames. + * + * @since 1.8 + */ + private final ClassStack _root; + + /** + * Whether types constructed within this frame (as part of the type it represents) + * contain self-references to types outside of it (that is, to types represented by + * enclosing frames): if so, the type is only valid within this resolution, so it + * must not be cached. Never true for the root frame. + * + * @since 1.8 + */ + private boolean _hasOuterReferences; + + /** + * Whether type parameters of the type this frame represents are being resolved + * to their bounds (to avoid infinite recursion for raw self-references) + * + * @since 1.8 + */ + private boolean _resolvingBounds; + + /** + * Incomplete types (see {@link ResolvedType}) constructed during this resolution, + * by key (shared by all frames, so only used via root): reused within this resolution + * (since self-references they contain are all resolved by its end), but not cached. + * + * @since 1.8 + */ + private HashMap _incompleteTypes; + public ClassStack(Class rootType) { this(null, rootType); } @@ -23,6 +60,7 @@ public ClassStack(Class rootType) { private ClassStack(ClassStack parent, Class curr) { _parent = parent; _current = curr; + _root = (parent == null) ? this : parent._root; } /** @@ -49,16 +87,45 @@ public void addSelfReference(ResolvedRecursiveType ref) * Method called when type that this stack frame represents is * fully resolved, allowing self-references to be completed * (if there are any) + * + * @deprecated Since 1.8 use {@link #resolveSelfReferences(ResolvedType, Function)} */ + @Deprecated public void resolveSelfReferences(ResolvedType resolved) + { + resolveSelfReferences(resolved, null); + } + + /** + * Method called when type that this stack frame represents is + * fully resolved, allowing self-references to be completed + * (if there are any). Self-references that represent a type different from the + * resolved one (like ones with different type bindings) get the actual type they + * represent from supplier given by function (null if self-reference represents the + * resolved type), if any (see {@link ResolvedRecursiveType#setReference(ResolvedType, Supplier)}). + * + * @since 1.8 + */ + public void resolveSelfReferences(ResolvedType resolved, + Function> actualTypes) { if (_selfRefs != null) { for (ResolvedRecursiveType ref : _selfRefs) { - ref.setReference(resolved); + ref.setReference(resolved, (actualTypes == null) ? null : actualTypes.apply(ref)); } } } + /** + * @return True if there are self-references to the type this frame represents + * (see {@link #addSelfReference}) + * + * @since 1.8 + */ + public boolean hasSelfReferences() { + return (_selfRefs != null); + } + public ClassStack find(Class cls) { if (_current == cls) return this; @@ -69,4 +136,92 @@ public ClassStack find(Class cls) } return null; } + + /* + /********************************************************************** + /* Tracking of incomplete types [classmate#128] + /********************************************************************** + */ + + /** + * Method called (on the innermost frame) when a self-reference to type represented + * by given (enclosing) frame has been created: types represented by frames in between + * (including this one) then contain self-references to types outside of them. + * + * @since 1.8 + */ + public void selfReferenceCreated(ResolvedRecursiveType ref, ClassStack target) + { + target.addSelfReference(ref); + for (ClassStack frame = this; frame != target; frame = frame._parent) { + frame._hasOuterReferences = true; + } + } + + /** + * @return True if types constructed within this frame contain self-references + * to types outside of it (see {@link #selfReferenceCreated}) + * + * @since 1.8 + */ + public boolean hasOuterReferences() { + return _hasOuterReferences; + } + + /** + * Method for finding incomplete type with given key constructed earlier during this + * resolution, if any. Since it may contain self-references to any types being + * resolved, types represented by this frame and enclosing frames (except for the + * root) are considered to contain self-references to types outside of them. + * + * @return Incomplete type to reuse, if any; {@code null} if none + * + * @since 1.8 + */ + public ResolvedType findIncomplete(ResolvedTypeKey key) + { + if ((key == null) || (_root._incompleteTypes == null)) { + return null; + } + ResolvedType type = _root._incompleteTypes.get(key); + if (type != null) { + for (ClassStack frame = this; frame != _root; frame = frame._parent) { + frame._hasOuterReferences = true; + } + } + return type; + } + + /** + * Method for registering incomplete type constructed during this resolution, + * so that it may be reused (see {@link #findIncomplete}). + * + * @since 1.8 + */ + public void addIncomplete(ResolvedTypeKey key, ResolvedType type) + { + if (key != null) { + if (_root._incompleteTypes == null) { + _root._incompleteTypes = new HashMap(); + } + _root._incompleteTypes.put(key, type); + } + } + + /** + * Accessor for checking whether type parameters of the type this frame represents + * are being resolved to their bounds. + * + * @since 1.8 + */ + public boolean isResolvingBounds() { + return _resolvingBounds; + } + + /** + * @since 1.8 + */ + public void setResolvingBounds(boolean state) { + _resolvingBounds = state; + } } diff --git a/src/test/java/com/fasterxml/classmate/TypeResolver128Test.java b/src/test/java/com/fasterxml/classmate/TypeResolver128Test.java new file mode 100644 index 0000000..9fb9ae5 --- /dev/null +++ b/src/test/java/com/fasterxml/classmate/TypeResolver128Test.java @@ -0,0 +1,616 @@ +package com.fasterxml.classmate; + +import java.util.*; +import java.util.function.Supplier; + +import com.fasterxml.classmate.members.ResolvedField; +import com.fasterxml.classmate.types.ResolvedRecursiveType; + +/** + * Tests for [classmate#128]: caching of types with self-references via supertypes, + * and equality of self-references with fully resolved types. + */ +public class TypeResolver128Test extends BaseTest +{ + static class Base { } + + // Self-references via supertypes + static class A extends Base { } + static class B extends Base { } + + static class Mid extends Base { } + static class Outer extends Mid { } + + static class ArrA extends Base { } + static class ArrB extends Base { } + + static class ListA extends Base> { } + static class ListB extends Base { } + + static class SelfComparable implements Comparable, Supplier { + @Override + public int compareTo(SelfComparable o) { return 0; } + @Override + public String get() { return null; } + } + + // Self-references nested within other types + static class LNode extends Base> { } + + static class N extends Base>[]> { + public T value; + } + + // Self-references via supertypes, escaping resolution via members, subtypes + static class FieldBase { + public List values; + public T[] array; + } + static class FA extends FieldBase { } + static class FB extends FieldBase { } + static class FSub extends FieldBase { } + + static class Pair { } + static class Dup extends Pair { } + enum Color { RED } + + // (note: type parameters must not be named like classes `A`, `B` declared here: + // javac 8 would resolve them as those classes in `extends` clause) + static class SubPair extends Pair { } + + static class X extends Base { } + static class Y extends Base { } + static class Z extends Pair> { } + + // Cycle of 3 types via type parameters + static class C1 extends Pair { } + static class C2 extends Pair { } + static class C3 extends Pair { } + + // Many types referring to each other: incomplete types must be reused + // within resolution (to avoid exponential resolution time) + static class Base10 { } + static class T0 extends Base10 { } + static class T1 extends Base10 { } + static class T2 extends Base10 { } + static class T3 extends Base10 { } + static class T4 extends Base10 { } + static class T5 extends Base10 { } + static class T6 extends Base10 { } + static class T7 extends Base10 { } + static class T8 extends Base10 { } + static class T9 extends Base10 { } + + // Raw self-reference to generic type + @SuppressWarnings("rawtypes") + static class GMid extends Base { + public T value; + } + static class GOuter extends GMid { } + + // Incomplete type nested along with self-reference + static class HBase { + public T value; + } + static class H extends HBase> { } + static class I extends Base { } + + // Self-reference to enclosing type within bound + static class BMid { } + @SuppressWarnings("rawtypes") + static class BOuter> { } + + // Raw self-reference at the bottom of deeply nested shared type parameters + @SuppressWarnings("rawtypes") + static class L0 extends Base { } + static class L1 extends L0> { } + static class L2 extends L1> { } + static class L3 extends L2> { } + static class L4 extends L3> { } + static class L5 extends L4> { } + static class L6 extends L5> { } + static class L7 extends L6> { } + static class L8 extends L7> { } + static class L9 extends L8> { } + static class L10 extends L9> { } + static class L11 extends L10> { } + static class L12 extends L11> { } + static class L13 extends L12> { } + static class L14 extends L13> { } + static class L15 extends L14> { } + static class L16 extends L15> { } + static class L17 extends L16> { } + static class L18 extends L17> { } + static class L19 extends L18> { } + static class L20 extends L19> { } + static class L21 extends L20> { } + static class L22 extends L21> { } + static class L23 extends L22> { } + static class L24 extends L23> { } + static class L25 extends L24> { } + static class L26 extends L25> { } + static class L27 extends L26> { } + static class L28 extends L27> { } + static class L29 extends L28> { } + static class L30 extends L29> { } + + // Self-reference nested within bound of the type (with different bindings) + static class W>> { + public T value; + } + + // Self-reference as type parameter (with different bindings), not F-bounded + static class WD> { + public A a; + } + + // [classmate#132]: members via self-referential interface + interface Top { void top(); } + interface BaseI extends Top { X get(); } + interface MidI extends BaseI { } + interface OuterI extends MidI { } + + // Self-reference as main type for member resolution + static class SB { + public int base; + } + static class SR> extends SB { + public T x; + } + + // Bound with own type variables, but not in order + static class PP, U> { + public U u; + } + + // Incomplete F-bounded type, self-reference nested in bound + @SuppressWarnings("rawtypes") + static class NOuter extends HBase { } + static class NFB>> extends HBase { } + + // Incomplete F-bounded type + @SuppressWarnings("rawtypes") + static class FBOuter extends HBase { } + static class FBounded> extends HBase { } + + // Raw self-reference with raw bound + @SuppressWarnings("rawtypes") + static class RawBound extends Base { } + + /* + /********************************************************************** + /* Caching + /********************************************************************** + */ + + public void testSelfReferenceViaSupertypeNotCached() + { + TypeResolver resolver = new TypeResolver(); + resolver.resolve(A.class); + _verifyFullyResolved(resolver.resolve(B.class).getParentClass() + .getTypeParameters().get(0), A.class); + // but types themselves are cached + assertSame(resolver.resolve(A.class), resolver.resolve(A.class)); + assertSame(resolver.resolve(B.class), resolver.resolve(B.class)); + } + + public void testSelfReferenceViaSuperclassNotCached() + { + TypeResolver resolver = new TypeResolver(); + resolver.resolve(Outer.class); + _verifyFullyResolved(resolver.resolve(Mid.class).getParentClass() + .getTypeParameters().get(0), Outer.class); + } + + public void testSelfReferenceViaSupertypeInArrayNotCached() + { + TypeResolver resolver = new TypeResolver(); + resolver.resolve(ArrA.class); + _verifyFullyResolved(resolver.resolve(ArrB.class).getParentClass() + .getTypeParameters().get(0), ArrA.class); + ResolvedType elem = resolver.resolve(ArrB[].class).getArrayElementType(); + _verifyFullyResolved(elem.getParentClass().getTypeParameters().get(0), ArrA.class); + } + + public void testSelfReferenceViaSupertypeInTypeParameterNotCached() + { + TypeResolver resolver = new TypeResolver(); + resolver.resolve(ListA.class); + ResolvedType listOfB = resolver.resolve(List.class, ListB.class); + ResolvedType b = listOfB.getTypeParameters().get(0); + _verifyFullyResolved(b.getParentClass().getTypeParameters().get(0), ListA.class); + } + + // Types not containing self-references to types being resolved are still cached + public void testTypesWithoutSelfReferencesCached() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType type = resolver.resolve(SelfComparable.class); + ResolvedType supplier = type.findSupertype(Supplier.class); + assertSame(supplier, resolver.resolve(Supplier.class, String.class)); + assertSame(type, resolver.resolve(SelfComparable.class)); + } + + // Incomplete types obtained from resolved type must not be cached when used + // for resolving other types later on + public void testIncompleteTypeViaMembersNotCached() + { + TypeResolver resolver = new TypeResolver(); + ResolvedTypeWithMembers members = new MemberResolver(resolver) + .resolve(resolver.resolve(FA.class), null, null); + assertEquals(2, members.getMemberFields().length); + // member types themselves must not contain incomplete types either + for (ResolvedField field : members.getMemberFields()) { + ResolvedType type = field.getType(); + ResolvedType b = type.isArray() ? type.getArrayElementType() + : type.getTypeParameters().get(0); + _verifyFullyResolved(b.getParentClass().getTypeParameters().get(0), FA.class); + } + // and are cached + ResolvedTypeWithMembers members2 = new MemberResolver(resolver) + .resolve(resolver.resolve(FA.class), null, null); + assertSame(members.getMemberFields()[0].getType(), members2.getMemberFields()[0].getType()); + + ResolvedType listOfB = resolver.resolve(List.class, FB.class); + _verifyFullyResolved(listOfB.getTypeParameters().get(0).getParentClass() + .getTypeParameters().get(0), FA.class); + ResolvedType arrayOfB = resolver.resolve(FB[].class); + _verifyFullyResolved(arrayOfB.getArrayElementType().getParentClass() + .getTypeParameters().get(0), FA.class); + } + + public void testIncompleteTypeViaArrayTypeNotCached() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType b = resolver.resolve(FA.class).getParentClass().getTypeParameters().get(0); + resolver.arrayType(b); + _verifyFullyResolved(resolver.resolve(FB[].class).getArrayElementType() + .getParentClass().getTypeParameters().get(0), FA.class); + } + + public void testIncompleteTypeViaSubtypeNotCached() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType baseOfB = resolver.resolve(FA.class).getParentClass(); + ResolvedType sub = resolver.resolveSubtype(baseOfB, FSub.class); + // incomplete type itself is not included in subtype either + _verifyFullyResolved(sub.getTypeParameters().get(0).getParentClass() + .getTypeParameters().get(0), FA.class); + _verifyFullyResolved(resolver.resolve(FieldBase.class, FB.class).getTypeParameters() + .get(0).getParentClass().getTypeParameters().get(0), FA.class); + _verifyFullyResolved(resolver.resolve(FSub.class, FB.class).getTypeParameters() + .get(0).getParentClass().getTypeParameters().get(0), FA.class); + } + + public void testIncompleteReferencedTypeViaSubtypeNotCached() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType y = resolver.resolve(X.class).getParentClass().getTypeParameters().get(0); + ResolvedType z = y.getParentClass().getTypeParameters().get(0); + // `Pair>` with self-references to `X` and `Y` + ResolvedType pair = z.getParentClass(); + assertTrue(TypeResolver.isSelfReference(pair.getTypeParameters().get(0))); + resolver.resolveSubtype(pair, SubPair.class); + ResolvedType listOfY = resolver.resolve(List.class, Y.class); + ResolvedType z2 = listOfY.getTypeParameters().get(0).getParentClass() + .getTypeParameters().get(0); + _verifyFullyResolved(z2.getParentClass().getTypeParameters().get(0), X.class); + } + + // Subtyping types with self-references to incomplete types, cyclic via + // type parameters, must terminate and produce stand-alone types + public void testSubtypeWithCyclicIncompleteTypes() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType c2 = resolver.resolve(C1.class).getParentClass().getTypeParameters().get(0); + ResolvedType c3 = c2.getParentClass().getTypeParameters().get(0); + // `Pair` with self-references to both + ResolvedType pair = c3.getParentClass(); + assertTrue(TypeResolver.isSelfReference(pair.getTypeParameters().get(0))); + assertTrue(TypeResolver.isSelfReference(pair.getTypeParameters().get(1))); + ResolvedType sub = resolver.resolveSubtype(pair, SubPair.class); + assertSame(SubPair.class, sub.getErasedType()); + _verifyFullyResolved(sub.getTypeParameters().get(0), C1.class); + _verifyFullyResolved(sub.getTypeParameters().get(1), C2.class); + assertEquals(resolver.resolve(SubPair.class, C1.class, C2.class), sub); + } + + // Incomplete types must be completed by all `resolve()` methods + public void testIncompleteTypeResolvedDirectly() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType b = resolver.resolve(A.class).getParentClass().getTypeParameters().get(0); + ResolvedType direct = resolver.resolve(b); + assertNotSame(b, direct); + _verifyFullyResolved(direct.getParentClass().getTypeParameters().get(0), A.class); + assertSame(resolver.resolve(B.class), direct); + assertSame(direct, resolver.resolve(TypeBindings.emptyBindings(), b)); + } + + // Incomplete types nested within types with self-references must be completed too + public void testIncompleteTypeNestedWithSelfReferenceCompleted() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType h = resolver.resolve(H.class); + ResolvedTypeWithMembers members = new MemberResolver(resolver).resolve(h, null, null); + assertEquals(1, members.getMemberFields().length); + ResolvedType pair = members.getMemberFields()[0].getType(); + assertSame(Pair.class, pair.getErasedType()); + _verifyFullyResolved(pair.getTypeParameters().get(0).getParentClass() + .getTypeParameters().get(0), H.class); + _verifyFullyResolved(pair.getTypeParameters().get(1), H.class); + assertEquals(resolver.resolve(Pair.class, I.class, H.class), pair); + } + + // Self-references to enclosing types (within incomplete types) are valid as-is, + // so subtyping to the type itself must not change it + public void testSubtypeRetainsSelfReferenceToEnclosingType() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType outer = resolver.resolve(BOuter.class); + ResolvedType mid = outer.getTypeParameters().get(0); + assertTrue(TypeResolver.isSelfReference(mid.getTypeParameters().get(0))); + ResolvedType sub = resolver.resolveSubtype(outer, BOuter.class); + assertEquals(outer, sub); + assertEquals(sub, outer); + assertSame(sub, resolver.resolveSubtype(outer, BOuter.class)); + } + + public void testIncompleteTypesReusedWithinResolution() + { + TypeResolver resolver = new TypeResolver(); + List params = resolver.resolve(T0.class).getParentClass().getTypeParameters(); + assertTrue(TypeResolver.isSelfReference(params.get(0))); + // `T2` first resolved as part of `T1`, then reused + ResolvedType t2 = params.get(2); + assertSame(T2.class, t2.getErasedType()); + assertSame(t2, params.get(1).getParentClass().getTypeParameters().get(2)); + // but not cached + ResolvedType t2Direct = resolver.resolve(T2.class); + assertNotSame(t2, t2Direct); + assertTrue(TypeResolver.isSelfReference(t2Direct.getParentClass().getTypeParameters().get(2))); + assertSame(t2Direct, resolver.resolve(T2.class)); + } + + /* + /********************************************************************** + /* Equality + /********************************************************************** + */ + + public void testSelfReferenceInTypeParameterEquality() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType viaParent = resolver.resolve(LNode.class).getParentClass(); + ResolvedType direct = resolver.resolve(Base.class, + resolver.resolve(List.class, LNode.class)); + assertTrue(TypeResolver.isSelfReference(viaParent.getTypeParameters().get(0) + .getTypeParameters().get(0))); + _verifyEqual(direct, viaParent); + } + + public void testNestedSelfReferenceEquality() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType viaParent = resolver.resolve(N.class, String.class).getParentClass() + .getTypeParameters().get(0); + ResolvedType direct = resolver.arrayType(resolver.resolve(N.class, + resolver.resolve(N.class, String.class))); + assertTrue(TypeResolver.isSelfReference(viaParent.getArrayElementType())); + _verifyEqual(direct, viaParent); + + // but not equal to differently parameterized type + ResolvedType other = resolver.arrayType(resolver.resolve(N.class, + resolver.resolve(N.class, Integer.class))); + assertFalse(viaParent.equals(other)); + assertFalse(other.equals(viaParent)); + } + + public void testSelfReferenceResolvedForSubtype() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType viaParent = resolver.resolve(N.class, String.class).getParentClass(); + ResolvedType subtype = resolver.resolveSubtype(viaParent, N.class); + assertEquals(resolver.resolve(N.class, String.class), subtype); + } + + // Raw self-reference must refer to raw type, not to (differently parameterized) + // type being resolved + public void testRawSelfReferenceToGenericType() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType outer = resolver.resolve(GMid.class, String.class).getParentClass() + .getTypeParameters().get(0); + assertSame(GOuter.class, outer.getErasedType()); + ResolvedType parent = outer.getParentClass(); + assertEquals(resolver.resolve(GMid.class, Object.class), parent); + + ResolvedTypeWithMembers members = new MemberResolver(resolver).resolve(outer, null, null); + assertEquals(1, members.getMemberFields().length); + assertSame(Object.class, members.getMemberFields()[0].getType().getErasedType()); + } + + // Self-reference with nested bindings (like `N>` within `N`) must refer + // to type with those bindings + public void testNestedSelfReferenceReferencedType() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType selfRef = resolver.resolve(N.class, String.class).getParentClass() + .getTypeParameters().get(0).getArrayElementType(); + assertTrue(TypeResolver.isSelfReference(selfRef)); + ResolvedType nOfString = resolver.resolve(N.class, String.class); + // referenced type is the type being resolved (for backwards compatibility)... + assertEquals(nOfString, selfRef.getSelfReferencedType()); + // but actual type has own bindings + ResolvedType actual = ((ResolvedRecursiveType) selfRef).getActualType(); + assertEquals(resolver.resolve(N.class, nOfString), actual); + assertSame(actual, ((ResolvedRecursiveType) selfRef).getActualType()); + + // and members are resolved using own bindings too + ResolvedTypeWithMembers members = new MemberResolver(resolver).resolve(selfRef, null, null); + assertEquals(1, members.getMemberFields().length); + assertEquals(nOfString, members.getMemberFields()[0].getType()); + } + + // Checking for self-references must not take exponential time for shared types + public void testSelfReferenceWithSharedTypeParameters() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType type = resolver.resolve(L30.class, String.class); + ResolvedType l0 = type; + while (l0.getErasedType() != L0.class) { + l0 = l0.getParentClass(); + } + // raw self-reference (within `L0>`) represents raw `L0` + ResolvedType selfRef = l0.getParentClass().getTypeParameters().get(0); + assertTrue(TypeResolver.isSelfReference(selfRef)); + assertEquals(resolver.resolve(L0.class), ((ResolvedRecursiveType) selfRef).getActualType()); + } + + // Results of `resolve()` must be complete, so resolving again returns same type + public void testResolveWithSelfReferenceParameterIdempotent() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType e = resolver.resolve(Enum.class).getTypeParameters().get(0); + assertTrue(TypeResolver.isSelfReference(e)); + ResolvedType listOfE = resolver.resolve(List.class, e); + assertSame(listOfE, resolver.resolve(listOfE)); + // (not cached, since it contains self-reference, but equal) + assertEquals(listOfE, resolver.resolve(List.class, e)); + } + + // Self-reference (in raw `Enum`) must be merged with compatible type as the type + // it represents + public void testSubtypeMergingSelfReference() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType rawEnum = resolver.resolve(Enum.class); + assertTrue(TypeResolver.isSelfReference(rawEnum.getTypeParameters().get(0))); + ResolvedType enumOfColor = resolver.resolve(Enum.class, + resolver.resolve(Enum.class, Color.class)); + ResolvedType sub = resolver.resolveSubtype( + resolver.resolve(Pair.class, rawEnum, enumOfColor), Dup.class); + assertEquals(resolver.resolve(Dup.class, enumOfColor), sub); + } + + // Self-reference nested within bounds of a type represents type with own bindings + public void testNestedSelfReferenceInBound() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType w = resolver.resolve(W.class); + ResolvedTypeWithMembers members = new MemberResolver(resolver).resolve(w, null, null); + assertEquals(1, members.getMemberFields().length); + ResolvedType expW = resolver.resolve(W.class, String.class, Object.class); + assertEquals(resolver.resolve(List.class, expW), members.getMemberFields()[0].getType()); + } + + // Self-reference as type parameter represents the type itself only if bound to it + // (like `E` in `Enum>`), not otherwise + public void testSelfReferenceTypeParameterNotBoundToSelf() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType param = resolver.resolve(WD.class).getTypeParameters().get(1); + assertTrue(TypeResolver.isSelfReference(param)); + ResolvedType exp = resolver.resolve(WD.class, String.class, Object.class); + assertEquals(exp, ((ResolvedRecursiveType) param).getActualType()); + assertEquals(exp, resolver.resolveSubtype(param, WD.class)); + + ResolvedTypeWithMembers members = new MemberResolver(resolver).resolve(param, null, null); + assertEquals(1, members.getMemberFields().length); + assertSame(String.class, members.getMemberFields()[0].getType().getErasedType()); + } + + // [classmate#132]: members inherited via self-referential interface (of type only + // valid within its resolution context) must not be lost + public void testMembersViaSelfReferentialInterface() + { + TypeResolver resolver = new TypeResolver(); + MemberResolver mr = new MemberResolver(resolver); + ResolvedType outerInMid = resolver.resolve(MidI.class, String.class) + .getImplementedInterfaces().get(0).getTypeParameters().get(0); + assertSame(OuterI.class, outerInMid.getErasedType()); + assertEquals(2, mr.resolve(outerInMid, null, null).getMemberMethods().length); + assertEquals(2, mr.resolve(resolver.resolve(OuterI.class), null, null).getMemberMethods().length); + } + + // Members of self-reference include inherited ones + public void testMembersOfSelfReference() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType selfRef = resolver.resolve(SR.class).getTypeParameters().get(0); + assertTrue(TypeResolver.isSelfReference(selfRef)); + ResolvedTypeWithMembers members = new MemberResolver(resolver).resolve(selfRef, null, null); + assertEquals(2, members.getMemberFields().length); + } + + // Self-reference represents the type itself only if bound to it with own type + // variables in order (`PP` means `PP` with `U = T`, not raw `PP`) + public void testSelfReferenceWithReorderedBound() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType raw = resolver.resolve(PP.class); + ResolvedType selfRef = raw.getTypeParameters().get(0); + assertTrue(TypeResolver.isSelfReference(selfRef)); + ResolvedType actual = ((ResolvedRecursiveType) selfRef).getActualType(); + assertNotSame(raw, actual); + assertEquals(selfRef.getTypeParameters(), actual.getTypeParameters()); + } + + // Incomplete raw F-bounded type with nested self-reference is completed too + // (nested self-reference representing type with own bindings) + public void testIncompleteNestedFBoundedTypeCompleted() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType b = resolver.resolve(NOuter.class).getParentClass().getTypeParameters().get(0); + assertSame(NFB.class, b.getErasedType()); + assertTrue(b._isIncomplete()); + ResolvedType completed = resolver.resolve(b); + assertFalse(completed._isIncomplete()); + assertEquals(resolver.resolve(NFB.class), completed); + assertSame(completed, resolver.resolve(b)); + } + + // Incomplete raw F-bounded type is equal to (and replaced by) stand-alone raw type + public void testIncompleteFBoundedTypeCompleted() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType b = resolver.resolve(FBOuter.class).getParentClass().getTypeParameters().get(0); + assertSame(FBounded.class, b.getErasedType()); + ResolvedType direct = resolver.resolve(FBounded.class); + assertEquals(direct, b); + assertSame(direct, resolver.resolve(b)); + _verifyFullyResolved(resolver.resolve(b).getParentClass().getTypeParameters().get(0), FBOuter.class); + } + + public void testRawSelfReferenceWithRawBound() + { + TypeResolver resolver = new TypeResolver(); + ResolvedType type = resolver.resolve(RawBound.class); + ResolvedType param = type.getParentClass().getTypeParameters().get(0); + assertTrue(TypeResolver.isSelfReference(param)); + assertSame(RawBound.class, param.getErasedType()); + assertEquals(1, param.getTypeParameters().size()); + } + + /* + /********************************************************************** + /* Helper methods + /********************************************************************** + */ + + private void _verifyFullyResolved(ResolvedType type, Class expType) + { + assertSame(expType, type.getErasedType()); + assertFalse(TypeResolver.isSelfReference(type)); + assertNotNull(type.getParentClass()); + assertSame(expType.getSuperclass(), type.getParentClass().getErasedType()); + } + + private void _verifyEqual(ResolvedType t1, ResolvedType t2) + { + assertEquals(t1, t2); + assertEquals(t2, t1); + assertEquals(t1.hashCode(), t2.hashCode()); + assertEquals(1, new HashSet(Arrays.asList(t1, t2)).size()); + } +} diff --git a/src/test/java/com/fasterxml/classmate/types/ResolvedArrayTypeTest.java b/src/test/java/com/fasterxml/classmate/types/ResolvedArrayTypeTest.java index 21b581c..80e124a 100644 --- a/src/test/java/com/fasterxml/classmate/types/ResolvedArrayTypeTest.java +++ b/src/test/java/com/fasterxml/classmate/types/ResolvedArrayTypeTest.java @@ -103,24 +103,29 @@ public void multiDimArrayOfSelfReferenceNotCached() { assertEquals(Base.class, elem.getParentClass().getErasedType()); } - // Raw self-reference has no bindings, so it must not be equal to any parameterization - // (would break transitivity, and with it Sets/Maps); known limitation: also not equal - // to fully resolved raw type (`GNode[]`) + // Raw self-reference must not be equal to any parameterization (would break + // transitivity, and with it Sets/Maps); but [classmate#128] it is equal to fully + // resolved raw type (`GNode[]`) @Test public void arrayOfRawRecursiveTypeEqualityIsTransitive() { TypeResolver resolver = new TypeResolver(); - ResolvedType viaParent = resolver.resolve(GNode.class).getParentClass() + ResolvedType viaParent = resolver.resolve(GNode.class, String.class).getParentClass() .getTypeParameters().get(0); ResolvedType strings = resolver.arrayType(resolver.resolve(GNode.class, String.class)); ResolvedType integers = resolver.arrayType(resolver.resolve(GNode.class, Integer.class)); + ResolvedType raw = resolver.arrayType(resolver.resolve(GNode.class)); assertTrue(TypeResolver.isSelfReference(viaParent.getArrayElementType())); assertFalse(viaParent.equals(strings)); assertFalse(viaParent.equals(integers)); + assertEquals(raw, viaParent); + assertEquals(viaParent, raw); + assertEquals(raw.hashCode(), viaParent.hashCode()); Set types = new HashSet(); types.add(viaParent); types.add(strings); types.add(integers); + types.add(raw); assertEquals(3, types.size()); }