aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-21 06:00:55 +0000
committer_ov4 <ov4@ov4.org>2026-10-21 06:00:55 +0000
commitbfe020bfa91640e7a6f1a516dc752fa754249088 (patch)
treefd82055133f45fdeaacddd4e62b210f4b63e0c03
parentc56877035d6600b463a9cbf0e339f7548c0b46fd (diff)
feat(codegen): support explicit template arguments
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--include/cps/CodeGen.h6
-rw-r--r--lib/CodeGen/CodeGen.cc33
-rw-r--r--lib/CodeGen/ExpressionGen.cc11
-rw-r--r--lib/CodeGen/StatementGen.cc4
-rw-r--r--lib/CodeGen/ValueTypeGen.cc6
-rw-r--r--tests/i/template-explicit-args/case.json1
-rw-r--r--tests/i/template-explicit-args/expected.txt9
-rw-r--r--tests/i/template-explicit-args/notes.md5
-rw-r--r--tests/i/template-explicit-args/program.cps47
-rw-r--r--tests/iii/template-explicit-errors/case.json10
-rw-r--r--tests/iii/template-explicit-errors/program.cps24
11 files changed, 146 insertions, 10 deletions
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index 124ead1..23487f6 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -185,9 +185,11 @@ class CodeGen {
llvm::Value *loadFromLValue(const LValueInfo &LV);
llvm::Value *emitAllocationSize(ExprAST *Expr);
llvm::Value *emitCall(const std::string &Callee,
- const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, int Line);
+ const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, int Line,
+ const std::vector<std::string> *Explicit = nullptr);
const FuncSig *resolveCall(const std::string &Name,
- const std::vector<std::unique_ptr<ExprAST>> &Args);
+ const std::vector<std::unique_ptr<ExprAST>> &Args,
+ const std::vector<std::string> *Explicit = nullptr);
struct OperatorResolution {
const FuncSig *Signature = nullptr;
bool Ambiguous = false;
diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc
index 3e5b890..4844ece 100644
--- a/lib/CodeGen/CodeGen.cc
+++ b/lib/CodeGen/CodeGen.cc
@@ -5,6 +5,7 @@
#include "cps/MemoryHandler.h"
#include "cps/ExceptionHandler.h"
#include "Builtins.h"
+#include "TemplateClone.h"
#include "cps/TypeAST.h"
#include "cps/TemplateAST.h"
#include "llvm/IR/Instructions.h"
@@ -501,9 +502,15 @@ Value *CodeGen::emitVariadicArgument(ExprAST *Expr, const std::string &Callee, s
// Resolve using static source types only; argument evaluation belongs to marshalling.
const FuncSig *CodeGen::resolveCall(const std::string &Name,
- const std::vector<std::unique_ptr<ExprAST>> &Args) {
+ const std::vector<std::unique_ptr<ExprAST>> &Args,
+ const std::vector<std::string> *Explicit) {
auto Overloads = FuncGen->getOverloads(Name);
bool HasTemplates = hasFunctionTemplate(Name);
+ bool HasExplicit = Explicit && !Explicit->empty();
+ if (HasExplicit && !HasTemplates) {
+ reportError("Explicit template arguments require a function template: %s", Name.c_str());
+ return nullptr;
+ }
if (Overloads.empty() && !HasTemplates) {
reportError("Call to undefined function %s", Name.c_str());
return nullptr;
@@ -529,6 +536,7 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
}
const FuncSig *Ordinary = nullptr;
for (const FuncSig *Sig : Overloads) {
+ if (HasExplicit) break;
if (Sig->Params.size() != Actual.size()) continue;
bool Match = true;
for (size_t I = 0; I < Actual.size(); ++I) {
@@ -560,6 +568,16 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
IdentifierMap<std::vector<std::string>> Bindings;
for (const auto &Param : Definition.Decl->getParams()) Bindings.emplace(Param.Name, std::vector<std::string>{});
bool Match = true;
+ if (HasExplicit) {
+ 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);
+ }
+ }
+ if (!Match) continue;
for (size_t I = 0; I < Actual.size(); ++I) {
const auto &P = Params[I];
const TypeInfo *Type = Actual[I].Type;
@@ -568,10 +586,21 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
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; }
- } else if (!Type->sameType(resolveType(P.TypeName))) { Match = false; break; }
+ }
}
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;
diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc
index 15d0fe3..3377a39 100644
--- a/lib/CodeGen/ExpressionGen.cc
+++ b/lib/CodeGen/ExpressionGen.cc
@@ -150,7 +150,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
return emitMemberCall(Call);
if (auto *Call = dynamic_cast<CallExprAST*>(Expr))
- return emitCall(Call->getCallee(), Call->getArgs(), Call->getLine());
+ return emitCall(Call->getCallee(), Call->getArgs(), Call->getLine(), &Call->getTemplateArgs());
if (auto *D = dynamic_cast<DesignatorExprAST*>(Expr)) {
LValueInfo LV;
@@ -192,10 +192,15 @@ Value *CodeGen::emitBinaryOp(int Op, Value *L, const TypeInfo *LT,
}
Value *CodeGen::emitCall(const std::string &Callee,
- const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, int Line) {
+ const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, int Line,
+ const std::vector<std::string> *Explicit) {
SaveAndRestore<int> Location(DiagnosticLine, Line ? Line : DiagnosticLine);
std::string Name = Callee;
if (const BuiltinInfo *B = findBuiltin(Name)) {
+ if (Explicit && !Explicit->empty()) {
+ reportError("Explicit template arguments are not allowed on builtin %s", B->Name);
+ return nullptr;
+ }
Name = B->Name;
if (ArgExprs.size() != B->Arity) {
reportError("%s expects %u arg%s", B->Name, B->Arity, B->Arity == 1 ? "" : "s");
@@ -230,7 +235,7 @@ Value *CodeGen::emitCall(const std::string &Callee,
}
std::vector<Value*> Args;
- const FuncSig *Sig = resolveCall(Name, ArgExprs);
+ const FuncSig *Sig = resolveCall(Name, ArgExprs, Explicit);
if (!Sig || !marshalCallArgs(*Sig, ArgExprs, Line, Args)) return nullptr;
return FuncGen->emitCall(*Sig, Args);
}
diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc
index 2186aa0..7a73e51 100644
--- a/lib/CodeGen/StatementGen.cc
+++ b/lib/CodeGen/StatementGen.cc
@@ -104,9 +104,9 @@ void CodeGen::emitStmtImpl(StmtAST *Stmt) {
}
if (auto *Call = dynamic_cast<CallStmtAST*>(Stmt)) {
- Value *Result = emitCall(Call->getCallee(), Call->getArgs(), Call->getLine());
+ Value *Result = emitCall(Call->getCallee(), Call->getArgs(), Call->getLine(), &Call->getTemplateArgs());
const FuncSig *Sig = hasCallable(Call->getCallee())
- ? resolveCall(Call->getCallee(), Call->getArgs()) : nullptr;
+ ? resolveCall(Call->getCallee(), Call->getArgs(), &Call->getTemplateArgs()) : nullptr;
const TypeInfo *Type = Sig ? resolveType(Sig->ReturnTypeName) : nullptr;
if (Result && Type && (Type->IsClass || containsString(Type))) materializeOperatorReceiver(Result, Type);
return;
diff --git a/lib/CodeGen/ValueTypeGen.cc b/lib/CodeGen/ValueTypeGen.cc
index 487d61d..1079b14 100644
--- a/lib/CodeGen/ValueTypeGen.cc
+++ b/lib/CodeGen/ValueTypeGen.cc
@@ -136,9 +136,13 @@ const TypeInfo *CodeGen::getExprTypeInfo(ExprAST *Expr) {
if (auto *Call = dynamic_cast<CallExprAST*>(Expr)) {
const std::string &Name = Call->getCallee();
if (const BuiltinInfo *B = findBuiltin(Name)) {
+ if (!Call->getTemplateArgs().empty()) {
+ reportError("Explicit template arguments are not allowed on builtin %s", B->Name);
+ return nullptr;
+ }
return resolveType(B->ReturnTypeName);
}
- if (const FuncSig *Sig = resolveCall(Name, Call->getArgs())) {
+ if (const FuncSig *Sig = resolveCall(Name, Call->getArgs(), &Call->getTemplateArgs())) {
return resolveType(Sig->ReturnTypeName);
}
diff --git a/tests/i/template-explicit-args/case.json b/tests/i/template-explicit-args/case.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/tests/i/template-explicit-args/case.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/i/template-explicit-args/expected.txt b/tests/i/template-explicit-args/expected.txt
new file mode 100644
index 0000000..1c46cb5
--- /dev/null
+++ b/tests/i/template-explicit-args/expected.txt
@@ -0,0 +1,9 @@
+2.000000
+3
+tail
+0.000000
+2.500000
+9
+200
+100
+chain
diff --git a/tests/i/template-explicit-args/notes.md b/tests/i/template-explicit-args/notes.md
new file mode 100644
index 0000000..b487fdd
--- /dev/null
+++ b/tests/i/template-explicit-args/notes.md
@@ -0,0 +1,5 @@
+# template-explicit-args
+
+Human review: pending. Expected output was independently traced by AI, not captured from cpsc.
+
+Basis: TEMPLATE is a project extension. Max explicitly selects REAL and INTEGER, producing 2.000000 and 3. Second binds its first type explicitly and deduces STRING for its second, returning tail. Zero's otherwise undeducible return type is explicitly REAL, producing 0.000000. CALL and bare Print statements produce 2.500000 and 9. Pick uses the ordinary exact overload without explicit arguments (200) and the template with them (100). Echo preserves the STRING chain through a result method, and discarded STRING results exercise statement cleanup without output.
diff --git a/tests/i/template-explicit-args/program.cps b/tests/i/template-explicit-args/program.cps
new file mode 100644
index 0000000..123bda6
--- /dev/null
+++ b/tests/i/template-explicit-args/program.cps
@@ -0,0 +1,47 @@
+TEMPLATE <T>
+FUNCTION Max(A : T, B : T) RETURNS T
+ IF A > B THEN
+ RETURN A
+ ENDIF
+ RETURN B
+ENDFUNCTION
+
+TEMPLATE <A, B>
+FUNCTION Second(First : A, Last : B) RETURNS B
+ RETURN Last
+ENDFUNCTION
+
+TEMPLATE <T>
+FUNCTION Zero() RETURNS T
+ RETURN (T)0
+ENDFUNCTION
+
+TEMPLATE <T>
+PROCEDURE Print(Value : T)
+ OUTPUT Value
+ENDPROCEDURE
+
+TEMPLATE <T>
+FUNCTION Echo(Value : T) RETURNS T
+ RETURN Value
+ENDFUNCTION
+
+TEMPLATE <T>
+OVERLOAD FUNCTION Pick(Value : T) RETURNS INTEGER
+ RETURN 100
+ENDFUNCTION
+OVERLOAD FUNCTION Pick(Value : INTEGER) RETURNS INTEGER
+ RETURN 200
+ENDFUNCTION
+
+OUTPUT Max<REAL>(1.5, 2.0)
+OUTPUT Max<INTEGER>(Max<INTEGER>(1, 2), 3)
+OUTPUT Second<INTEGER>(7, "tail")
+OUTPUT Zero<REAL>()
+CALL Print<REAL>(2.5)
+Print<INTEGER>(9)
+OUTPUT Pick(7)
+OUTPUT Pick<INTEGER>(7)
+OUTPUT (STRING)Echo<STRING>("chain").C_STR()
+CALL Echo<STRING>("discarded")
+Echo<STRING>("also discarded")
diff --git a/tests/iii/template-explicit-errors/case.json b/tests/iii/template-explicit-errors/case.json
new file mode 100644
index 0000000..657f69e
--- /dev/null
+++ b/tests/iii/template-explicit-errors/case.json
@@ -0,0 +1,10 @@
+{
+ "errors": [
+ {"reason": "No matching overload for Max", "line": 19},
+ {"reason": "No matching overload for Max", "line": 20},
+ {"reason": "Explicit template arguments require a function template: Plain", "line": 21},
+ {"reason": "Explicit template arguments are not allowed on builtin EOF", "line": 22},
+ {"reason": "Explicit template arguments are not allowed on builtin MALLOC", "line": 23},
+ {"reason": "Ambiguous call to Tied", "line": 24}
+ ]
+}
diff --git a/tests/iii/template-explicit-errors/program.cps b/tests/iii/template-explicit-errors/program.cps
new file mode 100644
index 0000000..5236328
--- /dev/null
+++ b/tests/iii/template-explicit-errors/program.cps
@@ -0,0 +1,24 @@
+TEMPLATE <T>
+FUNCTION Max(A : T, B : T) RETURNS T
+ RETURN A
+ENDFUNCTION
+
+FUNCTION Plain(Value : INTEGER) RETURNS INTEGER
+ RETURN Value
+ENDFUNCTION
+
+TEMPLATE <A>
+OVERLOAD FUNCTION Tied(Value : A) RETURNS INTEGER
+ RETURN 1
+ENDFUNCTION
+TEMPLATE <B>
+OVERLOAD FUNCTION Tied(Value : B) RETURNS INTEGER
+ RETURN 2
+ENDFUNCTION
+
+OUTPUT Max<INTEGER, REAL>(1, 2)
+OUTPUT Max<REAL>(1, 2.5)
+OUTPUT Plain<INTEGER>(1)
+OUTPUT EOF<STRING>("file.txt")
+CALL MALLOC<INTEGER>(8)
+OUTPUT Tied<INTEGER>(1)