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[] 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; }