Fetched best method overload according to arg types provided

This commit is contained in:
bigguy345 committed 2025-12-29 15:05:39 +02:00
1 parent 2ab48129b3
commit 7e25b1c858
2 files changed
+263 -10

No files matched your search

@@ -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<String> 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<MethodInfo> 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<FieldInfo> 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<FieldInfo> 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.
@@ -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<MethodInfo> getAllMethodOverloads(String methodName) {
java.util.List<MethodInfo> 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<MethodInfo> 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<MethodInfo> 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<FieldInfo> 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<FieldInfo> 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.