From 7b77c1935fb2970bf9adc7b5b741c1dd7bb69f70 Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Wed, 31 Dec 2025 23:36:44 +0200 Subject: [PATCH] Added super() error validation --- .../interpreter/ErrorUnderlineRenderer.java | 15 +- .../script/interpreter/ScriptDocument.java | 196 +++++++++++- .../interpreter/type/ScriptTypeInfo.java | 291 ++++++++++++++++++ .../script/interpreter/type/TypeInfo.java | 32 +- 4 files changed, 515 insertions(+), 19 deletions(-) diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ErrorUnderlineRenderer.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ErrorUnderlineRenderer.java index b9778e6ab..b5c8cfd38 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ErrorUnderlineRenderer.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ErrorUnderlineRenderer.java @@ -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); } } 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 118521741..740870e71 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 @@ -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 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 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 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 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 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( 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 7febf5dc1..a9bcc0333 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 @@ -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 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 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 getAllMethodOverloadsInHierarchy(String methodName) { + List 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 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 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 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 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 ==================== 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 9ec721ab3..2d9d97252 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 @@ -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.