aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-18 08:09:06 +0000
committer_ov4 <ov4@ov4.org>2026-10-18 08:09:06 +0000
commit30fced09675acfd2b9ab6db6be8359a70d3f0402 (patch)
tree88dc4587cb71d9dc9bdd9a57c4e2de8a98d7f06e
parent345e3d419eafb0e234aecb4a33f37efe2afbbf53 (diff)
feat(operators): allow member assignment operator overloads
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--AGENTS/CONVENTIONS.md2
-rw-r--r--doc/language/keyword/cpsc-keyword-operator.7.md2
-rw-r--r--include/cps/Operator.h1
-rw-r--r--lib/CodeGen/ConstructorGen.cc14
-rw-r--r--lib/CodeGen/FunctionGen.cc6
-rw-r--r--lib/Parser/ParserFunction.cc18
-rw-r--r--tests/i/operator-assign/case.json1
-rw-r--r--tests/i/operator-assign/expected.txt7
-rw-r--r--tests/i/operator-assign/program.cps39
-rw-r--r--tests/iii/operator-assign-global/case.json1
-rw-r--r--tests/iii/operator-assign-global/program.cps3
11 files changed, 91 insertions, 3 deletions
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md
index b8ca3e9..f72cb86 100644
--- a/AGENTS/CONVENTIONS.md
+++ b/AGENTS/CONVENTIONS.md
@@ -211,3 +211,5 @@ ExceptionHandler, truncates its result and retains the final uncaught handler.
Identifiers may begin with an underscore. The case-insensitive `__cps_` prefix is reserved in every user identifier, including EXTERN declarations.
**Library units.** Line-leading `#LIB` marks SourceUnit::IsLibrary. RuntimeCheck uses the externally visible i32 `__cps_user_line`; FunctionGen’s shared call hook updates it only on non-library to library calls. Runtime helper line arguments use RuntimeCheck::lineValue too. Compiler diagnostics retain the owning file location.
+
+Assignment operators are member-only `OVERLOAD PROCEDURE OPERATOR<-` with one explicit parameter. emitAssignment resolves the exact RHS static type before fallback storage copying. Internal assignment/constructor BYREF bindings may use designators or materialized temporaries; ordinary BYREF calls still require bare variables.
diff --git a/doc/language/keyword/cpsc-keyword-operator.7.md b/doc/language/keyword/cpsc-keyword-operator.7.md
index 5ba3999..090921f 100644
--- a/doc/language/keyword/cpsc-keyword-operator.7.md
+++ b/doc/language/keyword/cpsc-keyword-operator.7.md
@@ -57,3 +57,5 @@ OUTPUT Sum.Value
# SEE ALSO
[cpsc-keyword-overload(7)](cpsc-keyword-overload.7.md), [cpsc-keyword-function(7)](cpsc-keyword-function.7.md), [cpsc-keyword-class(7)](cpsc-keyword-class.7.md), [cpsc-keyword-include(7)](cpsc-keyword-include.7.md).
+
+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 bitwise assignment. The procedure takes exactly one explicit parameter; BYREF may bind a materialized temporary. Keyword NEW assignment retains its allocation behavior. Self-assignment handling belongs to the operator body.
diff --git a/include/cps/Operator.h b/include/cps/Operator.h
index 304793e..acf5483 100644
--- a/include/cps/Operator.h
+++ b/include/cps/Operator.h
@@ -8,6 +8,7 @@ inline std::string operatorSpelling(int Op) {
switch (Op) {
case '+': case '-': case '*': case '/': case '&': case '<': case '>':
return std::string(1, static_cast<char>(Op));
+ case tok_assign: return "<-";
case tok_div: return "DIV";
case tok_mod: return "MOD";
case tok_eq: return "=";
diff --git a/lib/CodeGen/ConstructorGen.cc b/lib/CodeGen/ConstructorGen.cc
index 4bf5568..9fc762c 100644
--- a/lib/CodeGen/ConstructorGen.cc
+++ b/lib/CodeGen/ConstructorGen.cc
@@ -142,6 +142,20 @@ void CodeGen::emitAssignment(const LValueInfo &Target, ExprAST *Expr, const Symb
}
return;
}
+ if (Info && Info->IsClass) {
+ auto Resolution = resolveOperator(tok_assign, Info, getExprTypeInfo(Expr));
+ if (Resolution.Ambiguous) {
+ reportError("Ambiguous assignment operator");
+ return;
+ }
+ if (const FuncSig *Sig = Resolution.Signature) {
+ if (!checkOperatorAccess(*Sig)) return;
+ std::vector<Value *> Args = {Target.Addr};
+ if (marshalCallArgs(*Sig, std::vector<ExprAST *>{Expr}, DiagnosticLine, Args))
+ FuncGen->emitCall(*Sig, Args);
+ return;
+ }
+ }
Value *Val = emitCoercedExpr(Expr, Info);
if (Val) Builder->CreateStore(Val, Target.Addr);
}
diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc
index 37df8ff..1f7bc2b 100644
--- a/lib/CodeGen/FunctionGen.cc
+++ b/lib/CodeGen/FunctionGen.cc
@@ -132,6 +132,12 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) {
return nullptr;
}
}
+ if (Proto->getOperator() == tok_assign &&
+ (Proto->getOwnerClass().empty() || !Proto->isOverload() ||
+ !sameIdentifier(Proto->getReturnType(), "VOID"))) {
+ reportError("Assignment operator must be an OVERLOAD member PROCEDURE");
+ return nullptr;
+ }
if (int Op = Proto->getOperator()) {
size_t Count = Proto->getParams().size() + !Proto->getOwnerClass().empty();
bool ValidCount = Op == '-' ? Count == 1 || Count == 2
diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc
index 65e0b85..cf8ecf8 100644
--- a/lib/Parser/ParserFunction.cc
+++ b/lib/Parser/ParserFunction.cc
@@ -214,12 +214,23 @@ std::unique_ptr<FunctionDefAST> Parser::ParseFunction(const std::string &OwnerCl
std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerClass, bool IsPublic, bool IsOverload) {
int Line = Lex.getLine();
getNextToken();
- if (CurTok != tok_identifier && CurTok != tok_new && CurTok != tok_delete) {
+ if (CurTok != tok_identifier && CurTok != tok_new && CurTok != tok_delete && CurTok != tok_operator) {
cps::sourceDiagnostic(Lex.getLine(), "error: Expected procedure name\n");
return nullptr;
}
- std::string Name = CurTok == tok_new ? "NEW" : CurTok == tok_delete ? "DELETE" : Lex.IdentifierStr;
+ int Operator = 0;
+ bool IsOperator = CurTok == tok_operator;
+ std::string OperatorName;
+ if (IsOperator) {
+ OperatorName = ParseOperatorName(Operator);
+ if (OperatorName.empty()) return nullptr;
+ if (Operator != tok_assign || OwnerClass.empty() || !IsOverload) {
+ sourceDiagnostic(Line, "error: Assignment operator must be an OVERLOAD member PROCEDURE at line %d\n", Line);
+ return nullptr;
+ }
+ }
+ std::string Name = IsOperator ? OperatorName : CurTok == tok_new ? "NEW" : CurTok == tok_delete ? "DELETE" : Lex.IdentifierStr;
if (OwnerClass.empty() && sameIdentifier(Name, "DELETE")) {
cps::sourceDiagnostic(Lex.getLine(), "error: destructor DELETE is only allowed in a CLASS at line %d\n", Line);
return nullptr;
@@ -228,7 +239,7 @@ std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerC
cps::sourceDiagnostic(Lex.getLine(), "error: constructor NEW is only allowed in a CLASS at line %d\n", Line);
return nullptr;
}
- getNextToken();
+ if (!IsOperator) getNextToken();
auto Args = ParsePrototypeArgs(/*AllowByRef=*/true);
if (!Args) return nullptr;
@@ -237,6 +248,7 @@ std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerC
return nullptr;
}
auto Proto = std::make_unique<PrototypeAST>(Name, std::move(*Args), "VOID", false, OwnerClass, IsPublic, IsOverload);
+ Proto->setOperator(Operator);
std::vector<std::unique_ptr<StmtAST>> Body = ParseBlock({tok_endprocedure});
diff --git a/tests/i/operator-assign/case.json b/tests/i/operator-assign/case.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/tests/i/operator-assign/case.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/i/operator-assign/expected.txt b/tests/i/operator-assign/expected.txt
new file mode 100644
index 0000000..aaf45ba
--- /dev/null
+++ b/tests/i/operator-assign/expected.txt
@@ -0,0 +1,7 @@
+copy:3
+copy:3
+copy:7
+copy:7
+copy:5
+copy:7
+7:7:7:5
diff --git a/tests/i/operator-assign/program.cps b/tests/i/operator-assign/program.cps
new file mode 100644
index 0000000..d534806
--- /dev/null
+++ b/tests/i/operator-assign/program.cps
@@ -0,0 +1,39 @@
+CLASS Value
+ N : INTEGER
+ OVERLOAD PROCEDURE NEW()
+ ENDPROCEDURE
+ OVERLOAD PROCEDURE NEW(BYREF Other : Value)
+ N <- Other.N
+ ENDPROCEDURE
+ OVERLOAD PROCEDURE OPERATOR<-(BYREF Other : Value)
+ OUTPUT "copy:", Other.N
+ N <- Other.N
+ ENDPROCEDURE
+ OVERLOAD PROCEDURE OPERATOR<-(Text : C_STRING)
+ N <- LENGTH((STRING)Text)
+ ENDPROCEDURE
+ENDCLASS
+FUNCTION Make() RETURNS Value
+ DECLARE X : Value
+ X.N <- 7
+ RETURN X
+ENDFUNCTION
+TYPE ValuePtr = ^Value
+TYPE Box
+ DECLARE Item : Value
+ENDTYPE
+DECLARE A : Value
+DECLARE B : Value
+A.N <- 3
+B <- A
+B <- B
+B <- Make()
+A <- (C_STRING)"hello"
+DECLARE R : Box
+R.Item <- B
+DECLARE Items : ARRAY[1:1] OF Value
+Items[1] <- A
+DECLARE P : ValuePtr
+P <- ^A
+P^ <- B
+OUTPUT A.N, ":", B.N, ":", R.Item.N, ":", Items[1].N
diff --git a/tests/iii/operator-assign-global/case.json b/tests/iii/operator-assign-global/case.json
new file mode 100644
index 0000000..7a2e21d
--- /dev/null
+++ b/tests/iii/operator-assign-global/case.json
@@ -0,0 +1 @@
+{"errors":[{"reason":"Assignment operator must be","line":1}]}
diff --git a/tests/iii/operator-assign-global/program.cps b/tests/iii/operator-assign-global/program.cps
new file mode 100644
index 0000000..6f6454e
--- /dev/null
+++ b/tests/iii/operator-assign-global/program.cps
@@ -0,0 +1,3 @@
+OVERLOAD PROCEDURE OPERATOR<-(X : INTEGER)
+ENDPROCEDURE
+OUTPUT 0