From 3b7798338f8a3b494179622013cb26f75c5320fc Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Fri, 13 Mar 2026 03:53:05 +0200 Subject: [PATCH] Proper TypeInfo.typeParam overload that is much needed + IntersectionTypeInfo for type parameter > --- .../script/interpreter/ScriptDocument.java | 9 +- .../interpreter/type/GenericContext.java | 17 +- .../type/IntersectionTypeInfo.java | 160 ++++++++++++++++++ .../script/interpreter/type/TypeInfo.java | 62 +++++++ 4 files changed, 229 insertions(+), 19 deletions(-) create mode 100644 src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/IntersectionTypeInfo.java diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java index a6ea09ac3..a602fce6a 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java @@ -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) 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; } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/GenericContext.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/GenericContext.java index e657e4c07..394000384 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/GenericContext.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/GenericContext.java @@ -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 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); } } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/IntersectionTypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/IntersectionTypeInfo.java new file mode 100644 index 000000000..74e039559 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/IntersectionTypeInfo.java @@ -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 >} creates an intersection + * of Number and Comparable. + * + *

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.

+ * + *

This is used by {@link GenericContext} when building bound fallbacks for type + * parameters with additional interface bounds.

+ */ +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 additionalBounds; + + private IntersectionTypeInfo(TypeInfo primaryBound, List 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 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 getAllMethodOverloads(String methodName) { + Set 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 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() + "}"; + } +} diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java index 2e1fa9aa5..1c5e36967 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java @@ -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 getAllMethods() { + List result = new ArrayList<>(super.getAllMethods()); + for (TypeInfo bound : intersection.getAdditionalBounds()) + result.addAll(bound.getAllMethods()); + + return result; + } + @Override + public List getAllFields() { + List 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. + * + *

If the TypeParamInfo has additional bounds (e.g., {@code T extends Number & Comparable}), + * 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.

+ * + * @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 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; }