diff options
| author | Lam Wei Lun <weilun.lam@gmail.com> | 2026-01-21 02:47:13 +0800 |
|---|---|---|
| committer | Lam Wei Lun <weilun.lam@gmail.com> | 2026-01-21 02:47:13 +0800 |
| commit | 14463b3961c03eac5c4f2fa493a354bc866bf6c2 (patch) | |
| tree | 50cac4e039b312be91af80c2092fbc2361e2b49d /src | |
| parent | 451e6b1fff77bccedccdb745e4ee56cbeef31b79 (diff) | |
| parent | 6165c437410052fa177fc1245c176ec6709a9da7 (diff) | |
Upstream main
Diffstat (limited to 'src')
| -rw-r--r-- | src/analysis/typecheck.c | 138 | ||||
| -rw-r--r-- | src/codegen/codegen_decl.c | 7 | ||||
| -rw-r--r-- | src/parser/parser_core.c | 6 | ||||
| -rw-r--r-- | src/parser/parser_stmt.c | 31 |
4 files changed, 165 insertions, 17 deletions
diff --git a/src/analysis/typecheck.c b/src/analysis/typecheck.c index e3abf10..2b06bb4 100644 --- a/src/analysis/typecheck.c +++ b/src/analysis/typecheck.c @@ -69,10 +69,114 @@ static Symbol *tc_lookup(TypeChecker *tc, const char *name) return NULL; } +// ** Move Semantics Helpers ** + +static int is_safe_to_copy(TypeChecker *tc, Type *t) +{ + // Use parser's helper if available, or simple heuristic + return is_type_copy(tc->pctx, t); +} + +static void check_use_validity(TypeChecker *tc, ASTNode *var_node, Symbol *sym) +{ + if (!sym || !var_node) + { + return; + } + + if (sym->is_moved) + { + char msg[256]; + snprintf( + msg, 255, + "Use of moved value '%s'. This type owns resources and cannot be implicitly copied.", + sym->name); + tc_error(tc, var_node->token, msg); + } +} + +static void mark_symbol_moved(TypeChecker *tc, Symbol *sym, ASTNode *context_node) +{ + if (!sym) + { + return; + } + + // Only move if type is NOT Copy + Type *t = sym->type_info; + if (t && !is_safe_to_copy(tc, t)) + { + sym->is_moved = 1; + } +} + +static void mark_symbol_valid(TypeChecker *tc, Symbol *sym) +{ + if (sym) + { + sym->is_moved = 0; + } +} + // ** Node Checkers ** static void check_node(TypeChecker *tc, ASTNode *node); +static void check_expr_binary(TypeChecker *tc, ASTNode *node) +{ + check_node(tc, node->binary.left); + check_node(tc, node->binary.right); + + // Assignment Logic for Moves + if (strcmp(node->binary.op, "=") == 0) + { + // If RHS is a var, it might Move + if (node->binary.right->type == NODE_EXPR_VAR) + { + Symbol *rhs_sym = tc_lookup(tc, node->binary.right->var_ref.name); + if (rhs_sym) + { + mark_symbol_moved(tc, rhs_sym, node); + } + } + + // LHS is being (re-)initialized, so it becomes Valid. + if (node->binary.left->type == NODE_EXPR_VAR) + { + Symbol *lhs_sym = tc_lookup(tc, node->binary.left->var_ref.name); + if (lhs_sym) + { + mark_symbol_valid(tc, lhs_sym); + } + } + } +} + +static void check_expr_call(TypeChecker *tc, ASTNode *node) +{ + check_node(tc, node->call.callee); + + // Check arguments + ASTNode *arg = node->call.args; + while (arg) + { + check_node(tc, arg); + + // If argument is passed by VALUE, and it's a variable, it MOVES. + // If passed by ref (UNARY '&'), the child was checked but Is Not A Var Node itself. + if (arg->type == NODE_EXPR_VAR) + { + Symbol *sym = tc_lookup(tc, arg->var_ref.name); + if (sym) + { + mark_symbol_moved(tc, sym, node); + } + } + + arg = arg->next; + } +} + static void check_block(TypeChecker *tc, ASTNode *block) { tc_enter_scope(tc); @@ -140,6 +244,16 @@ static void check_var_decl(TypeChecker *tc, ASTNode *node) { check_type_compatibility(tc, decl_type, init_type, node->token); } + + // Move Analysis: If initializing from another variable, it moves. + if (node->var_decl.init_expr->type == NODE_EXPR_VAR) + { + Symbol *init_sym = tc_lookup(tc, node->var_decl.init_expr->var_ref.name); + if (init_sym) + { + mark_symbol_moved(tc, init_sym, node); + } + } } // If type is not explicit, we should ideally infer it from init_expr. @@ -187,6 +301,9 @@ static void check_expr_var(TypeChecker *tc, ASTNode *node) { node->type_info = sym->type_info; } + + // Check for Use-After-Move + check_use_validity(tc, node, sym); } static void check_node(TypeChecker *tc, ASTNode *node) @@ -240,15 +357,26 @@ static void check_node(TypeChecker *tc, ASTNode *node) tc_exit_scope(tc); break; case NODE_EXPR_BINARY: - check_node(tc, node->binary.left); - check_node(tc, node->binary.right); + check_expr_binary(tc, node); break; case NODE_EXPR_CALL: - check_node(tc, node->call.callee); - check_node(tc, node->call.args); + check_expr_call(tc, node); break; default: - // Generic recursion for lists. + // Generic recursion for lists and other nodes. + // Special case for Return to trigger move? + if (node->type == NODE_RETURN && node->ret.value) + { + // If returning a variable by value, it is moved. + if (node->ret.value->type == NODE_EXPR_VAR) + { + Symbol *sym = tc_lookup(tc, node->ret.value->var_ref.name); + if (sym) + { + mark_symbol_moved(tc, sym, node); + } + } + } break; } diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c index 8ada7d6..932420f 100644 --- a/src/codegen/codegen_decl.c +++ b/src/codegen/codegen_decl.c @@ -853,6 +853,13 @@ void emit_impl_vtables(ParserContext *ctx, FILE *out) emitted[count].strct = strct; count++; + if (0 == strcmp(trait, "Copy")) + { + // Marker trait, no runtime vtable needed + ref = ref->next; + continue; + } + fprintf(out, "%s_VTable %s_%s_VTable = {", trait, strct, trait); ASTNode *m = node->impl_trait.methods; diff --git a/src/parser/parser_core.c b/src/parser/parser_core.c index 3c2805c..464c905 100644 --- a/src/parser/parser_core.c +++ b/src/parser/parser_core.c @@ -622,6 +622,12 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * sprintf(code, "impl %s { fn to_string(self) -> char* { return \"%s { ... }\"; } }", name, name); } + else if (0 == strcmp(trait, "Copy")) + { + // Marker trait for Copy/Move semantics + code = xmalloc(1024); + sprintf(code, "impl Copy for %s {}", name); + } if (code) { diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index c5efe8a..a8c8df5 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -4269,30 +4269,37 @@ ASTNode *parse_import(ParserContext *ctx, Lexer *l) strncpy(fn, t.start + 1, ln); fn[ln] = 0; - // Resolve relative paths (if starts with ./ or ../ + // Resolve paths relative to current file char resolved_path[1024]; - if (fn[0] == '.' && (fn[1] == '/' || (fn[1] == '.' && fn[2] == '/'))) + int is_explicit_relative = (fn[0] == '.' && (fn[1] == '/' || (fn[1] == '.' && fn[2] == '/'))); + + // Try to resolve relative to current file if not absolute + if (fn[0] != '/') { - // Relative import - resolve relative to current file char *current_dir = xstrdup(g_current_filename); char *last_slash = strrchr(current_dir, '/'); if (last_slash) { *last_slash = 0; // Truncate to directory + + // Handle explicit relative differently? + // Existing logic enforced it. Let's try to verify existence first. + + // Construct candidate path const char *leaf = fn; - if (leaf[0] == '.' && leaf[1] == '/') + // Clean up ./ prefix for cleaner path construction if we want + // but keeping it is fine too, /path/to/./file works. + + snprintf(resolved_path, sizeof(resolved_path), "%s/%s", current_dir, leaf); + + // If it's an explicit relative path, OR if the file exists at this relative location + if (is_explicit_relative || access(resolved_path, R_OK) == 0) { - leaf += 2; + free(fn); + fn = xstrdup(resolved_path); } - snprintf(resolved_path, sizeof(resolved_path), "%s/%s", current_dir, leaf); - } - else - { - snprintf(resolved_path, sizeof(resolved_path), "%s", fn); } free(current_dir); - free(fn); - fn = xstrdup(resolved_path); } // Check if file exists, if not try system-wide paths |
