diff options
| -rw-r--r-- | README.md | 10 | ||||
| -rw-r--r-- | src/codegen/codegen.c | 2 | ||||
| -rw-r--r-- | src/parser/parser_core.c | 13 | ||||
| -rw-r--r-- | src/parser/parser_expr.c | 29 | ||||
| -rw-r--r-- | tests/features/test_copy_trait.zc | 12 | ||||
| -rw-r--r-- | tests/features/test_move_semantics.zc | 41 | ||||
| -rw-r--r-- | tests/features/test_smart_derive.zc | 26 | ||||
| -rw-r--r-- | tests/misc/test_edge_cases.zc | 6 |
8 files changed, 94 insertions, 45 deletions
@@ -662,7 +662,15 @@ Decorate functions and structs to modify compiler behavior. | `@weak` | Fn | Weak symbol linkage. | | `@section("name")` | Fn | Place code in specific section. | | `@noreturn` | Fn | Function does not return (e.g. exit). | -| `@derived(...)` | Struct | Auto-implement traits (e.g. `Debug`). | +| `@derive(...)` | Struct | Auto-implement traits. Supports `Debug`, `Eq` (Smart Derive), `Copy`, `Clone`. | + +### Smart Derives + +Zen C provides "Smart Derives" that respect Move Semantics: + +- **`@derive(Eq)`**: Generates an equality method that takes arguments by reference (`fn eq(self, other: T*)`). + - When comparing two non-Copy structs (`a == b`), the compiler automatically passes `b` by reference (`&b`) to avoid moving it. + - Recursive equality checks on fields also prefer pointer access to prevent ownership transfer. ### 14. Inline Assembly diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 01c8204..b25c79c 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -461,7 +461,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } fprintf(out, "%s__eq(&", base); codegen_expression(ctx, node->binary.left, out); - fprintf(out, ", "); + fprintf(out, ", &"); codegen_expression(ctx, node->binary.right, out); fprintf(out, ")"); if (strcmp(node->binary.op, "!=") == 0) diff --git a/src/parser/parser_core.c b/src/parser/parser_core.c index c3c91fe..acab268 100644 --- a/src/parser/parser_core.c +++ b/src/parser/parser_core.c @@ -585,17 +585,19 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * ASTNode *fdef = find_struct_def(ctx, ft); if (fdef && fdef->type == NODE_ENUM) { - // Enum field: compare tags + // Enum field: compare tags (pointer access via auto-deref) sprintf(cmp, "self.%s.tag == other.%s.tag", fn, fn); } else if (fdef && fdef->type == NODE_STRUCT) { - // Struct field: use _eq function - sprintf(cmp, "%s__eq(&self.%s, other.%s)", ft, fn, fn); + // Struct field: use _eq function, pass addresses + // self.field is L-value, other.field is L-value (auto-deref from pointer) + // We need addresses of them: &self.field, &other.field + sprintf(cmp, "%s__eq(&self.%s, &other.%s)", ft, fn, fn); } else { - // Primitive or unknown: use == + // Primitive or unknown: use == (auto-deref) sprintf(cmp, "self.%s == other.%s", fn, fn); } strcat(body, cmp); @@ -610,7 +612,8 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * strcat(body, ";"); } code = xmalloc(4096 + 1024); - sprintf(code, "impl %s { fn eq(self, other: %s) -> bool { %s } }", name, name, body); + // Updated signature: other is a pointer T* + sprintf(code, "impl %s { fn eq(self, other: %s*) -> bool { %s } }", name, name, body); } else if (0 == strcmp(trait, "Debug")) { diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 97d6218..a79bb21 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -4115,9 +4115,34 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) } } + // Handle RHS (Argument 2) Auto-Ref if needed + ASTNode *arg2 = rhs; + if (sig->total_args > 1 && sig->arg_types[1] && + sig->arg_types[1]->kind == TYPE_POINTER) + { + Type *rt = rhs->type_info; + int is_rhs_ptr = (rt && rt->kind == TYPE_POINTER); + if (!is_rhs_ptr) // Need pointer, have value + { + int is_rvalue = + (rhs->type == NODE_EXPR_CALL || rhs->type == NODE_EXPR_BINARY || + rhs->type == NODE_EXPR_STRUCT_INIT || + rhs->type == NODE_EXPR_CAST || rhs->type == NODE_MATCH); + + ASTNode *addr = ast_create(NODE_EXPR_UNARY); + addr->unary.op = is_rvalue ? xstrdup("&_rval") : xstrdup("&"); + addr->unary.operand = rhs; + if (rt) + { + addr->type_info = type_new_ptr(rt); + } + arg2 = addr; + } + } + call->call.args = arg1; - arg1->next = rhs; - rhs->next = NULL; + arg1->next = arg2; + arg2->next = NULL; call->type_info = sig->ret_type; call->resolved_type = type_to_string(sig->ret_type); diff --git a/tests/features/test_copy_trait.zc b/tests/features/test_copy_trait.zc index 120dc5d..994ccee 100644 --- a/tests/features/test_copy_trait.zc +++ b/tests/features/test_copy_trait.zc @@ -12,7 +12,7 @@ struct Mover { val: int; } -fn test_copy_trait() { +test "copy_trait" { var p1 = Point { x: 10, y: 20 }; var p2 = p1; // Copy, not move @@ -24,18 +24,14 @@ fn test_copy_trait() { p2.x = 30; assert(p1.x == 10, "p1 changed after p2 modification"); assert(p2.x == 30, "p2 modification failed"); + + println "Copy Trait Works!"; } -fn test_move_default() { +test "move_default" { var m1 = Mover { val: 1 }; var m2 = m1; // Moved // Uncommenting this should cause compile error // var m3 = m1; } - -fn main() { - test_copy_trait(); - test_move_default(); - "Copy Trait Works!"; -} diff --git a/tests/features/test_move_semantics.zc b/tests/features/test_move_semantics.zc index 70390c2..bf0d717 100644 --- a/tests/features/test_move_semantics.zc +++ b/tests/features/test_move_semantics.zc @@ -7,7 +7,7 @@ struct Mover { val: int; } -fn test_basic_move() { +test "basic_move" { var p1 = Mover { val: 10 }; var p2 = p1; // p1 moved to p2 @@ -18,14 +18,14 @@ fn test_basic_move() { // var p3 = p1; } -fn test_primitive_copy() { +test "primitive_copy" { var i = 10; var j = i; // Copy var k = i; // Copy again - should be valid assert(k == 10, "Primitive copy failed"); } -fn test_reassignment() { +test "reassignment" { var m1 = Mover { val: 1 }; var m2 = m1; // m1 moved @@ -34,31 +34,22 @@ fn test_reassignment() { assert(m3.val == 2, "Resurrection failed"); } -fn test_func_arg(m: Mover) { +fn consume(m: Mover) { assert(m.val == 10, "Func arg failed"); } -fn main() { - test_basic_move(); - test_primitive_copy(); - test_reassignment(); - +test "func_arg" { var m = Mover { val: 10 }; - test_func_arg(m); // m moved - - // ** Negative Tests (Uncomment to verify) ** - - // 1. Use after move (Assignment) - // test_basic_move(); // See line 18 inside function + consume(m); // m moved - // 2. Use after move (Call) - // test_func_arg(m); // Should fail: Use of moved value 'm' - - // 3. Use after return - /* - fn fail_return(m: Mover) -> Mover { - var m2 = m; - return m; // Should fail: Use of moved value 'm' - } - */ + // 2. Use after move (Call - Negative Test) + // consume(m); // Should fail: Use of moved value 'm' +} + +/* +// 3. Use after return (Negative Test) +fn fail_return(m: Mover) -> Mover { + var m2 = m; + return m; // Should fail: Use of moved value 'm' } +*/ diff --git a/tests/features/test_smart_derive.zc b/tests/features/test_smart_derive.zc new file mode 100644 index 0000000..705a12f --- /dev/null +++ b/tests/features/test_smart_derive.zc @@ -0,0 +1,26 @@ + +@derive(Eq) +struct Container { + val: int; +} + +// Ensure derived eq uses pointers by trying to use 'c2' after comparison +test "eq_moves" { + var c1 = Container { val: 10 }; + var c2 = Container { val: 10 }; + + // This should call Container__eq(&c1, &c2) + // If it passed by value, c2 would be moved here + if c1 == c2 { + // c2 must still be valid + assert(c2.val == 10, "c2 moved during equality check!"); + } else { + assert(false, "c1 != c2"); + } + + // Explicitly verify c2 is still valid + c2.val = 20; + assert(c2.val == 20, "c2 invalid"); + + println "Smart Derive Eq Works!"; +} diff --git a/tests/misc/test_edge_cases.zc b/tests/misc/test_edge_cases.zc index f1a4cf5..0d50744 100644 --- a/tests/misc/test_edge_cases.zc +++ b/tests/misc/test_edge_cases.zc @@ -9,7 +9,7 @@ fn test_empty_struct() { var e1 = Empty {}; var e2 = Empty {}; - if (!e1.eq(e2)) { + if (!e1.eq(&e2)) { println "FAIL: Empty struct eq failed"; exit(1); } @@ -35,12 +35,12 @@ fn test_many_fields() { var m2 = ManyFields { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8 }; var m3 = ManyFields { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 9 }; // h differs - if (!m1.eq(m2)) { + if (!m1.eq(&m2)) { println "FAIL: equal structs not detected as equal"; exit(1); } - if (m1.eq(m3)) { + if (m1.eq(&m3)) { println "FAIL: different structs detected as equal"; exit(1); } |
