From a74c7d687c77121d82c2f8541a863ce269623b27 Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Thu, 20 Nov 2025 04:44:59 +0100
Subject: [PATCH 01/11] Typecheck procedure signatures
---
.../compiler/analysis/StaticAnalysis.java | 68 +++++++++++++++----
1 file changed, 55 insertions(+), 13 deletions(-)
diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java b/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java
index eacf81b14..3ec3a1171 100644
--- a/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java
+++ b/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java
@@ -14,6 +14,8 @@
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.Static;
import com.laytonsmith.core.compiler.CompilerEnvironment;
+import com.laytonsmith.core.compiler.signature.FunctionSignatures;
+import com.laytonsmith.core.compiler.signature.SignatureBuilder;
import com.laytonsmith.core.constructs.Auto;
import com.laytonsmith.core.constructs.CClassType;
import com.laytonsmith.core.constructs.CFunction;
@@ -379,28 +381,68 @@ public CClassType typecheck(ParseTree ast, Environment env, Set argTypes = new ArrayList<>(ast.numberOfChildren());
+ List argTargets = new ArrayList<>(ast.numberOfChildren());
for(ParseTree child : ast.getChildren()) {
- this.typecheck(child, env, exceptions);
+ argTypes.add(this.typecheck(child, env, exceptions));
+ argTargets.add(child.getTarget());
}
- // Return procedure return type.
+ // Get procedure declaration.
String procName = cFunc.val();
- Scope scope = this.getTermScope(ast);
- if(scope != null) {
- Set decls = scope.getDeclarations(Namespace.PROCEDURE, procName);
- if(decls.isEmpty()) {
- return CClassType.AUTO; // Proc cannot be resolved. Exception for this is already generated.
- } else {
- // TODO - Get the most specific type when multiple declarations exist.
- return decls.iterator().next().getType();
- }
- } else {
+ Target procTarget = cFunc.getTarget();
+ Scope procRefScope = this.getTermScope(ast);
+ if(procRefScope == null) {
+
// If this runs, then a proc reference was created without setting its Scope using setTermScope().
exceptions.add(new ConfigCompileException("Procedure cannot be resolved (missing procedure scope,"
+ " this is an internal error that should never happen): "
- + procName, cFunc.getTarget()));
+ + procName, procTarget));
return CClassType.AUTO;
}
+ Set decls = procRefScope.getDeclarations(Namespace.PROCEDURE, procName);
+ if(decls.isEmpty()) {
+ return CClassType.AUTO; // Proc cannot be resolved. Exception already generated.
+ }
+
+ // Create procedure signatures.
+ // TODO - Consider creating one SignatureBuilder per declaration. With the current implementation, matching any possible declaration is sufficient. We want it to match all instead.
+ List procReturnTypes = new ArrayList<>(1);
+ for(Declaration decl : decls) {
+ if(decl instanceof ProcDeclaration procDecl) {
+
+ // Create new procedure signature.
+ SignatureBuilder signatureBuilder = new SignatureBuilder(procDecl.getType());
+ for(ParamDeclaration paramDecl : procDecl.getParameters()) {
+ CClassType paramType = paramDecl.getType();
+ if(paramType.isVariadicType()) {
+ signatureBuilder.varParam(
+ paramType.getVarargsBaseType(), paramDecl.getIdentifier(), null);
+ } else {
+ signatureBuilder.param(paramType,
+ paramDecl.getIdentifier(), null, paramDecl.getDefaultValue() != null);
+ }
+ }
+
+ // Typecheck arguments against new procedure signature.
+ FunctionSignatures procSignature = signatureBuilder.build();
+ procReturnTypes.add(procSignature.getReturnType(
+ procTarget, argTypes, argTargets, env, exceptions));
+ } else {
+
+ // If this runs, then a wrong declaration type proc declaration was created.
+ exceptions.add(new ConfigCompileException("Procedure resolves to non-procedure declaration"
+ + " (this is an internal error that should never happen): "
+ + procName + " -> " + decl.getIdentifier(), procTarget));
+ }
+ }
+ if(procReturnTypes.isEmpty()) {
+ return CClassType.AUTO; // No ProcDeclarations found. Exception already generated.
+ }
+
+ // Return procedure return type.
+ // TODO - Get the most specific type when multiple declarations exist.
+ return procReturnTypes.get(0);
} else {
throw new Error("Unsupported " + CFunction.class.getSimpleName()
+ " type in type checking for node with value: " + cFunc.val());
From c792f50e477573e3a13fdcca5643adfe1a7c4052 Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Fri, 21 Nov 2025 04:09:00 +0100
Subject: [PATCH 02/11] Disallow mandatory-after-optional params in proc
signature typechecking
---
.../java/com/laytonsmith/core/Procedure.java | 18 ++++++++++++++----
.../core/compiler/analysis/StaticAnalysis.java | 16 +++++++++++++---
2 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/src/main/java/com/laytonsmith/core/Procedure.java b/src/main/java/com/laytonsmith/core/Procedure.java
index 8e62f139c..43cbb2805 100644
--- a/src/main/java/com/laytonsmith/core/Procedure.java
+++ b/src/main/java/com/laytonsmith/core/Procedure.java
@@ -66,10 +66,12 @@ public Procedure(String name, CClassType returnType, List varList, Sm
this.definedAt = t;
this.varList = new HashMap<>();
this.procComment = procComment;
+ boolean optionalParamDetected = false;
for(int i = 0; i < varList.size(); i++) {
IVariable var = varList.get(i);
if(var.getDefinedType().isVariadicType() && i != varList.size() - 1) {
- throw new CREFormatException("Varargs can only be added to the last argument.", t);
+ throw new CREFormatException(
+ "Varargs can only be added to the last parameter in a procedure.", var.getTarget());
}
try {
this.varList.put(var.getVariableName(), var.clone());
@@ -77,14 +79,22 @@ public Procedure(String name, CClassType returnType, List varList, Sm
this.varList.put(var.getVariableName(), var);
}
this.varIndex.add(var);
- if(var.getDefinedType().isVariadicType() && var.ival() != CNull.UNDEFINED) {
- throw new CREFormatException("Varargs may not have default values", t);
+ boolean paramOptional = (var.ival() != CNull.UNDEFINED);
+ if(paramOptional) {
+ if(var.getDefinedType().isVariadicType()) {
+ throw new CREFormatException("Varargs may not have default values.", var.getTarget());
+ }
+ optionalParamDetected = true;
+ } else if(optionalParamDetected && !var.getDefinedType().isVariadicType()) {
+ throw new CREFormatException(
+ "Procedure parameters after optional parameters must be optional or varargs.", var.getTarget());
}
this.originals.put(var.getVariableName(), var.ival());
}
this.tree = tree;
if(!PROCEDURE_NAME_REGEX.matcher(name).matches()) {
- throw new CREFormatException("Procedure names must start with an underscore, and may only contain letters, underscores, and digits. (Found " + this.name + ")", t);
+ throw new CREFormatException("Procedure names must start with an underscore,"
+ + " and may only contain letters, underscores, and digits. (Found " + this.name + ")", t);
}
//Let's look through the tree now, and see if this is possibly constant or not.
//If it is, it may or may not help us during compilation, but if it's not,
diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java b/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java
index 3ec3a1171..7ab7cafb0 100644
--- a/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java
+++ b/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java
@@ -406,21 +406,31 @@ public CClassType typecheck(ParseTree ast, Environment env, Set procReturnTypes = new ArrayList<>(1);
for(Declaration decl : decls) {
if(decl instanceof ProcDeclaration procDecl) {
// Create new procedure signature.
SignatureBuilder signatureBuilder = new SignatureBuilder(procDecl.getType());
+ boolean optionalParamDetected = false;
+ boolean varargsParamDetected = false;
for(ParamDeclaration paramDecl : procDecl.getParameters()) {
CClassType paramType = paramDecl.getType();
+ if(varargsParamDetected) {
+ return CClassType.AUTO; // Invalid procedure signature. Exception(s) already generated.
+ }
if(paramType.isVariadicType()) {
signatureBuilder.varParam(
paramType.getVarargsBaseType(), paramDecl.getIdentifier(), null);
+ varargsParamDetected = true;
} else {
- signatureBuilder.param(paramType,
- paramDecl.getIdentifier(), null, paramDecl.getDefaultValue() != null);
+ boolean paramOptional = paramDecl.getDefaultValue() != null;
+ signatureBuilder.param(paramType, paramDecl.getIdentifier(), null, paramOptional);
+ if(paramOptional) {
+ optionalParamDetected = true;
+ } else if(optionalParamDetected) {
+ return CClassType.AUTO; // Invalid procedure signature. Exception(s) already generated.
+ }
}
}
From 0e1db49358ace4e7f38d7dbee5819cf15fce1838 Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Sun, 23 Nov 2025 18:31:42 +0100
Subject: [PATCH 03/11] Update procedure behavior
- Prevent injected `@arguments` variable overwriting an explicitly declared procedure parameter named `@arguments`.
- Initialize procedure varargs parameter with an empty array if it matches no arguments. Prevents varargs parameter value being `CNull.UNDEFINED` when it has no matching arguments.
---
.../java/com/laytonsmith/core/Procedure.java | 118 ++++++++++--------
.../core/functions/DataHandling.java | 5 +-
2 files changed, 69 insertions(+), 54 deletions(-)
diff --git a/src/main/java/com/laytonsmith/core/Procedure.java b/src/main/java/com/laytonsmith/core/Procedure.java
index 43cbb2805..483221a56 100644
--- a/src/main/java/com/laytonsmith/core/Procedure.java
+++ b/src/main/java/com/laytonsmith/core/Procedure.java
@@ -7,6 +7,7 @@
import com.laytonsmith.core.constructs.CClassType;
import com.laytonsmith.core.constructs.CFunction;
import com.laytonsmith.core.constructs.CNull;
+import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.CVoid;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.IVariable;
@@ -199,92 +200,107 @@ public Mixed cexecute(List args, Environment env, Target t) {
* Executes this procedure, with the arguments that were passed in
*
* @param args The arguments passed to the procedure call.
- * @param oldEnv The environment to be cloned.
+ * @param parentEnv The environment to be cloned.
* @param t
* @return
*/
- public Mixed execute(List args, Environment oldEnv, Target t) {
- boolean prev = oldEnv.getEnv(GlobalEnv.class).getCloneVars();
- oldEnv.getEnv(GlobalEnv.class).setCloneVars(false);
+ public Mixed execute(List args, Environment parentEnv, Target t) {
+ boolean prevCloneVars = parentEnv.getEnv(GlobalEnv.class).getCloneVars();
+ parentEnv.getEnv(GlobalEnv.class).setCloneVars(false);
Environment env;
try {
- env = oldEnv.clone();
+ env = parentEnv.clone();
env.getEnv(GlobalEnv.class).setCloneVars(true);
} catch (CloneNotSupportedException ex) {
throw new RuntimeException(ex);
}
- oldEnv.getEnv(GlobalEnv.class).setCloneVars(prev);
+ parentEnv.getEnv(GlobalEnv.class).setCloneVars(prevCloneVars);
Script fakeScript = Script.GenerateScript(tree, env.getEnv(GlobalEnv.class).GetLabel(), null);
// Create container for the @arguments variable.
CArray arguments = new CArray(Target.UNKNOWN, this.varIndex.size());
+ env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(CArray.TYPE, "@arguments", arguments, t));
+
+ // Initialize varargs parameter if present.
+ CArray vararg = null;
+ IVariable varargsVar = (!this.varIndex.isEmpty()
+ && this.varIndex.get(this.varIndex.size() - 1).getDefinedType().isVariadicType()
+ ? this.varIndex.get(this.varIndex.size() - 1) : null);
+ if(varargsVar != null) {
+ vararg = new CArray(t); // TODO - Add type once generics are implemented.
+ Target varargsTarget = (args.size() >= this.varIndex.size()
+ ? args.get(this.varIndex.size() - 1).getTarget() : t);
+ env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(CArray.TYPE,
+ varargsVar.getVariableName(), vararg, varargsTarget));
+ }
// Handle passed procedure arguments.
int varInd;
- CArray vararg = null;
for(varInd = 0; varInd < args.size(); varInd++) {
- Mixed c = args.get(varInd);
- arguments.push(c, t);
- if(this.varIndex.size() > varInd
- || (!this.varIndex.isEmpty()
- && this.varIndex.get(this.varIndex.size() - 1).getDefinedType().isVariadicType())) {
- IVariable var;
- if(varInd < this.varIndex.size() - 1
- || !this.varIndex.get(this.varIndex.size() - 1).getDefinedType().isVariadicType()) {
- var = this.varIndex.get(varInd);
- } else {
- var = this.varIndex.get(this.varIndex.size() - 1);
- if(vararg == null) {
- // TODO: Once generics are added, add the type
- vararg = new CArray(t);
- env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(CArray.TYPE,
- var.getVariableName(), vararg, c.getTarget()));
- }
- }
- // Type check "void" value.
- if(c instanceof CVoid
- && !(var.getDefinedType().equals(Auto.TYPE) || var.getDefinedType().equals(CVoid.TYPE))) {
- throw new CRECastException("Procedure \"" + name + "\" expects a value of type "
- + var.getDefinedType().val() + " in argument " + (varInd + 1) + ", but"
- + " a void value was found instead.", c.getTarget());
- }
+ // Get argument value.
+ Mixed argVal = args.get(varInd);
- // Type check vararg parameter.
- if(var.getDefinedType().isVariadicType()) {
- if(InstanceofUtil.isInstanceof(c.typeof(), var.getDefinedType().getVarargsBaseType(), env)) {
- vararg.push(c, t);
- continue;
- } else {
- throw new CRECastException("Procedure \"" + name + "\" expects a value of type "
- + var.getDefinedType().val() + " in argument " + (varInd + 1) + ", but"
- + " a value of type " + c.typeof() + " was found instead.", c.getTarget());
- }
- }
+ // Add argument value to @arguments array.
+ arguments.push(argVal, t);
+
+ // Get parameter to assign argument value to.
+ IVariable var;
+ if(this.varIndex.size() > varInd) {
+ var = this.varIndex.get(varInd);
+ } else if(varargsVar != null) {
+ var = varargsVar;
+ } else {
+ continue; // No parameters remaining and no varargs present. Ignore excessive arguments.
+ }
+
+ // Type check "void" argument/parameter.
+ if(argVal instanceof CVoid
+ && !(var.getDefinedType().equals(Auto.TYPE) || var.getDefinedType().equals(CVoid.TYPE))) {
+ throw new CRECastException("Procedure \"" + name + "\" expects a value of type "
+ + var.getDefinedType().val() + " in argument " + (varInd + 1) + ", but"
+ + " a void value was found instead.", argVal.getTarget());
+ }
- // Type check non-vararg parameter.
- if(InstanceofUtil.isInstanceof(c.typeof(), var.getDefinedType(), env)) {
- env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(var.getDefinedType(),
- var.getVariableName(), c, c.getTarget()));
+ // Type check vararg argument/parameter.
+ if(var.getDefinedType().isVariadicType()) {
+ if(InstanceofUtil.isInstanceof(argVal.typeof(), var.getDefinedType().getVarargsBaseType(), env)) {
+ vararg.push(argVal, t);
continue;
} else {
throw new CRECastException("Procedure \"" + name + "\" expects a value of type "
+ var.getDefinedType().val() + " in argument " + (varInd + 1) + ", but"
- + " a value of type " + c.typeof() + " was found instead.", c.getTarget());
+ + " a value of type " + argVal.typeof() + " was found instead.", argVal.getTarget());
}
}
+
+ // Type check non-vararg argument/parameter.
+ if(InstanceofUtil.isInstanceof(argVal.typeof(), var.getDefinedType(), env)) {
+ env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(var.getDefinedType(),
+ var.getVariableName(), argVal, argVal.getTarget()));
+ continue;
+ } else {
+ throw new CRECastException("Procedure \"" + name + "\" expects a value of type "
+ + var.getDefinedType().val() + " in argument " + (varInd + 1) + ", but"
+ + " a value of type " + argVal.typeof() + " was found instead.", argVal.getTarget());
+ }
}
// Assign default values to remaining proc arguments.
- while(varInd < this.varIndex.size()) {
- String varName = this.varIndex.get(varInd++).getVariableName();
+ int nonVarargVarSize = (vararg == null ? this.varIndex.size() : this.varIndex.size() - 1);
+ while(varInd < nonVarargVarSize) {
+ IVariable paramVar = this.varIndex.get(varInd++);
+ String varName = paramVar.getVariableName();
Mixed defVal = this.originals.get(varName);
+ if(defVal == CNull.UNDEFINED) {
+ defVal = new CString("", paramVar.getTarget()); // Fill in empty string for undefined parameters.
+ }
env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(Auto.TYPE, varName, defVal, defVal.getTarget()));
arguments.push(defVal, t);
}
- env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(CArray.TYPE, "@arguments", arguments, t));
+ // Execute procedure body.
StackTraceManager stManager = env.getEnv(GlobalEnv.class).GetStackTraceManager();
stManager.addStackTraceElement(new ConfigRuntimeException.StackTraceElement("proc " + name, getTarget()));
try {
diff --git a/src/main/java/com/laytonsmith/core/functions/DataHandling.java b/src/main/java/com/laytonsmith/core/functions/DataHandling.java
index f0dfc4296..08dbca786 100644
--- a/src/main/java/com/laytonsmith/core/functions/DataHandling.java
+++ b/src/main/java/com/laytonsmith/core/functions/DataHandling.java
@@ -1594,8 +1594,7 @@ public static Procedure getProcedure(Target t, Environment env, Script parent, P
env.getEnv(GlobalEnv.class).ClearFlag(GlobalEnv.FLAG_VAR_ARGS_ALLOWED);
}
if(paramDefaultValues[i] instanceof IVariable ivar) {
- paramDefaultValues[i] = env.getEnv(GlobalEnv.class)
- .GetVarList().get(ivar.getVariableName(), t, true, env).ival();
+ paramDefaultValues[i] = originalList.get(ivar.getVariableName(), t, env).ival();
}
// Mark proc as not constant if a default parameter is not a constant.
@@ -1678,7 +1677,7 @@ public static Procedure getProcedure(Target t, Environment env, Script parent, P
varNames.add(varName);
// Get IVariable value.
- Mixed ivarVal = (paramDefaultValue != null ? paramDefaultValue : new CString("", t));
+ Mixed ivarVal = (paramDefaultValue != null ? paramDefaultValue : CNull.UNDEFINED);
// Store IVariable object clone.
vars.add(new IVariable(ivar.getDefinedType(), ivar.getVariableName(), ivarVal, ivar.getTarget()));
From c5f680147c2fc63f7bf7973ec3b3c86ffe1ec93f Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Tue, 25 Nov 2025 03:32:03 +0100
Subject: [PATCH 04/11] Add compile error on missing returns in procedures
Generate compile error for missing return statements in any code path of non-void procedure code.
---
.../core/functions/ControlFlow.java | 4 +--
.../core/functions/DataHandling.java | 28 +++++++++++++++++++
2 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
index b571627e0..ecca7be4a 100644
--- a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
+++ b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
@@ -2708,8 +2708,8 @@ public CClassType typecheck(StaticAnalysis analysis,
}
}
- // Return void.
- return CVoid.TYPE;
+ // Return null to indicate that this call does not result in a value.
+ return null;
}
@Override
diff --git a/src/main/java/com/laytonsmith/core/functions/DataHandling.java b/src/main/java/com/laytonsmith/core/functions/DataHandling.java
index 08dbca786..611c98b7a 100644
--- a/src/main/java/com/laytonsmith/core/functions/DataHandling.java
+++ b/src/main/java/com/laytonsmith/core/functions/DataHandling.java
@@ -1699,6 +1699,34 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntime
return CVoid.VOID;
}
+ @Override
+ public CClassType typecheck(StaticAnalysis analysis,
+ ParseTree ast, Environment env, Set exceptions) {
+
+ // Fall back to super implementation for invalid usage.
+ if(ast.numberOfChildren() < 2) {
+ return super.typecheck(analysis, ast, env, exceptions);
+ }
+
+ // Get procedure declared return type (not the proc() return type).
+ CClassType returnType = (ast.getChildAt(0).getData() instanceof CClassType type ? type : CClassType.AUTO);
+
+ // Typecheck arguments excluding code block.
+ for(int i = 0; i < ast.numberOfChildren() - 1; i++) {
+ analysis.typecheck(ast.getChildAt(i), env, exceptions);
+ }
+
+ // Typecheck code block.
+ CClassType codeBlockType = analysis.typecheck(ast.getChildAt(ast.numberOfChildren() - 1), env, exceptions);
+ if(returnType != CClassType.AUTO && returnType != CVoid.TYPE && codeBlockType != null) {
+ exceptions.add(new ConfigCompileException(
+ "Missing return statement; Not all code paths return a value.", ast.getTarget()));
+ }
+
+ // Return void.
+ return CVoid.TYPE;
+ }
+
@Override
public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast,
Environment env, Set exceptions) {
From 03f63356bae19a7b35f81570d8c702912654bd19 Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Tue, 25 Nov 2025 04:37:12 +0100
Subject: [PATCH 05/11] Implement getReturnType() in control flow functions
- Forward `null` return type during typechecking when the function won't return at all.
- Return least specific type among possible return types in control flow functions with multiple possible outcomes. These essentially need to return a multi-type CClassType, but this is the best we can do in the current code base without introducing harm.
---
.../core/functions/ControlFlow.java | 165 ++++++++++++++++++
1 file changed, 165 insertions(+)
diff --git a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
index ecca7be4a..6a5e3d50d 100644
--- a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
+++ b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
@@ -84,6 +84,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -438,6 +439,56 @@ public FunctionSignatures getSignatures() {
return super.getSignatures();
}
+ @Override
+ public CClassType getReturnType(Target t, List argTypes,
+ List argTargets, Environment env, Set exceptions) {
+
+ // Get return type based on the function signatures. This generates all necessary compile errors.
+ CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
+
+ // Override return type with null if the first condition (or lonely else code) is terminating.
+ if(argTypes.size() >= 1 && argTypes.get(0) == null) {
+ return null;
+ }
+
+ // Override return type with null if all code branches (including the else code branch) are terminating.
+ if((argTypes.size() & 0x01) != 0x00) { // (size % 2) != 0.
+
+ // Get all code branch return types.
+ boolean allBranchesTerminate = true;
+ Set returnTypes = new HashSet<>();
+ for(int i = 1; i < argTypes.size(); i += 2) {
+ CClassType argType = argTypes.get(i);
+ returnTypes.add(argType);
+ allBranchesTerminate &= (argType == null);
+ }
+ CClassType defaultArgType = argTypes.get(argTypes.size() - 1);
+ returnTypes.add(defaultArgType);
+ allBranchesTerminate &= (defaultArgType == null);
+
+ // Return null if all branches (including the else code branch) are terminating.
+ if(allBranchesTerminate) {
+ return null;
+ }
+
+ // Return an occurring return type if all return types extend that type.
+ // TODO - Make this return a multitype instead as soon as all typechecking code supports multitypes.
+ for(CClassType type1 : returnTypes) {
+ if(type1 != null) {
+ for(CClassType type2 : returnTypes) {
+ if(!InstanceofUtil.isInstanceof(type2, type1, env)) {
+ break;
+ }
+ }
+ return type1;
+ }
+ }
+ }
+
+ // Return the super result.
+ return retType;
+ }
+
@Override
public boolean useSpecialExec() {
return true;
@@ -726,6 +777,56 @@ public FunctionSignatures getSignatures() {
return super.getSignatures();
}
+ @Override
+ public CClassType getReturnType(Target t, List argTypes,
+ List argTargets, Environment env, Set exceptions) {
+
+ // Get return type based on the function signatures. This generates all necessary compile errors.
+ CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
+
+ // Override return type with null if the value is terminating.
+ if(argTypes.size() >= 1 && argTypes.get(0) == null) {
+ return null;
+ }
+
+ // Override return type with null if all cases (including the default case) are terminating.
+ if((argTypes.size() & 0x01) == 0x00) { // (size % 2) == 0.
+
+ // Get all case/default code branch return types.
+ boolean allBranchesTerminate = true;
+ Set returnTypes = new HashSet<>();
+ for(int i = 2; i < argTypes.size(); i += 2) {
+ CClassType argType = argTypes.get(i);
+ returnTypes.add(argType);
+ allBranchesTerminate &= (argType == null);
+ }
+ CClassType defaultArgType = argTypes.get(argTypes.size() - 1);
+ returnTypes.add(defaultArgType);
+ allBranchesTerminate &= (defaultArgType == null);
+
+ // Return null if all cases (including the default case) are terminating.
+ if(allBranchesTerminate) {
+ return null;
+ }
+
+ // Return an occurring return type if all return types extend that type.
+ // TODO - Make this return a multitype instead as soon as all typechecking code supports multitypes.
+ for(CClassType type1 : returnTypes) {
+ if(type1 != null) {
+ for(CClassType type2 : returnTypes) {
+ if(!InstanceofUtil.isInstanceof(type2, type1, env)) {
+ break;
+ }
+ }
+ }
+ return type1;
+ }
+ }
+
+ // Return the super result.
+ return retType;
+ }
+
@Override
public Scope linkScope(StaticAnalysis analysis, Scope parentScope,
ParseTree ast, Environment env, Set exceptions) {
@@ -1119,6 +1220,22 @@ public FunctionSignatures getSignatures() {
.param(null, "loopCode", "The code that is executed in the loop.").build();
}
+ @Override
+ public CClassType getReturnType(Target t, List argTypes,
+ List argTargets, Environment env, Set exceptions) {
+
+ // Get return type based on the function signatures. This generates all necessary compile errors.
+ CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
+
+ // Override return type with null if the assign or condition is terminating.
+ if(argTypes.size() == 4 && (argTypes.get(0) == null || argTypes.get(1) == null)) {
+ return null;
+ }
+
+ // Return the super result.
+ return retType;
+ }
+
@Override
public Class extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
@@ -1383,6 +1500,22 @@ public FunctionSignatures getSignatures() {
+ " false in the first iteration of the loop.").build();
}
+ @Override
+ public CClassType getReturnType(Target t, List argTypes,
+ List argTargets, Environment env, Set exceptions) {
+
+ // Get return type based on the function signatures. This generates all necessary compile errors.
+ CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
+
+ // Override return type with null if the assign or condition is terminating.
+ if(argTypes.size() == 5 && (argTypes.get(0) == null || argTypes.get(1) == null)) {
+ return null;
+ }
+
+ // Return the super result.
+ return retType;
+ }
+
@Override
public Scope linkScope(StaticAnalysis analysis, Scope parentScope,
ParseTree ast, Environment env, Set exceptions) {
@@ -2117,6 +2250,22 @@ public FunctionSignatures getSignatures() {
.param(null, "code", "The code that is executed in the loop.", true).build();
}
+ @Override
+ public CClassType getReturnType(Target t, List argTypes,
+ List argTargets, Environment env, Set exceptions) {
+
+ // Get return type based on the function signatures. This generates all necessary compile errors.
+ CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
+
+ // Override return type with null if the condition is terminating.
+ if(argTypes.size() == 2 && argTypes.get(0) == null) {
+ return null;
+ }
+
+ // Return the super result.
+ return retType;
+ }
+
@Override
public boolean useSpecialExec() {
return true;
@@ -2241,6 +2390,22 @@ public FunctionSignatures getSignatures() {
"The loop condition that is checked each time after the code is executed.").build();
}
+ @Override
+ public CClassType getReturnType(Target t, List argTypes,
+ List argTargets, Environment env, Set exceptions) {
+
+ // Get return type based on the function signatures. This generates all necessary compile errors.
+ CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
+
+ // Override return type with null if the code branch or condition is terminating.
+ if(argTypes.size() == 2 && (argTypes.get(0) == null || argTypes.get(1) == null)) {
+ return null;
+ }
+
+ // Return the super result.
+ return retType;
+ }
+
@Override
public Integer[] numArgs() {
return new Integer[]{2};
From 254d17109d159c15f3cadaacc200c8d6b4ad58b9 Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Wed, 26 Nov 2025 04:32:20 +0100
Subject: [PATCH 06/11] Implement throw() function signatures
---
.../core/functions/Exceptions.java | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/main/java/com/laytonsmith/core/functions/Exceptions.java b/src/main/java/com/laytonsmith/core/functions/Exceptions.java
index 916aab22e..f64f65bb7 100644
--- a/src/main/java/com/laytonsmith/core/functions/Exceptions.java
+++ b/src/main/java/com/laytonsmith/core/functions/Exceptions.java
@@ -28,6 +28,8 @@
import com.laytonsmith.core.compiler.VariableScope;
import com.laytonsmith.core.compiler.analysis.Scope;
import com.laytonsmith.core.compiler.analysis.StaticAnalysis;
+import com.laytonsmith.core.compiler.signature.FunctionSignatures;
+import com.laytonsmith.core.compiler.signature.SignatureBuilder;
import com.laytonsmith.core.constructs.CArray;
import com.laytonsmith.core.constructs.CClassType;
import com.laytonsmith.core.constructs.CClosure;
@@ -375,6 +377,29 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommand
throw throwable;
}
}
+
+ @Override
+ public FunctionSignatures getSignatures() {
+ return new SignatureBuilder(null).param(CArray.TYPE, "exception", "The exception to throw.")
+ .newSignature(null).param(CREThrowable.TYPE, "exception", "The exception to throw.")
+ .newSignature(null)
+ .param(CString.TYPE, "exceptionType", "The exception type string.")
+ .param(CString.TYPE, "msg", "The exception message.")
+ .param(CArray.TYPE, "causedBy", "The causing exception object.", true)
+ .newSignature(null)
+ .param(CString.TYPE, "exceptionType", "The exception type string.")
+ .param(CString.TYPE, "msg", "The exception message.")
+ .param(CREThrowable.TYPE, "causedBy", "The causing exception object.", true)
+ .newSignature(null)
+ .param(CClassType.TYPE, "exceptionType", "The exception type.")
+ .param(CString.TYPE, "msg", "The exception message.")
+ .param(CArray.TYPE, "causedBy", "The causing exception object.", true)
+ .newSignature(null)
+ .param(CClassType.TYPE, "exceptionType", "The exception type.")
+ .param(CString.TYPE, "msg", "The exception message.")
+ .param(CREThrowable.TYPE, "causedBy", "The causing exception object.", true)
+ .build();
+ }
}
@api
From 215f3b5d0ff60a81e1d18a6bfdc96a7359bf3899 Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Wed, 26 Nov 2025 23:42:05 +0100
Subject: [PATCH 07/11] Throw exception when invoking proc forward declaration
---
.../core/compiler/keywords/ProcKeyword.java | 19 ++++++++++++++-----
.../core/functions/Exceptions.java | 4 +++-
2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/src/main/java/com/laytonsmith/core/compiler/keywords/ProcKeyword.java b/src/main/java/com/laytonsmith/core/compiler/keywords/ProcKeyword.java
index 394e4ec8b..65414a31c 100644
--- a/src/main/java/com/laytonsmith/core/compiler/keywords/ProcKeyword.java
+++ b/src/main/java/com/laytonsmith/core/compiler/keywords/ProcKeyword.java
@@ -12,8 +12,9 @@
import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.exceptions.ConfigCompileException;
+import com.laytonsmith.core.exceptions.CRE.CREInvalidProcedureException;
import com.laytonsmith.core.functions.DataHandling;
-import com.laytonsmith.core.functions.Meta;
+import com.laytonsmith.core.functions.Exceptions;
import com.laytonsmith.core.functions.Compiler;
import java.util.List;
@@ -71,11 +72,19 @@ public int process(List list, int keywordPosition) throws ConfigCompi
list.remove(keywordPosition); // Remove __cbrace__ function node.
} else {
- // Define as forward declaration by add a "noop()" as code block.
+ // Define as forward declaration by adding an exception throw as code block.
+ FileOptions forwardImplFileOptions = list.get(keywordPosition + 1).getFileOptions();
+ ParseTree throwNode = new ParseTree(new CFunction(Exceptions._throw.NAME, Target.UNKNOWN),
+ forwardImplFileOptions, true);
+ throwNode.addChild(new ParseTree(
+ new CString(CREInvalidProcedureException.TYPE.getSimpleName(), Target.UNKNOWN),
+ options, true));
+ throwNode.addChild(new ParseTree(
+ new CString("Cannot invoke procedure forward declaration.", Target.UNKNOWN),
+ options, true));
ParseTree statement = new ParseTree(new CFunction(Compiler.__statements__.NAME, Target.UNKNOWN),
- list.get(keywordPosition + 1).getFileOptions(), true);
- statement.addChild(new ParseTree(new CFunction(Meta.noop.NAME, Target.UNKNOWN),
- list.get(keywordPosition + 1).getFileOptions(), true));
+ forwardImplFileOptions, true);
+ statement.addChild(throwNode);
procNode.addChild(statement);
// Remove processed nodes from AST.
diff --git a/src/main/java/com/laytonsmith/core/functions/Exceptions.java b/src/main/java/com/laytonsmith/core/functions/Exceptions.java
index f64f65bb7..c0bcefb6b 100644
--- a/src/main/java/com/laytonsmith/core/functions/Exceptions.java
+++ b/src/main/java/com/laytonsmith/core/functions/Exceptions.java
@@ -284,9 +284,11 @@ public List isScope(List children) {
@seealso({_try.class, com.laytonsmith.tools.docgen.templates.Exceptions.class})
public static class _throw extends AbstractFunction {
+ public static final String NAME = "throw";
+
@Override
public String getName() {
- return "throw";
+ return NAME;
}
@Override
From afcdcf1613a714d8b95f5d85e950d942355ab959 Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Thu, 16 Jul 2026 04:16:53 +0200
Subject: [PATCH 08/11] Implement try() and complex_try() function signatures
---
.../core/functions/Exceptions.java | 68 +++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/src/main/java/com/laytonsmith/core/functions/Exceptions.java b/src/main/java/com/laytonsmith/core/functions/Exceptions.java
index c0bcefb6b..9be1861eb 100644
--- a/src/main/java/com/laytonsmith/core/functions/Exceptions.java
+++ b/src/main/java/com/laytonsmith/core/functions/Exceptions.java
@@ -211,6 +211,40 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommand
return CVoid.VOID;
}
+ @Override
+ public FunctionSignatures getSignatures() {
+ return new SignatureBuilder(CVoid.TYPE)
+ .param(null, "tryCode", "The try code block.")
+ .param(null, "catchCode", "The catch code block.", true)
+ .newSignature(CVoid.TYPE)
+ .param(null, "tryCode", "The try code block.")
+ .param(null, "exVar", "The exception variable.")
+ .param(null, "catchCode", "The catch code block.")
+ .param(CString.TYPE, "exceptionType", "The exception type to catch.")
+ .newSignature(CVoid.TYPE)
+ .param(null, "tryCode", "The try code block.")
+ .param(null, "exVar", "The exception variable.")
+ .param(null, "catchCode", "The catch code block.")
+ .param(CArray.TYPE, "exceptionTypes", "Array of exception types to catch.", true).build();
+ }
+
+ @Override
+ public CClassType getReturnType(Target t, List argTypes,
+ List argTargets, Environment env, Set exceptions) {
+
+ // Get return type based on the function signatures. This generates all necessary compile errors.
+ CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
+
+ // Return null if both try and catch block do not return.
+ if((argTypes.size() == 2 && argTypes.get(0) == null && argTypes.get(1) == null)
+ || (argTypes.size() >= 3 && argTypes.get(0) == null && argTypes.get(2) == null)) {
+ return null;
+ }
+
+ // Return return type based on function signatures.
+ return retType;
+ }
+
@Override
@SuppressWarnings({"checkstyle:fallthrough", "checkstyle:defaultcomeslast"}) // Intended for control flow.
public Scope linkScope(StaticAnalysis analysis, Scope parentScope,
@@ -585,6 +619,40 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes)
return CVoid.VOID;
}
+ @Override
+ public FunctionSignatures getSignatures() {
+
+ // TODO - Define a better way to define "[catchVariable, catchCode]*, [catchCode]" in function signatures.
+ return new SignatureBuilder(CVoid.TYPE)
+ .param(null, "tryCode", "The try code block.")
+ .varParam(null, "[catchVariable, catchCode]*, [catchCode]", "The catch blocks.").build();
+ }
+
+ @Override
+ public CClassType getReturnType(Target t, List argTypes,
+ List argTargets, Environment env, Set exceptions) {
+
+ // Get return type based on the function signatures. This generates all necessary compile errors.
+ CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
+
+ // Return null if both try and all catch blocks do not return.
+ if(argTypes.size() > 1 && argTypes.get(0) == null) {
+ search: {
+ for(int i = 2; i < argTypes.size(); i += 2) {
+ if(argTypes.get(i) != null) {
+ break search;
+ }
+ }
+ if(!MathUtils.isEven(argTypes.size()) || argTypes.get(argTypes.size() - 1) == null) {
+ return null;
+ }
+ }
+ }
+
+ // Return return type based on function signatures.
+ return retType;
+ }
+
@Override
public Scope linkScope(StaticAnalysis analysis, Scope parentScope,
ParseTree ast, Environment env, Set exceptions) {
From b665b41adbf054bd3ca65c8562decc9e4cbcb540 Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Thu, 16 Jul 2026 05:31:21 +0200
Subject: [PATCH 09/11] Update control flow function typechecking
---
.../core/functions/ControlFlow.java | 167 +++++++-----------
.../core/functions/Exceptions.java | 5 +-
2 files changed, 65 insertions(+), 107 deletions(-)
diff --git a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
index 6a5e3d50d..ba1b28807 100644
--- a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
+++ b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java
@@ -1,5 +1,6 @@
package com.laytonsmith.core.functions;
+import com.laytonsmith.PureUtilities.MathUtils;
import com.laytonsmith.PureUtilities.TermColors;
import com.laytonsmith.PureUtilities.Common.StreamUtils;
import com.laytonsmith.PureUtilities.Version;
@@ -136,14 +137,22 @@ public Mixed exec(Target t, Environment env, Mixed... args)
@Override
public FunctionSignatures getSignatures() {
/*
- * TODO - Decide how to define the ternary return value.
- * Note that getReturnType is overridden, so these signatures are not used for typechecking.
+ * TODO - Decide how to define the ternary return value.
*/
return new SignatureBuilder(CClassType.AUTO, MatchType.MATCH_FIRST)
.param(Booleanish.TYPE, "cond", "The condition.")
.param(Mixed.TYPE, "ifValue", "The value that is returned when the condition is true.")
.param(Mixed.TYPE, "elseValue", "The value that is returned when the condition is false.")
- .newSignature(CVoid.TYPE).param(Booleanish.TYPE, "cond", "The condition.")
+ .newSignature(CClassType.AUTO)
+ .param(Booleanish.TYPE, "cond", "The condition.")
+ .param(Mixed.TYPE, "ifValue", "The value that is returned when the condition is true.")
+ .param(null, "elseCode", "The optional code that runs when the condition is false. This code must be terminating.")
+ .newSignature(CClassType.AUTO)
+ .param(Booleanish.TYPE, "cond", "The condition.")
+ .param(null, "ifCode", "The code that runs when the condition is true. This code must be terminating.")
+ .param(Mixed.TYPE, "elseValue", "The value that is returned when the condition is false.")
+ .newSignature(CVoid.TYPE)
+ .param(Booleanish.TYPE, "cond", "The condition.")
.param(null, "ifCode", "The code that runs when the condition is true.")
.param(null, "elseCode", "The optional code that runs when the condition is false.", true).build();
}
@@ -155,9 +164,9 @@ public CClassType getReturnType(Target t, List argTypes,
// Get return type based on the function signatures. This generates all necessary compile errors.
CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
- // When void is returned, ternary usage could still be possible when a branch is terminating.
- // It is also possible that both branches are terminating, in which case this should return null as well.
- if(retType == CVoid.TYPE && argTypes.size() == 3) {
+ // Return the type of the other code branch / value if one branch is terminating.
+ // Return null if both branches are terminating.
+ if(argTypes.size() == 3) {
// Return the type of the other branch if one branch is terminating (ternary, terminating or void).
if(argTypes.get(1) == null) {
@@ -182,7 +191,7 @@ public CClassType getReturnType(Target t, List argTypes,
}
}
- // Return the super result.
+ // Return super result.
return retType;
}
@@ -432,11 +441,14 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes)
@Override
public FunctionSignatures getSignatures() {
+
/*
- * TODO - Implement a way to define [cond, code]* using signatures, and use it here.
- * Also check switch() and switch_ic(), as they need the same feature.
+ * TODO - Implement a way to define [cond, code]*, [elseCode] using signatures, and use it here.
+ * Also check complex_try(), switch() and switch_ic(), as they need the same feature.
+ * Implement Booleanish typecheck for all conditions. These are currently checked in getReturnType().
*/
- return super.getSignatures();
+ return new SignatureBuilder(CVoid.TYPE)
+ .varParam(null, "[cond, code]*, [elseCode]", "The conditional code blocks.").build();
}
@Override
@@ -446,25 +458,28 @@ public CClassType getReturnType(Target t, List argTypes,
// Get return type based on the function signatures. This generates all necessary compile errors.
CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
- // Override return type with null if the first condition (or lonely else code) is terminating.
- if(argTypes.size() >= 1 && argTypes.get(0) == null) {
- return null;
- }
-
// Override return type with null if all code branches (including the else code branch) are terminating.
- if((argTypes.size() & 0x01) != 0x00) { // (size % 2) != 0.
+ if(argTypes.size() >= 1) {
// Get all code branch return types.
boolean allBranchesTerminate = true;
Set returnTypes = new HashSet<>();
for(int i = 1; i < argTypes.size(); i += 2) {
- CClassType argType = argTypes.get(i);
- returnTypes.add(argType);
- allBranchesTerminate &= (argType == null);
+
+ // Typecheck condition arguments.
+ // TODO - Remove this typecheck when it is implemented through function signatures.
+ CClassType condType = argTypes.get(i - 1);
+ StaticAnalysis.requireType(condType, Booleanish.TYPE, argTargets.get(i - 1), env, exceptions);
+
+ CClassType codeType = argTypes.get(i);
+ returnTypes.add(codeType);
+ allBranchesTerminate &= (codeType == null);
+ }
+ if(MathUtils.isOdd(argTypes.size())) {
+ CClassType defaultArgType = argTypes.get(argTypes.size() - 1);
+ returnTypes.add(defaultArgType);
+ allBranchesTerminate &= (defaultArgType == null);
}
- CClassType defaultArgType = argTypes.get(argTypes.size() - 1);
- returnTypes.add(defaultArgType);
- allBranchesTerminate &= (defaultArgType == null);
// Return null if all branches (including the else code branch) are terminating.
if(allBranchesTerminate) {
@@ -473,19 +488,21 @@ public CClassType getReturnType(Target t, List argTypes,
// Return an occurring return type if all return types extend that type.
// TODO - Make this return a multitype instead as soon as all typechecking code supports multitypes.
+ search:
for(CClassType type1 : returnTypes) {
if(type1 != null) {
for(CClassType type2 : returnTypes) {
- if(!InstanceofUtil.isInstanceof(type2, type1, env)) {
- break;
+ if(type2 != null && !InstanceofUtil.isInstanceof(type2, type1, env)) {
+ continue search;
}
}
return type1;
}
}
+ return CClassType.AUTO;
}
- // Return the super result.
+ // Return super result.
return retType;
}
@@ -513,7 +530,7 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope,
}
// Handle optional else branch in separate scope.
- if((ast.numberOfChildren() & 0x01) == 0x01) { // (size % 2) == 1.
+ if(MathUtils.isOdd(ast.numberOfChildren())) {
analysis.linkScope(analysis.createNewScope(parentScope),
ast.getChildAt(ast.numberOfChildren() - 1), env, exceptions);
}
@@ -770,11 +787,14 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes)
@Override
public FunctionSignatures getSignatures() {
+
/*
* TODO - Implement a way to define [case, code]* using signatures, and use it here.
- * Also check ifelse() and switch_ic(), as they need the same feature.
+ * Also check complex_try(), ifelse() and switch_ic(), as they need the same feature.
*/
- return super.getSignatures();
+ return new SignatureBuilder(CVoid.TYPE)
+ .param(Mixed.TYPE, "value", "The switch value.")
+ .varParam(null, "[caseVal, caseCode]*, [defaultCode]", "The case blocks.").build();
}
@Override
@@ -784,13 +804,8 @@ public CClassType getReturnType(Target t, List argTypes,
// Get return type based on the function signatures. This generates all necessary compile errors.
CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
- // Override return type with null if the value is terminating.
- if(argTypes.size() >= 1 && argTypes.get(0) == null) {
- return null;
- }
-
// Override return type with null if all cases (including the default case) are terminating.
- if((argTypes.size() & 0x01) == 0x00) { // (size % 2) == 0.
+ if(argTypes.size() >= 2) {
// Get all case/default code branch return types.
boolean allBranchesTerminate = true;
@@ -800,9 +815,11 @@ public CClassType getReturnType(Target t, List argTypes,
returnTypes.add(argType);
allBranchesTerminate &= (argType == null);
}
- CClassType defaultArgType = argTypes.get(argTypes.size() - 1);
- returnTypes.add(defaultArgType);
- allBranchesTerminate &= (defaultArgType == null);
+ if(MathUtils.isEven(argTypes.size())) {
+ CClassType defaultArgType = argTypes.get(argTypes.size() - 1);
+ returnTypes.add(defaultArgType);
+ allBranchesTerminate &= (defaultArgType == null);
+ }
// Return null if all cases (including the default case) are terminating.
if(allBranchesTerminate) {
@@ -811,19 +828,21 @@ public CClassType getReturnType(Target t, List argTypes,
// Return an occurring return type if all return types extend that type.
// TODO - Make this return a multitype instead as soon as all typechecking code supports multitypes.
+ search:
for(CClassType type1 : returnTypes) {
if(type1 != null) {
for(CClassType type2 : returnTypes) {
- if(!InstanceofUtil.isInstanceof(type2, type1, env)) {
- break;
+ if(type2 != null && !InstanceofUtil.isInstanceof(type2, type1, env)) {
+ continue search;
}
}
+ return type1;
}
- return type1;
}
+ return CClassType.AUTO;
}
- // Return the super result.
+ // Return super result.
return retType;
}
@@ -858,7 +877,7 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope,
}
// Handle optional default branch in separate scope.
- if((children.size() & 0x01) == 0x00) { // (size % 2) == 0.
+ if(MathUtils.isEven(children.size())) {
analysis.linkScope(
analysis.createNewScope(caseParentScope), children.get(children.size() - 1), env, exceptions);
}
@@ -1220,22 +1239,6 @@ public FunctionSignatures getSignatures() {
.param(null, "loopCode", "The code that is executed in the loop.").build();
}
- @Override
- public CClassType getReturnType(Target t, List argTypes,
- List argTargets, Environment env, Set exceptions) {
-
- // Get return type based on the function signatures. This generates all necessary compile errors.
- CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
-
- // Override return type with null if the assign or condition is terminating.
- if(argTypes.size() == 4 && (argTypes.get(0) == null || argTypes.get(1) == null)) {
- return null;
- }
-
- // Return the super result.
- return retType;
- }
-
@Override
public Class extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
@@ -1500,22 +1503,6 @@ public FunctionSignatures getSignatures() {
+ " false in the first iteration of the loop.").build();
}
- @Override
- public CClassType getReturnType(Target t, List argTypes,
- List argTargets, Environment env, Set exceptions) {
-
- // Get return type based on the function signatures. This generates all necessary compile errors.
- CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
-
- // Override return type with null if the assign or condition is terminating.
- if(argTypes.size() == 5 && (argTypes.get(0) == null || argTypes.get(1) == null)) {
- return null;
- }
-
- // Return the super result.
- return retType;
- }
-
@Override
public Scope linkScope(StaticAnalysis analysis, Scope parentScope,
ParseTree ast, Environment env, Set exceptions) {
@@ -2250,22 +2237,6 @@ public FunctionSignatures getSignatures() {
.param(null, "code", "The code that is executed in the loop.", true).build();
}
- @Override
- public CClassType getReturnType(Target t, List argTypes,
- List argTargets, Environment env, Set exceptions) {
-
- // Get return type based on the function signatures. This generates all necessary compile errors.
- CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
-
- // Override return type with null if the condition is terminating.
- if(argTypes.size() == 2 && argTypes.get(0) == null) {
- return null;
- }
-
- // Return the super result.
- return retType;
- }
-
@Override
public boolean useSpecialExec() {
return true;
@@ -2390,22 +2361,6 @@ public FunctionSignatures getSignatures() {
"The loop condition that is checked each time after the code is executed.").build();
}
- @Override
- public CClassType getReturnType(Target t, List argTypes,
- List argTargets, Environment env, Set exceptions) {
-
- // Get return type based on the function signatures. This generates all necessary compile errors.
- CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
-
- // Override return type with null if the code branch or condition is terminating.
- if(argTypes.size() == 2 && (argTypes.get(0) == null || argTypes.get(1) == null)) {
- return null;
- }
-
- // Return the super result.
- return retType;
- }
-
@Override
public Integer[] numArgs() {
return new Integer[]{2};
diff --git a/src/main/java/com/laytonsmith/core/functions/Exceptions.java b/src/main/java/com/laytonsmith/core/functions/Exceptions.java
index 9be1861eb..862028a2b 100644
--- a/src/main/java/com/laytonsmith/core/functions/Exceptions.java
+++ b/src/main/java/com/laytonsmith/core/functions/Exceptions.java
@@ -622,7 +622,10 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes)
@Override
public FunctionSignatures getSignatures() {
- // TODO - Define a better way to define "[catchVariable, catchCode]*, [catchCode]" in function signatures.
+ /*
+ * TODO - Implement a way to define [catchVariable, catchCode]*, [catchCode] using signatures, and use it here.
+ * Also check ifelse(), switch() and switch_ic(), as they need the same feature.
+ */
return new SignatureBuilder(CVoid.TYPE)
.param(null, "tryCode", "The try code block.")
.varParam(null, "[catchVariable, catchCode]*, [catchCode]", "The catch blocks.").build();
From 4b5c72a033d9f46349896aa907a52484eff69306 Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Fri, 17 Jul 2026 06:07:48 +0200
Subject: [PATCH 10/11] Add none type and missing return false error tests
---
.../core/MethodScriptCompilerTest.java | 122 ++++++++++++++++++
1 file changed, 122 insertions(+)
diff --git a/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java b/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java
index 1a34c8796..fc293338b 100644
--- a/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java
+++ b/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java
@@ -1444,4 +1444,126 @@ public void testSoftCastSyntaxCompiles() throws Exception {
sa.setLocalEnable(true);
MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, env, null, true), env, env.getEnvClasses(), sa);
}
+
+ @Test
+ public void testNoFalseMissingReturnInProcErrors() throws Exception {
+ String script = """
+
+ int proc _a() {
+ return 1;
+ }
+ _a();
+ int proc _b(@a, @b) {
+ if(@a) {
+ return 1;
+ } else if(@b) {
+ return 1;
+ } else {
+ return 1;
+ }
+ }
+ _b(1, 2);
+ int proc _c(@a) {
+ switch(@a) {
+ case 1: return 1;
+ case 2: return 1;
+ default: return 1;
+ }
+ }
+ _c(1);
+ int proc _d(@a) {
+ switch_ic(@a) {
+ case 'a': return 1;
+ case 'b': return 1;
+ default: return 1;
+ }
+ }
+ _d('str');
+ int proc _e() {
+ try(return(1), return(1));
+ }
+ _e();
+ int proc _f() {
+ try {
+ return 1;
+ } catch(RangeException @ex) {
+ return 1;
+ } catch(CastException @ex) {
+ return 1;
+ }
+ }
+ _f();
+ """;
+ Environment env = Static.GenerateStandaloneEnvironment();
+ StaticAnalysis sa = new StaticAnalysis(true);
+ sa.setLocalEnable(true);
+ MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, env, null, true), env, env.getEnvClasses(), sa);
+ }
+
+ @Test
+ public void testNoFalseNoneTypeErrorsInBranchAssigns() throws Exception {
+ String script = """
+
+ boolean @cond = dyn(true);
+ boolean @cond2 = dyn(true);
+ string @str = dyn('');
+
+ int @a1 = if(@cond, 1, 2);
+ int @a2 = if(@cond, die(), 2);
+ int @a3 = if(@cond, 1, die());
+
+ int @b1 = if(@cond) {1} else if(@cond2) {2} else {3}
+ int @b2 = if(@cond) {die()} else if(@cond2) {2} else {3}
+ int @b3 = if(@cond) {1} else if(@cond2) {die()} else {3}
+ int @b4 = if(@cond) {1} else if(@cond2) {2} else {die()}
+ int @b5 = if(@cond) {die()} else if(@cond2) {die()} else {3}
+ int @b6 = if(@cond) {die()} else if(@cond2) {2} else {die()}
+ int @b7 = if(@cond) {1} else if(@cond2) {die()} else {die()}
+
+ int @c1 = switch(@str) {
+ default: 1
+ }
+ int @c2 = switch(@str) {
+ case 1: 1
+ default: 1
+ }
+ int @c3 = switch(@str) {
+ case 1: die()
+ default: 1
+ }
+ int @c4 = switch(@str) {
+ case 1: 1
+ default: die()
+ }
+ int @c5 = switch(@str) {
+ case 1: 1
+ case 2: 1
+ default: 1
+ }
+ int @c6 = switch(@str) {
+ case 1: die()
+ case 2: 1
+ default: 1
+ }
+ int @c7 = switch(@str) {
+ case 1: 1
+ case 2: die()
+ default: 1
+ }
+ int @c8 = switch(@str) {
+ case 1: 1
+ case 2: 1
+ default: die()
+ }
+
+ int @d1 = if(@cond, 1, try(die(), die()));
+
+ int @e1 = if(@cond, 1, try {die()} catch(Exception @ex) {die()});
+ int @e2 = if(@cond, 1, try {die()} catch(RangeException @ex) {die()} catch(CastException @ex) {die()});
+ """;
+ Environment env = Static.GenerateStandaloneEnvironment();
+ StaticAnalysis sa = new StaticAnalysis(true);
+ sa.setLocalEnable(true);
+ MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, env, null, true), env, env.getEnvClasses(), sa);
+ }
}
From e90e4c83fe6be9d134c68b46bf7f2fb730eaee9f Mon Sep 17 00:00:00 2001
From: Pieter12345
Date: Fri, 17 Jul 2026 17:40:57 +0200
Subject: [PATCH 11/11] Implement func sign noneTypeAllowed per param + Fix
dor/dand signatures
- Implement function signature none type allowed boolean per Param instead of per FunctionSignature. This is reflected in the function signatures string.
- Apply none type allowed boolean to all type of Param instead of only to varparams.
- Update function signatures of `dand()` and `dor()`. Fixes NPE when calling `dor()` without arguments (given that SA is enabled).
---
.../compiler/signature/FunctionSignature.java | 20 ++----
.../core/compiler/signature/Param.java | 34 +++++++++-
.../compiler/signature/SignatureBuilder.java | 36 ++++++++--
.../core/functions/BasicLogic.java | 65 +++++++++++++++++--
4 files changed, 132 insertions(+), 23 deletions(-)
diff --git a/src/main/java/com/laytonsmith/core/compiler/signature/FunctionSignature.java b/src/main/java/com/laytonsmith/core/compiler/signature/FunctionSignature.java
index 73dedf58a..53c67cec4 100644
--- a/src/main/java/com/laytonsmith/core/compiler/signature/FunctionSignature.java
+++ b/src/main/java/com/laytonsmith/core/compiler/signature/FunctionSignature.java
@@ -20,21 +20,17 @@ public class FunctionSignature {
private final ReturnType returnType;
private final List params;
private final List throwsList;
- private boolean noneIsAllowed;
/**
* Creates a new {@link FunctionSignature} with the given properties.
* @param returnType - The function return type.
* @param params - The function parameters.
* @param throwsList - The list of possibly thrown exceptions by the function.
- * @param noneIsAllowed - If the none (Java {@code null}) type is allowed and should be treated as
- * {@link CClassType.AUTO}.
*/
- public FunctionSignature(ReturnType returnType, List params, List throwsList, boolean noneIsAllowed) {
+ public FunctionSignature(ReturnType returnType, List params, List throwsList) {
this.returnType = returnType;
this.params = params;
this.throwsList = throwsList;
- this.noneIsAllowed = noneIsAllowed;
}
/**
@@ -43,7 +39,7 @@ public FunctionSignature(ReturnType returnType, List params, List
* @param returnType - The function return type.
*/
public FunctionSignature(ReturnType returnType) {
- this(returnType, new ArrayList<>(), new ArrayList<>(), false);
+ this(returnType, new ArrayList<>(), new ArrayList<>());
}
/**
@@ -61,10 +57,6 @@ protected void addThrows(Throws throwsObj) {
this.throwsList.add(throwsObj);
}
- protected void setNoneIsAllowed(boolean noneIsAllowed) {
- this.noneIsAllowed = noneIsAllowed;
- }
-
/**
* Gets the function's return type.
* @return The return type.
@@ -109,7 +101,8 @@ public boolean matches(List argTypes, Environment env, boolean allow
// Match normal or optional parameter.
if(argIndex < argTypes.size()
- && InstanceofUtil.isInstanceof(argTypes.get(argIndex), param.getType(), env)) {
+ && ((param.getNoneTypeAllowed() && argTypes.get(argIndex) == null)
+ || InstanceofUtil.isInstanceof(argTypes.get(argIndex), param.getType(), env))) {
// Keep track of the optional parameter match.
if(param.isOptional()) {
@@ -130,7 +123,7 @@ public boolean matches(List argTypes, Environment env, boolean allow
// Match as many arguments as possible with this varparam.
int numMatches = 0;
while(argIndex < argTypes.size()
- && ((argTypes.get(argIndex) == null && this.noneIsAllowed)
+ && ((param.getNoneTypeAllowed() && argTypes.get(argIndex) == null)
|| InstanceofUtil.isInstanceof(argTypes.get(argIndex), param.getType(), env))) {
argIndex++;
numMatches++;
@@ -172,7 +165,8 @@ public boolean matches(List argTypes, Environment env, boolean allow
*/
public String getParamTypesString() {
return "(" + StringUtils.Join(this.params, ", ", null, null, null, (Param param) -> {
- String ret = (param.getType() == null ? "any" : param.getType().getSimpleName());
+ String ret = (param.getType() == null ? "any" : (param.getNoneTypeAllowed()
+ ? param.getType().getSimpleName() + "|none" : param.getType().getSimpleName()));
if(param.isVarParam()) {
ret += "...";
}
diff --git a/src/main/java/com/laytonsmith/core/compiler/signature/Param.java b/src/main/java/com/laytonsmith/core/compiler/signature/Param.java
index 9a1642ead..a74189111 100644
--- a/src/main/java/com/laytonsmith/core/compiler/signature/Param.java
+++ b/src/main/java/com/laytonsmith/core/compiler/signature/Param.java
@@ -13,6 +13,7 @@ public class Param {
private final String desc;
private final boolean isVarParam;
private final boolean isOptional;
+ private final boolean noneTypeAllowed;
/**
* Creates a new {@link Param} with the given properties.
@@ -24,14 +25,34 @@ public class Param {
* of the given type, or one argument of type {@code array}. {@code false} otherwise.
* Note that a varparam is only usable as type {@code array}.
* @param isOptional - {@code true} if the parameter is optional, {@code false} otherwise.
+ * @param noneTypeAllowed - {@code true} if the none (Java {@code null}) type is allowed to match this parameter in
+ * addition to what the parameter type already matches.
+ * {@code false} otherwise. If the parameter type is {@code null}, then this boolean parameter is ignored.
*/
- public Param(CClassType type, String name, String desc, boolean isVarParam, boolean isOptional) {
+ public Param(CClassType type, String name, String desc,
+ boolean isVarParam, boolean isOptional, boolean noneTypeAllowed) {
assert !isVarParam || !isOptional : "A parameter cannot be variadic and optional at the same time.";
this.type = type;
this.name = name;
this.desc = desc;
this.isVarParam = isVarParam;
this.isOptional = isOptional;
+ this.noneTypeAllowed = noneTypeAllowed;
+ }
+
+ /**
+ * Creates a new {@link Param} with the given properties.
+ * Parameters cannot be variadic and optional at the same time, as varparams already imply optionality.
+ * @param type - The (parent) type of the parameter.
+ * @param name - The name of the parameter.
+ * @param desc - The description of the parameter.
+ * @param isVarParam - {@code true} if the parameter is a varparam, meaning that it matches zero or more arguments
+ * of the given type, or one argument of type {@code array}. {@code false} otherwise.
+ * Note that a varparam is only usable as type {@code array}.
+ * @param isOptional - {@code true} if the parameter is optional, {@code false} otherwise.
+ */
+ public Param(CClassType type, String name, String desc, boolean isVarParam, boolean isOptional) {
+ this(type, name, desc, isVarParam, isOptional, false);
}
/**
@@ -85,4 +106,15 @@ public boolean isVarParam() {
public boolean isOptional() {
return this.isOptional;
}
+
+ /**
+ * Gets whether the none type is allowed to match this parameter in addition to what the parameter type matches.
+ * When this returns {@code false}, the none type can still match this parameter if {@ref #getType()} returns
+ * {@code null}.
+ * @return {@code true} if a none type argument can match this parameter in addition to what the parameter type
+ * matches, {@code false} otherwise.
+ */
+ public boolean getNoneTypeAllowed() {
+ return this.noneTypeAllowed;
+ }
}
diff --git a/src/main/java/com/laytonsmith/core/compiler/signature/SignatureBuilder.java b/src/main/java/com/laytonsmith/core/compiler/signature/SignatureBuilder.java
index 41a6b490c..77603585a 100644
--- a/src/main/java/com/laytonsmith/core/compiler/signature/SignatureBuilder.java
+++ b/src/main/java/com/laytonsmith/core/compiler/signature/SignatureBuilder.java
@@ -56,6 +56,22 @@ public SignatureBuilder(CClassType returnType, String returnValDesc, MatchType m
this.signature = new FunctionSignature(new ReturnType(returnType, returnValDesc));
}
+ /**
+ * Adds a normal function parameter. Parameters should be added from left to right.
+ * @param paramType - The {@link CClassType} of the parameter.
+ * @param paramName - The name of the parameter.
+ * @param paramDesc - The description of the parameter.
+ * @param isOptional - Whether the parameter is optional or not.
+ * @param noneTypeAllowed - Whether a none type (Java {@code null}) is allowed to match this parameter in addition
+ * to what the parameter type already matches.
+ * @return This {@link SignatureBuilder}, for chaining builder methods.
+ */
+ public SignatureBuilder param(
+ CClassType paramType, String paramName, String paramDesc, boolean isOptional, boolean noneTypeAllowed) {
+ this.signature.addParam(new Param(paramType, paramName, paramDesc, false, isOptional, noneTypeAllowed));
+ return this;
+ }
+
/**
* Adds a normal function parameter. Parameters should be added from left to right.
* @param paramType - The {@link CClassType} of the parameter.
@@ -80,6 +96,21 @@ public SignatureBuilder param(CClassType paramType, String paramName, String par
return this.param(paramType, paramName, paramDesc, false);
}
+ /**
+ * Adds a variadic function parameter (varparam). Parameters should be added from left to right.
+ * @param paramType - The {@link CClassType} of the parameter (the 'paramType' in 'paramType... paramName').
+ * @param paramName - The name of the parameter.
+ * @param paramDesc - The description of the parameter.
+ * @param noneTypeAllowed - Whether a none type (Java {@code null}) is allowed to match an entry in this parameter
+ * in addition to what the parameter type already matches.
+ * @return This {@link SignatureBuilder}, for chaining builder methods.
+ */
+ public SignatureBuilder varParam(
+ CClassType paramType, String paramName, String paramDesc, boolean noneTypeAllowed) {
+ this.signature.addParam(new Param(paramType, paramName, paramDesc, true, false, noneTypeAllowed));
+ return this;
+ }
+
/**
* Adds a variadic function parameter (varparam). Parameters should be added from left to right.
* @param paramType - The {@link CClassType} of the parameter (the 'paramType' in 'paramType... paramName').
@@ -103,11 +134,6 @@ public SignatureBuilder throwsEx(Class extends CREThrowable> exception, String
return this;
}
- public SignatureBuilder setNoneIsAllowed(boolean allowed) {
- this.signature.setNoneIsAllowed(allowed);
- return this;
- }
-
/**
* Finalizes the last function signature and starts a new function signature with the given return type.
* @param returnType - The return type of the new function signature.
diff --git a/src/main/java/com/laytonsmith/core/functions/BasicLogic.java b/src/main/java/com/laytonsmith/core/functions/BasicLogic.java
index 064028027..e5844cbd4 100644
--- a/src/main/java/com/laytonsmith/core/functions/BasicLogic.java
+++ b/src/main/java/com/laytonsmith/core/functions/BasicLogic.java
@@ -16,7 +16,6 @@
import com.laytonsmith.core.compiler.analysis.StaticAnalysis;
import com.laytonsmith.core.compiler.signature.FunctionSignatures;
import com.laytonsmith.core.compiler.signature.SignatureBuilder;
-import com.laytonsmith.core.constructs.Auto;
import com.laytonsmith.core.constructs.CArray;
import com.laytonsmith.core.constructs.CBoolean;
import com.laytonsmith.core.constructs.CClassType;
@@ -28,6 +27,7 @@
import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.CSymbol;
import com.laytonsmith.core.constructs.CVoid;
+import com.laytonsmith.core.constructs.InstanceofUtil;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.environments.GlobalEnv;
@@ -1275,7 +1275,37 @@ public FunctionSignatures getSignatures() {
* Note that getReturnType could be overridden, not using this signature for typechecking.
* That implementation is not yet possible until A OR B OR ... types can be described.
*/
- return new SignatureBuilder(CClassType.AUTO).varParam(Mixed.TYPE, "vals", null).build();
+ return new SignatureBuilder(CClassType.AUTO)
+ .varParam(Mixed.TYPE, "vals", "The values.")
+ .newSignature(CClassType.AUTO)
+ .param(Mixed.TYPE, "val", "The first value.")
+ .varParam(Mixed.TYPE, "vals", "The values.")
+ .param(Mixed.TYPE, "termVal", "The final terminating (non-returning) value.", true, true).build();
+ }
+
+ @Override
+ public CClassType getReturnType(Target t, List argTypes,
+ List argTargets, Environment env, Set exceptions) {
+
+ // Get return type based on the function signatures. This generates all necessary compile errors.
+ CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
+
+ // Return an occurring argument type if all argument types extend that type.
+ // TODO - Make this return a multitype instead as soon as all typechecking code supports multitypes.
+ search:
+ for(CClassType type1 : argTypes) {
+ if(type1 != null) {
+ for(CClassType type2 : argTypes) {
+ if(type2 != null && !InstanceofUtil.isInstanceof(type2, type1, env)) {
+ continue search;
+ }
+ }
+ return type1;
+ }
+ }
+
+ // Return super result.
+ return retType;
}
@Override
@@ -1570,8 +1600,35 @@ public FunctionSignatures getSignatures() {
* Note that getReturnType could be overridden, not using this signature for typechecking.
* That implementation is not yet possible until A OR B OR ... types can be described.
*/
- return new SignatureBuilder(Auto.TYPE).varParam(Mixed.TYPE, "vals", null)
- .setNoneIsAllowed(true).build();
+ return new SignatureBuilder(CClassType.AUTO)
+ .param(Mixed.TYPE, "val", "The first value.")
+ .varParam(Mixed.TYPE, "vals", "The values.")
+ .param(Mixed.TYPE, "termVal", "The final terminating (non-returning) value.", true, true).build();
+ }
+
+ @Override
+ public CClassType getReturnType(Target t, List argTypes,
+ List argTargets, Environment env, Set exceptions) {
+
+ // Get return type based on the function signatures. This generates all necessary compile errors.
+ CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions);
+
+ // Return an occurring argument type if all argument types extend that type.
+ // TODO - Make this return a multitype instead as soon as all typechecking code supports multitypes.
+ search:
+ for(CClassType type1 : argTypes) {
+ if(type1 != null) {
+ for(CClassType type2 : argTypes) {
+ if(type2 != null && !InstanceofUtil.isInstanceof(type2, type1, env)) {
+ continue search;
+ }
+ }
+ return type1;
+ }
+ }
+
+ // Return super result.
+ return retType;
}
@Override