aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-28 13:36:16 +0000
committer_ov4 <ov4@ov4.org>2026-10-28 13:36:16 +0000
commitb28384fed1ce638e8b00b72dba974e9fd7e55eb4 (patch)
treeb0b342b148c1d5b33197f7a59d0a7a7edb92af1c
parentcaa88a9b36d9e1dfae40ecb31aee05bc2891fa17 (diff)
feat(overload): support default return types
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--include/cps/FunctionAST.h3
-rw-r--r--include/cps/FunctionGen.h2
-rw-r--r--include/cps/Lexer.h1
-rw-r--r--lib/CodeGen/CallGen.cc10
-rw-r--r--lib/CodeGen/FunctionGen.cc27
-rw-r--r--lib/CodeGen/OperatorGen.cc8
-rw-r--r--lib/CodeGen/TemplateClone.cc1
-rw-r--r--lib/Lexer/Lexer.cc1
-rw-r--r--lib/Parser/ParserFunction.cc13
-rw-r--r--tests/i/default-returns/case.json1
-rw-r--r--tests/i/default-returns/expected.txt12
-rw-r--r--tests/i/default-returns/notes.md7
-rw-r--r--tests/i/default-returns/program.cps54
-rw-r--r--tests/iii/default-return-errors/case.json10
-rw-r--r--tests/iii/default-return-errors/program.cps33
-rw-r--r--tests/iii/default-return-template/case.json5
-rw-r--r--tests/iii/default-return-template/program.cps5
-rw-r--r--tests/iii/multifile-default-return/case.json6
-rw-r--r--tests/iii/multifile-default-return/first.cps3
-rw-r--r--tests/iii/multifile-default-return/program.cps3
-rw-r--r--tests/iii/multifile-default-return/second.cps3
21 files changed, 206 insertions, 2 deletions
diff --git a/include/cps/FunctionAST.h b/include/cps/FunctionAST.h
index 9188015..5280d58 100644
--- a/include/cps/FunctionAST.h
+++ b/include/cps/FunctionAST.h
@@ -35,6 +35,7 @@ class PrototypeAST {
std::string OwnerClass;
bool IsPublic;
bool IsVariadic = false;
+ bool IsDefaultReturn = false;
int Operator = 0;
public:
@@ -49,6 +50,8 @@ public:
const std::vector<ParamDecl> &getParams() const { return Params; }
const std::string &getReturnType() const { return ReturnType; }
bool isOverload() const { return IsOverload; }
+ bool isDefaultReturn() const { return IsDefaultReturn; }
+ void setDefaultReturn(bool V) { IsDefaultReturn = V; }
int getOperator() const { return Operator; }
void setOperator(int Op) { Operator = Op; }
bool isExternal() const { return IsExternal; }
diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h
index c3c530b..5e895fe 100644
--- a/include/cps/FunctionGen.h
+++ b/include/cps/FunctionGen.h
@@ -42,6 +42,7 @@ struct FuncSig {
std::string ParamKey;
std::string ReturnKey;
bool IsOverload = false;
+ bool IsDefaultReturn = false;
const PrototypeAST *Prototype = nullptr;
std::string ReturnTypeName;
std::vector<ParamSig> Params;
@@ -122,6 +123,7 @@ public:
void setVisibility(std::function<bool(size_t)> Predicate) { Visible = std::move(Predicate); }
std::vector<const FuncSig *> getOverloads(const std::string &Name, bool AllUnits = false) const;
static bool isReservedRuntimeName(const std::string &Name);
+ static const FuncSig *selectDefaultReturn(const std::vector<const FuncSig *> &Candidates);
bool hasFunction(const std::string &Name) const { return !getOverloads(Name).empty(); }
const FuncSig *getSignatureByLink(const std::string &Name) const;
diff --git a/include/cps/Lexer.h b/include/cps/Lexer.h
index 3d057a3..1268582 100644
--- a/include/cps/Lexer.h
+++ b/include/cps/Lexer.h
@@ -79,6 +79,7 @@ enum Token {
tok_call = -406,
tok_byref = -407,
tok_byval = -408,
+ tok_default = -409,
tok_openfile = -500,
tok_readfile = -501,
diff --git a/lib/CodeGen/CallGen.cc b/lib/CodeGen/CallGen.cc
index f3a6a9d..0741763 100644
--- a/lib/CodeGen/CallGen.cc
+++ b/lib/CodeGen/CallGen.cc
@@ -204,7 +204,7 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
}
// A context with a type prefers candidates that return it; a strict
// context requires one. Without a context (or after a cast's preference
- // finds nothing) selection stays parameter-only.
+ // finds nothing) a default may resolve return-only ambiguity.
const bool PreferResult = Context && Context->Type;
auto ReturnsWanted = [&](const FuncSig *Sig) {
const TypeInfo *Ret = resolveType(Sig->ReturnTypeName);
@@ -217,6 +217,7 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
// FilterReturn keeps only candidates whose result is the wanted type.
auto Scan = [&](bool Lenient, bool FilterReturn) -> const FuncSig * {
const FuncSig *First = nullptr;
+ std::vector<const FuncSig *> Matches;
Count = 0;
for (const FuncSig *Sig : Overloads) {
if (HasExplicit) break;
@@ -235,8 +236,15 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
ParamMatched = true;
if (FilterReturn && !ReturnsWanted(Sig)) continue;
++Count;
+ Matches.push_back(Sig);
if (!First) First = Sig;
}
+ if (!FilterReturn && Count > 1) {
+ if (const FuncSig *Default = FunctionGen::selectDefaultReturn(Matches)) {
+ Count = 1;
+ return Default;
+ }
+ }
return First;
};
auto ReportAmbiguous = [&](bool FilterReturn) {
diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc
index c162b46..a0c520d 100644
--- a/lib/CodeGen/FunctionGen.cc
+++ b/lib/CodeGen/FunctionGen.cc
@@ -113,9 +113,30 @@ const FuncSig *FunctionGen::getSignatureByLink(const std::string &Name) const {
return &Signatures.at(It->second.first)[It->second.second];
}
+// Defaults resolve only return-type ambiguity within one parameter signature.
+const FuncSig *FunctionGen::selectDefaultReturn(const std::vector<const FuncSig *> &Candidates) {
+ const FuncSig *Selected = nullptr;
+ for (const FuncSig *Sig : Candidates) {
+ if (!sameIdentifier(Sig->Name, Candidates.front()->Name) ||
+ !sameIdentifier(Sig->ParamKey, Candidates.front()->ParamKey)) return nullptr;
+ if (Sig->IsDefaultReturn) {
+ if (Selected) return nullptr;
+ Selected = Sig;
+ }
+ }
+ return Selected;
+}
+
// Copy-constructor, operator, MAIN and name-collision rules for a
// FUNCTION/PROCEDURE prototype; reports the first violation.
bool FunctionGen::validatePrototype(PrototypeAST *Proto) {
+ if (Proto->isDefaultReturn()) {
+ const TypeInfo *Return = Types.resolve(Proto->getReturnType());
+ if (!Proto->isOverload() || (Return && Return->isVoid())) {
+ reportError("DEFAULT requires a non-VOID OVERLOAD FUNCTION");
+ return false;
+ }
+ }
if (!Proto->getOwnerClass().empty() && sameIdentifier(Proto->getName(), "NEW") &&
Proto->getParams().size() == 1) {
const auto &P = Proto->getParams()[0];
@@ -192,6 +213,7 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) {
Sig.OwnerUnit = CurrentUnit;
Sig.Name = Proto->getLinkName();
Sig.IsOverload = Proto->isOverload();
+ Sig.IsDefaultReturn = Proto->isDefaultReturn();
Sig.Prototype = Proto;
Sig.ReturnTypeName = Proto->getReturnType();
@@ -247,6 +269,11 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) {
return nullptr;
}
if (sameIdentifier(Existing.ParamKey, Sig.ParamKey) &&
+ Existing.IsDefaultReturn && Sig.IsDefaultReturn) {
+ reportError("Duplicate DEFAULT return for %s(%s)", Sig.Name.c_str(), Sig.ParamKey.c_str());
+ return nullptr;
+ }
+ if (sameIdentifier(Existing.ParamKey, Sig.ParamKey) &&
sameIdentifier(Existing.ReturnKey, Sig.ReturnKey)) {
if (Existing.OwnerUnit != CurrentUnit)
reportError("Duplicate definition %s in %s and %s", Sig.Name.c_str(),
diff --git a/lib/CodeGen/OperatorGen.cc b/lib/CodeGen/OperatorGen.cc
index 560b167..7cfac00 100644
--- a/lib/CodeGen/OperatorGen.cc
+++ b/lib/CodeGen/OperatorGen.cc
@@ -38,6 +38,7 @@ CodeGen::OperatorResolution CodeGen::resolveOperator(
if (Lenient && !CanConvert) break;
const FuncSig *Selected = nullptr;
int Count = 0;
+ std::vector<const FuncSig *> Matches;
for (const FuncSig *Sig : Candidates) {
bool Member = !Sig->Prototype->getOwnerClass().empty();
if (Sig->Prototype->getOperator() != Op ||
@@ -47,8 +48,15 @@ CodeGen::OperatorResolution CodeGen::resolveOperator(
ParamMatched = true;
if (FilterReturn && !ReturnsWanted(Sig)) continue;
++Count;
+ Matches.push_back(Sig);
if (!Selected) Selected = Sig;
}
+ if (!FilterReturn && Count > 1) {
+ if (const FuncSig *Default = FunctionGen::selectDefaultReturn(Matches)) {
+ Selected = Default;
+ Count = 1;
+ }
+ }
if (Selected) {
Result.Signature = Selected;
Result.Ambiguous = Count > 1;
diff --git a/lib/CodeGen/TemplateClone.cc b/lib/CodeGen/TemplateClone.cc
index 7374ee8..b0aaf0c 100644
--- a/lib/CodeGen/TemplateClone.cc
+++ b/lib/CodeGen/TemplateClone.cc
@@ -220,6 +220,7 @@ std::unique_ptr<FunctionDefAST> TemplateClone::function(const FunctionDefAST *So
auto CopyProto = std::make_unique<PrototypeAST>(Name, std::move(Params), type(Proto->getReturnType()),
Proto->isExternal(), OwnerClass, Proto->isPublic(), Proto->isOverload());
CopyProto->setOperator(Proto->getOperator());
+ CopyProto->setDefaultReturn(Proto->isDefaultReturn());
CopyProto->setVariadic(Proto->isVariadic());
auto Copy = located(std::make_unique<FunctionDefAST>(std::move(CopyProto), body(Source->getBody())), Source);
PackNames = std::move(SavedNames);
diff --git a/lib/Lexer/Lexer.cc b/lib/Lexer/Lexer.cc
index 40bfce3..e75cbba 100644
--- a/lib/Lexer/Lexer.cc
+++ b/lib/Lexer/Lexer.cc
@@ -77,6 +77,7 @@ const std::unordered_map<std::string, int> Keywords = {
{"ENDPROCEDURE", tok_endprocedure},
{"RETURN", tok_return},
{"RETURNS", tok_returns},
+ {"DEFAULT", tok_default},
{"CALL", tok_call},
{"BYREF", tok_byref},
{"BYVAL", tok_byval},
diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc
index 8f3a117..2f80c42 100644
--- a/lib/Parser/ParserFunction.cc
+++ b/lib/Parser/ParserFunction.cc
@@ -203,8 +203,13 @@ std::unique_ptr<FunctionDefAST> Parser::ParseFunction(const std::string &OwnerCl
if (!Args) return nullptr;
std::string RetType = "INTEGER";
+ bool IsDefaultReturn = false;
if (CurTok == tok_returns) {
getNextToken();
+ if (CurTok == tok_default) {
+ IsDefaultReturn = true;
+ getNextToken();
+ }
if (CurTok == tok_array) {
cps::sourceError(Lex.getLine(), "A FUNCTION cannot RETURN an array; use a BYREF PROCEDURE parameter instead");
return nullptr;
@@ -218,8 +223,14 @@ std::unique_ptr<FunctionDefAST> Parser::ParseFunction(const std::string &OwnerCl
auto Proto = std::make_unique<PrototypeAST>(Name, std::move(*Args), RetType, false, OwnerClass, IsPublic, IsOverload);
Proto->setOperator(Operator);
+ Proto->setDefaultReturn(IsDefaultReturn);
- return ParseFunctionBody(std::move(Proto), tok_endfunction, "expected ENDFUNCTION", Line);
+ auto Def = ParseFunctionBody(std::move(Proto), tok_endfunction, "expected ENDFUNCTION", Line);
+ if (IsDefaultReturn && !CurrentTemplate.empty()) {
+ sourceError(Line, "DEFAULT return types are not supported in templates");
+ return nullptr;
+ }
+ return Def;
}
std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerClass, bool IsPublic, bool IsOverload) {
diff --git a/tests/i/default-returns/case.json b/tests/i/default-returns/case.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/tests/i/default-returns/case.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/i/default-returns/expected.txt b/tests/i/default-returns/expected.txt
new file mode 100644
index 0000000..3813090
--- /dev/null
+++ b/tests/i/default-returns/expected.txt
@@ -0,0 +1,12 @@
+2.5
+4
+3
+3
+2
+6.5
+7
+8.5
+9
+10.5
+11
+12
diff --git a/tests/i/default-returns/notes.md b/tests/i/default-returns/notes.md
new file mode 100644
index 0000000..973986f
--- /dev/null
+++ b/tests/i/default-returns/notes.md
@@ -0,0 +1,7 @@
+# Default returns
+
+Human review: pending. Expected output was independently traced by AI, not captured from cpsc.
+
+Basis: DEFAULT and return-type overloading are project extensions.
+
+Trace: Foo(INTEGER) defaults to REAL 2.5, while Foo(REAL) defaults to INTEGER 4. INTEGER assignment and cast select 3; a C_INT cast falls back to the default 2.5 then converts to 2. Get defaults to 6.5, member addition to 8.5, and global negation to 10.5; INTEGER assignments select 7, 9 and 11. The nested Foo call defaults to REAL, selecting Consume(REAL), which returns 12.
diff --git a/tests/i/default-returns/program.cps b/tests/i/default-returns/program.cps
new file mode 100644
index 0000000..a6a0694
--- /dev/null
+++ b/tests/i/default-returns/program.cps
@@ -0,0 +1,54 @@
+OVERLOAD FUNCTION Foo(X : INTEGER) RETURNS DEFAULT REAL
+ RETURN 2.5
+ENDFUNCTION
+OVERLOAD FUNCTION Foo(X : INTEGER) RETURNS INTEGER
+ RETURN 3
+ENDFUNCTION
+OVERLOAD FUNCTION Foo(X : REAL) RETURNS DEFAULT INTEGER
+ RETURN 4
+ENDFUNCTION
+OVERLOAD FUNCTION Foo(X : REAL) RETURNS REAL
+ RETURN 5.5
+ENDFUNCTION
+CLASS Box
+ Value : INTEGER
+ OVERLOAD FUNCTION Get() RETURNS DEFAULT REAL
+ RETURN 6.5
+ ENDFUNCTION
+ OVERLOAD FUNCTION Get() RETURNS INTEGER
+ RETURN 7
+ ENDFUNCTION
+ OVERLOAD FUNCTION OPERATOR+(Other : Box) RETURNS DEFAULT REAL
+ RETURN 8.5
+ ENDFUNCTION
+ OVERLOAD FUNCTION OPERATOR+(Other : Box) RETURNS INTEGER
+ RETURN 9
+ ENDFUNCTION
+ENDCLASS
+OVERLOAD FUNCTION OPERATOR-(X : Box) RETURNS DEFAULT REAL
+ RETURN 10.5
+ENDFUNCTION
+OVERLOAD FUNCTION OPERATOR-(X : Box) RETURNS INTEGER
+ RETURN 11
+ENDFUNCTION
+OVERLOAD FUNCTION Consume(X : REAL) RETURNS INTEGER
+ RETURN 12
+ENDFUNCTION
+DECLARE B : Box
+DECLARE N : INTEGER
+OUTPUT Foo(1)
+OUTPUT Foo(1.0)
+N <- Foo(1)
+OUTPUT N
+OUTPUT (INTEGER)Foo(1)
+OUTPUT (C_INT)Foo(1)
+OUTPUT B.Get()
+N <- B.Get()
+OUTPUT N
+OUTPUT B + B
+N <- B + B
+OUTPUT N
+OUTPUT -B
+N <- -B
+OUTPUT N
+OUTPUT Consume(Foo(1))
diff --git a/tests/iii/default-return-errors/case.json b/tests/iii/default-return-errors/case.json
new file mode 100644
index 0000000..654bca5
--- /dev/null
+++ b/tests/iii/default-return-errors/case.json
@@ -0,0 +1,10 @@
+{
+ "errors": [
+ {"reason": "Duplicate DEFAULT return", "line": 4},
+ {"reason": "DEFAULT requires a non-VOID OVERLOAD FUNCTION", "line": 7},
+ {"reason": "DEFAULT requires a non-VOID OVERLOAD FUNCTION", "line": 10},
+ {"reason": "No matching overload for Typed\\(\\) returning BOOLEAN", "line": 25},
+ {"reason": "Ambiguous call to Ambiguous", "line": 26},
+ {"reason": "Ambiguous call to Clash", "line": 33}
+ ]
+}
diff --git a/tests/iii/default-return-errors/program.cps b/tests/iii/default-return-errors/program.cps
new file mode 100644
index 0000000..cd30994
--- /dev/null
+++ b/tests/iii/default-return-errors/program.cps
@@ -0,0 +1,33 @@
+OVERLOAD FUNCTION Duplicate(X : INTEGER) RETURNS DEFAULT INTEGER
+ RETURN 1
+ENDFUNCTION
+OVERLOAD FUNCTION duplicate(X : INTEGER) RETURNS DEFAULT REAL
+ RETURN 2.0
+ENDFUNCTION
+FUNCTION Plain() RETURNS DEFAULT INTEGER
+ RETURN 1
+ENDFUNCTION
+OVERLOAD FUNCTION Empty() RETURNS DEFAULT VOID
+ENDFUNCTION
+OVERLOAD FUNCTION Ambiguous() RETURNS INTEGER
+ RETURN 1
+ENDFUNCTION
+OVERLOAD FUNCTION Ambiguous() RETURNS REAL
+ RETURN 2.0
+ENDFUNCTION
+OVERLOAD FUNCTION Typed() RETURNS DEFAULT INTEGER
+ RETURN 1
+ENDFUNCTION
+OVERLOAD FUNCTION Typed() RETURNS REAL
+ RETURN 2.0
+ENDFUNCTION
+DECLARE Flag : BOOLEAN
+Flag <- Typed()
+OUTPUT Ambiguous()
+OVERLOAD FUNCTION Clash(A : STRING, B : CHAR) RETURNS DEFAULT INTEGER
+ RETURN 1
+ENDFUNCTION
+OVERLOAD FUNCTION Clash(A : CHAR, B : STRING) RETURNS REAL
+ RETURN 2.0
+ENDFUNCTION
+OUTPUT Clash("a", "b")
diff --git a/tests/iii/default-return-template/case.json b/tests/iii/default-return-template/case.json
new file mode 100644
index 0000000..6e43a53
--- /dev/null
+++ b/tests/iii/default-return-template/case.json
@@ -0,0 +1,5 @@
+{
+ "errors": [
+ {"reason": "DEFAULT return types are not supported in templates", "line": 2}
+ ]
+}
diff --git a/tests/iii/default-return-template/program.cps b/tests/iii/default-return-template/program.cps
new file mode 100644
index 0000000..8f71900
--- /dev/null
+++ b/tests/iii/default-return-template/program.cps
@@ -0,0 +1,5 @@
+TEMPLATE <T>
+OVERLOAD FUNCTION Pick(X : T) RETURNS DEFAULT INTEGER
+ RETURN 1
+ENDFUNCTION
+OUTPUT 1
diff --git a/tests/iii/multifile-default-return/case.json b/tests/iii/multifile-default-return/case.json
new file mode 100644
index 0000000..5679a0f
--- /dev/null
+++ b/tests/iii/multifile-default-return/case.json
@@ -0,0 +1,6 @@
+{
+ "sources": ["program.cps", "first.cps", "second.cps"],
+ "errors": [
+ {"reason": "Duplicate DEFAULT return", "file": "second.cps", "line": 1}
+ ]
+}
diff --git a/tests/iii/multifile-default-return/first.cps b/tests/iii/multifile-default-return/first.cps
new file mode 100644
index 0000000..a376dda
--- /dev/null
+++ b/tests/iii/multifile-default-return/first.cps
@@ -0,0 +1,3 @@
+OVERLOAD FUNCTION Pick(X : INTEGER) RETURNS DEFAULT INTEGER
+ RETURN 1
+ENDFUNCTION
diff --git a/tests/iii/multifile-default-return/program.cps b/tests/iii/multifile-default-return/program.cps
new file mode 100644
index 0000000..15feac7
--- /dev/null
+++ b/tests/iii/multifile-default-return/program.cps
@@ -0,0 +1,3 @@
+INCLUDE "first.cps"
+INCLUDE "second.cps"
+OUTPUT 1
diff --git a/tests/iii/multifile-default-return/second.cps b/tests/iii/multifile-default-return/second.cps
new file mode 100644
index 0000000..70b4667
--- /dev/null
+++ b/tests/iii/multifile-default-return/second.cps
@@ -0,0 +1,3 @@
+OVERLOAD FUNCTION Pick(X : INTEGER) RETURNS DEFAULT REAL
+ RETURN 2.0
+ENDFUNCTION