From 25ae363250e479701ed532c55c255e043c8240da Mon Sep 17 00:00:00 2001 From: _ov4 Date: Wed, 21 Oct 2026 06:04:52 +0000 Subject: feat(codegen): add variadic function templates Co-Authored-By: Claude Opus 5.5 --- lib/CodeGen/CodeGen.cc | 113 +++++++++++++++---------- tests/i/template-variadic/case.json | 1 + tests/i/template-variadic/expected.txt | 12 +++ tests/i/template-variadic/notes.md | 9 ++ tests/i/template-variadic/program.cps | 37 ++++++++ tests/iii/template-variadic-errors/case.json | 11 +++ tests/iii/template-variadic-errors/program.cps | 39 +++++++++ 7 files changed, 176 insertions(+), 46 deletions(-) create mode 100644 tests/i/template-variadic/case.json create mode 100644 tests/i/template-variadic/expected.txt create mode 100644 tests/i/template-variadic/notes.md create mode 100644 tests/i/template-variadic/program.cps create mode 100644 tests/iii/template-variadic-errors/case.json create mode 100644 tests/iii/template-variadic-errors/program.cps diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index 4844ece..9a678ca 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -10,6 +10,7 @@ #include "cps/TemplateAST.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Verifier.h" +#include #include #include @@ -557,59 +558,79 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, } } if (Ordinary) return Ordinary; - const TemplateDefinition *Selected = nullptr; - IdentifierMap> SelectedBindings; if (HasTemplates) { - for (const auto &Definition : Templates.at(Name)) { - auto *Function = Definition.Decl->getFunction(); - if (!Function || Definition.Decl->isVariadic() || !isVisible(Definition.OwnerUnit)) continue; - const auto &Params = Function->getProto()->getParams(); - if (Params.size() != Actual.size()) continue; - IdentifierMap> Bindings; - for (const auto &Param : Definition.Decl->getParams()) Bindings.emplace(Param.Name, std::vector{}); - bool Match = true; - if (HasExplicit) { + // A matching non-variadic template wins before any variadic candidate. + for (bool Variadic : {false, true}) { + const TemplateDefinition *Selected = nullptr; + detail::TemplateBindings SelectedBindings; + for (const auto &Definition : Templates.at(Name)) { + auto *Function = Definition.Decl->getFunction(); + if (!Function || Definition.Decl->isVariadic() != Variadic || !isVisible(Definition.OwnerUnit)) continue; + const auto &Params = Function->getProto()->getParams(); const auto &TemplateParams = Definition.Decl->getParams(); - if (Explicit->size() > TemplateParams.size()) continue; - for (size_t I = 0; I < Explicit->size(); ++I) { - const TypeInfo *Type = resolveType((*Explicit)[I]); - if (!Type || Type->isVoid()) { Match = false; break; } - Bindings.at(TemplateParams[I].Name).push_back(Type->Name); + bool HasPackParam = !Params.empty() && Params.back().IsPack; + size_t FixedCount = Params.size() - HasPackParam; + if (HasPackParam ? Actual.size() < FixedCount : Actual.size() != FixedCount) continue; + detail::TemplateBindings Bindings; + for (const auto &Param : TemplateParams) Bindings.emplace(Param.Name, std::vector{}); + bool Match = true; + if (HasExplicit) { + size_t FixedTypes = TemplateParams.size() - Variadic; + if (!Variadic && Explicit->size() > FixedTypes) continue; + for (size_t I = 0; I < Explicit->size(); ++I) { + const TypeInfo *Type = resolveType((*Explicit)[I]); + if (!Type || Type->isVoid()) { Match = false; break; } + Bindings.at(TemplateParams[std::min(I, FixedTypes)].Name).push_back(Type->Name); + } } - } - if (!Match) continue; - for (size_t I = 0; I < Actual.size(); ++I) { - const auto &P = Params[I]; - const TypeInfo *Type = Actual[I].Type; - if (!Type || Type->isVoid() || Actual[I].Rank != (P.IsArray ? P.Rank : 0)) { Match = false; break; } - auto Binding = Bindings.find(P.TypeName); - if (Binding != Bindings.end()) { - if (Binding->second.empty()) Binding->second.push_back(Type->Name); - else if (!Type->sameType(resolveType(Binding->second.front()))) { Match = false; break; } + if (!Match) continue; + for (size_t I = 0; I < FixedCount; ++I) { + const auto &P = Params[I]; + const TypeInfo *Type = Actual[I].Type; + if (!Type || Type->isVoid() || Actual[I].Rank != (P.IsArray ? P.Rank : 0)) { Match = false; break; } + auto Binding = Bindings.find(P.TypeName); + if (Binding != Bindings.end()) { + if (Binding->second.empty()) Binding->second.push_back(Type->Name); + else if (!Type->sameType(resolveType(Binding->second.front()))) { Match = false; break; } + } } + if (HasPackParam) { + auto &Pack = Bindings.at(Params.back().TypeName); + // Explicit pack types constrain a prefix; deduction fills the rest. + if (Pack.size() > Actual.size() - FixedCount) Match = false; + for (size_t I = FixedCount; Match && I < Actual.size(); ++I) { + const TypeInfo *Type = Actual[I].Type; + size_t Index = I - FixedCount; + if (!Type || Type->isVoid() || Actual[I].Rank) { Match = false; break; } + if (Index < Pack.size()) { + if (!Type->sameType(resolveType(Pack[Index]))) Match = false; + } else Pack.push_back(Type->Name); + } + } + for (const auto &Param : TemplateParams) + if (!Param.IsPack && Bindings.at(Param.Name).empty()) Match = false; + if (!Match) continue; + // Composite parameter types can use bindings deduced at later positions, + // but do not themselves contribute deductions. + detail::TemplateClone Substitute(Bindings, [&](int, const std::string &) { Match = false; }); + for (size_t I = 0; I < FixedCount; ++I) { + const auto &TypeName = Params[I].TypeName; + auto Binding = Bindings.find(TypeName); + std::string Expected = Binding != Bindings.end() ? Binding->second.front() + : TypeName.find('<') != std::string::npos ? Substitute.type(TypeName) : TypeName; + if (!Match || !Actual[I].Type->sameType(resolveType(Expected))) { Match = false; break; } + } + if (!Match) continue; + if (Selected) { + reportError("Ambiguous call to %s(%s)", Name.c_str(), Key.c_str()); + return nullptr; + } + Selected = &Definition; + SelectedBindings = std::move(Bindings); } - for (const auto &[Param, Types] : Bindings) if (Types.empty()) Match = false; - if (!Match) continue; - // Composite parameter types can use bindings deduced at later positions, - // but do not themselves contribute deductions. - detail::TemplateClone Substitute(Bindings, [&](int, const std::string &) { Match = false; }); - for (size_t I = 0; I < Actual.size(); ++I) { - const auto &TypeName = Params[I].TypeName; - auto Binding = Bindings.find(TypeName); - std::string Expected = Binding != Bindings.end() ? Binding->second.front() - : TypeName.find('<') != std::string::npos ? Substitute.type(TypeName) : TypeName; - if (!Match || !Actual[I].Type->sameType(resolveType(Expected))) { Match = false; break; } - } - if (!Match) continue; - if (Selected) { - reportError("Ambiguous call to %s(%s)", Name.c_str(), Key.c_str()); - return nullptr; - } - Selected = &Definition; - SelectedBindings = std::move(Bindings); + if (Selected) return instantiateFunction(*Selected, SelectedBindings); } } - if (Selected) return instantiateFunction(*Selected, SelectedBindings); reportError("No matching overload for %s(%s)", Name.c_str(), Key.c_str()); return nullptr; } diff --git a/tests/i/template-variadic/case.json b/tests/i/template-variadic/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/i/template-variadic/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/i/template-variadic/expected.txt b/tests/i/template-variadic/expected.txt new file mode 100644 index 0000000..a3177f6 --- /dev/null +++ b/tests/i/template-variadic/expected.txt @@ -0,0 +1,12 @@ +1 +2.500000 +c +str +1 +2.500000 +7 +3.500000 +30 +10 +20 +10 diff --git a/tests/i/template-variadic/notes.md b/tests/i/template-variadic/notes.md new file mode 100644 index 0000000..7771cfa --- /dev/null +++ b/tests/i/template-variadic/notes.md @@ -0,0 +1,9 @@ +# Variadic function templates + +Human review: pending. + +TEMPLATE and parameter packs are compiler extensions. PrintAll outputs each head before recursively forwarding the remaining arguments. Its ordinary parameterless overload terminates the recursion and produces no output. The first call therefore prints INTEGER 1, REAL 2.500000, CHAR c and STRING str in order; the direct empty call prints nothing. Explicit INTEGER binds the first type of the next call, which prints 1 and 2.500000. + +Forward explicitly binds the first pack entry to INTEGER, deduces its REAL tail and forwards both the type pack and value pack, producing 7 and 3.500000. Its empty instantiation forwards empty packs to the parameterless base case and produces no output. + +Pick returns 30 for the exact ordinary INTEGER overload, 10 for the fixed template on REAL, and 20 for the variadic two-argument template. Explicit INTEGER selects the fixed template and returns 10. REAL formatting follows the project's six-decimal output convention. diff --git a/tests/i/template-variadic/program.cps b/tests/i/template-variadic/program.cps new file mode 100644 index 0000000..3127c89 --- /dev/null +++ b/tests/i/template-variadic/program.cps @@ -0,0 +1,37 @@ +OVERLOAD PROCEDURE PrintAll() +ENDPROCEDURE + +TEMPLATE +OVERLOAD PROCEDURE PrintAll(First : T, Others : Rest...) + OUTPUT First + CALL PrintAll(Others...) +ENDPROCEDURE + +TEMPLATE +PROCEDURE Forward(Values : Types...) + CALL PrintAll(Values...) +ENDPROCEDURE + +TEMPLATE +OVERLOAD FUNCTION Pick(Value : T) RETURNS INTEGER + RETURN 10 +ENDFUNCTION + +TEMPLATE +OVERLOAD FUNCTION Pick(First : T, Others : Rest...) RETURNS INTEGER + RETURN 20 +ENDFUNCTION + +OVERLOAD FUNCTION Pick(Value : INTEGER) RETURNS INTEGER + RETURN 30 +ENDFUNCTION + +CALL PrintAll(1, 2.5, 'c', "str") +CALL PrintAll() +CALL PrintAll(1, 2.5) +CALL Forward(7, 3.5) +CALL Forward() +OUTPUT Pick(1) +OUTPUT Pick(1.0) +OUTPUT Pick(1, 2) +OUTPUT Pick(1) diff --git a/tests/iii/template-variadic-errors/case.json b/tests/iii/template-variadic-errors/case.json new file mode 100644 index 0000000..ac4093a --- /dev/null +++ b/tests/iii/template-variadic-errors/case.json @@ -0,0 +1,11 @@ +{ + "errors": [ + {"reason": "No matching overload for MissingBase", "line": 3}, + {"reason": "A parameter pack expansion is only allowed in an argument list", "line": 8}, + {"reason": "A parameter pack expansion is only allowed in an argument list", "line": 14}, + {"reason": "A parameter pack expansion is only allowed in an argument list", "line": 19}, + {"reason": "No matching overload for Exact", "line": 37}, + {"reason": "No matching overload for Exact", "line": 38}, + {"reason": "Ambiguous call to Tied", "line": 39} + ] +} diff --git a/tests/iii/template-variadic-errors/program.cps b/tests/iii/template-variadic-errors/program.cps new file mode 100644 index 0000000..df2ebc1 --- /dev/null +++ b/tests/iii/template-variadic-errors/program.cps @@ -0,0 +1,39 @@ +TEMPLATE +PROCEDURE MissingBase(First : T, Others : Rest...) + CALL MissingBase(Others...) +ENDPROCEDURE + +TEMPLATE +PROCEDURE BadOutput(Values : Types...) + OUTPUT Values... +ENDPROCEDURE + +TEMPLATE +PROCEDURE BadIndex(Values : Types...) + DECLARE Items : ARRAY[1:2] OF INTEGER + OUTPUT Items[Values...] +ENDPROCEDURE + +TEMPLATE +PROCEDURE BadOperand(Values : Types...) + OUTPUT 1 + Values... +ENDPROCEDURE + +TEMPLATE +PROCEDURE Exact(First : T, Others : Rest...) +ENDPROCEDURE + +TEMPLATE +OVERLOAD PROCEDURE Tied(Values : A...) +ENDPROCEDURE +TEMPLATE +OVERLOAD PROCEDURE Tied(Values : B...) +ENDPROCEDURE + +CALL MissingBase(1) +CALL BadOutput(1) +CALL BadIndex(1) +CALL BadOperand(1) +CALL Exact(1, 2) +CALL Exact(1, 2.5) +CALL Tied(1) -- cgit v1.3.1-4-g156e