diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-28 13:36:16 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-28 13:36:16 +0000 |
| commit | b28384fed1ce638e8b00b72dba974e9fd7e55eb4 (patch) | |
| tree | b0b342b148c1d5b33197f7a59d0a7a7edb92af1c | |
| parent | caa88a9b36d9e1dfae40ecb31aee05bc2891fa17 (diff) | |
feat(overload): support default return types
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | include/cps/FunctionAST.h | 3 | ||||
| -rw-r--r-- | include/cps/FunctionGen.h | 2 | ||||
| -rw-r--r-- | include/cps/Lexer.h | 1 | ||||
| -rw-r--r-- | lib/CodeGen/CallGen.cc | 10 | ||||
| -rw-r--r-- | lib/CodeGen/FunctionGen.cc | 27 | ||||
| -rw-r--r-- | lib/CodeGen/OperatorGen.cc | 8 | ||||
| -rw-r--r-- | lib/CodeGen/TemplateClone.cc | 1 | ||||
| -rw-r--r-- | lib/Lexer/Lexer.cc | 1 | ||||
| -rw-r--r-- | lib/Parser/ParserFunction.cc | 13 | ||||
| -rw-r--r-- | tests/i/default-returns/case.json | 1 | ||||
| -rw-r--r-- | tests/i/default-returns/expected.txt | 12 | ||||
| -rw-r--r-- | tests/i/default-returns/notes.md | 7 | ||||
| -rw-r--r-- | tests/i/default-returns/program.cps | 54 | ||||
| -rw-r--r-- | tests/iii/default-return-errors/case.json | 10 | ||||
| -rw-r--r-- | tests/iii/default-return-errors/program.cps | 33 | ||||
| -rw-r--r-- | tests/iii/default-return-template/case.json | 5 | ||||
| -rw-r--r-- | tests/iii/default-return-template/program.cps | 5 | ||||
| -rw-r--r-- | tests/iii/multifile-default-return/case.json | 6 | ||||
| -rw-r--r-- | tests/iii/multifile-default-return/first.cps | 3 | ||||
| -rw-r--r-- | tests/iii/multifile-default-return/program.cps | 3 | ||||
| -rw-r--r-- | tests/iii/multifile-default-return/second.cps | 3 |
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 |
