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 2842a57c0..118521741 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 @@ -4217,7 +4217,7 @@ public class ScriptDocument { for (ScriptTypeInfo scriptType : scriptTypes.values()) { scriptType.clearErrors(); // Clear previous errors detectMethodInheritanceForType(scriptType); - validateScriptType(scriptType); // Validate after detecting inheritance + scriptType.validate(); // Validate after detecting inheritance } } @@ -4255,126 +4255,6 @@ public class ScriptDocument { } } - /** - * Validate a script type for missing interface methods and constructor matching. - */ - private void validateScriptType(ScriptTypeInfo scriptType) { - // Skip validation for interfaces - they don't implement methods - if (scriptType.getKind() == TypeInfo.Kind.INTERFACE) - return; - - // Check that all interface methods are implemented - if (scriptType.hasImplementedInterfaces()) { - for (TypeInfo iface : scriptType.getImplementedInterfaces()) { - if (iface == null || !iface.isResolved()) { - // Mark unresolved interface error - scriptType.setError(ScriptTypeInfo.ErrorType.UNRESOLVED_INTERFACE, - "Cannot resolve interface"); - continue; - } - validateInterfaceImplementation(scriptType, iface); - } - } - - // Check that extending class has a matching constructor - if (scriptType.hasSuperClass()) { - TypeInfo superClass = scriptType.getSuperClass(); - if (superClass == null || !superClass.isResolved()) { - scriptType.setError(ScriptTypeInfo.ErrorType.UNRESOLVED_PARENT, - "Cannot resolve parent class " + scriptType.getSuperClassName()); - } else { - validateConstructorChain(scriptType, superClass); - } - } - } - - /** - * Validate that a script type implements all methods from an interface. - */ - private void validateInterfaceImplementation(ScriptTypeInfo scriptType, TypeInfo iface) { - // Get all methods from the interface - Class javaClass = iface.getJavaClass(); - if (javaClass == null || !javaClass.isInterface()) - return; - - try { - for (java.lang.reflect.Method javaMethod : javaClass.getMethods()) { - // Skip static and default methods - if (java.lang.reflect.Modifier.isStatic(javaMethod.getModifiers())) - continue; - - // Check if scriptType has a matching method - String methodName = javaMethod.getName(); - int paramCount = javaMethod.getParameterCount(); - - boolean found = false; - List overloads = scriptType.getAllMethodOverloads(methodName); - for (MethodInfo method : overloads) { - if (parameterTypesMatch(method, javaMethod)) { - found = true; - break; - } - } - - if (!found) { - // Build signature string for error message - StringBuilder sig = new StringBuilder("("); - Class[] paramTypes = javaMethod.getParameterTypes(); - for (int i = 0; i < paramTypes.length; i++) { - if (i > 0) - sig.append(", "); - sig.append(paramTypes[i].getSimpleName()); - } - sig.append(")"); - - scriptType.addMissingMethodError(iface, methodName, sig.toString()); - } - } - } catch (Exception e) { - // Security or linkage error - } - } - - /** - * Validate that a script type has a constructor compatible with its parent class. - * This checks that for each parent constructor, there's a matching constructor in the child. - */ - private void validateConstructorChain(ScriptTypeInfo scriptType, TypeInfo superClass) { - // If no constructors defined in script type, it has an implicit default constructor - // Check if parent has a no-arg constructor - if (!scriptType.hasConstructors()) { - boolean parentHasNoArg = false; - - if (superClass instanceof ScriptTypeInfo) { - ScriptTypeInfo parentScript = (ScriptTypeInfo) superClass; - if (!parentScript.hasConstructors() || parentScript.findConstructor(0) != null) { - parentHasNoArg = true; - } - } else { - Class javaClass = superClass.getJavaClass(); - if (javaClass != null) { - try { - for (java.lang.reflect.Constructor ctor : javaClass.getConstructors()) { - if (ctor.getParameterCount() == 0) { - parentHasNoArg = true; - break; - } - } - } catch (Exception e) { - // Security error - } - } - } - - if (!parentHasNoArg) { - scriptType.addConstructorMismatchError(superClass, - superClass.getSimpleName() + "()"); - } - } - // If script type has constructors, we'd need to check super() calls - that's more complex - // For now, we just validate the implicit default constructor case - } - /** * Search for a matching method in a type hierarchy (class inheritance). * @param type The type to search in (and its superclasses) diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodSignature.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodSignature.java index ba7d3e61a..5cde0ac63 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodSignature.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodSignature.java @@ -2,6 +2,7 @@ package noppes.npcs.client.gui.util.script.interpreter.method; import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -78,4 +79,17 @@ public final class MethodSignature { sb.append(")"); return sb.toString(); } + + public static String asString(Method javaMethod) { + StringBuilder sig = new StringBuilder(javaMethod.getName()); + sig.append("("); + Class[] paramTypes = javaMethod.getParameterTypes(); + for (int i = 0; i < paramTypes.length; i++) { + if (i > 0) + sig.append(", "); + sig.append(paramTypes[i].getSimpleName()); + } + sig.append(")"); + return sig.toString(); + } } 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 0f84772f5..7febf5dc1 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 @@ -2,8 +2,11 @@ package noppes.npcs.client.gui.util.script.interpreter.type; 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.method.MethodSignature; 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.HashMap; import java.util.List; @@ -347,45 +350,6 @@ public class ScriptTypeInfo extends TypeInfo { /** * Represents a missing interface method error. */ - public static class MissingMethodError { - private final TypeInfo interfaceType; - private final String methodName; - private final String signature; - - public MissingMethodError(TypeInfo interfaceType, String methodName, String signature) { - this.interfaceType = interfaceType; - this.methodName = methodName; - this.signature = signature; - } - - public TypeInfo getInterfaceType() { return interfaceType; } - public String getMethodName() { return methodName; } - public String getSignature() { return signature; } - - public String getMessage() { - return "Class must implement method '" + methodName + signature + "' from interface " + interfaceType.getSimpleName(); - } - } - - /** - * Represents a constructor mismatch error. - */ - public static class ConstructorMismatchError { - private final TypeInfo parentType; - private final String parentConstructorSignature; - - public ConstructorMismatchError(TypeInfo parentType, String parentConstructorSignature) { - this.parentType = parentType; - this.parentConstructorSignature = parentConstructorSignature; - } - - public TypeInfo getParentType() { return parentType; } - public String getParentConstructorSignature() { return parentConstructorSignature; } - - public String getMessage() { - return "Class extends " + parentType.getSimpleName() + " but has no constructor matching " + parentConstructorSignature; - } - } // Error tracking private ErrorType errorType = ErrorType.NONE; @@ -411,8 +375,8 @@ public class ScriptTypeInfo extends TypeInfo { /** * Add a missing interface method error. */ - public void addMissingMethodError(TypeInfo interfaceType, String methodName, String signature) { - missingMethodErrors.add(new MissingMethodError(interfaceType, methodName, signature)); + public void addMissingMethodError(TypeInfo interfaceType, String signature) { + missingMethodErrors.add(new MissingMethodError(interfaceType, signature)); } /** @@ -431,4 +395,197 @@ public class ScriptTypeInfo extends TypeInfo { missingMethodErrors.clear(); constructorMismatchErrors.clear(); } -} + + public static class MissingMethodError { + private final TypeInfo interfaceType; + private final String signature; + + public MissingMethodError(TypeInfo interfaceType, String signature) { + this.interfaceType = interfaceType; + this.signature = signature; + } + + public String getMessage() { + return "Class must implement method '" + signature + "' from interface " + interfaceType.getSimpleName(); + } + } + + /** + * Represents a constructor mismatch error. + */ + public static class ConstructorMismatchError { + private final TypeInfo parentType; + private final String parentConstructorSignature; + + public ConstructorMismatchError(TypeInfo parentType, String parentConstructorSignature) { + this.parentType = parentType; + this.parentConstructorSignature = parentConstructorSignature; + } + + public String getMessage() { + return "Class extends " + parentType.getSimpleName() + " but has no constructor matching " + parentConstructorSignature; + } + } + + // ==================== VALIDATION ==================== + + /** + * Validate this script type for missing interface methods and constructor matching. + */ + @Override + public void validate() { + // Check that extending class has a matching constructor + if (hasSuperClass()) { + TypeInfo superClass = getSuperClass(); + if (superClass == null || !superClass.isResolved()) { + setError(ErrorType.UNRESOLVED_PARENT, + "Cannot resolve parent class " + getSuperClassName()); + } else { + validateConstructorChain(superClass); + } + } + + // Skip validation for interfaces - they don't implement methods + if (getKind() == Kind.INTERFACE) + return; + + // Check that all interface methods are implemented + if (hasImplementedInterfaces()) { + for (TypeInfo iface : getImplementedInterfaces()) { + if (iface == null || !iface.isResolved()) { + // Mark unresolved interface error + setError(ErrorType.UNRESOLVED_INTERFACE, "Cannot resolve interface"); + continue; + } + validateInterfaceImplementation(iface); + } + } + } + + /** + * Validate that this script type implements all methods from an interface. + */ + private void validateInterfaceImplementation(TypeInfo iface) { + // Handle Java interfaces + Class javaClass = iface.getJavaClass(); + if (javaClass != null && javaClass.isInterface()) { + try { + for (Method javaMethod : javaClass.getMethods()) { + // Skip static and default methods + if (Modifier.isStatic(javaMethod.getModifiers())) + continue; + + // Check if this type has a matching method + String methodName = javaMethod.getName(); + int paramCount = javaMethod.getParameterCount(); + + boolean found = false; + List overloads = getAllMethodOverloads(methodName); + for (MethodInfo method : overloads) { + if (parameterTypesMatch(method, javaMethod)) { + found = true; + break; + } + } + + //Limit to one error at a time to not spam all missing methods + if (!found && missingMethodErrors.isEmpty()) + addMissingMethodError(iface, MethodSignature.asString(javaMethod)); + } + } catch (Exception e) { + } + return; + } + + // Handle script-defined interfaces (ScriptType) + if (iface instanceof ScriptTypeInfo) { + ScriptTypeInfo ifaceType = (ScriptTypeInfo) iface; + + // Check all methods declared in the interface + for (MethodInfo ifaceMethod : ifaceType.getAllMethodsFlat()) { + MethodSignature ifaceSignature = ifaceMethod.getSignature(); + String methodName = ifaceMethod.getName(); + + boolean found = false; + List overloads = getAllMethodOverloads(methodName); + for (MethodInfo method : overloads) { + if (method.getSignature().equals(ifaceSignature)) { + found = true; + break; + } + } + + //Limit to one error at a time to not spam all missing methods + if (!found && missingMethodErrors.isEmpty()) + addMissingMethodError(iface, ifaceSignature.toString()); + } + } + } + + /** + * Validate that this script type has a constructor compatible with its parent class. + * This checks that for each parent constructor, there's a matching constructor in the child. + */ + private void validateConstructorChain(TypeInfo superClass) { + // If no constructors defined in script type, it has an implicit default constructor + // Check if parent has a no-arg constructor + if (!hasConstructors()) { + boolean parentHasNoArg = false; + + if (superClass instanceof ScriptTypeInfo) { + ScriptTypeInfo parentScript = (ScriptTypeInfo) superClass; + if (!parentScript.hasConstructors() || parentScript.findConstructor(0) != null) { + parentHasNoArg = true; + } + } else { + Class javaClass = superClass.getJavaClass(); + if (javaClass != null) { + try { + for (java.lang.reflect.Constructor ctor : javaClass.getConstructors()) { + if (ctor.getParameterCount() == 0) { + parentHasNoArg = true; + break; + } + } + } catch (Exception e) { + // Security error + } + } + } + + if (!parentHasNoArg) { + addConstructorMismatchError(superClass, superClass.getSimpleName()); + } + } + // If script type has constructors, we'd need to check super() calls - that's more complex + // For now, we just validate the implicit default constructor case + } + + /** + * Check if a MethodInfo's parameter types match a Java reflection Method's parameter types. + */ + private boolean parameterTypesMatch(MethodInfo methodInfo, Method javaMethod) { + List params = methodInfo.getParameters(); + Class[] javaParams = javaMethod.getParameterTypes(); + + if (params.size() != javaParams.length) + return false; + + for (int i = 0; i < params.size(); i++) { + TypeInfo paramType = params.get(i).getDeclaredType(); + if (paramType == null) + continue; // Unresolved param, skip check + + Class javaParamClass = javaParams[i]; + String javaParamName = javaParamClass.getName(); + + // Compare type names + if (!paramType.getFullName().equals(javaParamName) && + !paramType.getSimpleName().equals(javaParamClass.getSimpleName())) { + return false; + } + } + + return true; + } +} \ No newline at end of file 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 7e2b512c2..9ec721ab3 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 @@ -461,6 +461,14 @@ public class TypeInfo { return null; } + /** + * Validate this type. Default implementation does nothing (for Java types). + * Override in ScriptTypeInfo to validate script-defined types. + */ + public void validate() { + // Default: no validation for Java types + } + @Override public String toString() { return "TypeInfo{" + fullName + ", " + kind + ", resolved=" + resolved + "}";