aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-22 15:12:43 +0000
committer_ov4 <ov4@ov4.org>2026-10-22 15:21:11 +0000
commit98ef8bc4dfcebe44eece933ff0affc776b7199dd (patch)
treed02ffca5fd65852344a3e032bfbd3a4638c79dac
parent8b6ba7222d9990bc19e355eabcf046a7ea773545 (diff)
fix(codegen): raise a runtime error when a FUNCTION ends without RETURN
Report the function and its header line, then throw, instead of silently returning zero. FUNCTION MAIN keeps its implicit result. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
-rw-r--r--doc/language/keyword/cpsc-keyword-function.7.md2
-rw-r--r--include/cps/FunctionGen.h7
-rw-r--r--lib/CodeGen/CodeGen.cc8
-rw-r--r--lib/CodeGen/FunctionGen.cc4
4 files changed, 20 insertions, 1 deletions
diff --git a/doc/language/keyword/cpsc-keyword-function.7.md b/doc/language/keyword/cpsc-keyword-function.7.md
index a7de9a4..d49a07d 100644
--- a/doc/language/keyword/cpsc-keyword-function.7.md
+++ b/doc/language/keyword/cpsc-keyword-function.7.md
@@ -15,7 +15,7 @@ ENDFUNCTION
# DESCRIPTION
-Call a function in an expression as `name(arguments)`. A call may precede its top-level definition. RETURNS specifies the result type; omitting it defaults to INTEGER. RETURN supplies a value of that type. Whole-array results are unsupported.
+Call a function in an expression as `name(arguments)`. A call may precede its top-level definition. RETURNS specifies the result type; omitting it defaults to INTEGER. RETURN supplies a value of that type. If execution reaches ENDFUNCTION without a RETURN, a run-time error is reported and an exception is thrown (the top-level MAIN is exempt). Whole-array results are unsupported.
As a compiler extension, `name(arguments)` may also be a complete statement that discards the result. This differs from guide section 8.2, which requires function calls to occur within expressions. Built-in functions support the same form.
diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h
index 6c5c7c0..f5e5ba2 100644
--- a/include/cps/FunctionGen.h
+++ b/include/cps/FunctionGen.h
@@ -86,6 +86,7 @@ private:
std::function<void(const FuncSig &)> BeforeCall;
ArrayParamBinder BindArrayParam;
std::function<void(llvm::Value *, const std::string &, int)> RegisterCopy;
+ std::function<void(int, const std::string &)> MissingReturn;
ClangABI ABI{Context, Types}; // clang context created on the first EXTERN
void createArgumentAllocas(llvm::Function *F, const std::vector<ParamDecl> &Params, bool IsMethod);
@@ -105,6 +106,12 @@ public:
void setArrayParamBinder(ArrayParamBinder Binder) { BindArrayParam = std::move(Binder); }
+ // Called (with the FUNCTION header line and name) where a non-VOID FUNCTION
+ // body can fall off its end; emits the runtime error and throw.
+ void setMissingReturnHandler(std::function<void(int, const std::string &)> Handler) {
+ MissingReturn = std::move(Handler);
+ }
+
void setCopyRegistrar(std::function<void(llvm::Value *, const std::string &, int)> Registrar) {
RegisterCopy = std::move(Registrar);
}
diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc
index e45a3db..772dc82 100644
--- a/lib/CodeGen/CodeGen.cc
+++ b/lib/CodeGen/CodeGen.cc
@@ -77,6 +77,14 @@ CodeGen::CodeGen() {
activateStackObject(registerStackObject(Addr, TypeName, Line));
});
+ FuncGen->setMissingReturnHandler([this](int Line, const std::string &Name) {
+ Value *Msg = Builder->CreateGlobalString("[Fatal] line %d: FUNCTION %s ended without RETURN\n",
+ "err_missing_return", 0, TheModule.get());
+ Value *NameStr = Builder->CreateGlobalString(Name, "function_name", 0, TheModule.get());
+ RuntimeChecker->emitStderr(Msg, {RuntimeChecker->lineValue(Line), NameStr});
+ Exceptions->emitThrow();
+ });
+
SetupExternalFunctions();
}
diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc
index 48c9978..c233205 100644
--- a/lib/CodeGen/FunctionGen.cc
+++ b/lib/CodeGen/FunctionGen.cc
@@ -444,6 +444,10 @@ Function *FunctionGen::emitFunctionDef(FunctionDefAST *FuncAST,
if (!Builder.GetInsertBlock()->hasTerminator()) {
if (sameIdentifier(Proto->getReturnType(), "VOID")) {
Exceptions.recordExit(Builder.CreateRetVoid());
+ } else if (MissingReturn && !(!IsMethod && sameIdentifier(Proto->getName(), "MAIN"))) {
+ // Falling off the end of a FUNCTION is a runtime error, not a zero value.
+ // The handler ends the block with a throw (unreachable).
+ MissingReturn(FuncAST->getLine(), Proto->getName());
} else {
Exceptions.recordExit(Builder.CreateRet(Constant::getNullValue(TheFunction->getReturnType())));
}