aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/cps/CodeGen.h7
-rw-r--r--include/cps/Operator.h2
-rw-r--r--lib/CodeGen/CodeGen.cc12
-rw-r--r--lib/CodeGen/ExpressionGen.cc22
-rw-r--r--lib/CodeGen/OperatorGen.cc71
-rw-r--r--lib/CodeGen/ValueTypeGen.cc6
-rw-r--r--lib/Parser/Parser.cc15
-rw-r--r--lib/Parser/ParserExpression.cc25
-rw-r--r--lib/Parser/ParserFunction.cc17
-rw-r--r--tests/i/operator-members/case.json1
-rw-r--r--tests/i/operator-members/expected.txt12
-rw-r--r--tests/i/operator-members/notes.md7
-rw-r--r--tests/i/operator-members/program.cps66
-rw-r--r--tests/iii/operator-member-errors/case.json20
-rw-r--r--tests/iii/operator-member-errors/notes.md3
-rw-r--r--tests/iii/operator-member-errors/program.cps18
16 files changed, 282 insertions, 22 deletions
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index 30e6d06..9b2d6c5 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -160,11 +160,18 @@ class CodeGen {
bool Ambiguous = false;
};
OperatorResolution resolveOperator(int Op, const TypeInfo *Left, const TypeInfo *Right = 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);
+ llvm::Value *emitOperatorValues(const FuncSig &Signature, llvm::Value *Left,
+ const TypeInfo *LeftType, llvm::Value *Right);
llvm::Value *emitVariadicArgument(ExprAST *Expr, const std::string &Callee, size_t Position);
bool marshalCallArgs(const FuncSig &Signature,
const std::vector<std::unique_ptr<ExprAST>> &ArgExprs,
int Line,
std::vector<llvm::Value*> &Out);
+ bool marshalCallArgs(const FuncSig &Signature, const std::vector<ExprAST*> &ArgExprs,
+ int Line, std::vector<llvm::Value*> &Out);
llvm::Value *symbolAddress(const SymbolInfo &Info);
void emitAssignment(const LValueInfo &Target, ExprAST *Expr,
const SymbolInfo *Symbol = nullptr);
diff --git a/include/cps/Operator.h b/include/cps/Operator.h
index 7cbb5d9..304793e 100644
--- a/include/cps/Operator.h
+++ b/include/cps/Operator.h
@@ -22,7 +22,7 @@ inline std::string operatorSpelling(int Op) {
}
inline std::string operatorName(int Op) {
- return "OPERATOR" + operatorSpelling(Op);
+ return "OPERATOR " + operatorSpelling(Op);
}
} // namespace cps
diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc
index 61fd837..f7c4de6 100644
--- a/lib/CodeGen/CodeGen.cc
+++ b/lib/CodeGen/CodeGen.cc
@@ -369,6 +369,14 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature,
const std::vector<std::unique_ptr<ExprAST>> &ArgExprs,
int Line,
std::vector<Value*> &Out) {
+ std::vector<ExprAST*> Args;
+ for (const auto &Arg : ArgExprs) Args.push_back(Arg.get());
+ return marshalCallArgs(Signature, Args, Line, Out);
+}
+
+bool CodeGen::marshalCallArgs(const FuncSig &Signature,
+ const std::vector<ExprAST*> &ArgExprs,
+ int Line, std::vector<Value*> &Out) {
SaveAndRestore<int> Location(DiagnosticLine, Line ? Line : DiagnosticLine);
const FuncSig *Sig = &Signature;
const std::string &Callee = Sig->Name;
@@ -379,7 +387,7 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature,
for (size_t i = 0; i < ArgExprs.size(); ++i) {
if (i >= Sig->Params.size()) {
- Value *V = emitVariadicArgument(ArgExprs[i].get(), Callee, i + 1);
+ Value *V = emitVariadicArgument(ArgExprs[i], Callee, i + 1);
if (!V) return false;
Out.push_back(V);
continue;
@@ -387,7 +395,7 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature,
const ParamSig &P = Sig->Params[i];
const std::string &TypeName = P.TypeName;
bool IsByRef = P.IsByRef;
- ExprAST *ArgExpr = ArgExprs[i].get();
+ ExprAST *ArgExpr = ArgExprs[i];
if (P.IsArray) {
if (!Arrays->emitArrayArgument(ArgExpr, P.TypeName, P.Rank, P.DeclaredBounds,
diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc
index 8fb13c9..a01cab2 100644
--- a/lib/CodeGen/ExpressionGen.cc
+++ b/lib/CodeGen/ExpressionGen.cc
@@ -73,6 +73,16 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
}
if (auto *Unary = dynamic_cast<UnaryExprAST*>(Expr)) {
+ if (isWholeArrayVar(Unary->getOperand())) {
+ reportError("A whole array cannot be used as an operand; index it");
+ return nullptr;
+ }
+ auto Overload = resolveOperator(Unary->getOp(), getExprTypeInfo(Unary->getOperand()));
+ if (Overload.Ambiguous) {
+ reportError("Ambiguous overload for %s", operatorName(Unary->getOp()).c_str());
+ return nullptr;
+ }
+ if (Overload.Signature) return emitOperatorCall(*Overload.Signature, Unary->getOperand());
if (Unary->getOp() == tok_not) {
const TypeInfo *Info = getExprTypeInfo(Unary->getOperand());
Value *Operand = Info && Info->isC()
@@ -123,6 +133,16 @@ 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());
+ if (LT && RT) {
+ auto Overload = resolveOperator(Bin->getOp(), LT, RT);
+ 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.Signature)
+ return emitOperatorCall(*Overload.Signature, Bin->getLHS(), Bin->getRHS());
+ }
Value *L = emitExpr(Bin->getLHS());
Value *R = emitExpr(Bin->getRHS());
return emitBinaryOp(Bin->getOp(), L, LT, R, RT, Bin->getLine());
@@ -165,7 +185,7 @@ Value *CodeGen::emitBinaryOp(int Op, Value *L, const TypeInfo *LT,
reportError("Ambiguous overload for %s(%s, %s)", operatorName(Op).c_str(), LT->Name.c_str(), RT->Name.c_str());
return nullptr;
}
- if (Overload.Signature) return FuncGen->emitCall(*Overload.Signature, {L, R});
+ 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);
if ((LT && LT->isUserKind()) || (RT && RT->isUserKind()))
diff --git a/lib/CodeGen/OperatorGen.cc b/lib/CodeGen/OperatorGen.cc
index 83c515b..d340ed7 100644
--- a/lib/CodeGen/OperatorGen.cc
+++ b/lib/CodeGen/OperatorGen.cc
@@ -1,7 +1,9 @@
#include "cps/CodeGen.h"
#include "cps/Operator.h"
+#include "cps/TypeAST.h"
using namespace cps;
+using namespace llvm;
// Probing candidates must not evaluate operands or diagnose absent overloads:
// both static typing and emission use this same selection.
@@ -9,12 +11,73 @@ CodeGen::OperatorResolution CodeGen::resolveOperator(
int Op, const TypeInfo *Left, const TypeInfo *Right) {
OperatorResolution Result;
if (!Left) return Result;
- for (const FuncSig *Sig : FuncGen->getOverloads(operatorName(Op))) {
- if (Sig->Prototype->getOperator() != Op || Sig->Params.size() != (Right ? 2u : 1u)) continue;
- if (!Left->sameType(resolveType(Sig->Params[0].TypeName))) continue;
- if (Right && !Right->sameType(resolveType(Sig->Params[1].TypeName))) continue;
+ auto Candidates = FuncGen->getOverloads(operatorName(Op));
+ if (Left->IsClass) {
+ auto Members = FuncGen->getOverloads(Left->Name + "." + operatorName(Op));
+ Candidates.insert(Candidates.end(), Members.begin(), Members.end());
+ }
+ 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 && !Left->sameType(resolveType(Sig->Params[0].TypeName))) continue;
+ if (Right && !Right->sameType(resolveType(Sig->Params[Member ? 0 : 1].TypeName))) continue;
if (Result.Signature) Result.Ambiguous = true;
else Result.Signature = Sig;
}
return Result;
}
+
+bool CodeGen::checkOperatorAccess(const FuncSig &Signature) {
+ const PrototypeAST *Proto = Signature.Prototype;
+ if (!Proto->getOwnerClass().empty() && !Proto->isPublic() &&
+ !sameIdentifier(CurrentClass, Proto->getOwnerClass())) {
+ reportError("Cannot access private method %s of type %s",
+ Proto->getName().c_str(), Proto->getOwnerClass().c_str());
+ return false;
+ }
+ return true;
+}
+
+Value *CodeGen::materializeOperatorReceiver(Value *Val, const TypeInfo *Type) {
+ if (!Val || !Type) return nullptr;
+ auto *Storage = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(),
+ Type->LLVMType, "operator_receiver");
+ size_t Object = registerStackObject(Storage, Type->Name, DiagnosticLine, false, true);
+ Builder->CreateStore(Val, Storage);
+ activateStackObject(Object);
+ return Storage;
+}
+
+Value *CodeGen::emitOperatorCall(const FuncSig &Signature, ExprAST *Left, ExprAST *Right) {
+ if (!checkOperatorAccess(Signature)) return nullptr;
+ std::vector<Value*> Args;
+ std::vector<ExprAST*> Explicit;
+ if (Signature.Prototype->getOwnerClass().empty()) {
+ Explicit.push_back(Left);
+ } else {
+ Value *Receiver = nullptr;
+ if (dynamic_cast<VariableExprAST*>(Left) || dynamic_cast<ArrayAccessExprAST*>(Left) ||
+ dynamic_cast<DesignatorExprAST*>(Left)) {
+ LValueInfo LV;
+ if (!emitLValue(Left, LV)) return nullptr;
+ Receiver = LV.Addr;
+ } else {
+ Receiver = materializeOperatorReceiver(emitExpr(Left), getExprTypeInfo(Left));
+ }
+ if (!Receiver) return nullptr;
+ Args.push_back(Receiver);
+ }
+ if (Right) Explicit.push_back(Right);
+ if (!marshalCallArgs(Signature, Explicit, DiagnosticLine, Args)) return nullptr;
+ return FuncGen->emitCall(Signature, Args);
+}
+
+// CASE has already evaluated and captured its selector and label values.
+Value *CodeGen::emitOperatorValues(const FuncSig &Signature, Value *Left,
+ const TypeInfo *LeftType, Value *Right) {
+ if (!checkOperatorAccess(Signature)) return nullptr;
+ if (!Signature.Prototype->getOwnerClass().empty())
+ Left = materializeOperatorReceiver(Left, LeftType);
+ if (!Left) return nullptr;
+ return FuncGen->emitCall(Signature, {Left, Right});
+}
diff --git a/lib/CodeGen/ValueTypeGen.cc b/lib/CodeGen/ValueTypeGen.cc
index b09a5d9..e7a3f77 100644
--- a/lib/CodeGen/ValueTypeGen.cc
+++ b/lib/CodeGen/ValueTypeGen.cc
@@ -70,10 +70,14 @@ const TypeInfo *CodeGen::getExprTypeInfo(ExprAST *Expr) {
if (dynamic_cast<AddrOfExprAST*>(Expr)) return nullptr;
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;
+ if (Overload.Signature) return resolveType(Overload.Signature->ReturnTypeName);
if (Unary->getOp() == tok_not) {
return resolveType("BOOLEAN");
}
- const TypeInfo *Operand = getExprTypeInfo(Unary->getOperand());
if (Operand && Operand->isC()) return Types->promoteCScalar(Operand);
if (Operand && !Operand->isC() && !Operand->isUserKind())
return Operand->isReal() ? resolveType("REAL") : resolveType("INTEGER");
diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc
index f22e3c6..afe0e80 100644
--- a/lib/Parser/Parser.cc
+++ b/lib/Parser/Parser.cc
@@ -48,7 +48,7 @@ void Parser::syncToStatementStart() {
switch (CurTok) {
case tok_include: case tok_global: case tok_endglobal:
case tok_include_obj: case tok_extern:
- case tok_delete: case tok_constant: case tok_declare: case tok_identifier: case tok_input: case tok_output:
+ case tok_delete: case tok_constant: case tok_declare: case tok_identifier: case tok_operator: case tok_input: case tok_output:
case tok_case: case tok_endcase: case tok_otherwise:
case tok_if: case tok_while: case tok_repeat: case tok_for:
case tok_overload: case tok_function: case tok_procedure: case tok_call: case tok_return:
@@ -163,7 +163,7 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
if (CurTok == tok_declare) {
return ParseDeclare();
}
- else if (CurTok == tok_identifier) {
+ else if (CurTok == tok_identifier || CurTok == tok_operator) {
return ParseAssignStmt();
}
else if (CurTok == tok_input) {
@@ -284,9 +284,16 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() {
// Bare-name and single-index targets degrade to their legacy nodes so the
// existing assignment code paths stay untouched.
std::unique_ptr<StmtAST> Parser::ParseAssignStmt() {
- std::string Name = Lex.IdentifierStr;
int Line = Lex.getLine();
- getNextToken();
+ std::string Name;
+ if (CurTok == tok_operator) {
+ int Op;
+ Name = ParseOperatorName(Op);
+ if (Name.empty()) return nullptr;
+ } else {
+ Name = Lex.IdentifierStr;
+ getNextToken();
+ }
if (CurTok == '(') {
getNextToken();
diff --git a/lib/Parser/ParserExpression.cc b/lib/Parser/ParserExpression.cc
index a4f4fcc..6cde5bf 100644
--- a/lib/Parser/ParserExpression.cc
+++ b/lib/Parser/ParserExpression.cc
@@ -41,9 +41,16 @@ std::unique_ptr<ExprAST> Parser::ParseNumberExpr() {
}
std::unique_ptr<ExprAST> Parser::ParseIdentifierExpr() {
- std::string IdName = Lex.IdentifierStr;
int Line = Lex.getLine();
- getNextToken();
+ std::string IdName;
+ if (CurTok == tok_operator) {
+ int Op;
+ IdName = ParseOperatorName(Op);
+ if (IdName.empty()) return nullptr;
+ } else {
+ IdName = Lex.IdentifierStr;
+ getNextToken();
+ }
if (CurTok == '(' && !atCaseBoundary()) {
getNextToken();
@@ -110,15 +117,21 @@ bool Parser::ParseDesignatorSuffix(std::vector<DesignatorAccess> &Out) {
Out.push_back(std::move(A));
} else if (CurTok == '.') {
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 field name after '.' at line %d\n", Lex.getLine());
return false;
}
DesignatorAccess A;
A.Kind = AccessKind::Field;
- A.FieldName = CurTok == tok_new ? "NEW" : CurTok == tok_delete ? "DELETE" : Lex.IdentifierStr;
+ if (CurTok == tok_operator) {
+ int Op;
+ A.FieldName = ParseOperatorName(Op);
+ if (A.FieldName.empty()) return false;
+ } else {
+ A.FieldName = CurTok == tok_new ? "NEW" : CurTok == tok_delete ? "DELETE" : Lex.IdentifierStr;
+ getNextToken();
+ }
Out.push_back(std::move(A));
- getNextToken();
} else if (CurTok == '^') {
getNextToken();
DesignatorAccess A;
@@ -172,7 +185,7 @@ std::unique_ptr<ExprAST> Parser::ParsePrimaryImpl() {
case tok_new:
cps::sourceDiagnostic(Lex.getLine(), "error: NEW is only allowed on the right of an assignment at line %d\n", Lex.getLine());
return nullptr;
- case tok_identifier: return ParseIdentifierExpr();
+ case tok_identifier: case tok_operator: return ParseIdentifierExpr();
case tok_number_int: return ParseNumberExpr();
case tok_number_real: return ParseNumberExpr();
case tok_string_literal: {
diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc
index 3ff54da..dfb689a 100644
--- a/lib/Parser/ParserFunction.cc
+++ b/lib/Parser/ParserFunction.cc
@@ -140,6 +140,10 @@ std::string Parser::ParseOperatorName(int &Op) {
return {};
}
getNextToken();
+ if (CurTok != '(') {
+ sourceDiagnostic(Lex.getLine(), "error: Expected '(' after operator name at line %d\n", Lex.getLine());
+ return {};
+ }
return operatorName(Op);
}
@@ -339,13 +343,20 @@ std::unique_ptr<StmtAST> Parser::ParseExternDecl() {
std::unique_ptr<StmtAST> Parser::ParseCallStmt() {
int Line = Lex.getLine();
getNextToken();
- if (CurTok != tok_identifier) {
+ if (CurTok != tok_identifier && CurTok != tok_operator) {
cps::sourceDiagnostic(Lex.getLine(), "error: Expected callee name after CALL\n");
return nullptr;
}
- std::string Callee = Lex.IdentifierStr;
- getNextToken();
+ std::string Callee;
+ if (CurTok == tok_operator) {
+ int Op;
+ Callee = ParseOperatorName(Op);
+ if (Callee.empty()) return nullptr;
+ } else {
+ Callee = Lex.IdentifierStr;
+ getNextToken();
+ }
std::vector<DesignatorAccess> Accesses;
if (!ParseDesignatorSuffix(Accesses)) return nullptr;
diff --git a/tests/i/operator-members/case.json b/tests/i/operator-members/case.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/tests/i/operator-members/case.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/i/operator-members/expected.txt b/tests/i/operator-members/expected.txt
new file mode 100644
index 0000000..24ab52b
--- /dev/null
+++ b/tests/i/operator-members/expected.txt
@@ -0,0 +1,12 @@
+9
+-3
+negated
+3!
+8
+8
+8
+6
+make
+number
+6
+caught
diff --git a/tests/i/operator-members/notes.md b/tests/i/operator-members/notes.md
new file mode 100644
index 0000000..2b741c9
--- /dev/null
+++ b/tests/i/operator-members/notes.md
@@ -0,0 +1,7 @@
+# operator-members
+
+Human review: pending. Expected output was independently traced by AI, not captured from cpsc.
+
+Basis: operator overloading is a project extension; arguments match exact types.
+
+Trace: chained additions produce 3 + 2 + 4 = 9. Unary minus returns -3; NOT deliberately returns negated. A method can use its private concatenation operator, yielding 3!. Box addition modifies the original receiver from 3 to 8. Explicit member addition then global subtraction yields 8; explicit global subtraction yields 6. Discarded unary/global calls produce no output. MakeBox prints make exactly once, then Number prints number exactly once; their result has value 4 + 2 = 6. A throwing global multiplication reaches CATCH.
diff --git a/tests/i/operator-members/program.cps b/tests/i/operator-members/program.cps
new file mode 100644
index 0000000..d17184c
--- /dev/null
+++ b/tests/i/operator-members/program.cps
@@ -0,0 +1,66 @@
+CLASS Box
+Value : INTEGER
+OVERLOAD FUNCTION OPERATOR+(rhs : INTEGER) RETURNS Box
+DECLARE result : Box
+result.Value <- Value + rhs
+RETURN result
+ENDFUNCTION
+OVERLOAD FUNCTION OPERATOR+(rhs : Box) RETURNS INTEGER
+Value <- Value + rhs.Value
+RETURN Value
+ENDFUNCTION
+OVERLOAD FUNCTION OPERATOR-() RETURNS INTEGER
+RETURN -Value
+ENDFUNCTION
+OVERLOAD FUNCTION OPERATOR NOT() RETURNS STRING
+RETURN "negated"
+ENDFUNCTION
+PRIVATE OVERLOAD FUNCTION OPERATOR&(rhs : STRING) RETURNS STRING
+RETURN NUM_TO_STR(Value) & rhs
+ENDFUNCTION
+FUNCTION Describe() RETURNS STRING
+RETURN this & "!"
+ENDFUNCTION
+ENDCLASS
+OVERLOAD FUNCTION OPERATOR-(lhs : Box, rhs : INTEGER) RETURNS INTEGER
+RETURN lhs.Value - rhs
+ENDFUNCTION
+OVERLOAD FUNCTION OPERATOR*(lhs : Box, rhs : Box) RETURNS INTEGER
+THROW
+RETURN 0
+ENDFUNCTION
+FUNCTION MakeBox() RETURNS Box
+OUTPUT "make"
+DECLARE result : Box
+result.Value <- 4
+RETURN result
+ENDFUNCTION
+FUNCTION Number() RETURNS INTEGER
+OUTPUT "number"
+RETURN 2
+ENDFUNCTION
+DECLARE a : Box
+DECLARE b : Box
+DECLARE result : Box
+a.Value <- 3
+b.Value <- 5
+result <- (a + 2) + 4
+OUTPUT result.Value
+OUTPUT -a
+OUTPUT NOT a
+OUTPUT a.Describe()
+OUTPUT a + b
+OUTPUT a.Value
+OUTPUT a.OPERATOR+(1) - 1
+OUTPUT OPERATOR-(a, 2)
+CALL a.OPERATOR-( )
+a.OPERATOR-( )
+CALL OPERATOR-(a, 1)
+OPERATOR-(a, 1)
+result <- MakeBox() + Number()
+OUTPUT result.Value
+TRY
+OUTPUT a * b
+CATCH
+OUTPUT "caught"
+ENDTRY
diff --git a/tests/iii/operator-member-errors/case.json b/tests/iii/operator-member-errors/case.json
new file mode 100644
index 0000000..1e8f8ae
--- /dev/null
+++ b/tests/iii/operator-member-errors/case.json
@@ -0,0 +1,20 @@
+{
+ "errors": [
+ {
+ "reason": "Ambiguous overload",
+ "line": 14
+ },
+ {
+ "reason": "Cannot access private method",
+ "line": 15
+ },
+ {
+ "reason": "Cannot access private method",
+ "line": 16
+ },
+ {
+ "reason": "whole array cannot be used as an operand",
+ "line": 18
+ }
+ ]
+}
diff --git a/tests/iii/operator-member-errors/notes.md b/tests/iii/operator-member-errors/notes.md
new file mode 100644
index 0000000..204ab43
--- /dev/null
+++ b/tests/iii/operator-member-errors/notes.md
@@ -0,0 +1,3 @@
+# operator-member-errors
+
+Reject equally matching member/global candidates, implicit and explicit private access, and unary operations on whole arrays.
diff --git a/tests/iii/operator-member-errors/program.cps b/tests/iii/operator-member-errors/program.cps
new file mode 100644
index 0000000..9b48135
--- /dev/null
+++ b/tests/iii/operator-member-errors/program.cps
@@ -0,0 +1,18 @@
+CLASS Box
+Value : INTEGER
+OVERLOAD FUNCTION OPERATOR+(rhs : Box) RETURNS INTEGER
+RETURN 1
+ENDFUNCTION
+PRIVATE OVERLOAD FUNCTION OPERATOR&(rhs : STRING) RETURNS STRING
+RETURN rhs
+ENDFUNCTION
+ENDCLASS
+OVERLOAD FUNCTION OPERATOR+(lhs : Box, rhs : Box) RETURNS INTEGER
+RETURN 2
+ENDFUNCTION
+DECLARE a : Box
+OUTPUT a + a
+OUTPUT a & "x"
+OUTPUT a.OPERATOR&("x")
+DECLARE items : ARRAY[1:2] OF Box
+OUTPUT NOT items