From 691a7fb2c78639fad13cd9218c8f4851b2f340f5 Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Fri, 13 Mar 2026 17:47:29 +0200 Subject: [PATCH] unified TypeInfo API access to it uses exposed TypeInfo members instead of inner raw Class reflection calls --- .../autocomplete/JSAutocompleteProvider.java | 176 +++++---- .../JavaAutocompleteProvider.java | 168 ++------ .../script/interpreter/ScriptDocument.java | 35 +- .../interpreter/type/ScriptTypeInfo.java | 68 +++- .../script/interpreter/type/TypeInfo.java | 371 ++++++++++++++---- 5 files changed, 508 insertions(+), 310 deletions(-) diff --git a/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JSAutocompleteProvider.java b/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JSAutocompleteProvider.java index b342a5384..e69102b59 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JSAutocompleteProvider.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JSAutocompleteProvider.java @@ -1,15 +1,16 @@ package noppes.npcs.client.gui.util.script.autocomplete; import noppes.npcs.client.gui.util.script.interpreter.js_parser.*; +import noppes.npcs.client.gui.util.script.interpreter.field.EnumConstantInfo; import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo; import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo; import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.*; +import noppes.npcs.client.gui.util.script.interpreter.type.ScriptTypeInfo; import noppes.npcs.client.gui.util.script.interpreter.type.TypeChecker; import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo; +import noppes.npcs.client.gui.util.script.interpreter.type.TypeResolver; import noppes.npcs.config.ConfigScript; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; @@ -37,6 +38,15 @@ public class JSAutocompleteProvider extends JavaAutocompleteProvider { /** * Add suggestions for member access (after dot). * Uses ScriptDocument's unified type resolution system. + * + * Fixes applied vs original implementation: + * - Single getMemberAccessResolvePosition() call (was duplicated) + * - Single getMergeRegistryType() call (was duplicated) + * - Shared addedMethods/addedFields sets across ALL sources for cross-source deduplication + * - Unified nested type lookup via getAllNestedTypes() instead of raw reflection + * - Single Object method injection path (registry-based via fromJSMethod) + * - Name-based Object.class guard instead of fragile Class identity comparison + * - Logged SecurityException instead of silently swallowed */ protected void addMemberSuggestions(Context context, List items) { String receiverExpr = context.receiverExpression; @@ -44,113 +54,105 @@ public class JSAutocompleteProvider extends JavaAutocompleteProvider { return; } + // Resolve position once — used for both type resolution and static context detection + int resolvePos = getMemberAccessResolvePosition(context); + // Use ScriptDocument's resolveExpressionType - handles both Java and JS - TypeInfo receiverType = document.resolveExpressionType(receiverExpr, getMemberAccessResolvePosition(context)); + TypeInfo receiverType = document.resolveExpressionType(receiverExpr, resolvePos); if (receiverType == null || !receiverType.isResolved()) { return; } - // Check for synthetic types first (e.g., Nashorn Java object, custom types) - SyntheticType syntheticType = - document.getTypeResolver().getSyntheticType(receiverType.getSimpleName()); + // --- JS-SPECIFIC: synthetic type early return --- + SyntheticType syntheticType = document.getTypeResolver().getSyntheticType(receiverType.getSimpleName()); if (syntheticType != null) { addSyntheticTypeSuggestions(syntheticType, items, context.methodsOnly); return; } - boolean isStaticContext = isStaticAccess(receiverExpr, getMemberAccessResolvePosition(context)); + boolean isStaticContext = isStaticAccess(receiverExpr, resolvePos); + // Resolve merge-registry type once — used for both method and field fallback Class javaClass = receiverType.getJavaClass(); - if (javaClass != null) { - Set addedMethods = new HashSet<>(); - for (Method method : javaClass.getMethods()) { - if (!Modifier.isPublic(method.getModifiers())) continue; - if (isStaticContext && !Modifier.isStatic(method.getModifiers())) continue; - String sig = method.getName() + "(" + method.getParameterCount() + ")"; - if (addedMethods.add(sig)) { - items.add(AutocompleteItem.fromMethod(MethodInfo.fromReflection(method, receiverType), context.methodsOnly)); - } + JSTypeInfo mergeRegistryType = (javaClass != null + && receiverType.getSyntheticMethods().isEmpty() + && receiverType.getSyntheticFields().isEmpty()) + ? getMergeRegistryType(javaClass) : null; + + // Shared deduplication sets — prevent the same method/field appearing from multiple sources + Set addedMethods = new HashSet<>(); + Set addedFields = new HashSet<>(); + + // ---- SHARED: unified method enumeration (TypeInfo.getAllMethods covers reflection + synthetics) ---- + for (MethodInfo method : receiverType.getAllMethods()) { + if (isStaticContext && !Modifier.isStatic(method.getModifiers())) + continue; + String sig = method.getName() + "(" + method.getParameterCount() + ")"; + if (addedMethods.add(sig)) + items.add(AutocompleteItem.fromMethod(method, context.methodsOnly)); + } + + // --- JS-SPECIFIC: merge registry type fallback for methods --- + if (mergeRegistryType != null) { + addMethodsFromType(mergeRegistryType, receiverType, items, addedMethods, context.methodsOnly, + isStaticContext); + } + + if (!context.methodsOnly) { + // ---- SHARED: unified field enumeration with deduplication ---- + for (FieldInfo field : receiverType.getAllFields()) { + if (isStaticContext && !Modifier.isStatic(field.getModifiers())) + continue; + if (addedFields.add(field.getName())) + items.add(AutocompleteItem.fromField(field)); } - List effectiveSyntheticMethods = receiverType.getSyntheticMethods(); - if (!effectiveSyntheticMethods.isEmpty()) { - for (MethodInfo method : effectiveSyntheticMethods) { - if (isStaticContext && !method.isStatic()) continue; - items.add(AutocompleteItem.fromMethod(method, context.methodsOnly)); - } - } else { - JSTypeInfo jsType = getMergeRegistryType(javaClass); - if (jsType != null) { - addMethodsFromType(jsType, receiverType, items, new HashSet<>(), context.methodsOnly, isStaticContext); - } + + // --- JS-SPECIFIC: merge registry type fallback for fields --- + if (mergeRegistryType != null) { + addFieldsFromType(mergeRegistryType, receiverType, items, addedFields, isStaticContext); } - if (!context.methodsOnly) { - for (Field field : javaClass.getFields()) { - if (!Modifier.isPublic(field.getModifiers())) continue; - if (isStaticContext && !Modifier.isStatic(field.getModifiers())) continue; - items.add(AutocompleteItem.fromField(FieldInfo.fromReflection(field, receiverType))); - } - if (!receiverType.getSyntheticFields().isEmpty()) { - for (FieldInfo field : receiverType.getSyntheticFields()) { - if (isStaticContext && !field.isStatic()) continue; - items.add(AutocompleteItem.fromField(field)); - } - } else { - JSTypeInfo jsType = getMergeRegistryType(javaClass); - if (jsType != null) { - addFieldsFromType(jsType, receiverType, items, new HashSet<>(), isStaticContext); - } - } + } + + // ---- SHARED: nested types (inner classes) in static context via getAllNestedTypes() ---- + if (isStaticContext && !context.methodsOnly) { + // ScriptTypeInfo.getAllNestedTypes() returns script inner classes directly; + // TypeInfo.getAllNestedTypes() uses reflection. No duplicate logic needed. + for (TypeInfo nestedType : receiverType.getAllNestedTypes()) { + items.add(AutocompleteItem.fromType(nestedType)); } - if (!isStaticContext && javaClass != Object.class) { - JSTypeInfo objJSType = registry.getType("Object"); - if (objJSType != null) { - for (JSMethodInfo m : objJSType.getMethods().values()) { - if (!m.isStatic()) { + } + + // Enum constants — polymorphic via TypeInfo.getEnumConstants() + if (!context.methodsOnly) { + for (EnumConstantInfo enumConstant : receiverType.getEnumConstants().values()) { + items.add(AutocompleteItem.fromField(enumConstant.getFieldInfo())); + } + } + + // --- JS-SPECIFIC: Object methods from JS registry (single unified path) --- + // Add Object.class JS methods for instance access, unless receiver IS Object itself. + // Uses name comparison instead of Class identity for robustness across classloaders. + if (!isStaticContext && (javaClass == null || !"java.lang.Object".equals(javaClass.getName()))) { + JSTypeInfo objJSType = registry.getType("Object"); + if (objJSType != null) { + for (JSMethodInfo m : objJSType.getMethods().values()) { + if (!m.isStatic()) { + String sig = m.getName() + "(" + m.getParameterCount() + ")"; + if (addedMethods.add(sig)) { items.add(AutocompleteItem.fromJSMethod(m, receiverType, 1, context.methodsOnly)); } } } } - return; } - Set syntheticMethodNames = new HashSet<>(); - for (MethodInfo method : receiverType.getSyntheticMethods()) { - syntheticMethodNames.add(method.getName()); - if (isStaticContext && !method.isStatic()) continue; - items.add(AutocompleteItem.fromMethod(method, context.methodsOnly)); - } - for (FieldInfo field : receiverType.getSyntheticFields()) { - if (!context.methodsOnly) { - if (isStaticContext && !field.isStatic()) continue; - if (syntheticMethodNames.contains(field.getName())) continue; - items.add(AutocompleteItem.fromField(field)); - } - } - - JSTypeInfo jsTypeInfo = receiverType.getJSTypeInfo(); - if (jsTypeInfo != null) { - addMethodsFromType(jsTypeInfo, receiverType, items, new HashSet<>(), context.methodsOnly, isStaticContext); - if (!context.methodsOnly && ConfigScript.ShowImplementationFieldsInAutocomplete) { - addFieldsFromType(jsTypeInfo, receiverType, items, new HashSet<>(), isStaticContext); - } - } - - if (!isStaticContext) { - Set addedObjMethods = new HashSet<>(); - for (Method m : Object.class.getMethods()) { - if (!Modifier.isPublic(m.getModifiers()) || Modifier.isStatic(m.getModifiers())) continue; - String sig = m.getName() + "(" + m.getParameterCount() + ")"; - if (addedObjMethods.add(sig)) { - items.add(AutocompleteItem.fromMethod(MethodInfo.fromReflection(m, receiverType), context.methodsOnly)); - } - } - JSTypeInfo objJSType = registry.getType("Object"); - if (objJSType != null) { - for (JSMethodInfo m : objJSType.getMethods().values()) { - if (!m.isStatic()) { - items.add(AutocompleteItem.fromJSMethod(m, receiverType, 1, context.methodsOnly)); - } + // --- JS-SPECIFIC: pure JS type fallback (javaClass == null, not ScriptTypeInfo) --- + if (javaClass == null && !(receiverType instanceof ScriptTypeInfo)) { + JSTypeInfo jsTypeInfo = receiverType.getJSTypeInfo(); + if (jsTypeInfo != null) { + addMethodsFromType(jsTypeInfo, receiverType, items, addedMethods, context.methodsOnly, isStaticContext); + if (!context.methodsOnly && ConfigScript.ShowImplementationFieldsInAutocomplete) { + addFieldsFromType(jsTypeInfo, receiverType, items, addedFields, isStaticContext); } } } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JavaAutocompleteProvider.java b/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JavaAutocompleteProvider.java index c268baf27..3aad528c3 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JavaAutocompleteProvider.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/autocomplete/JavaAutocompleteProvider.java @@ -10,8 +10,6 @@ import noppes.npcs.client.gui.util.script.interpreter.type.TypeChecker; import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo; import noppes.npcs.client.gui.util.script.interpreter.type.TypeResolver; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; @@ -100,92 +98,52 @@ public class JavaAutocompleteProvider implements AutocompleteProvider { if (receiverExpr == null || receiverExpr.isEmpty()) { return; } + + // Resolve position once — used for both type resolution and static context detection + int resolvePos = getMemberAccessResolvePosition(context); - // Resolve the type of the receiver expression - TypeInfo receiverType = document.resolveExpressionType(receiverExpr, getMemberAccessResolvePosition(context)); + TypeInfo receiverType = document.resolveExpressionType(receiverExpr, resolvePos); if (receiverType == null || !receiverType.isResolved()) { return; } - // Determine if this is a static context (accessing a class type) - boolean isStaticContext = isStaticAccess(receiverExpr, getMemberAccessResolvePosition(context)); + boolean isStaticContext = isStaticAccess(receiverExpr, resolvePos); - Class clazz = receiverType.getJavaClass(); - if (clazz == null) { - // Try ScriptTypeInfo - if (receiverType instanceof ScriptTypeInfo) { - addScriptTypeMembers((ScriptTypeInfo) receiverType, items, isStaticContext, context.methodsOnly); - } - return; - } - - // Add methods + // Shared deduplication sets across all member sources Set addedMethods = new HashSet<>(); - for (Method method : clazz.getMethods()) { - if (Modifier.isPublic(method.getModifiers())) { - // Filter by static context - if (isStaticContext && !Modifier.isStatic(method.getModifiers())) { - continue; - } - - String sig = method.getName() + "(" + method.getParameterCount() + ")"; - if (!addedMethods.contains(sig)) { - addedMethods.add(sig); - MethodInfo methodInfo = MethodInfo.fromReflection(method, receiverType); - items.add(AutocompleteItem.fromMethod(methodInfo, context.methodsOnly)); - } - } - } + Set addedFields = new HashSet<>(); - for (MethodInfo method : receiverType.getSyntheticMethods()) { - if (isStaticContext && !Modifier.isStatic(method.getModifiers())) - continue; - - items.add(AutocompleteItem.fromMethod(method, context.methodsOnly)); + // Unified method enumeration — works for Java types, ScriptTypeInfo, and intersection bounds + for (MethodInfo method : receiverType.getAllMethods()) { + if (isStaticContext && !Modifier.isStatic(method.getModifiers())) continue; + String sig = method.getName() + "(" + method.getParameterCount() + ")"; + if (addedMethods.add(sig)) + items.add(AutocompleteItem.fromMethod(method, context.methodsOnly)); } - // Add fields (skip if methodsOnly is true - but the filtering will be done in getSuggestions) if (!context.methodsOnly) { - for (Field field : clazz.getFields()) { - if (Modifier.isPublic(field.getModifiers())) { - // Filter by static context - if (isStaticContext && !Modifier.isStatic(field.getModifiers())) { - continue; - } - - FieldInfo fieldInfo = FieldInfo.fromReflection(field, receiverType); - items.add(AutocompleteItem.fromField(fieldInfo)); - } - } - - for (FieldInfo field : receiverType.getSyntheticFields()) { - if (isStaticContext && !Modifier.isStatic(field.getModifiers())) - continue; - - items.add(AutocompleteItem.fromField(field)); + // Unified field enumeration with deduplication + for (FieldInfo field : receiverType.getAllFields()) { + if (isStaticContext && !Modifier.isStatic(field.getModifiers())) continue; + if (addedFields.add(field.getName())) + items.add(AutocompleteItem.fromField(field)); } } - // Add nested types (inner classes/interfaces) in static context + // Nested types (inner classes) in static context via getAllNestedTypes() if (isStaticContext && !context.methodsOnly) { - try { - for (Class nested : clazz.getDeclaredClasses()) { - if (!Modifier.isPublic(nested.getModifiers())) { - continue; - } - // Convert $ notation to dot notation for resolution - String nestedFullName = nested.getName().replace('$', '.'); - TypeInfo nestedType = TypeResolver.getInstance().resolveFullName(nestedFullName); - if (nestedType != null && nestedType.isResolved()) { - items.add(AutocompleteItem.fromType(nestedType)); - } - } - } catch (SecurityException ignored) { - // Some runtimes may restrict getDeclaredClasses() + for (TypeInfo nestedType : receiverType.getAllNestedTypes()) { + items.add(AutocompleteItem.fromType(nestedType)); + } + } + + // Enum constants — polymorphic via TypeInfo.getEnumConstants() + if (!context.methodsOnly) { + for (EnumConstantInfo enumConstant : receiverType.getEnumConstants().values()) { + items.add(AutocompleteItem.fromField(enumConstant.getFieldInfo())); } } - } /** @@ -218,76 +176,6 @@ public class JavaAutocompleteProvider implements AutocompleteProvider { return Math.max(0, Math.min(context.prefixStart, context.text.length())); } - /** - * Add members from a script-defined type. - */ - protected void addScriptTypeMembers(ScriptTypeInfo scriptType, List items, boolean isStaticContext) { - addScriptTypeMembers(scriptType, items, isStaticContext, false); - } - - /** - * Add members from a script-defined type. - * @param scriptType The script type to get members from - * @param items The list to add items to - * @param isStaticContext Whether we're in a static context - * @param forMethodReference If true, only add methods with no parentheses in insert text - */ - protected void addScriptTypeMembers(ScriptTypeInfo scriptType, List items, boolean isStaticContext, boolean forMethodReference) { - // Add methods (getMethods returns Map>) - for (List overloads : scriptType.getMethods().values()) { - for (MethodInfo method : overloads) { - // Filter by static context - if (isStaticContext && !method.isStatic()) { - continue; - } - items.add(AutocompleteItem.fromMethod(method, forMethodReference)); - } - } - - // Add fields (skip if methodsOnly/forMethodReference is true) - if (!forMethodReference) { - // Add fields (getFields returns Map) - for (FieldInfo field : scriptType.getFields().values()) { - // Filter by static context - if (isStaticContext && !field.isStatic()) { - continue; - } - items.add(AutocompleteItem.fromField(field)); - } - - // Add enum constants (getEnumConstants returns Map) - for (EnumConstantInfo enumConstant : scriptType.getEnumConstants().values()) { - items.add(AutocompleteItem.fromField(enumConstant.getFieldInfo())); - } - - // Add nested types (inner classes/interfaces) in static context - if (isStaticContext) { - for (ScriptTypeInfo inner : scriptType.getInnerClasses()) { - items.add(AutocompleteItem.fromType(inner)); - } - } - } - - // Add parent class members - if (scriptType.hasSuperClass()) { - TypeInfo superType = scriptType.getSuperClass(); - if (superType != null && superType.getJavaClass() != null) { - Class superClazz = superType.getJavaClass(); - Set addedMethods = new HashSet<>(); - for (Method method : superClazz.getMethods()) { - if (Modifier.isPublic(method.getModifiers())) { - String sig = method.getName() + "(" + method.getParameterCount() + ")"; - if (!addedMethods.contains(sig)) { - addedMethods.add(sig); - MethodInfo methodInfo = MethodInfo.fromReflection(method, superType); - items.add(AutocompleteItem.fromMethod(methodInfo, forMethodReference)); - } - } - } - } - } - } - /** * Add suggestions based on current scope (not after a dot). */ 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 dfe41103d..fe8e3ac29 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 @@ -4308,6 +4308,37 @@ public class ScriptDocument { return resolveTypeAndTrackUsage(typeName); } + private TypeInfo substituteTypeParams(TypeInfo type, int position) { + if (type == null) + return null; + + if (!type.isResolved() && type.getSimpleName() != null + && !type.getSimpleName().contains(".")) { + TypeInfo sub = resolveType(type.getSimpleName(), position); + return (sub != null && sub.isResolved()) ? sub : type; + } + + if (type.isParameterized()) { + List args = type.getAppliedTypeArgs(); + if (args != null && !args.isEmpty()) { + List substituted = new ArrayList<>(args.size()); + boolean anyChanged = false; + for (TypeInfo arg : args) { + TypeInfo sub = substituteTypeParams(arg, position); + if (sub != arg) + anyChanged = true; + substituted.add(sub); + } + if (anyChanged) { + TypeInfo raw = type.getRawType(); + return raw != null ? raw.parameterize(substituted) : type.parameterize(substituted); + } + } + } + + return type; + } + /** * Position-aware type resolution overload. * Resolves a type name with positional context so that an unqualified inner class name @@ -4330,7 +4361,7 @@ public class ScriptDocument { // If already resolved, no need for positional fallback if (resolved != null && resolved.isResolved()) { - return resolved; + return substituteTypeParams(resolved, position); } if (typeName != null && !typeName.contains(".")) { @@ -9967,7 +9998,7 @@ for (ScriptTypeInfo type:scriptTypes.values()) { TokenType type = isClassTypeInfo ? varInfo.isGlobal() ? TokenType.GLOBAL_FIELD - : TokenType.LOCAL_FIELD : info.getTokenType(); + : TokenType.LOCAL_FIELD : TokenType.METHOD_CALL; marks.add(new ScriptLine.Mark(start, end, type, isClassTypeInfo ? varInfo : ctorCall)); continue; } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java index 2c49b036a..5ee8d3783 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java @@ -10,6 +10,7 @@ import noppes.npcs.client.gui.util.script.interpreter.token.TokenType; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -176,8 +177,6 @@ public class ScriptTypeInfo extends TypeInfo { Set active = PARAMETERIZING.get(); if (!active.add(this)) { - // Re-entrant call on the same raw type — fall back to shallow parameterization - // (no method/field substitution) to break the infinite recursion. return super.parameterize(typeArgs); } @@ -310,7 +309,11 @@ public class ScriptTypeInfo extends TypeInfo { if (enumConstants.containsKey(fieldName)) return true; - + + for (FieldInfo f : getSyntheticFields()) + if (f.getName().equals(fieldName)) + return true; + return false; } @@ -325,6 +328,10 @@ public class ScriptTypeInfo extends TypeInfo { return enumConst.getFieldInfo(); } + for (FieldInfo f : getSyntheticFields()) + if (f.getName().equals(fieldName)) + return f; + return null; } @@ -361,6 +368,7 @@ public class ScriptTypeInfo extends TypeInfo { /** * Get all enum constants. */ + @Override public Map getEnumConstants() { return new HashMap<>(enumConstants); } @@ -380,32 +388,51 @@ public class ScriptTypeInfo extends TypeInfo { @Override public boolean hasMethod(String methodName) { - return methods.containsKey(methodName); + if (methods.containsKey(methodName)) + return true; + for (MethodInfo m : getSyntheticMethods()) + if (m.getName().equals(methodName)) + return true; + return false; } @Override public boolean hasMethod(String methodName, int paramCount) { List overloads = methods.get(methodName); - if (overloads == null) return false; - for (MethodInfo m : overloads) { - if (m.getParameterCount() == paramCount) { - return true; + if (overloads != null) { + for (MethodInfo m : overloads) { + if (m.getParameterCount() == paramCount) { + return true; + } } } + for (MethodInfo m : getSyntheticMethods()) + if (m.getName().equals(methodName) && m.getParameterCount() == paramCount) + return true; return false; } @Override public MethodInfo getMethodInfo(String methodName) { List overloads = methods.get(methodName); - return (overloads != null && !overloads.isEmpty()) ? overloads.get(0) : null; + if (overloads != null && !overloads.isEmpty()) + return overloads.get(0); + for (MethodInfo m : getSyntheticMethods()) + if (m.getName().equals(methodName)) + return m; + return null; } /** * Get all method overloads with the given name. */ + @Override public List getAllMethodOverloads(String methodName) { - return methods.getOrDefault(methodName, new ArrayList<>()); + List result = new ArrayList<>(methods.getOrDefault(methodName, Collections.emptyList())); + for (MethodInfo m : getSyntheticMethods()) + if (m.getName().equals(methodName)) + result.add(m); + return result; } /** @@ -432,7 +459,26 @@ public class ScriptTypeInfo extends TypeInfo { allMethods.addAll(overloads); } return allMethods; - } + } + + @Override + public List getAllMethods() { + List result = getAllMethodsFlat(); + result.addAll(getSyntheticMethods()); + return result; + } + + @Override + public List getAllFields() { + List result = new ArrayList<>(getFields().values()); + result.addAll(getSyntheticFields()); + return result; + } + + @Override + public List getAllNestedTypes() { + return new ArrayList<>(innerClasses); + } // ==================== CONSTRUCTOR MANAGEMENT ==================== 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 1c5e36967..25044707a 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 @@ -771,6 +771,149 @@ public class TypeInfo { } } + /** + * Returns all public instance members of this type for autocomplete/resolution. + * Override in subclasses to extend or replace the default Java reflection path. + */ + public List getAllMethods() { + if (javaClass != null) { + // Java reflection path: enumerate public methods + synthetics + List result = new ArrayList<>(); + try { + for (java.lang.reflect.Method m : javaClass.getMethods()) { + try { + result.add(MethodInfo.fromReflection(m, this)); + } catch (Exception e) { + // Skip individual methods that fail (e.g., linkage errors on obscure return types) + } + } + } catch (Exception ignored) {} + result.addAll(getSyntheticMethods()); + return result; + } + + // javaClass == null: check rawType, jsTypeInfo, synthetics in priority order + if (rawType != null && rawType != this) { + List rawMethods = rawType.getAllMethods(); + if (!rawMethods.isEmpty() && isParameterized()) { + Map bindings = TypeSubstitutor.createBindingsFromReceiver(this); + if (!bindings.isEmpty()) { + List substituted = new ArrayList<>(rawMethods.size()); + for (MethodInfo m : rawMethods) { + substituted.add(m.substituteTypeParams(bindings)); + } + return substituted; + } + } + return rawMethods; + } + + // No javaClass, no rawType — collect from jsTypeInfo and synthetics + List result = new ArrayList<>(); + if (jsTypeInfo != null) { + for (JSMethodInfo m : jsTypeInfo.getMethods().values()) { + result.add(MethodInfo.fromJSMethod(m, this)); + } + } + result.addAll(getSyntheticMethods()); + return result; + } + + public List getAllFields() { + if (javaClass != null) { + // Java reflection path: enumerate public fields + synthetics + List result = new ArrayList<>(); + try { + for (java.lang.reflect.Field f : javaClass.getFields()) { + try { + result.add(FieldInfo.fromReflection(f, this)); + } catch (Exception e) { + // Skip individual fields that fail rather than losing ALL fields. + } + } + } catch (Exception ignored) {} + result.addAll(getSyntheticFields()); + return result; + } + + // javaClass == null: check rawType, jsTypeInfo, synthetics in priority order + if (rawType != null && rawType != this) { + List rawFields = rawType.getAllFields(); + if (!rawFields.isEmpty() && isParameterized()) { + Map bindings = TypeSubstitutor.createBindingsFromReceiver(this); + if (!bindings.isEmpty()) { + List substituted = new ArrayList<>(rawFields.size()); + for (FieldInfo f : rawFields) { + substituted.add(f.substituteTypeParams(bindings)); + } + return substituted; + } + } + return rawFields; + } + + // No javaClass, no rawType — collect from jsTypeInfo and synthetics + List result = new ArrayList<>(); + if (jsTypeInfo != null) { + for (JSFieldInfo f : jsTypeInfo.getFields().values()) { + result.add(FieldInfo.fromJSField(f, this)); + } + } + result.addAll(getSyntheticFields()); + return result; + } + + /** + * Returns all public nested types (inner classes/interfaces/enums) of this type. + * Uses reflection to discover nested types from the Java class, filters to public only, + * converts internal '$' separators to '.' for name resolution, and resolves each via TypeResolver. + * + * Override in subclasses (e.g., ScriptTypeInfo) to return script-defined inner classes. + * + * @return List of resolved nested TypeInfo instances (may be empty, never null) + */ + public List getAllNestedTypes() { + if (javaClass != null) { + List result = new ArrayList<>(); + try { + for (Class nested : javaClass.getDeclaredClasses()) { + if (!Modifier.isPublic(nested.getModifiers())) { + continue; + } + // Convert JVM internal '$' separator to '.' for TypeResolver lookup + String nestedFullName = nested.getName().replace('$', '.'); + TypeInfo nestedType = TypeResolver.getInstance().resolveFullName(nestedFullName); + if (nestedType != null && nestedType.isResolved()) { + result.add(nestedType); + } + } + } catch (SecurityException ignored) { + // Some runtimes may restrict getDeclaredClasses() + } + return result; + } + + if (rawType != null && rawType != this) { + return rawType.getAllNestedTypes(); + } + + // No javaClass, no rawType — check jsTypeInfo for inner types + if (jsTypeInfo != null) { + Map innerTypes = jsTypeInfo.getInnerTypes(); + if (!innerTypes.isEmpty()) { + List result = new ArrayList<>(innerTypes.size()); + for (JSTypeInfo inner : innerTypes.values()) { + TypeInfo ti = TypeInfo.fromJSTypeInfo(inner); + if (ti != null) { + result.add(ti); + } + } + return result; + } + } + return Collections.emptyList(); + } + /** * Check if this type has a method with the given name. */ @@ -789,6 +932,8 @@ public class TypeInfo { } catch (Exception e) { // Security or linkage error } + } else if (rawType != null && rawType != this) { + return rawType.hasMethod(methodName); } TypeInfo obj = TypeInfo.object(); @@ -817,6 +962,8 @@ public class TypeInfo { } catch (Exception e) { // Security or linkage error } + } else if (rawType != null && rawType != this) { + return rawType.hasMethod(methodName, paramCount); } TypeInfo obj = TypeInfo.object(); if (obj != this) return obj.hasMethod(methodName); @@ -828,13 +975,15 @@ public class TypeInfo { * Override in ScriptTypeInfo for script-defined types. */ public boolean hasConstructors() { - if (javaClass == null) return false; - try { - return javaClass.getConstructors().length > 0; - } catch (Exception e) { - // Security or linkage error - return false; + if (javaClass != null) { + try { + return javaClass.getConstructors().length > 0; + } catch (Exception e) { + return false; + } } + if (rawType != null && rawType != this) return rawType.hasConstructors(); + return false; } /** @@ -843,16 +992,18 @@ public class TypeInfo { */ public List getConstructors() { List result = new ArrayList<>(); - if (javaClass == null) return result; - - try { - java.lang.reflect.Constructor[] constructors = javaClass.getConstructors(); - for (java.lang.reflect.Constructor ctor : constructors) { - result.add(MethodInfo.fromReflectionConstructor(ctor, this)); + if (javaClass != null) { + try { + java.lang.reflect.Constructor[] constructors = javaClass.getConstructors(); + for (java.lang.reflect.Constructor ctor : constructors) { + result.add(MethodInfo.fromReflectionConstructor(ctor, this)); + } + } catch (Exception e) { + // Security or linkage error } - } catch (Exception e) { - // Security or linkage error + return result; } + if (rawType != null && rawType != this) return rawType.getConstructors(); return result; } @@ -861,45 +1012,49 @@ public class TypeInfo { * Override in ScriptTypeInfo for script-defined types. */ public MethodInfo findConstructor(int argCount) { - if (javaClass == null) return null; - - try { - Constructor[] constructors = javaClass.getConstructors(); - for (Constructor ctor : constructors) { - if (ctor.getParameterCount() == argCount) { - return MethodInfo.fromReflectionConstructor(ctor, this); + if (javaClass != null) { + try { + Constructor[] constructors = javaClass.getConstructors(); + for (Constructor ctor : constructors) { + if (ctor.getParameterCount() == argCount) { + return MethodInfo.fromReflectionConstructor(ctor, this); + } } + } catch (Exception e) { + // Security or linkage error } - } catch (Exception e) { - // Security or linkage error + return null; } + if (rawType != null && rawType != this) return rawType.findConstructor(argCount); return null; } public MethodInfo findConstructor(TypeInfo[] argTypes) { - if (javaClass == null) return null; - - try { - Constructor[] constructors = javaClass.getConstructors(); - for (Constructor ctor : constructors) { - if (ctor.getParameterCount() == argTypes.length) { - Class[] paramTypes = ctor.getParameterTypes(); - boolean match = true; - for (int i = 0; i < argTypes.length; i++) { - TypeInfo paramTypeInfo = TypeInfo.fromClass(paramTypes[i]); - if (!TypeChecker.isTypeCompatible(paramTypeInfo, argTypes[i])) { - match = false; - break; + if (javaClass != null) { + try { + Constructor[] constructors = javaClass.getConstructors(); + for (Constructor ctor : constructors) { + if (ctor.getParameterCount() == argTypes.length) { + Class[] paramTypes = ctor.getParameterTypes(); + boolean match = true; + for (int i = 0; i < argTypes.length; i++) { + TypeInfo paramTypeInfo = TypeInfo.fromClass(paramTypes[i]); + if (!TypeChecker.isTypeCompatible(paramTypeInfo, argTypes[i])) { + match = false; + break; + } + } + if (match) { + return MethodInfo.fromReflectionConstructor(ctor, this); } } - if (match) { - return MethodInfo.fromReflectionConstructor(ctor, this); - } } + } catch (Exception e) { + // Security or linkage error } - } catch (Exception e) { - // Security or linkage error + return null; } + if (rawType != null && rawType != this) return rawType.findConstructor(argTypes); return null; } @@ -913,30 +1068,63 @@ public class TypeInfo { return jsTypeInfo.hasField(fieldName); } - if (javaClass == null) return false; - try { - for (java.lang.reflect.Field f : javaClass.getFields()) { - if (f.getName().equals(fieldName)) { - return true; + if (javaClass != null) { + try { + for (java.lang.reflect.Field f : javaClass.getFields()) { + if (f.getName().equals(fieldName)) { + return true; + } } + } catch (Exception e) { + // Security or linkage error } - } catch (Exception e) { - // Security or linkage error + return false; } + if (rawType != null && rawType != this) return rawType.hasField(fieldName); return false; } - public boolean hasEnumConstant(String constantName) { - if (javaClass == null || !javaClass.isEnum()) return false; + /** + * Returns all enum constants for this type as a name-to-EnumConstantInfo map. + * For Java enum types, builds the map via reflection. For script-defined enums, + * ScriptTypeInfo overrides this to return its stored constants directly. + * + * @return Map of constant name to EnumConstantInfo (empty if not an enum or no constants) + */ + public Map getEnumConstants() { + if (javaClass == null || !javaClass.isEnum()) { + if (rawType != null && rawType != this) { + return rawType.getEnumConstants(); + } + return Collections.emptyMap(); + } + Map result = new LinkedHashMap<>(); try { Object[] constants = javaClass.getEnumConstants(); for (Object constant : constants) { - if (constant.toString().equals(constantName)) - return true; + String name = constant.toString(); + result.put(name, EnumConstantInfo.fromReflection(name, this, null)); } } catch (Exception e) { - // Security or linkage error + // Security or linkage error — return whatever we have so far } + return result; + } + + public boolean hasEnumConstant(String constantName) { + if (javaClass != null && javaClass.isEnum()) { + try { + Object[] constants = javaClass.getEnumConstants(); + for (Object constant : constants) { + if (constant.toString().equals(constantName)) + return true; + } + } catch (Exception e) { + // Security or linkage error + } + return false; + } + if (rawType != null && rawType != this) return rawType.hasEnumConstant(constantName); return false; } @@ -944,16 +1132,19 @@ public class TypeInfo { * Get an enum constant by name. */ public EnumConstantInfo getEnumConstant(String constantName) { - if (javaClass == null || !javaClass.isEnum()) return null; - try { - Object[] constants = javaClass.getEnumConstants(); - for (Object constant : constants) { - if (constant.toString().equals(constantName)) - return EnumConstantInfo.fromReflection(constantName, this, null); + if (javaClass != null && javaClass.isEnum()) { + try { + Object[] constants = javaClass.getEnumConstants(); + for (Object constant : constants) { + if (constant.toString().equals(constantName)) + return EnumConstantInfo.fromReflection(constantName, this, null); + } + } catch (Exception e) { + // Security or linkage error } - } catch (Exception e) { - // Security or linkage error + return null; } + if (rawType != null && rawType != this) return rawType.getEnumConstant(constantName); return null; } @@ -976,6 +1167,13 @@ public class TypeInfo { } catch (Exception e) { // Security or linkage error } + } else if (rawType != null && rawType != this) { + MethodInfo rawMethod = rawType.getMethodInfo(methodName); + if (rawMethod != null && isParameterized()) { + Map bindings = TypeSubstitutor.createBindingsFromReceiver(this); + if (!bindings.isEmpty()) return rawMethod.substituteTypeParams(bindings); + } + return rawMethod; } TypeInfo obj = TypeInfo.object(); if (obj != this) return obj.getMethodInfo(methodName); @@ -1012,6 +1210,18 @@ public class TypeInfo { // Security or linkage error } if (!overloads.isEmpty()) return overloads; + } else if (rawType != null && rawType != this) { + java.util.List rawOverloads = rawType.getAllMethodOverloads(methodName); + if (!rawOverloads.isEmpty() && isParameterized()) { + Map bindings = TypeSubstitutor.createBindingsFromReceiver(this); + if (!bindings.isEmpty()) { + for (MethodInfo m : rawOverloads) { + overloads.add(m.substituteTypeParams(bindings)); + } + return overloads; + } + } + return rawOverloads; } TypeInfo obj = TypeInfo.object(); if (obj != this) return obj.getAllMethodOverloads(methodName); @@ -1078,16 +1288,25 @@ public class TypeInfo { return null; } - if (javaClass == null) return null; - try { - for (java.lang.reflect.Field f : javaClass.getFields()) { - if (f.getName().equals(fieldName)) { - // Create a synthetic FieldInfo from reflection - return FieldInfo.fromReflection(f, this); + if (javaClass != null) { + try { + for (java.lang.reflect.Field f : javaClass.getFields()) { + if (f.getName().equals(fieldName)) { + return FieldInfo.fromReflection(f, this); + } } + } catch (Exception e) { + // Security or linkage error } - } catch (Exception e) { - // Security or linkage error + return null; + } + if (rawType != null && rawType != this) { + FieldInfo rawField = rawType.getFieldInfo(fieldName); + if (rawField != null && isParameterized()) { + Map bindings = TypeSubstitutor.createBindingsFromReceiver(this); + if (!bindings.isEmpty()) return rawField.substituteTypeParams(bindings); + } + return rawField; } return null; } @@ -1206,7 +1425,19 @@ public class TypeInfo { return null; } - // Not a functional interface + // Not a functional interface — try rawType for parameterized script types + if (rawType != null && rawType != this) { + MethodInfo rawSAM = rawType.getSingleAbstractMethod(); + if (rawSAM != null && isParameterized()) { + Map bindings = TypeSubstitutor.createBindingsFromReceiver(this); + if (!bindings.isEmpty()) { + cachedSAM = rawSAM.substituteTypeParams(bindings); + return cachedSAM; + } + } + cachedSAM = rawSAM; + return cachedSAM; + } cachedSAM = null; return null;