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 a43210619..fa230c4ac 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 @@ -4513,6 +4513,10 @@ public class ScriptDocument { // Methods/functions - UNIFIED (uses shared 'methods' list) markMethodDeclarations(marks); + // Pre-infer constructor lambda parameter types so body resolution works + // Must run before markMethodCalls so lambda body chains/calls see typed parameters + inferConstructorLambdaTypes(); + // Method calls - UNIFIED (stores in shared 'methodCalls' list) markMethodCalls(marks); @@ -9377,6 +9381,57 @@ for (ScriptTypeInfo type:scriptTypes.values()) { } } + /** + * Pre-infer lambda parameter types for constructor arguments (new Foo(... lambda ...)). + * Constructor SAM inference normally happens in markImportedClassUsages, which runs AFTER + * markMethodCalls/markVariables/markChainedFieldAccesses. That ordering means lambda body + * resolution (e.g., event.getHookName()) fails because parameter types aren't set yet. + * This early pass sets inferredType on lambda parameters so body resolution works correctly. + */ + private void inferConstructorLambdaTypes() { + Pattern newExpr = Pattern.compile("\\bnew\\s+([A-Za-z][a-zA-Z0-9_.]*?)\\s*(?:<[^>]*>)?\\s*\\("); + Matcher m = newExpr.matcher(text); + + while (m.find()) { + if (isExcluded(m.start())) + continue; + if (isInImportOrPackage(m.start())) + continue; + + String className = m.group(1); + int openParen = m.end() - 1; + int closeParen = findMatchingParen(openParen); + if (closeParen < 0) + continue; + + String argsText = text.substring(openParen + 1, closeParen); + boolean hasLambda = isJavaScript() + ? (argsText.contains("function") || argsText.contains("=>")) + : argsText.contains("->"); + if (!hasLambda) + continue; + + TypeInfo info = resolveType(className, m.start(1)); + if (info == null || !info.isResolved()) + continue; + if (!info.hasConstructors()) + continue; + + List arguments = parseMethodArguments(openParen + 1, closeParen, null, info); + TypeInfo[] argTypes = arguments.stream().map(MethodCallInfo.Argument::getResolvedType) + .toArray(TypeInfo[]::new); + + MethodInfo constructor = info.findConstructor(argTypes); + if (constructor == null) { + constructor = info.findConstructor(arguments.size()); + } + + if (constructor != null && constructor.getParameters().size() == arguments.size()) { + parseMethodArguments(openParen + 1, closeParen, constructor, info); + } + } + } + private void markImportedClassUsages(List marks) { // Find uppercase identifiers followed by dot (static method calls, field access) Pattern classUsage = Pattern.compile("\\b([A-Za-z][a-zA-Z0-9_]*)\\s*\\."); diff --git a/tools/standalone-JaninoLoader-1.0.2.jar b/tools/standalone-JaninoLoader-1.0.2.jar index c935aa932..dc76828fd 100644 Binary files a/tools/standalone-JaninoLoader-1.0.2.jar and b/tools/standalone-JaninoLoader-1.0.2.jar differ