From c88bd50be9d0bb0dae56d177423248fc544cbd4a Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Wed, 11 Mar 2026 08:17:23 +0200 Subject: [PATCH] ScriptTypeInfo is now properly compared to TypeInfo parents + removed parent constructor mismatch errors from interfaces --- .../interpreter/type/ScriptTypeInfo.java | 13 ++--- .../script/interpreter/type/TypeChecker.java | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+), 9 deletions(-) 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 c4b8ff7f1..b75c4afcb 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 @@ -979,20 +979,18 @@ public class ScriptTypeInfo extends TypeInfo { // 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; + if (!parentScript.isInterface() && (!parentScript.hasConstructors() || parentScript.findConstructor(0) != null)) { + addConstructorMismatchError(superClass, superClass.getSimpleName()); } } else { Class javaClass = superClass.getJavaClass(); - if (javaClass != null) { + if (javaClass != null && !javaClass.isInterface()) { try { for (java.lang.reflect.Constructor ctor : javaClass.getConstructors()) { if (ctor.getParameterCount() == 0) { - parentHasNoArg = true; + addConstructorMismatchError(superClass, superClass.getSimpleName()); break; } } @@ -1002,9 +1000,6 @@ public class ScriptTypeInfo extends TypeInfo { } } - 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 diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java index 9f7e5369e..2e4a52b15 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java @@ -2,6 +2,8 @@ package noppes.npcs.client.gui.util.script.interpreter.type; import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeInfo; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashSet; import java.util.Set; @@ -110,6 +112,14 @@ public final class TypeChecker { return true; } + // ScriptTypeInfo inheritance: actual is a script-defined type that may extend/implement expected. + // ScriptTypeInfo.getJavaClass() returns null, so the Java isAssignableFrom gate below won't fire. + if (actual instanceof ScriptTypeInfo) { + if (isScriptTypeAssignableTo((ScriptTypeInfo) actual, expected)) { + return true; + } + } + // Object type compatibility (check inheritance) if (expected.getJavaClass() != null && actual.getJavaClass() != null) { Class expectedClass = expected.getJavaClass(); @@ -165,6 +175,46 @@ public final class TypeChecker { return false; } + /** + * Check assignability for ScriptTypeInfo by iteratively walking the extends/implements chain. + * Handles both direct and indirect inheritance (A extends B extends C implements D). + */ + private static boolean isScriptTypeAssignableTo(ScriptTypeInfo actual, TypeInfo expected) { + String expectedFull = expected.getFullName(); + String expectedSimple = expected.getSimpleName(); + + Deque worklist = new ArrayDeque<>(); + worklist.add(actual); + + while (!worklist.isEmpty()) { + TypeInfo current = worklist.poll(); + if (current == null || !current.isResolved()) continue; + + String currentFull = current.getFullName(); + if (currentFull != null) { + if (expectedFull != null && expectedFull.equals(currentFull)) return true; + if (expectedSimple != null && expectedSimple.equals(current.getSimpleName())) return true; + } + + if (current instanceof ScriptTypeInfo) { + ScriptTypeInfo scriptCurrent = (ScriptTypeInfo) current; + TypeInfo superClass = scriptCurrent.getSuperClass(); + if (superClass != null) { + worklist.add(superClass); + } + for (TypeInfo iface : scriptCurrent.getImplementedInterfaces()) { + worklist.add(iface); + } + } else if (current.getJavaClass() != null && expected.getJavaClass() != null) { + if (expected.getJavaClass().isAssignableFrom(current.getJavaClass())) { + return true; + } + } + } + + return false; + } + private static boolean areTypeArgumentsCompatible(TypeInfo expected, TypeInfo actual) { java.util.List expectedArgs = expected.getAppliedTypeArgs(); java.util.List actualArgs = actual.getAppliedTypeArgs();