Proper TypeInfo.typeParam overload that is much needed + IntersectionTypeInfo for type parameter <T extends X & Interface1 & Interface2<T>>

This commit is contained in:
bigguy345 committed 2026-03-13 03:53:05 +02:00
1 parent eae5a08932
commit 3b7798338f
4 files changed
+229 -19

No files matched your search

@@ -4343,14 +4343,7 @@ public class ScriptDocument {
// Check if the name is a declared type parameter on this class (e.g., E in class Box<E>)
TypeParamInfo typeParam = enclosing.getDeclaredTypeParam(baseTypeName);
if (typeParam != null) {
TypeInfo boundType = typeParam.getBoundTypeInfo();
if (boundType == null) {
typeParam.resolveBoundType();
boundType = typeParam.getBoundTypeInfo();
}
TypeInfo result = (boundType != null && boundType.isResolved())
? TypeInfo.typeParameter(baseTypeName, boundType)
: TypeInfo.typeParameter(baseTypeName);
TypeInfo result = TypeInfo.typeParameter(baseTypeName, typeParam);
for (int i = 0; i < arrayDims; i++) result = TypeInfo.arrayOf(result);
return result;
}
@@ -73,8 +73,9 @@ public final class GenericContext {
if (declared == null) continue;
String name = declared.getName();
if (name == null || name.isEmpty()) continue;
TypeInfo bound = declared.getBoundTypeInfo();
bounds.put(name, (bound != null && bound.isResolved()) ? bound : TypeInfo.OBJECT);
TypeInfo tp = TypeInfo.typeParameter(name, declared);
TypeInfo effectiveBound = tp.getBoundType();
bounds.put(name, effectiveBound != null ? effectiveBound : TypeInfo.OBJECT);
}
return new GenericContext(applied, bounds);
@@ -125,15 +126,9 @@ public final class GenericContext {
if (declared == null) continue;
String name = declared.getName();
if (name == null || name.isEmpty()) continue;
TypeInfo bound = declared.getBoundTypeInfo();
TypeInfo effectiveBound = (bound != null && bound.isResolved()) ? bound : TypeInfo.OBJECT;
// TODO: Goatee not sure what this was for
// List<TypeInfo> additionalBounds = declared.getAdditionalBoundTypes();
// if (additionalBounds != null && !additionalBounds.isEmpty()) {
// effectiveBound = IntersectionTypeInfo.of(effectiveBound, additionalBounds);
// }
bounds.put(name, effectiveBound);
TypeInfo tp = TypeInfo.typeParameter(name, declared);
TypeInfo effectiveBound = tp.getBoundType();
bounds.put(name, effectiveBound != null ? effectiveBound : TypeInfo.OBJECT);
}
}
@@ -0,0 +1,160 @@
package noppes.npcs.client.gui.util.script.interpreter.type;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Represents an intersection type from multiple bounds on a type parameter.
* For example, {@code <T extends Number & Comparable<T>>} creates an intersection
* of Number and Comparable.
*
* <p>The primary bound determines the display name and main type identity.
* Member lookup (hasMethod, getMethodInfo, hasField, getFieldInfo, getAllMethodOverloads)
* delegates to ALL constituent types, so methods from any bound are accessible.</p>
*
* <p>This is used by {@link GenericContext} when building bound fallbacks for type
* parameters with additional interface bounds.</p>
*/
public class IntersectionTypeInfo extends TypeInfo {
/** The primary (first) bound type — used for display and as the "main" type. */
private final TypeInfo primaryBound;
/** Additional interface bounds (everything after {@code &}). */
private final List<TypeInfo> additionalBounds;
private IntersectionTypeInfo(TypeInfo primaryBound, List<TypeInfo> additionalBounds) {
super(primaryBound.getSimpleName(), primaryBound.getFullName(), "",
primaryBound.getKind(), primaryBound.getJavaClass(), true, null, true);
this.primaryBound = primaryBound;
this.additionalBounds = additionalBounds;
}
/**
* Create an intersection type from a primary bound and additional interface bounds.
* If there are no additional bounds, returns the primary bound directly (no wrapper).
*
* @param primary the primary (first) bound type
* @param additional the additional interface bounds (may be empty)
* @return an IntersectionTypeInfo if there are additional bounds, otherwise the primary itself
*/
public static TypeInfo of(TypeInfo primary, List<TypeInfo> additional) {
if (additional == null || additional.isEmpty()) {
return primary;
}
return new IntersectionTypeInfo(primary, new ArrayList<>(additional));
}
@Override
public boolean hasMethod(String methodName) {
if (primaryBound.hasMethod(methodName)) return true;
for (TypeInfo bound : additionalBounds) {
if (bound.hasMethod(methodName)) return true;
}
return false;
}
@Override
public boolean hasMethod(String methodName, int paramCount) {
if (primaryBound.hasMethod(methodName, paramCount)) return true;
for (TypeInfo bound : additionalBounds) {
if (bound.hasMethod(methodName, paramCount)) return true;
}
return false;
}
@Override
public MethodInfo getMethodInfo(String methodName) {
MethodInfo m = primaryBound.getMethodInfo(methodName);
if (m != null) return m;
for (TypeInfo bound : additionalBounds) {
m = bound.getMethodInfo(methodName);
if (m != null) return m;
}
return null;
}
@Override
public List<MethodInfo> getAllMethodOverloads(String methodName) {
Set<MethodInfo> seen = new LinkedHashSet<>();
seen.addAll(primaryBound.getAllMethodOverloads(methodName));
for (TypeInfo bound : additionalBounds) {
seen.addAll(bound.getAllMethodOverloads(methodName));
}
return new ArrayList<>(seen);
}
@Override
public MethodInfo getBestMethodOverload(String methodName, TypeInfo[] argTypes) {
MethodInfo m = primaryBound.getBestMethodOverload(methodName, argTypes);
if (m != null) return m;
for (TypeInfo bound : additionalBounds) {
m = bound.getBestMethodOverload(methodName, argTypes);
if (m != null) return m;
}
return null;
}
@Override
public MethodInfo getBestMethodOverload(String methodName, TypeInfo expectedReturnType) {
MethodInfo m = primaryBound.getBestMethodOverload(methodName, expectedReturnType);
if (m != null) return m;
for (TypeInfo bound : additionalBounds) {
m = bound.getBestMethodOverload(methodName, expectedReturnType);
if (m != null) return m;
}
return null;
}
@Override
public boolean hasField(String fieldName) {
if (primaryBound.hasField(fieldName)) return true;
for (TypeInfo bound : additionalBounds) {
if (bound.hasField(fieldName)) return true;
}
return false;
}
@Override
public FieldInfo getFieldInfo(String fieldName) {
FieldInfo f = primaryBound.getFieldInfo(fieldName);
if (f != null) return f;
for (TypeInfo bound : additionalBounds) {
f = bound.getFieldInfo(fieldName);
if (f != null) return f;
}
return null;
}
public TypeInfo getPrimaryBound() {
return primaryBound;
}
public List<TypeInfo> getAdditionalBounds() {
return new ArrayList<>(additionalBounds);
}
@Override
public String getDisplayName() {
StringBuilder sb = new StringBuilder(primaryBound.getDisplayName());
for (TypeInfo bound : additionalBounds) {
sb.append(" & ").append(bound.getDisplayName());
}
return sb.toString();
}
@Override
public boolean isResolved() {
return true;
}
@Override
public String toString() {
return "IntersectionTypeInfo{" + getDisplayName() + "}";
}
}
@@ -194,12 +194,74 @@ public class TypeInfo {
public static TypeInfo typeParameter(String paramName, TypeInfo boundType) {
Class<?> javaClass = (boundType != null && boundType.javaClass != null) ? boundType.javaClass : Object.class;
Kind kind = (boundType != null) ? boundType.kind : Kind.CLASS;
if (boundType instanceof IntersectionTypeInfo) {
final IntersectionTypeInfo intersection = (IntersectionTypeInfo) boundType;
TypeInfo ti = new TypeInfo(paramName, paramName, "", kind, javaClass, true, null) {
@Override
public List<MethodInfo> getAllMethods() {
List<MethodInfo> result = new ArrayList<>(super.getAllMethods());
for (TypeInfo bound : intersection.getAdditionalBounds())
result.addAll(bound.getAllMethods());
return result;
}
@Override
public List<FieldInfo> getAllFields() {
List<FieldInfo> result = new ArrayList<>(super.getAllFields());
for (TypeInfo bound : intersection.getAdditionalBounds())
result.addAll(bound.getAllFields());
return result;
}
};
ti.typeParameterName = paramName;
ti.boundType = boundType;
return ti;
}
TypeInfo ti = new TypeInfo(paramName, paramName, "", kind, javaClass, true, null);
ti.typeParameterName = paramName;
ti.boundType = boundType;
return ti;
}
/**
* Create a TypeInfo representing a bounded type parameter from a {@link TypeParamInfo}.
* This is the single point of control for creating type parameter TypeInfos with bound handling.
*
* <p>If the TypeParamInfo has additional bounds (e.g., {@code T extends Number & Comparable<T>}),
* an {@link IntersectionTypeInfo} is created internally to represent the combined bound.
* This centralizes intersection creation so no caller needs to call {@code IntersectionTypeInfo.of()} directly.</p>
*
* @param paramName the type parameter name (e.g., "T")
* @param typeParam the TypeParamInfo containing bound information
* @return a TypeInfo tagged as a type parameter with the effective bound
*/
public static TypeInfo typeParameter(String paramName, TypeParamInfo typeParam) {
if (typeParam == null) {
return typeParameter(paramName);
}
TypeInfo primaryBound = typeParam.getBoundTypeInfo();
if (primaryBound == null) {
typeParam.resolveBoundType();
primaryBound = typeParam.getBoundTypeInfo();
}
TypeInfo effectiveBound;
if (primaryBound != null && primaryBound.isResolved()) {
List<TypeInfo> additionalBounds = typeParam.getAdditionalBoundTypes();
if (additionalBounds != null && !additionalBounds.isEmpty()) {
effectiveBound = IntersectionTypeInfo.of(primaryBound, additionalBounds);
} else {
effectiveBound = primaryBound;
}
} else {
effectiveBound = null;
}
return typeParameter(paramName, effectiveBound);
}
public boolean isTypeParameter() {
return typeParameterName != null;
}