mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 20:07:32 +00:00
Added super() error validation
This commit is contained in:
1 parent
861cdcd533
commit
7b77c1935f
4 files changed
+515
-19
No files matched your search
+9
-6
@@ -72,18 +72,21 @@ public class ErrorUnderlineRenderer {
|
||||
int methodEnd = methodStart + call.getMethodName().length();
|
||||
drawUnderlineForSpan(methodStart, methodEnd, lineStartX, baselineY,
|
||||
lineText, lineStart, lineEnd, ERROR_COLOR);
|
||||
}
|
||||
} else if (call.hasArgTypeError()) {
|
||||
// Handle return type mismatch (underline the method name) - currently commented out
|
||||
// if (call.hasReturnTypeMismatch()) { ... }
|
||||
|
||||
// Handle return type mismatch (underline the method name) - currently commented out
|
||||
// if (call.hasReturnTypeMismatch()) { ... }
|
||||
|
||||
// Handle arg type errors (underline specific arguments)
|
||||
if (call.hasArgTypeError()) {
|
||||
// Handle arg type errors (underline specific arguments)
|
||||
|
||||
for (MethodCallInfo.ArgumentTypeError error : call.getArgumentTypeErrors()) {
|
||||
MethodCallInfo.Argument arg = error.getArg();
|
||||
drawUnderlineForSpan(arg.getStartOffset(), arg.getEndOffset(),
|
||||
lineStartX, baselineY, lineText, lineStart, lineEnd, ERROR_COLOR);
|
||||
}
|
||||
} else if(call.hasError()){
|
||||
// Handle other call errors (underline the whole call)
|
||||
drawUnderlineForSpan(call.getMethodNameStart(), call.getCloseParenOffset() + 1,
|
||||
lineStartX, baselineY, lineText, lineStart, lineEnd, ERROR_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ScriptDocument {
|
||||
private static final Pattern MODIFIER_PATTERN = Pattern.compile(
|
||||
"\\b(public|protected|private|static|final|abstract|synchronized|native|default)\\b");
|
||||
private static final Pattern KEYWORD_PATTERN = Pattern.compile(
|
||||
"\\b(null|boolean|int|float|double|long|char|byte|short|void|if|else|switch|case|for|while|do|try|catch|finally|return|throw|var|let|const|function|continue|break|this|new|typeof|instanceof|import)\\b");
|
||||
"\\b(null|boolean|int|float|double|long|char|byte|short|void|if|else|switch|case|for|while|do|try|catch|finally|return|throw|var|let|const|function|continue|break|this|super|new|typeof|instanceof|import)\\b");
|
||||
|
||||
// Declarations - Updated to capture method parameters
|
||||
private static final Pattern IMPORT_PATTERN = Pattern.compile(
|
||||
@@ -1996,6 +1996,12 @@ public class ScriptDocument {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle super() constructor calls
|
||||
if (methodName.equals("super")) {
|
||||
handleSuperConstructorCall(marks, nameStart, nameEnd, openParen, closeParen);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse the arguments (first pass - without expected types for overload resolution)
|
||||
List<MethodCallInfo.Argument> arguments = parseMethodArguments(openParen + 1, closeParen, null);
|
||||
|
||||
@@ -2007,7 +2013,15 @@ public class ScriptDocument {
|
||||
MethodInfo resolvedMethod = null;
|
||||
|
||||
if (receiverType != null) {
|
||||
if (receiverType.hasMethod(methodName)) {
|
||||
// Check for method existence using hierarchy search if it's a ScriptTypeInfo
|
||||
boolean hasMethod = false;
|
||||
if (receiverType instanceof ScriptTypeInfo) {
|
||||
hasMethod = ((ScriptTypeInfo) receiverType).hasMethodInHierarchy(methodName);
|
||||
} else {
|
||||
hasMethod = receiverType.hasMethod(methodName);
|
||||
}
|
||||
|
||||
if (hasMethod) {
|
||||
|
||||
TypeInfo[] argTypes = arguments.stream().map(MethodCallInfo.Argument::getResolvedType).toArray(TypeInfo[]::new);
|
||||
// Get best method overload based on argument types
|
||||
@@ -2504,6 +2518,14 @@ public class ScriptDocument {
|
||||
return currentType;
|
||||
}
|
||||
}
|
||||
} else if (first.name.equals("super")) {
|
||||
// Resolve super to parent class
|
||||
ScriptTypeInfo enclosingType = findEnclosingScriptType(position);
|
||||
if (enclosingType != null && enclosingType.hasSuperClass()) {
|
||||
currentType = enclosingType.getSuperClass();
|
||||
} else {
|
||||
return null; // No parent class
|
||||
}
|
||||
} else {
|
||||
// Check if first segment is a type name for static access
|
||||
TypeInfo typeCheck = resolveType(first.name);
|
||||
@@ -2782,19 +2804,42 @@ public class ScriptDocument {
|
||||
|
||||
if (segment.isMethodCall) {
|
||||
// Method call - get return type with argument-based overload resolution
|
||||
if (currentType.hasMethod(segment.name)) {
|
||||
// Check for method existence using hierarchy search if it's a ScriptTypeInfo
|
||||
boolean hasMethod = false;
|
||||
if (currentType instanceof ScriptTypeInfo) {
|
||||
hasMethod = ((ScriptTypeInfo) currentType).hasMethodInHierarchy(segment.name);
|
||||
} else {
|
||||
hasMethod = currentType.hasMethod(segment.name);
|
||||
}
|
||||
|
||||
if (hasMethod) {
|
||||
// Parse argument types from the method call
|
||||
TypeInfo[] argTypes = parseArgumentTypes(segment.arguments, segment.start);
|
||||
|
||||
// Get the best matching overload based on argument types
|
||||
// getBestMethodOverload is now overridden in ScriptTypeInfo to search hierarchy
|
||||
MethodInfo methodInfo = currentType.getBestMethodOverload(segment.name, argTypes);
|
||||
return (methodInfo != null) ? methodInfo.getReturnType() : null;
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// Field access
|
||||
if (currentType.hasField(segment.name)) {
|
||||
FieldInfo fieldInfo = currentType.getFieldInfo(segment.name);
|
||||
// Field access - use hierarchy search for ScriptTypeInfo
|
||||
boolean hasField = false;
|
||||
FieldInfo fieldInfo = null;
|
||||
|
||||
if (currentType instanceof ScriptTypeInfo) {
|
||||
hasField = ((ScriptTypeInfo) currentType).hasFieldInHierarchy(segment.name);
|
||||
if (hasField) {
|
||||
fieldInfo = ((ScriptTypeInfo) currentType).getFieldInfoInHierarchy(segment.name);
|
||||
}
|
||||
} else {
|
||||
hasField = currentType.hasField(segment.name);
|
||||
if (hasField) {
|
||||
fieldInfo = currentType.getFieldInfo(segment.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasField) {
|
||||
return (fieldInfo != null) ? fieldInfo.getTypeInfo() : null;
|
||||
}
|
||||
return null;
|
||||
@@ -3322,6 +3367,76 @@ public class ScriptDocument {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the constructor that contains the given position.
|
||||
* Used for validating super() calls.
|
||||
*/
|
||||
private MethodInfo findEnclosingConstructor(int position) {
|
||||
ScriptTypeInfo enclosingType = findEnclosingScriptType(position);
|
||||
if (enclosingType == null)
|
||||
return null;
|
||||
|
||||
for (MethodInfo constructor : enclosingType.getConstructors()) {
|
||||
int bodyStart = constructor.getBodyStart();
|
||||
int bodyEnd = constructor.getBodyEnd();
|
||||
if (bodyStart >= 0 && bodyEnd > bodyStart && position >= bodyStart && position < bodyEnd) {
|
||||
return constructor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle super() constructor calls with validation and argument matching.
|
||||
*/
|
||||
private void handleSuperConstructorCall(List<ScriptLine.Mark> marks, int nameStart, int nameEnd, int openParen,
|
||||
int closeParen) {
|
||||
// Validate that we're in a constructor
|
||||
TokenErrorMessage errorMsg = null;
|
||||
|
||||
//Parent class
|
||||
ScriptTypeInfo enclosingType = findEnclosingScriptType(nameStart);
|
||||
MethodInfo enclosingConstructor = findEnclosingConstructor(nameStart);
|
||||
TypeInfo superClass = enclosingType != null ? enclosingType.getSuperClass() : null;
|
||||
|
||||
// Parse arguments and find matching parent constructor
|
||||
List<MethodCallInfo.Argument> arguments = parseMethodArguments(openParen + 1, closeParen, null);
|
||||
TypeInfo[] argTypes = arguments.stream().map(MethodCallInfo.Argument::getResolvedType).toArray(TypeInfo[]::new);
|
||||
|
||||
// Find matching constructor in parent class
|
||||
MethodInfo parentConstructor = superClass != null ? superClass.findConstructor(argTypes) : null;
|
||||
|
||||
if (enclosingType == null || !enclosingType.hasSuperClass()) {
|
||||
// No parent class - error
|
||||
errorMsg = TokenErrorMessage
|
||||
.from(enclosingType == null ? "'super()' can only be used within a class" : "Class '" + enclosingType.getSimpleName() + "' does not have a parent class")
|
||||
.clearOtherErrors();
|
||||
} else if (enclosingConstructor == null) {
|
||||
// Not in a constructor - error
|
||||
errorMsg = TokenErrorMessage.from("Call to 'super()' only allowed in constructor body").clearOtherErrors();
|
||||
} else if (superClass == null || !superClass.isResolved()) {
|
||||
// Parent class not resolved - error
|
||||
errorMsg = TokenErrorMessage.from(
|
||||
"Cannot resolve parent class '" + enclosingType.getSuperClassName() + "'");
|
||||
} else if (parentConstructor == null) {
|
||||
// No matching constructor found
|
||||
String argTypeStr = java.util.Arrays.stream(argTypes)
|
||||
.map(t -> t != null ? t.getSimpleName() : "unknown")
|
||||
.collect(java.util.stream.Collectors.joining(", "));
|
||||
errorMsg = TokenErrorMessage
|
||||
.from("No constructor found in '" + superClass.getSimpleName() + "' matching super(" + argTypeStr + ")")
|
||||
.clearOtherErrors();
|
||||
}
|
||||
|
||||
// Successfully resolved - mark as a valid method call (constructor call)
|
||||
MethodCallInfo callInfo = new MethodCallInfo("super", nameStart, nameEnd, openParen, closeParen, arguments,
|
||||
superClass, parentConstructor, false).setConstructor(true);
|
||||
callInfo.validate();
|
||||
methodCalls.add(callInfo);
|
||||
marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.IMPORT_KEYWORD,
|
||||
errorMsg != null ? errorMsg : callInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a method name is defined in this script.
|
||||
*/
|
||||
@@ -3540,9 +3655,10 @@ public class ScriptDocument {
|
||||
* Does NOT mark method calls (identifiers followed by parentheses) - those are handled by markMethodCalls.
|
||||
*/
|
||||
private void markChainedFieldAccesses(List<ScriptLine.Mark> marks) {
|
||||
// Pattern to find identifier chains: identifier.identifier, this.identifier, etc.
|
||||
// Start with an identifier or 'this' followed by at least one dot and another identifier
|
||||
Pattern chainPattern = Pattern.compile("\\b(this|[a-zA-Z_][a-zA-Z0-9_]*)\\s*\\.\\s*([a-zA-Z_][a-zA-Z0-9_]*)");
|
||||
// Pattern to find identifier chains: identifier.identifier, this.identifier, super.identifier, etc.
|
||||
// Start with an identifier, 'this', or 'super' followed by at least one dot and another identifier
|
||||
Pattern chainPattern = Pattern.compile(
|
||||
"\\b(this|super|[a-zA-Z_][a-zA-Z0-9_]*)\\s*\\.\\s*([a-zA-Z_][a-zA-Z0-9_]*)");
|
||||
Matcher m = chainPattern.matcher(text);
|
||||
|
||||
while (m.find()) {
|
||||
@@ -3618,6 +3734,7 @@ public class ScriptDocument {
|
||||
// Start with resolving the first segment
|
||||
TypeInfo currentType = null;
|
||||
boolean firstIsThis = firstSegment.equals("this");
|
||||
boolean firstIsSuper = firstSegment.equals("super");
|
||||
boolean firstIsPrecededByDot = isPrecededByDot(chainStart);
|
||||
|
||||
// Determine the starting index for marking
|
||||
@@ -3628,6 +3745,28 @@ public class ScriptDocument {
|
||||
// For 'this', we don't have a class context to resolve, but we can mark subsequent fields
|
||||
// as global fields if they exist in globalFields
|
||||
currentType = null; // We'll use globalFields for the next segment
|
||||
} else if (firstIsSuper) {
|
||||
// For 'super', resolve to parent class type
|
||||
ScriptTypeInfo enclosingType = findEnclosingScriptType(chainStart);
|
||||
if (enclosingType != null && enclosingType.hasSuperClass()) {
|
||||
currentType = enclosingType.getSuperClass();
|
||||
if (currentType == null || !currentType.isResolved()) {
|
||||
// Mark 'super' as error - parent class not resolved
|
||||
marks.add(new ScriptLine.Mark(segmentPositions.get(0)[0], segmentPositions.get(0)[1],
|
||||
TokenType.UNDEFINED_VAR, TokenErrorMessage
|
||||
.from("Cannot resolve parent class '" + enclosingType.getSuperClassName() + "'")
|
||||
.clearOtherErrors()));
|
||||
return; // Can't continue
|
||||
}
|
||||
} else {
|
||||
// Mark 'super' as error - no parent class
|
||||
String errorMsg = enclosingType == null ? "'super' can only be used within a class" : "Class '" + enclosingType.getSimpleName() + "' does not have a parent class";
|
||||
marks.add(new ScriptLine.Mark(segmentPositions.get(0)[0], segmentPositions.get(0)[1],
|
||||
TokenType.UNDEFINED_VAR, TokenErrorMessage.from(errorMsg).clearOtherErrors()));
|
||||
return; // Can't continue
|
||||
}
|
||||
marks.add(new ScriptLine.Mark(segmentPositions.get(0)[0], segmentPositions.get(0)[1],
|
||||
TokenType.IMPORT_KEYWORD, currentType));
|
||||
} else {
|
||||
// Try to resolve as a type first (class/interface/enum name)
|
||||
TypeInfo typeCheck = resolveType(firstSegment);
|
||||
@@ -3698,6 +3837,38 @@ public class ScriptDocument {
|
||||
marks.add(new ScriptLine.Mark(segPos[0], segPos[1], TokenType.UNDEFINED_VAR));
|
||||
currentType = null;
|
||||
}
|
||||
} else if (i == 1 && firstIsSuper) {
|
||||
// For "super.field", currentType is already the parent class
|
||||
// Search recursively through the inheritance hierarchy
|
||||
boolean found = false;
|
||||
FieldInfo fieldInfo = null;
|
||||
|
||||
if (currentType != null) {
|
||||
if (currentType instanceof ScriptTypeInfo) {
|
||||
found = ((ScriptTypeInfo) currentType).hasFieldInHierarchy(segment);
|
||||
if (found) {
|
||||
fieldInfo = ((ScriptTypeInfo) currentType).getFieldInfoInHierarchy(segment);
|
||||
}
|
||||
} else {
|
||||
// For Java classes, hasField already checks inheritance
|
||||
found = currentType.hasField(segment);
|
||||
if (found) {
|
||||
fieldInfo = currentType.getFieldInfo(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
FieldAccessInfo accessInfo = createFieldAccessInfo(segment, segPos[0], segPos[1], currentType,
|
||||
fieldInfo, isLastSegment, false);
|
||||
marks.add(new ScriptLine.Mark(segPos[0], segPos[1], TokenType.GLOBAL_FIELD, accessInfo));
|
||||
currentType = (fieldInfo != null) ? fieldInfo.getTypeInfo() : null;
|
||||
} else {
|
||||
String errorMsg = currentType != null ? "Field '" + segment + "' not found in parent class hierarchy starting from '" + currentType.getSimpleName() + "'" : "Cannot resolve field '" + segment + "'";
|
||||
marks.add(new ScriptLine.Mark(segPos[0], segPos[1], TokenType.UNDEFINED_VAR,
|
||||
TokenErrorMessage.from(errorMsg).clearOtherErrors()));
|
||||
currentType = null;
|
||||
}
|
||||
} else if (currentType != null && currentType.isResolved()) {
|
||||
// Check if this type has this field
|
||||
if (currentType.hasField(segment)) {
|
||||
@@ -4556,9 +4727,12 @@ public class ScriptDocument {
|
||||
List<MethodCallInfo.Argument> arguments = parseMethodArguments(openParen + 1, closeParen,
|
||||
null);
|
||||
int argCount = arguments.size();
|
||||
|
||||
TypeInfo[] argTypes = arguments.stream().map(MethodCallInfo.Argument::getResolvedType)
|
||||
.toArray(TypeInfo[]::new);
|
||||
|
||||
|
||||
// Try to find matching constructor (may be null if not found)
|
||||
MethodInfo constructor = info.hasConstructors() ? info.findConstructor(argCount) : null;
|
||||
MethodInfo constructor = info.hasConstructors() ? info.findConstructor(argTypes) : null;
|
||||
|
||||
// Create MethodCallInfo for constructor (even if null, so errors are tracked)
|
||||
MethodCallInfo ctorCall = MethodCallInfo.constructor(
|
||||
|
||||
+291
@@ -288,6 +288,297 @@ public class ScriptTypeInfo extends TypeInfo {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MethodInfo findConstructor(TypeInfo[] argTypes) {
|
||||
for (MethodInfo constructor : constructors) {
|
||||
if (constructor.getParameterCount() == argTypes.length) {
|
||||
boolean match = true;
|
||||
List<FieldInfo> params = constructor.getParameters();
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
TypeInfo paramType = params.get(i).getTypeInfo();
|
||||
if (!TypeChecker.isTypeCompatible(paramType, argTypes[i])) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match)
|
||||
return constructor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== INHERITANCE HIERARCHY SEARCH ====================
|
||||
|
||||
/**
|
||||
* Check if this type or any of its parent classes has a field with the given name.
|
||||
* This recursively searches up the inheritance tree.
|
||||
*/
|
||||
public boolean hasFieldInHierarchy(String fieldName) {
|
||||
// Check this class first
|
||||
if (hasField(fieldName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check parent class recursively
|
||||
if (superClass != null && superClass.isResolved()) {
|
||||
if (superClass instanceof ScriptTypeInfo) {
|
||||
return ((ScriptTypeInfo) superClass).hasFieldInHierarchy(fieldName);
|
||||
} else {
|
||||
// For Java classes, hasField already checks inheritance via reflection
|
||||
return superClass.hasField(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field info from this type or any of its parent classes.
|
||||
* This recursively searches up the inheritance tree.
|
||||
*/
|
||||
public FieldInfo getFieldInfoInHierarchy(String fieldName) {
|
||||
// Check this class first
|
||||
FieldInfo field = getFieldInfo(fieldName);
|
||||
if (field != null) {
|
||||
return field;
|
||||
}
|
||||
|
||||
// Check parent class recursively
|
||||
if (superClass != null && superClass.isResolved()) {
|
||||
if (superClass instanceof ScriptTypeInfo) {
|
||||
return ((ScriptTypeInfo) superClass).getFieldInfoInHierarchy(fieldName);
|
||||
} else {
|
||||
// For Java classes, getFieldInfo already checks inheritance via reflection
|
||||
return superClass.getFieldInfo(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this type or any of its parent classes has a method with the given name.
|
||||
* This recursively searches up the inheritance tree.
|
||||
*/
|
||||
public boolean hasMethodInHierarchy(String methodName) {
|
||||
// Check this class first
|
||||
if (hasMethod(methodName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check parent class recursively
|
||||
if (superClass != null && superClass.isResolved()) {
|
||||
if (superClass instanceof ScriptTypeInfo) {
|
||||
return ((ScriptTypeInfo) superClass).hasMethodInHierarchy(methodName);
|
||||
} else {
|
||||
// For Java classes, hasMethod already checks inheritance via reflection
|
||||
return superClass.hasMethod(methodName);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this type or any of its parent classes has a method with the given name and parameter count.
|
||||
* This recursively searches up the inheritance tree.
|
||||
*/
|
||||
public boolean hasMethodInHierarchy(String methodName, int paramCount) {
|
||||
// Check this class first
|
||||
if (hasMethod(methodName, paramCount)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check parent class recursively
|
||||
if (superClass != null && superClass.isResolved()) {
|
||||
if (superClass instanceof ScriptTypeInfo) {
|
||||
return ((ScriptTypeInfo) superClass).hasMethodInHierarchy(methodName, paramCount);
|
||||
} else {
|
||||
// For Java classes, hasMethod already checks inheritance via reflection
|
||||
return superClass.hasMethod(methodName, paramCount);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method info from this type or any of its parent classes.
|
||||
* This recursively searches up the inheritance tree.
|
||||
*/
|
||||
public MethodInfo getMethodInfoInHierarchy(String methodName) {
|
||||
// Check this class first
|
||||
MethodInfo method = getMethodInfo(methodName);
|
||||
if (method != null) {
|
||||
return method;
|
||||
}
|
||||
|
||||
// Check parent class recursively
|
||||
if (superClass != null && superClass.isResolved()) {
|
||||
if (superClass instanceof ScriptTypeInfo) {
|
||||
return ((ScriptTypeInfo) superClass).getMethodInfoInHierarchy(methodName);
|
||||
} else {
|
||||
// For Java classes, getMethodInfo already checks inheritance via reflection
|
||||
return superClass.getMethodInfo(methodName);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method info with specific parameter count from this type or any of its parent classes.
|
||||
* This recursively searches up the inheritance tree.
|
||||
*/
|
||||
public MethodInfo getMethodWithParamCountInHierarchy(String methodName, int paramCount) {
|
||||
// Check this class first
|
||||
MethodInfo method = getMethodWithParamCount(methodName, paramCount);
|
||||
if (method != null) {
|
||||
return method;
|
||||
}
|
||||
|
||||
// Check parent class recursively
|
||||
if (superClass != null && superClass.isResolved()) {
|
||||
if (superClass instanceof ScriptTypeInfo) {
|
||||
return ((ScriptTypeInfo) superClass).getMethodWithParamCountInHierarchy(methodName, paramCount);
|
||||
} else {
|
||||
// For Java classes, reflection already handles inheritance
|
||||
// We need to manually search for matching method
|
||||
List<MethodInfo> overloads = superClass.getAllMethodOverloads(methodName);
|
||||
for (MethodInfo m : overloads) {
|
||||
if (m.getParameterCount() == paramCount) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all method overloads with the given name from this type or any of its parent classes.
|
||||
* This recursively searches up the inheritance tree and returns all matching overloads.
|
||||
*/
|
||||
public List<MethodInfo> getAllMethodOverloadsInHierarchy(String methodName) {
|
||||
List<MethodInfo> result = new ArrayList<>();
|
||||
|
||||
// Get overloads from this class
|
||||
result.addAll(getAllMethodOverloads(methodName));
|
||||
|
||||
// Get overloads from parent class recursively
|
||||
if (superClass != null && superClass.isResolved()) {
|
||||
if (superClass instanceof ScriptTypeInfo) {
|
||||
result.addAll(((ScriptTypeInfo) superClass).getAllMethodOverloadsInHierarchy(methodName));
|
||||
} else {
|
||||
// For Java classes, getAllMethodOverloads already checks inheritance via reflection
|
||||
result.addAll(superClass.getAllMethodOverloads(methodName));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override getBestMethodOverload to search the inheritance hierarchy.
|
||||
* This ensures that method overload resolution considers parent class methods.
|
||||
*/
|
||||
@Override
|
||||
public MethodInfo getBestMethodOverload(String methodName, TypeInfo[] argTypes) {
|
||||
// Get all overloads from this class and parent classes
|
||||
List<MethodInfo> allOverloads = getAllMethodOverloadsInHierarchy(methodName);
|
||||
if (allOverloads.isEmpty())
|
||||
return null;
|
||||
|
||||
// If no arguments provided, try to find zero-arg method
|
||||
if (argTypes == null || argTypes.length == 0) {
|
||||
for (MethodInfo method : allOverloads) {
|
||||
if (method.getParameterCount() == 0) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
// Fall back to first overload if no zero-arg found
|
||||
return allOverloads.get(0);
|
||||
}
|
||||
|
||||
// Phase 1: Try exact match
|
||||
for (MethodInfo method : allOverloads) {
|
||||
if (method.getParameterCount() == argTypes.length) {
|
||||
boolean exactMatch = true;
|
||||
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 type match (assignability)
|
||||
for (MethodInfo method : allOverloads) {
|
||||
if (method.getParameterCount() == argTypes.length) {
|
||||
boolean compatible = true;
|
||||
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) {
|
||||
continue; // Allow null types (unresolved)
|
||||
}
|
||||
// Check if argType can be assigned to paramType
|
||||
if (!noppes.npcs.client.gui.util.script.interpreter.type.TypeChecker.isTypeCompatible(paramType,
|
||||
argType)) {
|
||||
compatible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (compatible)
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Fall back to first overload with matching parameter count
|
||||
for (MethodInfo method : allOverloads) {
|
||||
if (method.getParameterCount() == argTypes.length) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
// No match found, return first overload
|
||||
return allOverloads.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override getBestMethodOverload with return type expectation to search the inheritance hierarchy.
|
||||
*/
|
||||
@Override
|
||||
public MethodInfo getBestMethodOverload(String methodName, TypeInfo expectedReturnType) {
|
||||
List<MethodInfo> allOverloads = getAllMethodOverloadsInHierarchy(methodName);
|
||||
if (allOverloads.isEmpty())
|
||||
return null;
|
||||
|
||||
// If no expected return type, return first overload
|
||||
if (expectedReturnType == null) {
|
||||
return allOverloads.get(0);
|
||||
}
|
||||
|
||||
// First pass: look for return type compatible overload
|
||||
for (MethodInfo method : allOverloads) {
|
||||
TypeInfo returnType = method.getReturnType();
|
||||
if (returnType != null && noppes.npcs.client.gui.util.script.interpreter.type.TypeChecker.isTypeCompatible(
|
||||
expectedReturnType, returnType)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
// Second pass: return any overload (first one)
|
||||
return allOverloads.get(0);
|
||||
}
|
||||
|
||||
// ==================== INNER CLASS LOOKUP ====================
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ 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.token.TokenType;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -226,8 +227,8 @@ public class TypeInfo {
|
||||
if (javaClass == null) return null;
|
||||
|
||||
try {
|
||||
java.lang.reflect.Constructor<?>[] constructors = javaClass.getConstructors();
|
||||
for (java.lang.reflect.Constructor<?> ctor : constructors) {
|
||||
Constructor<?>[] constructors = javaClass.getConstructors();
|
||||
for (Constructor<?> ctor : constructors) {
|
||||
if (ctor.getParameterCount() == argCount) {
|
||||
return MethodInfo.fromReflectionConstructor(ctor, this);
|
||||
}
|
||||
@@ -237,6 +238,33 @@ public class TypeInfo {
|
||||
}
|
||||
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 (match) {
|
||||
return MethodInfo.fromReflectionConstructor(ctor, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Security or linkage error
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this type has a field with the given name.
|
||||
|
||||
Reference in new issue
Block a user