From e63b027faddddfc177ea7ee66fa97718f1dd614d Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Sat, 11 Oct 2025 21:32:21 +0200
Subject: [PATCH] Typecheck return()
Typecheck `return;` and `return @val;` value against its expected type from the surrounding procedure, closure or bind.
---
.../compiler/analysis/IncludeReference.java | 2 +-
.../core/compiler/analysis/Namespace.java | 3 +-
.../analysis/ReturnableDeclaration.java | 22 +++++++
.../analysis/ReturnableReference.java | 18 ++++++
.../core/functions/ControlFlow.java | 58 +++++++++++++++++++
.../core/functions/DataHandling.java | 19 +++++-
.../core/functions/EventBinding.java | 4 ++
7 files changed, 121 insertions(+), 5 deletions(-)
create mode 100644 src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableDeclaration.java
create mode 100644 src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableReference.java
diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/IncludeReference.java b/src/main/java/com/laytonsmith/core/compiler/analysis/IncludeReference.java
index a1ea4cccb1..df1c72d173 100644
--- a/src/main/java/com/laytonsmith/core/compiler/analysis/IncludeReference.java
+++ b/src/main/java/com/laytonsmith/core/compiler/analysis/IncludeReference.java
@@ -18,7 +18,7 @@ public class IncludeReference extends Reference {
* @param identifier - The include identifier (the path passed to include(), which should resolve to a file).
* @param inScope - The parent scope which the include should be linked to.
* @param outScope - The scope in which the include should be usable.
- * @param t - The target of the declaration.
+ * @param t - The target of the reference.
*/
public IncludeReference(String identifier, Scope inScope, Scope outScope, Target t) {
super(Namespace.INCLUDE, identifier, t);
diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/Namespace.java b/src/main/java/com/laytonsmith/core/compiler/analysis/Namespace.java
index cb0e1fdcfa..fcf9c47bac 100644
--- a/src/main/java/com/laytonsmith/core/compiler/analysis/Namespace.java
+++ b/src/main/java/com/laytonsmith/core/compiler/analysis/Namespace.java
@@ -9,5 +9,6 @@ public enum Namespace {
IVARIABLE,
IVARIABLE_ASSIGN,
PROCEDURE,
- INCLUDE
+ INCLUDE,
+ RETURNABLE
}
diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableDeclaration.java b/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableDeclaration.java
new file mode 100644
index 0000000000..9040fc2914
--- /dev/null
+++ b/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableDeclaration.java
@@ -0,0 +1,22 @@
+package com.laytonsmith.core.compiler.analysis;
+
+import com.laytonsmith.core.NodeModifiers;
+import com.laytonsmith.core.constructs.CClassType;
+import com.laytonsmith.core.constructs.Target;
+
+/**
+ * Represents a returnable declaration in a scope graph.
+ * @author P.J.S. Kools
+ */
+public class ReturnableDeclaration extends Declaration {
+
+ /**
+ * Creates a new {@link ReturnableDeclaration} in the {@link Namespace#RETURNABLE} namespace.
+ * @param type The expected return {@link CClassType}.
+ * @param modifiers The node modifiers.
+ * @param t The returnable target.
+ */
+ public ReturnableDeclaration(CClassType type, NodeModifiers modifiers, Target t) {
+ super(Namespace.RETURNABLE, null, type, modifiers, t);
+ }
+}
diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableReference.java b/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableReference.java
new file mode 100644
index 0000000000..af63eacb33
--- /dev/null
+++ b/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableReference.java
@@ -0,0 +1,18 @@
+package com.laytonsmith.core.compiler.analysis;
+
+import com.laytonsmith.core.constructs.Target;
+
+/**
+ * Represents a reference to a returnable in a scope graph.
+ * @author P.J.S. Kools
+ */
+public class ReturnableReference extends Reference {
+
+ /**
+ * Creates a new {@link ReturnableReference} in the {@link Namespace#RETURNABLE} scope.
+ * @param t - The target of the reference.
+ */
+ public ReturnableReference(Target t) {
+ super(Namespace.RETURNABLE, null, t);
+ }
+}
diff --git a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
index 0883b07c8f..cdd3919f79 100644
--- a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
+++ b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
@@ -23,6 +23,9 @@
import com.laytonsmith.core.compiler.FileOptions;
import com.laytonsmith.core.compiler.SelfStatement;
import com.laytonsmith.core.compiler.VariableScope;
+import com.laytonsmith.core.compiler.analysis.Declaration;
+import com.laytonsmith.core.compiler.analysis.Namespace;
+import com.laytonsmith.core.compiler.analysis.ReturnableReference;
import com.laytonsmith.core.compiler.analysis.Scope;
import com.laytonsmith.core.compiler.analysis.StaticAnalysis;
import com.laytonsmith.core.compiler.signature.FunctionSignatures;
@@ -2411,11 +2414,66 @@ public String docs() {
+ " see the docs on try/catch, particularly the finally clause for example).";
}
+ @Override
+ public CClassType typecheck(StaticAnalysis analysis,
+ ParseTree ast, Environment env, Set exceptions) {
+
+ // Get value type.
+ CClassType valType;
+ Target valTarget;
+ if(ast.numberOfChildren() == 0) {
+ valType = CVoid.TYPE;
+ valTarget = ast.getTarget();
+ } else if(ast.numberOfChildren() == 1) {
+ ParseTree valNode = ast.getChildAt(0);
+ valType = analysis.typecheck(valNode, env, exceptions);
+ valTarget = valNode.getTarget();
+ } else {
+
+ // Fall back to default behavior for invalid usage.
+ return super.typecheck(analysis, ast, env, exceptions);
+ }
+
+ // Resolve this returnable reference to its returnable declaration to get its required return type.
+ Scope scope = analysis.getTermScope(ast);
+ if(scope != null) {
+ Set decls = scope.getDeclarations(Namespace.RETURNABLE, null);
+ if(decls.size() == 0) {
+ exceptions.add(new ConfigCompileException("Return is not valid in this context.", ast.getTarget()));
+ } else {
+
+ // Type check return value for all found declared return types.
+ for(Declaration decl : decls) {
+ StaticAnalysis.requireType(valType, decl.getType(), valTarget, env, exceptions);
+ }
+ }
+ }
+
+ // Return void.
+ return CVoid.TYPE;
+ }
+
@Override
public Class extends CREThrowable>[] thrown() {
return null;
}
+ @Override
+ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast,
+ Environment env, Set exceptions) {
+
+ // Handle children. These will execute before this return().
+ Scope scope = super.linkScope(analysis, parentScope, ast, env, exceptions);
+
+ // Add returnable reference in new scope.
+ scope = analysis.createNewScope(scope);
+ scope.addReference(new ReturnableReference(ast.getTarget()));
+ analysis.setTermScope(ast, scope);
+
+ // Return scope.
+ return scope;
+ }
+
@Override
public boolean isRestricted() {
return false;
diff --git a/src/main/java/com/laytonsmith/core/functions/DataHandling.java b/src/main/java/com/laytonsmith/core/functions/DataHandling.java
index d07be64451..993971e8e4 100644
--- a/src/main/java/com/laytonsmith/core/functions/DataHandling.java
+++ b/src/main/java/com/laytonsmith/core/functions/DataHandling.java
@@ -40,6 +40,7 @@
import com.laytonsmith.core.compiler.analysis.ProcDeclaration;
import com.laytonsmith.core.compiler.analysis.ProcRootDeclaration;
import com.laytonsmith.core.compiler.analysis.Reference;
+import com.laytonsmith.core.compiler.analysis.ReturnableDeclaration;
import com.laytonsmith.core.compiler.analysis.Scope;
import com.laytonsmith.core.compiler.analysis.StaticAnalysis;
import com.laytonsmith.core.compiler.signature.FunctionSignatures;
@@ -1626,6 +1627,9 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast
// Allow procedures to perform lookups in the decl scope.
paramScope.addSpecificParent(declScope, Namespace.PROCEDURE);
+ // Create returnable declaration in the inner root scope.
+ paramScope.addDeclaration(new ReturnableDeclaration(retType, ast.getNodeModifiers(), ast.getTarget()));
+
// Return the declaration scope. Parameters and their default values are not accessible after the procedure.
return declScope;
}
@@ -2703,10 +2707,16 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast
return parentScope;
}
- // Handle optional return type argument.
+ // Handle optional return type argument (CClassType or CVoid, default to AUTO).
int ind = 0;
- if(ast.getChildAt(ind).getData().isInstanceOf(CClassType.TYPE)) {
- analysis.linkScope(parentScope, ast.getChildAt(ind++), env, exceptions);
+ CClassType retType;
+ if(ast.getChildAt(ind).getData() instanceof CClassType) {
+ retType = (CClassType) ast.getChildAt(ind++).getData();
+ } else if(ast.getChildAt(ind).getData().equals(CVoid.VOID)) {
+ ind++;
+ retType = CVoid.TYPE;
+ } else {
+ retType = CClassType.AUTO;
}
// Create parameter scope. Set parent scope if this closure type is allowed to resolve in the parent scope.
@@ -2732,6 +2742,9 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast
paramScope = scopes[0];
}
+ // Create returnable declaration in the inner root scope.
+ paramScope.addDeclaration(new ReturnableDeclaration(retType, ast.getNodeModifiers(), ast.getTarget()));
+
// Handle closure code.
ParseTree code = ast.getChildAt(ast.numberOfChildren() - 1);
analysis.linkScope(paramScope, code, env, exceptions);
diff --git a/src/main/java/com/laytonsmith/core/functions/EventBinding.java b/src/main/java/com/laytonsmith/core/functions/EventBinding.java
index e35efe281d..f562c596a8 100644
--- a/src/main/java/com/laytonsmith/core/functions/EventBinding.java
+++ b/src/main/java/com/laytonsmith/core/functions/EventBinding.java
@@ -19,6 +19,7 @@
import com.laytonsmith.core.compiler.SelfStatement;
import com.laytonsmith.core.compiler.VariableScope;
import com.laytonsmith.core.compiler.analysis.Namespace;
+import com.laytonsmith.core.compiler.analysis.ReturnableDeclaration;
import com.laytonsmith.core.compiler.analysis.Scope;
import com.laytonsmith.core.compiler.analysis.StaticAnalysis;
import com.laytonsmith.core.constructs.CArray;
@@ -337,6 +338,9 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope,
}
analysis.linkScope(paramScope, code, env, exceptions);
+ // Create returnable declaration in the inner root scope.
+ paramScope.addDeclaration(new ReturnableDeclaration(CVoid.TYPE, ast.getNodeModifiers(), ast.getTarget()));
+
// Allow code after bind() to access declarations in assigned values, but not parameters themselves.
return valScope;
}