From 7e25b1c85857963a73dd87df7876a9ecb6db728e Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Mon, 29 Dec 2025 15:05:39 +0200 Subject: [PATCH] Fetched best method overload according to arg types provided --- .../script/interpreter/ScriptDocument.java | 165 ++++++++++++++++-- .../script/interpreter/type/TypeInfo.java | 108 ++++++++++++ 2 files changed, 263 insertions(+), 10 deletions(-) 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 82dc31500..0ff134886 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 @@ -13,6 +13,7 @@ import noppes.npcs.client.gui.util.script.interpreter.token.Token; import noppes.npcs.client.gui.util.script.interpreter.token.TokenType; import noppes.npcs.client.gui.util.script.interpreter.type.ImportData; 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; @@ -1029,16 +1030,20 @@ public class ScriptDocument { if (pos < text.length() && text.charAt(pos) == '(') { // Method call if (currentType.hasMethod(segment)) { - MethodInfo methodInfo = currentType.getMethodInfo(segment); + // Parse argument types + int closeParen = findMatchingParen(pos); + if (closeParen < 0) return null; + String argsText = text.substring(pos + 1, closeParen).trim(); + TypeInfo[] argTypes = parseArgumentTypes(argsText, pos + 1); + + MethodInfo methodInfo = currentType.getBestMethodOverload(segment, argTypes); currentType = (methodInfo != null) ? methodInfo.getReturnType() : null; + + // Skip to after the closing paren + pos = closeParen + 1; } else { return null; } - - // Skip to after the closing paren - int closeParen = findMatchingParen(pos); - if (closeParen < 0) return null; - pos = closeParen + 1; } else { // Field access if (currentType.hasField(segment)) { @@ -1906,13 +1911,17 @@ public class ScriptDocument { if (receiverType != null) { if (receiverType.hasMethod(methodName)) { - resolvedMethod = receiverType.getMethodInfo(methodName); + + TypeInfo[] argTypes = arguments.stream().map(MethodCallInfo.Argument::getResolvedType).toArray(TypeInfo[]::new); + // Get best method overload based on argument types + resolvedMethod = receiverType.getBestMethodOverload(methodName, argTypes); + MethodCallInfo callInfo = new MethodCallInfo( methodName, nameStart, nameEnd, openParen, closeParen, arguments, receiverType, resolvedMethod, computedStaticAccess ); - // Only set expected type if this is the final expression (not followed by .field or .method) + // Set expected type for validation if this is the final expression if (!isFollowedByDot(closeParen)) { TypeInfo expectedType = findExpectedTypeAtPosition(nameStart); if (expectedType != null) { @@ -1932,7 +1941,10 @@ public class ScriptDocument { marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.UNDEFINED_VAR)); } else { if (isScriptMethod(methodName)) { - resolvedMethod = getScriptMethodInfo(methodName); + // Extract argument types from parsed arguments + + TypeInfo[] argTypes = arguments.stream().map(MethodCallInfo.Argument::getResolvedType).toArray(TypeInfo[]::new); + resolvedMethod = getScriptMethodInfo(methodName, argTypes); // Check if this is a method from a script type // Instance methods from script types cannot be called without a receiver @@ -2498,6 +2510,66 @@ public class ScriptDocument { return resolveExpressionType(receiverExpr, start); } + /** + * Parse argument types from a method call's argument list. + * @param argsText The text between parentheses (e.g., "20, 20" or "x, y.toString()") + * @param position The position in the source text + * @return Array of TypeInfo for each argument, or empty array if no arguments + */ + private TypeInfo[] parseArgumentTypes(String argsText, int position) { + if (argsText == null || argsText.trim().isEmpty()) { + return new TypeInfo[0]; + } + + // Split arguments by comma, respecting nested parentheses and strings + java.util.List args = new java.util.ArrayList<>(); + int depth = 0; + int start = 0; + boolean inString = false; + char stringChar = 0; + + for (int i = 0; i < argsText.length(); i++) { + char c = argsText.charAt(i); + + // Handle strings + if ((c == '"' || c == '\'') && (i == 0 || argsText.charAt(i-1) != '\\')) { + if (!inString) { + inString = true; + stringChar = c; + } else if (c == stringChar) { + inString = false; + } + continue; + } + + if (inString) continue; + + // Track parentheses depth + if (c == '(' || c == '[' || c == '{') { + depth++; + } else if (c == ')' || c == ']' || c == '}') { + depth--; + } else if (c == ',' && depth == 0) { + // Found argument separator at top level + args.add(argsText.substring(start, i).trim()); + start = i + 1; + } + } + + // Add the last argument + if (start < argsText.length()) { + args.add(argsText.substring(start).trim()); + } + + // Resolve each argument's type + TypeInfo[] argTypes = new TypeInfo[args.size()]; + for (int i = 0; i < args.size(); i++) { + argTypes[i] = resolveExpressionType(args.get(i), position); + } + + return argTypes; + } + /** * Helper class for chain segments (can be field access or method call). */ @@ -2762,7 +2834,7 @@ public class ScriptDocument { } if (targetType.hasMethod(methodName)) { - MethodInfo methodInfo = targetType.getMethodInfo(methodName); + MethodInfo methodInfo = targetType.getBestMethodOverload(methodName, argTypes); return (methodInfo != null) ? methodInfo.getReturnType() : null; } @@ -3076,6 +3148,79 @@ public class ScriptDocument { return null; } + /** + * Get the best matching script method overload based on argument types. + */ + private MethodInfo getScriptMethodInfo(String methodName, TypeInfo[] argTypes) { + List candidates = new ArrayList<>(); + + // Collect all methods with matching name + for (MethodInfo method : methods) { + if (method.getName().equals(methodName)) { + candidates.add(method); + } + } + + if (candidates.isEmpty()) { + return null; + } + + if (candidates.size() == 1) { + return candidates.get(0); + } + + // Use same 3-phase matching as TypeInfo.getBestMethodOverload + + // Phase 1: Look for exact match + for (MethodInfo method : candidates) { + List params = method.getParameters(); + if (params.size() != argTypes.length) { + continue; + } + + boolean exactMatch = true; + for (int i = 0; i < argTypes.length; i++) { + TypeInfo paramType = params.get(i).getDeclaredType(); + TypeInfo argType = argTypes[i]; + + if (argType == null || paramType == null || !argType.equals(paramType)) { + exactMatch = false; + break; + } + } + + if (exactMatch) { + return method; + } + } + + // Phase 2: Look for compatible match (widening/autoboxing) + for (MethodInfo method : candidates) { + List params = method.getParameters(); + if (params.size() != argTypes.length) { + continue; + } + + boolean compatible = true; + for (int i = 0; i < argTypes.length; i++) { + TypeInfo paramType = params.get(i).getDeclaredType(); + TypeInfo argType = argTypes[i]; + + if (argType != null && paramType != null && !TypeChecker.isTypeCompatible(paramType, argType)) { + compatible = false; + break; + } + } + + if (compatible) { + return method; + } + } + + // Phase 3: Fallback to first overload + return candidates.get(0); + } + /** * Check if a method belongs to a script-defined type (class/interface/enum). * Returns true if the method is defined inside a script type. 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 8c9adadd0..f20c4e9d6 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 @@ -272,6 +272,114 @@ public class TypeInfo { return null; } + /** + * Get all MethodInfo overloads for a method by name. + * Returns an empty list if not found. + */ + public java.util.List getAllMethodOverloads(String methodName) { + java.util.List overloads = new java.util.ArrayList<>(); + if (javaClass == null) return overloads; + try { + for (java.lang.reflect.Method m : javaClass.getMethods()) { + if (m.getName().equals(methodName)) { + overloads.add(MethodInfo.fromReflection(m, this)); + } + } + } catch (Exception e) { + // Security or linkage error + } + return overloads; + } + + /** + * Find the best matching method overload considering return type. + * First tries to find a match with compatible return type, then falls back to any match. + * + * @param methodName The name of the method + * @param expectedReturnType The expected return type (can be null) + * @return The best matching MethodInfo, or null if not found + */ + public MethodInfo getBestMethodOverload(String methodName, TypeInfo expectedReturnType) { + java.util.List overloads = getAllMethodOverloads(methodName); + if (overloads.isEmpty()) return null; + + // If no expected return type, return first overload + if (expectedReturnType == null) { + return overloads.get(0); + } + + // First pass: look for return type compatible overload + for (MethodInfo method : overloads) { + TypeInfo returnType = method.getReturnType(); + if (returnType != null && TypeChecker.isTypeCompatible(expectedReturnType, returnType)) { + return method; + } + } + // Second pass: return any overload (first one) + return overloads.get(0); + } + + /** + * Find the best matching method overload based on argument types. + * Uses Java's method resolution rules: exact match, then widening conversion, then autoboxing. + * + * @param methodName The name of the method + * @param argTypes The types of the arguments being passed + * @return The best matching MethodInfo, or null if not found + */ + public MethodInfo getBestMethodOverload(String methodName, TypeInfo[] argTypes) { + java.util.List overloads = getAllMethodOverloads(methodName); + if (overloads.isEmpty()) return null; + + // If no arguments provided, try to find zero-arg method + if (argTypes == null || argTypes.length == 0) { + for (MethodInfo method : overloads) { + if (method.getParameterCount() == 0) { + return method; + } + } + // Fall back to first overload if no zero-arg found + return overloads.get(0); + } + + // Phase 1: Try exact match + for (MethodInfo method : overloads) { + if (method.getParameterCount() == argTypes.length) { + boolean exactMatch = true; + java.util.List params = method.getParameters(); + for (int i = 0; i < argTypes.length; i++) { + TypeInfo paramType = params.get(i).getTypeInfo(); + TypeInfo argType = argTypes[i]; + if (paramType == null || argType == null || !paramType.equals(argType)) { + exactMatch = false; + break; + } + } + if (exactMatch) return method; + } + } + + // Phase 2: Try compatible match (widening, autoboxing, subtyping) + for (MethodInfo method : overloads) { + if (method.getParameterCount() == argTypes.length) { + boolean compatible = true; + java.util.List params = method.getParameters(); + for (int i = 0; i < argTypes.length; i++) { + TypeInfo paramType = params.get(i).getTypeInfo(); + TypeInfo argType = argTypes[i]; + if (paramType == null || argType == null || !TypeChecker.isTypeCompatible(paramType, argType)) { + compatible = false; + break; + } + } + if (compatible) return method; + } + } + + // Phase 3: Return first overload as fallback + return overloads.get(0); + } + /** * Get FieldInfo for a field by name. Returns null if not found. * Creates a synthetic FieldInfo based on reflection data.