aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AGENTS/CONVENTIONS/TEMPLATES_AND_OPERATORS.md10
-rw-r--r--AGENTS/PITFALLS/overload-template-operator.md12
-rw-r--r--doc/language/keyword/cpsc-keyword-operator.7.md8
-rw-r--r--include/cps/CodeGen.h7
-rw-r--r--lib/CodeGen/ControlFlowGen.cc10
-rw-r--r--lib/CodeGen/ExpressionGen.cc31
-rw-r--r--lib/CodeGen/OperatorGen.cc51
-rw-r--r--lib/CodeGen/ValueTypeGen.cc8
-rw-r--r--tests/iii/operator-global-errors/case.json2
-rw-r--r--tests/iii/operator-result-errors/case.json6
10 files changed, 105 insertions, 40 deletions
diff --git a/AGENTS/CONVENTIONS/TEMPLATES_AND_OPERATORS.md b/AGENTS/CONVENTIONS/TEMPLATES_AND_OPERATORS.md
index 7177b23..f7ebd78 100644
--- a/AGENTS/CONVENTIONS/TEMPLATES_AND_OPERATORS.md
+++ b/AGENTS/CONVENTIONS/TEMPLATES_AND_OPERATORS.md
@@ -40,10 +40,12 @@ Part of [Load-bearing conventions](../CONVENTIONS.md). Basic call resolution is
- `OperatorGen.cc` probes visible global and left-class member signatures without
emitting diagnostics or evaluating operands. Matching is exact; member/global ties
are ambiguous and access is checked only after selection.
-- Both getExprTypeInfo and emission must consult this resolver before hardcoded
- builtin result types. Returns may be any supported function result, including
- non-BOOLEAN comparisons/logic. emitCoercedExpr rejects implicit STRING to BOOLEAN;
- CASE separately requires BOOLEAN comparisons.
+- Both getExprTypeInfo and emission must consult this resolver with the expression's
+ result context before hardcoded builtin result types. A strict context requires the
+ overload's return type to match exactly; a cast only prefers it; without a context,
+ return-only-distinct candidates are ambiguous. Returns may be any supported function
+ result, including non-BOOLEAN comparisons/logic. emitCoercedExpr still rejects a
+ direct STRING to BOOLEAN conversion; CASE requires BOOLEAN comparisons.
- Operators use shared argument marshalling and exception-aware calls. Member
lvalues keep their original address; rvalues are materialized with Alive tracking
and destroyed at statement end.
diff --git a/AGENTS/PITFALLS/overload-template-operator.md b/AGENTS/PITFALLS/overload-template-operator.md
index 6f23238..5718b2d 100644
--- a/AGENTS/PITFALLS/overload-template-operator.md
+++ b/AGENTS/PITFALLS/overload-template-operator.md
@@ -50,11 +50,13 @@ Part of the [pitfalls index](../PITFALLS.md).
- Unary/binary operators require OVERLOAD FUNCTION, with supported arity and at least
one record/class/enum operand. Pointer aliases alone do not qualify.
-- Exact matching considers visible globals and left-class members together; ties are
- errors, including private/public ties.
-- Comparison and logic result types are declared, not assumed BOOLEAN; implicit
- STRING to BOOLEAN is rejected. CASE comparisons must return BOOLEAN. AND/OR
- overloads evaluate both operands.
+- Exact matching considers visible globals and left-class members together, and the
+ result type joins the match when the context supplies one; ties are errors,
+ including private/public ties. Without a context, return-only-distinct candidates
+ are ambiguous; casts only prefer their target type.
+- Comparison and logic result types are declared, not assumed BOOLEAN; a strict
+ BOOLEAN context requires an overload returning BOOLEAN. CASE comparisons must return
+ BOOLEAN. AND/OR overloads evaluate both operands.
- Temporary member receivers are destroyed at statement end, including exception
exits; ordinary DECLARE objects remain function-scoped.
- Explicit calls use OPERATOR+(a,b) or a.OPERATOR+(b).
diff --git a/doc/language/keyword/cpsc-keyword-operator.7.md b/doc/language/keyword/cpsc-keyword-operator.7.md
index d420c21..e18a5e5 100644
--- a/doc/language/keyword/cpsc-keyword-operator.7.md
+++ b/doc/language/keyword/cpsc-keyword-operator.7.md
@@ -29,9 +29,11 @@ OPERATOR is a compiler extension, absent from the CIE guide. Declare unary and b
Binary operators are `+ - * / DIV MOD & = <> < <= > >= AND OR`. Unary operators are `- NOT`. A global binary definition takes two parameters and a unary definition takes one; at least one parameter must be a record, class or enum. A pointer alias alone does not qualify. A member binary definition takes one explicit parameter and a unary definition takes none; this supplies the left or sole operand. PUBLIC and PRIVATE precede OVERLOAD as for other methods.
-Operands match exact static types, without implicit conversions, following OVERLOAD rules. For example, an INTEGER operand does not match a REAL parameter. Visible global definitions and methods of the left operand's class participate together; multiple matching candidates are an error. The selected method must be accessible: PRIVATE never causes a public fallback. Included units provide operators using the ordinary INCLUDE visibility rules. Return types do not distinguish overloads.
+Operands match exact static types, without implicit conversions, following OVERLOAD rules. For example, an INTEGER operand does not match a REAL parameter. Visible global definitions and methods of the left operand's class participate together. The selected method must be accessible: PRIVATE never causes a public fallback. Included units provide operators using the ordinary INCLUDE visibility rules.
-The expression has the selected function's declared return type. Comparisons and logic may return any supported function result type: a comparison returning STRING can be printed or assigned to STRING, but cannot be implicitly used as BOOLEAN. CASE comparisons must return BOOLEAN. A VOID result may be discarded through an explicit call but cannot be used as a value.
+Return types also distinguish operator overloads. When the context requires a result type (assignment, RETURN, condition, CASE comparison, subscript and similar positions), only definitions returning exactly that type match; if the operands match but no result does, the error is `No matching overload for OPERATOR +(T, U) returning R`. A cast `(R) expr` only prefers R and otherwise selects by operands before converting. Without an expected result type, such as OUTPUT or an operand of another operator, a unique operand match is selected and definitions that differ only by return type are ambiguous.
+
+The expression has the selected function's declared return type. Comparisons and logic may return any supported function result type: a comparison returning STRING can be printed or assigned to STRING, but a BOOLEAN context requires an overload returning BOOLEAN. CASE comparisons follow the same result-type rule and must return BOOLEAN. A VOID result may be discarded through an explicit call but cannot be used as a value.
Operators keep their existing precedence, operand count and associativity. Operands are evaluated once, from left to right; overloaded AND and OR evaluate both operands. Operators are independent: defining = does not define <>, and operands are never swapped to find a match. A matching overload takes precedence over the built-in operation; otherwise the existing built-in rules apply.
@@ -39,7 +41,7 @@ Member operators on variables, fields, array elements and pointer dereferences m
Explicit calls use `OPERATOR+(A, B)` for global definitions and `A.OPERATOR+(B)` for members. They also work as statements, optionally prefixed with CALL, to discard results. Class-valued call results support explicit member operator calls, such as `Make().OPERATOR+(B)`. Indexing, function-call, address-of and dereference operators cannot be overloaded.
-A class may define `OVERLOAD PROCEDURE OPERATOR<-(BYREF Other : ClassName)` or an overload taking another exact static type. Assignment to a class lvalue selects this member before falling back to same-class assignment; STRING-bearing aggregates copy their owned string fields, while other fields retain their existing copy behavior. The procedure takes exactly one explicit parameter; BYREF may bind a designator or materialized temporary. Keyword NEW assignment retains its allocation behavior. Self-assignment handling belongs to the operator body.
+A class may define `OVERLOAD PROCEDURE OPERATOR<-(BYREF Other : ClassName)` or an overload taking another exact static type. Assignment to a class lvalue selects this member by operand type — not by an expected result, since the operator is a procedure — before falling back to same-class assignment; STRING-bearing aggregates copy their owned string fields, while other fields retain their existing copy behavior. The procedure takes exactly one explicit parameter; BYREF may bind a designator or materialized temporary. Keyword NEW assignment retains its allocation behavior. Self-assignment handling belongs to the operator body.
# EXAMPLE
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index 99630ec..45660b8 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -184,7 +184,8 @@ class CodeGen {
const TypeInfo *Target);
llvm::Value *emitCast(CastExprAST *Cast);
llvm::Value *emitBinaryOp(int Op, llvm::Value *L, const TypeInfo *LT,
- llvm::Value *R, const TypeInfo *RT, int Line);
+ llvm::Value *R, const TypeInfo *RT, int Line,
+ const ResultContext *Context = nullptr);
llvm::Value *emitCBinaryOp(int Op, llvm::Value *L, const TypeInfo *Left,
llvm::Value *R, const TypeInfo *Right, int Line);
void emitCOutput(llvm::Value *Val, const TypeInfo *Info, bool AppendNewline);
@@ -222,8 +223,10 @@ class CodeGen {
struct OperatorResolution {
const FuncSig *Signature = nullptr;
bool Ambiguous = false;
+ bool WrongResult = false;
};
- OperatorResolution resolveOperator(int Op, const TypeInfo *Left, const TypeInfo *Right = nullptr);
+ OperatorResolution resolveOperator(int Op, const TypeInfo *Left, const TypeInfo *Right = nullptr,
+ const ResultContext *Context = nullptr);
bool checkOperatorAccess(const FuncSig &Signature);
llvm::Value *materializeOperatorReceiver(llvm::Value *Value, const TypeInfo *Type);
llvm::Value *emitOperatorCall(const FuncSig &Signature, ExprAST *Left, ExprAST *Right = nullptr);
diff --git a/lib/CodeGen/ControlFlowGen.cc b/lib/CodeGen/ControlFlowGen.cc
index a351162..fe9a954 100644
--- a/lib/CodeGen/ControlFlowGen.cc
+++ b/lib/CodeGen/ControlFlowGen.cc
@@ -229,7 +229,12 @@ void CodeGen::emitCaseStmt(CaseStmtAST *Stmt) {
}
if (containsString(LabelType) && (LabelConverted || !isObjectLValue(Label)))
materializeOperatorReceiver(LabelValue, LabelType);
- auto Overload = resolveOperator(Op, LeftType, LabelType);
+ ResultContext BoolContext{resolveType("BOOLEAN"), true};
+ auto Overload = resolveOperator(Op, LeftType, LabelType, &BoolContext);
+ if (Overload.WrongResult) {
+ reportError("CASE comparison must return BOOLEAN");
+ return nullptr;
+ }
if (Overload.Signature && !Overload.Ambiguous) {
const TypeInfo *Result = resolveType(Overload.Signature->ReturnTypeName);
if (!Result || Result->Kind != TypeKind::Boolean) {
@@ -237,7 +242,8 @@ void CodeGen::emitCaseStmt(CaseStmtAST *Stmt) {
return nullptr;
}
}
- Value *Result = emitBinaryOp(Op, LeftValue, LeftType, LabelValue, LabelType, Branch.Line);
+ Value *Result = emitBinaryOp(Op, LeftValue, LeftType, LabelValue, LabelType, Branch.Line,
+ &BoolContext);
cleanupTemporaries(Temporaries);
return Result;
};
diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc
index 38155e4..18c934d 100644
--- a/lib/CodeGen/ExpressionGen.cc
+++ b/lib/CodeGen/ExpressionGen.cc
@@ -75,11 +75,19 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
reportError("A whole array cannot be used as an operand; index it");
return nullptr;
}
- auto Overload = resolveOperator(Unary->getOp(), getExprTypeInfo(Unary->getOperand()));
+ const ResultContext *Context = resultContext(Expr);
+ const TypeInfo *OperandType = getExprTypeInfo(Unary->getOperand());
+ auto Overload = resolveOperator(Unary->getOp(), OperandType, nullptr, Context);
if (Overload.Ambiguous) {
reportError("Ambiguous overload for %s", operatorName(Unary->getOp()).c_str());
return nullptr;
}
+ if (Overload.WrongResult) {
+ reportError("No matching overload for %s(%s) returning %s",
+ operatorName(Unary->getOp()).c_str(),
+ OperandType ? OperandType->Name.c_str() : "?", Context->Type->Name.c_str());
+ return nullptr;
+ }
if (Overload.Signature) return emitOperatorCall(*Overload.Signature, Unary->getOperand());
if (Unary->getOp() == tok_not) {
const TypeInfo *Info = getExprTypeInfo(Unary->getOperand());
@@ -135,13 +143,20 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
// '&' path: their admissible operations are decided by name here.
const TypeInfo *LT = getExprTypeInfo(Bin->getLHS());
const TypeInfo *RT = getExprTypeInfo(Bin->getRHS());
+ const ResultContext *Context = resultContext(Expr);
if (LT && RT) {
- auto Overload = resolveOperator(Bin->getOp(), LT, RT);
+ auto Overload = resolveOperator(Bin->getOp(), LT, RT, Context);
if (Overload.Ambiguous) {
reportError("Ambiguous overload for %s(%s, %s)", operatorName(Bin->getOp()).c_str(),
LT->Name.c_str(), RT->Name.c_str());
return nullptr;
}
+ if (Overload.WrongResult) {
+ reportError("No matching overload for %s(%s, %s) returning %s",
+ operatorName(Bin->getOp()).c_str(), LT->Name.c_str(),
+ RT->Name.c_str(), Context->Type->Name.c_str());
+ return nullptr;
+ }
if (Overload.Signature)
return emitOperatorCall(*Overload.Signature, Bin->getLHS(), Bin->getRHS());
}
@@ -150,7 +165,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
return emitShortCircuit(Bin, LT, RT);
Value *L = emitExpr(Bin->getLHS());
Value *R = emitExpr(Bin->getRHS());
- return emitBinaryOp(Bin->getOp(), L, LT, R, RT, Bin->getLine());
+ return emitBinaryOp(Bin->getOp(), L, LT, R, RT, Bin->getLine(), Context);
}
if (auto *Call = dynamic_cast<MemberCallExprAST*>(Expr))
@@ -219,18 +234,24 @@ Value *CodeGen::emitShortCircuit(BinaryExprAST *Bin, const TypeInfo *LT, const T
// Ordinary binary expressions and CASE use the same typed operations.
Value *CodeGen::emitBinaryOp(int Op, Value *L, const TypeInfo *LT,
- Value *R, const TypeInfo *RT, int Line) {
+ Value *R, const TypeInfo *RT, int Line,
+ const ResultContext *Context) {
SaveAndRestore<int> Location(DiagnosticLine, Line ? Line : DiagnosticLine);
if (!L || !R) return nullptr;
if (!LT || !RT) {
reportError("Cannot determine operand types for %s", operatorName(Op).c_str());
return nullptr;
}
- auto Overload = resolveOperator(Op, LT, RT);
+ auto Overload = resolveOperator(Op, LT, RT, Context);
if (Overload.Ambiguous) {
reportError("Ambiguous overload for %s(%s, %s)", operatorName(Op).c_str(), LT->Name.c_str(), RT->Name.c_str());
return nullptr;
}
+ if (Overload.WrongResult) {
+ reportError("No matching overload for %s(%s, %s) returning %s", operatorName(Op).c_str(),
+ LT->Name.c_str(), RT->Name.c_str(), Context->Type->Name.c_str());
+ return nullptr;
+ }
if (Overload.Signature) return emitOperatorValues(*Overload.Signature, L, LT, R);
if ((LT && LT->isC()) || (RT && RT->isC()))
return emitCBinaryOp(Op, L, LT, R, RT, Line);
diff --git a/lib/CodeGen/OperatorGen.cc b/lib/CodeGen/OperatorGen.cc
index a840408..560b167 100644
--- a/lib/CodeGen/OperatorGen.cc
+++ b/lib/CodeGen/OperatorGen.cc
@@ -8,7 +8,7 @@ using namespace llvm;
// Probing candidates must not evaluate operands or diagnose absent overloads:
// both static typing and emission use this same selection.
CodeGen::OperatorResolution CodeGen::resolveOperator(
- int Op, const TypeInfo *Left, const TypeInfo *Right) {
+ int Op, const TypeInfo *Left, const TypeInfo *Right, const ResultContext *Context) {
OperatorResolution Result;
if (!Left) return Result;
auto Candidates = FuncGen->getOverloads(operatorName(Op));
@@ -25,17 +25,46 @@ CodeGen::OperatorResolution CodeGen::resolveOperator(
};
// Two CHARs compare as CHARs; only '&' always builds a STRING.
bool CanConvert = Right && (Op == '&' || Left->isString() || Right->isString());
- for (bool Lenient : {false, true}) {
- if (Lenient && !CanConvert) break;
- for (const FuncSig *Sig : Candidates) {
- bool Member = !Sig->Prototype->getOwnerClass().empty();
- if (Sig->Prototype->getOperator() != Op || Sig->Params.size() != (Right ? 2u : 1u) - Member) continue;
- if (!Member && !Accepts(Left, Sig->Params[0].TypeName, Lenient)) continue;
- if (Right && !Accepts(Right, Sig->Params[Member ? 0 : 1].TypeName, Lenient)) continue;
- if (Result.Signature) Result.Ambiguous = true;
- else Result.Signature = Sig;
+ const bool PreferResult = Context && Context->Type;
+ auto ReturnsWanted = [&](const FuncSig *Sig) {
+ const TypeInfo *Ret = resolveType(Sig->ReturnTypeName);
+ return Ret && Ret->sameType(Context->Type);
+ };
+ bool ParamMatched = false;
+ auto RunLayers = [&](bool FilterReturn) {
+ Result.Signature = nullptr;
+ Result.Ambiguous = false;
+ for (bool Lenient : {false, true}) {
+ if (Lenient && !CanConvert) break;
+ const FuncSig *Selected = nullptr;
+ int Count = 0;
+ for (const FuncSig *Sig : Candidates) {
+ bool Member = !Sig->Prototype->getOwnerClass().empty();
+ if (Sig->Prototype->getOperator() != Op ||
+ Sig->Params.size() != (Right ? 2u : 1u) - Member) continue;
+ if (!Member && !Accepts(Left, Sig->Params[0].TypeName, Lenient)) continue;
+ if (Right && !Accepts(Right, Sig->Params[Member ? 0 : 1].TypeName, Lenient)) continue;
+ ParamMatched = true;
+ if (FilterReturn && !ReturnsWanted(Sig)) continue;
+ ++Count;
+ if (!Selected) Selected = Sig;
+ }
+ if (Selected) {
+ Result.Signature = Selected;
+ Result.Ambiguous = Count > 1;
+ break;
+ }
}
- if (Result.Signature) break;
+ };
+
+ if (PreferResult) {
+ RunLayers(true);
+ if (Result.Signature || Result.Ambiguous) return Result;
+ }
+ if (!Context || !Context->Exact) {
+ RunLayers(false);
+ } else if (ParamMatched) {
+ Result.WrongResult = true;
}
return Result;
}
diff --git a/lib/CodeGen/ValueTypeGen.cc b/lib/CodeGen/ValueTypeGen.cc
index dddb1b0..6026821 100644
--- a/lib/CodeGen/ValueTypeGen.cc
+++ b/lib/CodeGen/ValueTypeGen.cc
@@ -86,8 +86,8 @@ const TypeInfo *CodeGen::getExprTypeInfo(ExprAST *Expr) {
if (auto *Unary = dynamic_cast<UnaryExprAST*>(Expr)) {
if (isWholeArrayVar(Unary->getOperand())) return nullptr;
const TypeInfo *Operand = getExprTypeInfo(Unary->getOperand());
- auto Overload = resolveOperator(Unary->getOp(), Operand);
- if (Overload.Ambiguous) return nullptr;
+ auto Overload = resolveOperator(Unary->getOp(), Operand, nullptr, resultContext(Expr));
+ if (Overload.Ambiguous || Overload.WrongResult) return nullptr;
if (Overload.Signature) return resolveType(Overload.Signature->ReturnTypeName);
if (Unary->getOp() == tok_not) {
return resolveType("BOOLEAN");
@@ -102,8 +102,8 @@ const TypeInfo *CodeGen::getExprTypeInfo(ExprAST *Expr) {
const TypeInfo *L = getExprTypeInfo(Bin->getLHS());
const TypeInfo *R = getExprTypeInfo(Bin->getRHS());
if (!L || !R || isWholeArrayVar(Bin->getLHS()) || isWholeArrayVar(Bin->getRHS())) return nullptr;
- auto Overload = resolveOperator(Bin->getOp(), L, R);
- if (Overload.Ambiguous) return nullptr;
+ auto Overload = resolveOperator(Bin->getOp(), L, R, resultContext(Expr));
+ if (Overload.Ambiguous || Overload.WrongResult) return nullptr;
if (Overload.Signature) return resolveType(Overload.Signature->ReturnTypeName);
switch (Bin->getOp()) {
case tok_eq:
diff --git a/tests/iii/operator-global-errors/case.json b/tests/iii/operator-global-errors/case.json
index b12dc98..3cbd8dd 100644
--- a/tests/iii/operator-global-errors/case.json
+++ b/tests/iii/operator-global-errors/case.json
@@ -1,7 +1,7 @@
{
"errors": [
{
- "reason": "Cannot implicitly convert STRING to BOOLEAN",
+ "reason": "No matching overload for OPERATOR <\\(Box, Box\\) returning BOOLEAN",
"line": 12
},
{
diff --git a/tests/iii/operator-result-errors/case.json b/tests/iii/operator-result-errors/case.json
index 67abec2..5bd1c72 100644
--- a/tests/iii/operator-result-errors/case.json
+++ b/tests/iii/operator-result-errors/case.json
@@ -1,15 +1,15 @@
{
"errors": [
{
- "reason": "Cannot implicitly convert STRING to BOOLEAN",
+ "reason": "No matching overload for OPERATOR =\\(Box, Box\\) returning BOOLEAN",
"line": 8
},
{
- "reason": "Cannot implicitly convert STRING to BOOLEAN",
+ "reason": "No matching overload for OPERATOR =\\(Box, Box\\) returning BOOLEAN",
"line": 12
},
{
- "reason": "Cannot implicitly convert STRING to BOOLEAN",
+ "reason": "No matching overload for OPERATOR =\\(Box, Box\\) returning BOOLEAN",
"line": 17
},
{