diff options
| -rw-r--r-- | src/parser/parser.h | 5 | ||||
| -rw-r--r-- | src/parser/parser_expr.c | 208 | ||||
| -rw-r--r-- | src/parser/parser_stmt.c | 46 | ||||
| -rw-r--r-- | src/parser/parser_utils.c | 15 | ||||
| -rw-r--r-- | std/vec.zc | 1 | ||||
| -rw-r--r-- | tests/features/test_auto_deref.zc | 34 | ||||
| -rw-r--r-- | tests/features/test_move_semantics.zc | 64 | ||||
| -rw-r--r-- | tests/memory/test_memory_safety.zc | 2 |
8 files changed, 356 insertions, 19 deletions
diff --git a/src/parser/parser.h b/src/parser/parser.h index b3837eb..f82edf4 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -42,6 +42,7 @@ typedef struct Symbol Token decl_token; int is_const_value; int const_int_val; + int is_moved; struct Symbol *next; } Symbol; @@ -427,6 +428,10 @@ ASTNode *parse_trait(ParserContext *ctx, Lexer *l); ASTNode *parse_impl(ParserContext *ctx, Lexer *l); ASTNode *parse_impl_trait(ParserContext *ctx, Lexer *l); ASTNode *parse_test(ParserContext *ctx, Lexer *l); + +// Move semantics helpers +int is_type_copy(Type *t); +void check_move_usage(ParserContext *ctx, ASTNode *node, Token t); ASTNode *parse_include(ParserContext *ctx, Lexer *l); ASTNode *parse_import(ParserContext *ctx, Lexer *l); ASTNode *parse_comptime(ParserContext *ctx, Lexer *l); diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index f7590e3..cc647c7 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -9,6 +9,65 @@ Type *get_field_type(ParserContext *ctx, Type *struct_type, const char *field_name); +int is_type_copy(Type *t) +{ + if (!t) + { + return 1; // Default to Copy for unknown types to avoid annoyance + } + + switch (t->kind) + { + case TYPE_INT: + case TYPE_I8: + case TYPE_I16: + case TYPE_I32: + case TYPE_I64: + case TYPE_U8: + case TYPE_U16: + case TYPE_U32: + case TYPE_U64: + case TYPE_F32: + case TYPE_F64: + case TYPE_BOOL: + case TYPE_CHAR: + case TYPE_VOID: + case TYPE_POINTER: // Pointers are Copy + case TYPE_FUNCTION: + case TYPE_ENUM: // Enums are integers + return 1; + + case TYPE_STRUCT: + // Structs are MOVE by default + return 0; + + case TYPE_ARRAY: + // Arrays decay or are fixed size context dependent, but usually not simplistic copy + // For Zen-C safety, let's treat them as Copy if they are treated as pointers, + // but if it's a value assignment, C doesn't support it anyway unless wrapped in struct. + return 0; + + default: + return 1; + } +} + +void check_move_usage(ParserContext *ctx, ASTNode *node, Token t) +{ + if (!node) + { + return; + } + if (node->type == NODE_EXPR_VAR) + { + Symbol *sym = find_symbol_entry(ctx, node->var_ref.name); + if (sym && sym->is_moved) + { + zpanic_at(t, "Use of moved value '%s'", node->var_ref.name); + } + } +} + static int type_is_unsigned(Type *t) { if (!t) @@ -636,9 +695,6 @@ void analyze_lambda_captures(ParserContext *ctx, ASTNode *lambda) for (int i = 0; i < num_refs; i++) { free(all_refs[i]); - } - if (all_refs) - { free(all_refs); } } @@ -1758,6 +1814,31 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) } ASTNode *arg = parse_expression(ctx, l); + + // Move Semantics Logic (Added for known funcs) + check_move_usage(ctx, arg, arg ? arg->token : t1); + if (arg && arg->type == NODE_EXPR_VAR) + { + Type *t = find_symbol_type_info(ctx, arg->var_ref.name); + if (!t) + { + Symbol *s = find_symbol_entry(ctx, arg->var_ref.name); + if (s) + { + t = s->type_info; + } + } + + if (!is_type_copy(t)) + { + Symbol *s = find_symbol_entry(ctx, arg->var_ref.name); + if (s) + { + s->is_moved = 1; + } + } + } + if (!head) { head = arg; @@ -1864,6 +1945,31 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) } ASTNode *arg = parse_expression(ctx, l); + + // Move Semantics Logic (Added) + check_move_usage(ctx, arg, arg ? arg->token : t1); + if (arg && arg->type == NODE_EXPR_VAR) + { + Type *t = find_symbol_type_info(ctx, arg->var_ref.name); + if (!t) + { + Symbol *s = find_symbol_entry(ctx, arg->var_ref.name); + if (s) + { + t = s->type_info; + } + } + + if (!is_type_copy(t)) + { + Symbol *s = find_symbol_entry(ctx, arg->var_ref.name); + if (s) + { + s->is_moved = 1; + } + } + } + if (!head) { head = arg; @@ -2125,6 +2231,31 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) } ASTNode *arg = parse_expression(ctx, l); + + // Move Semantics Logic + check_move_usage(ctx, arg, arg ? arg->token : t1); + if (arg && arg->type == NODE_EXPR_VAR) + { + Type *t = find_symbol_type_info(ctx, arg->var_ref.name); + if (!t) + { + Symbol *s = find_symbol_entry(ctx, arg->var_ref.name); + if (s) + { + t = s->type_info; + } + } + + if (!is_type_copy(t)) + { + Symbol *s = find_symbol_entry(ctx, arg->var_ref.name); + if (s) + { + s->is_moved = 1; + } + } + } + if (!head) { head = arg; @@ -3201,6 +3332,31 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) } ASTNode *arg = parse_expression(ctx, l); + + // Move Semantics Logic + check_move_usage(ctx, arg, arg ? arg->token : t1); + if (arg && arg->type == NODE_EXPR_VAR) + { + Type *t = find_symbol_type_info(ctx, arg->var_ref.name); + if (!t) + { + Symbol *s = find_symbol_entry(ctx, arg->var_ref.name); + if (s) + { + t = s->type_info; + } + } + + if (!is_type_copy(t)) + { + Symbol *s = find_symbol_entry(ctx, arg->var_ref.name); + if (s) + { + s->is_moved = 1; + } + } + } + if (!head) { head = arg; @@ -3577,6 +3733,52 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) bin->binary.left = lhs; bin->binary.right = rhs; + // Move Semantics Logic + if (op.type == TOK_OP && is_token(op, "=")) // Assignment "=" + { + // 1. RHS is being read: Check validity + check_move_usage(ctx, rhs, op); + + // 2. Mark RHS as moved (Transfer ownership) if it's a Move type + if (rhs->type == NODE_EXPR_VAR) + { + Type *t = find_symbol_type_info(ctx, rhs->var_ref.name); + // If type info not on var, try looking up symbol + if (!t) + { + Symbol *s = find_symbol_entry(ctx, rhs->var_ref.name); + if (s) + { + t = s->type_info; + } + } + + if (!is_type_copy(t)) + { + Symbol *s = find_symbol_entry(ctx, rhs->var_ref.name); + if (s) + { + s->is_moved = 1; + } + } + } + + // 3. LHS is being written: Resurrect (it is now valid) + if (lhs->type == NODE_EXPR_VAR) + { + Symbol *s = find_symbol_entry(ctx, lhs->var_ref.name); + if (s) + { + s->is_moved = 0; + } + } + } + else // All other binary ops (including +=, -=, etc. which read LHS first) + { + check_move_usage(ctx, lhs, op); + check_move_usage(ctx, rhs, op); + } + if (op.type == TOK_LANGLE) { bin->binary.op = xstrdup("<"); diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index db47d16..8fa0a1e 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -1235,6 +1235,29 @@ ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l) n->var_decl.init_expr = init; + // Move Semantics Logic for Initialization + check_move_usage(ctx, init, init ? init->token : name_tok); + if (init && init->type == NODE_EXPR_VAR) + { + Type *t = find_symbol_type_info(ctx, init->var_ref.name); + if (!t) + { + Symbol *s = find_symbol_entry(ctx, init->var_ref.name); + if (s) + { + t = s->type_info; + } + } + if (!is_type_copy(t)) + { + Symbol *s = find_symbol_entry(ctx, init->var_ref.name); + if (s) + { + s->is_moved = 1; + } + } + } + // Global detection: Either no scope (yet) OR root scope (no parent) if (!ctx->current_scope || !ctx->current_scope->parent) { @@ -1483,6 +1506,11 @@ ASTNode *parse_return(ParserContext *ctx, Lexer *l) else { n->ret.value = parse_expression(ctx, l); + check_move_usage(ctx, n->ret.value, n->ret.value ? n->ret.value->token : lexer_peek(l)); + + // Note: Returning a non-Copy variable effectively moves it out. + // We could mark it as moved, but scope ends anyway. + // The critical part is checking we aren't returning an ALREADY moved value. } } @@ -1668,8 +1696,8 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) char *iter_method = "iterator"; // Check for reference iteration: for x in &vec - if (obj_expr->type == NODE_EXPR_UNARY && - obj_expr->unary.op && strcmp(obj_expr->unary.op, "&") == 0) + if (obj_expr->type == NODE_EXPR_UNARY && obj_expr->unary.op && + strcmp(obj_expr->unary.op, "&") == 0) { obj_expr = obj_expr->unary.operand; iter_method = "iter_ref"; @@ -3403,14 +3431,16 @@ ASTNode *parse_impl(ParserContext *ctx, Lexer *l) // If target struct is generic, register this impl as a template ASTNode *def = find_struct_def(ctx, name2); - if (def && ((def->type == NODE_STRUCT && def->strct.is_template) || + if (def && ((def->type == NODE_STRUCT && def->strct.is_template) || (def->type == NODE_ENUM && def->enm.is_template))) { - const char *gp = "T"; - if (def->type == NODE_STRUCT && def->strct.generic_param_count > 0) - gp = def->strct.generic_params[0]; - // TODO: Enum generic params support if needed - register_impl_template(ctx, name2, gp, n); + const char *gp = "T"; + if (def->type == NODE_STRUCT && def->strct.generic_param_count > 0) + { + gp = def->strct.generic_params[0]; + } + // TODO: Enum generic params support if needed + register_impl_template(ctx, name2, gp, n); } return n; diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c index f96ed24..4973111 100644 --- a/src/parser/parser_utils.c +++ b/src/parser/parser_utils.c @@ -1520,7 +1520,8 @@ ASTNode *copy_ast_replacing(ASTNode *n, const char *p, const char *c, const char break; case NODE_IMPL_TRAIT: new_node->impl_trait.trait_name = xstrdup(n->impl_trait.trait_name); - new_node->impl_trait.target_type = replace_type_str(n->impl_trait.target_type, p, c, os, ns); + new_node->impl_trait.target_type = + replace_type_str(n->impl_trait.target_type, p, c, os, ns); new_node->impl_trait.methods = copy_ast_replacing(n->impl_trait.methods, p, c, os, ns); break; default: @@ -1884,13 +1885,15 @@ void instantiate_methods(ParserContext *ctx, GenericImplTemplate *it, it->struct_name, mangled_struct_name); it->impl_node->next = backup_next; // Restore - ASTNode *meth = NULL; - if (new_impl->type == NODE_IMPL) { + if (new_impl->type == NODE_IMPL) + { new_impl->impl.struct_name = xstrdup(mangled_struct_name); meth = new_impl->impl.methods; - } else if (new_impl->type == NODE_IMPL_TRAIT) { + } + else if (new_impl->type == NODE_IMPL_TRAIT) + { new_impl->impl_trait.target_type = xstrdup(mangled_struct_name); meth = new_impl->impl_trait.methods; } @@ -2009,7 +2012,7 @@ void instantiate_generic(ParserContext *ctx, const char *tpl, const char *arg, ASTNode *i = ast_create(NODE_STRUCT); i->strct.name = xstrdup(m); i->strct.is_template = 0; - + // Copy type attributes (e.g. has_drop) i->type_info = type_new(TYPE_STRUCT); i->type_info->name = xstrdup(m); @@ -2033,7 +2036,7 @@ void instantiate_generic(ParserContext *ctx, const char *tpl, const char *arg, ASTNode *i = ast_create(NODE_ENUM); i->enm.name = xstrdup(m); i->enm.is_template = 0; - + // Copy type attributes (e.g. has_drop) i->type_info = type_new(TYPE_ENUM); i->type_info->name = xstrdup(m); @@ -111,7 +111,6 @@ impl Vec<T> { self.grow(); } self.data[self.len] = item; - self.data[self.len] = item; self.len = self.len + 1; } diff --git a/tests/features/test_auto_deref.zc b/tests/features/test_auto_deref.zc new file mode 100644 index 0000000..b7b0c51 --- /dev/null +++ b/tests/features/test_auto_deref.zc @@ -0,0 +1,34 @@ + +struct Point { + x: int; + y: int; +} + +test "auto_deref" { + var p = Point { x: 10, y: 20 }; + var ptr = &p; + + // This uses the dot operator on a pointer + // In standard C, this requires ->. In Zen-C w/ Auto-Deref, . should work. + assert(ptr.x == 10, "read ptr.x failed"); + + ptr.y = 30; + assert(p.y == 30, "write ptr.y failed"); + + assert(ptr.y == 30, "read ptr.y after write failed"); + + var i = Inner { val: 42 }; + var o = Outer { inner: i }; + var p_out = &o; + + // Nested access: p.inner.val -> p->inner.val + assert(p_out.inner.val == 42, "Nested access failed"); +} + +struct Inner { + val: int; +} + +struct Outer { + inner: Inner; +} diff --git a/tests/features/test_move_semantics.zc b/tests/features/test_move_semantics.zc new file mode 100644 index 0000000..70390c2 --- /dev/null +++ b/tests/features/test_move_semantics.zc @@ -0,0 +1,64 @@ + +struct Point { + x: int; +} + +struct Mover { + val: int; +} + +fn test_basic_move() { + var p1 = Mover { val: 10 }; + var p2 = p1; // p1 moved to p2 + + // Valid usage of p2 + assert(p2.val == 10, "p2 should be valid"); + + // Invalid usage of p1 (Uncomment to test compiler error) + // var p3 = p1; +} + +fn 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() { + var m1 = Mover { val: 1 }; + var m2 = m1; // m1 moved + + m1 = Mover { val: 2 }; // Resurrect m1 + var m3 = m1; // Valid now + assert(m3.val == 2, "Resurrection failed"); +} + +fn test_func_arg(m: Mover) { + assert(m.val == 10, "Func arg failed"); +} + +fn main() { + test_basic_move(); + test_primitive_copy(); + test_reassignment(); + + 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 + + // 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' + } + */ +} diff --git a/tests/memory/test_memory_safety.zc b/tests/memory/test_memory_safety.zc index 652fe21..020a7ba 100644 --- a/tests/memory/test_memory_safety.zc +++ b/tests/memory/test_memory_safety.zc @@ -183,7 +183,7 @@ test "test_immutable" { "Distance: {dist}"; var sum = accumulate(10, 5); "Accumulate: {sum}"; - var p3 = process_point(p1); + var p3 = process_point(Point { x: 0, y: 0 }); "Processed: ({p3.x}, {p3.y})"; counter_immut(); } |
