// SPDX-License-Identifier: MIT #include "../utils/colors.h" #include "typecheck_internal.h" #include "../constants.h" #include "typecheck.h" #include "comptime_interpreter.h" #include "diagnostics/diagnostics.h" #include "move_check.h" #include "platform/misra.h" #include #include // External helpers from parser // ** Internal Helpers ** // The variant a match pattern names. The parser stores it mangled // (`Shape__Circle`); `Shape::Circle` and a bare `Circle` are accepted too. NULL // for patterns that are not a single enum variant (`_`, `A || B`, literals). static const char *match_pattern_variant(const char *pattern, const char *enum_name) { if (!pattern || strstr(pattern, "||") || strchr(pattern, ',') || strstr(pattern, " or ")) { return NULL; } size_t enum_len = enum_name ? strlen(enum_name) : 0; if (enum_len && strncmp(pattern, enum_name, enum_len) == 0) { const char *rest = pattern + enum_len; if (strncmp(rest, "__", 2) == 0 || strncmp(rest, "::", 2) == 0) { return rest + 2; } } const char *sep = NULL; const char *p = pattern; while ((p = strstr(p, "::")) != NULL) { sep = p; p += 2; } return sep ? sep + 2 : pattern; } // Type of the `index`-th of `count` bindings of a match case, read from the // payload of the variant the pattern names. NULL when it cannot be told (not an // enum, unknown variant, unresolved generic): the caller keeps UNSAFE_ANY. static Type *match_binding_type(TypeChecker *tc, Type *scrutinee, const char *pattern, int index, int count) { Type *t = get_inner_type(scrutinee); while (t && t->kind == TYPE_POINTER) { t = get_inner_type(t->inner); } const char *variant = t ? match_pattern_variant(pattern, t->name) : NULL; if (!t || !t->name || !variant) { return NULL; } ASTNode *def = find_struct_def(tc->pctx, t->name); if (!def || def->kind != NODE_ENUM) { return NULL; } ASTNode *v = def->enm.variants; while (v) { if (v->kind == NODE_ENUM_VARIANT && v->variant.name && strcmp(v->variant.name, variant) == 0) { Type *payload = v->variant.payload; if (!payload) { return NULL; } if (count == 1) { return payload; } // Several bindings destructure a tuple payload: one per field. ASTNode *tuple = payload->name ? find_struct_def(tc->pctx, payload->name) : NULL; if (!tuple || tuple->kind != NODE_STRUCT) { return NULL; } ASTNode *field = tuple->strct.fields; int i = 0; while (field) { if (field->kind == NODE_FIELD) { if (i == index) { // Tuple structs registered for payloads only keep the // field type as text. if (!field->type_info && field->field.type) { return type_from_string_helper(tc->pctx, field->field.type); } return field->type_info; } i++; } field = field->next; } return NULL; } v = v->next; } return NULL; } void check_node(TypeChecker *tc, ASTNode *node, int depth) { if (!node || !tc) { return; } RECURSION_GUARD_TOKEN(tc->pctx, node->token, ); if (depth > 1024) { tc_error(tc, node->token, "Expression too deep"); RECURSION_EXIT(tc->pctx); return; } switch (node->kind) { case NODE_ROOT: { ASTNode *child = node->root.children; while (child) { check_node(tc, child, depth + 1); child = child->next; } } break; case NODE_BLOCK: check_block(tc, node, depth + 1); break; case NODE_VAR_DECL: check_var_decl(tc, node, depth + 1); break; case NODE_FUNCTION: check_function(tc, node, depth + 1); break; case NODE_TRAIT: tc_check_trait(tc, node, depth + 1); break; case NODE_IMPL: tc_check_impl(tc, node, depth + 1); break; case NODE_IMPL_TRAIT: tc_check_impl_trait(tc, node, depth + 1); break; case NODE_IMPORT: check_node(tc, node->import_stmt.module_root, depth + 1); break; case NODE_EXPR_VAR: check_expr_var(tc, node); break; case NODE_EXPR_LITERAL: check_expr_literal(tc, node); break; case NODE_RETURN: tc->func_return_count++; if (node->ret.value) { // A returned expression is used: do not apply the statement-context // rules (notably Rule 17.7, unused return value) to calls inside it. int old_stmt_ctx = tc->is_stmt_context; tc->is_stmt_context = 0; check_node(tc, node->ret.value, depth + 1); tc->is_stmt_context = old_stmt_ctx; } // Check return type compatibility with function if (tc->current_func) { const char *ret_type = tc->current_func->func.ret_type; int func_is_void = !ret_type || strcmp(ret_type, "void") == 0; if (func_is_void && node->ret.value) { tc_error(tc, node->token, "Return with value in void function"); } else if (!func_is_void && !node->ret.value) { char msg[MAX_SHORT_MSG_LEN]; const char *rule = tc->pctx->config->misra_mode ? "MISRA Rule 2.1: " : ""; snprintf(msg, sizeof(msg), "%sReturn without value in function returning '%s'", rule, ret_type); tc_error(tc, node->token, msg); } else if (node->ret.value && tc->current_func->func.ret_type_info) { apply_implicit_struct_pointer_conversions(tc, &node->ret.value, tc->current_func->func.ret_type_info); Type *returned = node->ret.value->type_info; Type *expected = get_inner_type(tc->current_func->func.ret_type_info); // In a method whose `self` is a pointer, `return self;` returns the // value: codegen emits `return *self;` (see codegen_stmt.c). if (returned && expected && returned->kind == TYPE_POINTER && expected->kind != TYPE_POINTER && node->ret.value->kind == NODE_EXPR_VAR && strcmp(node->ret.value->var_ref.name, "self") == 0) { returned = returned->inner; } check_type_compatibility(tc, tc->current_func->func.ret_type_info, returned, node->token, node->ret.value, 0); } } tc->is_unreachable = 1; break; // Control flow with nested nodes. case NODE_IF: { int old_stmt_ctx = tc->is_stmt_context; tc->is_stmt_context = 0; check_node(tc, node->if_stmt.condition, depth + 1); tc->is_stmt_context = old_stmt_ctx; // Validate condition is boolean-compatible if (node->if_stmt.condition && node->if_stmt.condition->type_info) { Type *cond_type = resolve_alias(node->if_stmt.condition->type_info); if (tc->pctx->config->misra_mode) { misra_check_condition_boolean(tc->pctx, node->if_stmt.condition->type_info, node->if_stmt.condition->token); int inv; if (is_expression_invariant(tc, node->if_stmt.condition, &inv)) { misra_check_invariant_condition(tc->pctx, node->if_stmt.condition->token); } } else if (cond_type->kind != TYPE_BOOL && !is_integer_type(cond_type) && cond_type->kind != TYPE_POINTER && cond_type->kind != TYPE_STRING) { const char *hints[] = {"If conditions must be boolean, integer, or pointer", NULL}; tc_error_with_hints(tc, node->if_stmt.condition->token, "Condition must be a truthy type", hints); } } MoveState *initial_state = tc->pctx->move_state; int initial_unreachable = tc->is_unreachable; misra_check_compound_body(tc->pctx, node->if_stmt.then_body, "if"); if (node->if_stmt.else_body) { if (node->if_stmt.else_body->kind == NODE_IF) { misra_check_terminal_else(tc->pctx, node); } else { misra_check_compound_body(tc->pctx, node->if_stmt.else_body, "else"); } } if (initial_state) { tc->pctx->move_state = move_state_clone(initial_state); } check_node(tc, node->if_stmt.then_body, depth + 1); MoveState *after_then = tc->pctx->move_state; int then_unreachable = tc->is_unreachable; MoveState *after_else = NULL; int else_unreachable = initial_unreachable; tc->is_unreachable = initial_unreachable; // Reset for else branch if (node->if_stmt.else_body) { if (initial_state) { tc->pctx->move_state = move_state_clone(initial_state); } check_node(tc, node->if_stmt.else_body, depth + 1); after_else = tc->pctx->move_state; else_unreachable = tc->is_unreachable; } tc->pctx->move_state = initial_state; if (initial_state) { MoveState *merge_a = then_unreachable ? NULL : after_then; MoveState *merge_b = else_unreachable ? NULL : (node->if_stmt.else_body ? after_else : initial_state); // Only merge reachable paths move_state_merge(initial_state, merge_a, merge_b); if (after_then) { move_state_free(after_then); } if (after_else) { move_state_free(after_else); } } tc->is_unreachable = then_unreachable && else_unreachable; break; } case NODE_MATCH: check_node(tc, node->match_stmt.expr, depth + 1); misra_check_match_stmt(tc->pctx, node); // Visit each match case { MoveState *match_initial_state = tc->pctx->move_state; MoveState *merged_state = NULL; int match_initial_unreachable = tc->is_unreachable; int all_unreachable = 1; ASTNode *mcase = node->match_stmt.cases; int has_default = 0; int clause_count = 0; (void)clause_count; while (mcase) { if (mcase->kind == NODE_MATCH_CASE) { if (mcase->match_case.is_default) { has_default = 1; } clause_count++; if (match_initial_state) { tc->pctx->move_state = move_state_clone(match_initial_state); } tc->is_unreachable = match_initial_unreachable; tc_enter_scope(tc); if (mcase->match_case.binding_count > 0) { for (int i = 0; i < mcase->match_case.binding_count; i++) { char *bname = mcase->match_case.binding_names[i]; if (bname) { Type *bt = match_binding_type(tc, node->match_stmt.expr->type_info, mcase->match_case.pattern, i, mcase->match_case.binding_count); if (!bt) { bt = type_new(TYPE_UNSAFE_ANY); } if (mcase->match_case.binding_refs && mcase->match_case.binding_refs[i]) { bt = type_new_ptr(bt); } tc_add_symbol(tc, bname, bt, mcase->token, 0); } } } check_node(tc, mcase->match_case.body, depth + 1); tc_exit_scope(tc); // MISRA Rule 16.3: An unconditional break or return shall terminate every // switch-clause if (tc->pctx->config->misra_mode && !tc->is_unreachable && mcase->match_case.body && mcase->match_case.body->kind == NODE_BLOCK && mcase->match_case.body->block.statements) { tc_error(tc, mcase->token, "MISRA Rule 16.3: match case must end in break or return"); } if (!tc->is_unreachable) { all_unreachable = 0; if (tc->pctx->move_state) { move_state_merge_into(&merged_state, tc->pctx->move_state); } } if (tc->pctx->move_state && tc->pctx->move_state != match_initial_state) { move_state_free(tc->pctx->move_state); } } mcase = mcase->next; } if (!has_default) { all_unreachable = 0; if (match_initial_state) { move_state_merge_into(&merged_state, match_initial_state); } if (!tc->pctx->config->misra_mode) { const char *hints[] = {"Add a default '_' case to handle all possibilities", NULL}; tc_error_with_hints(tc, node->token, "Match may not be exhaustive (no default case)", hints); } misra_check_match_stmt(tc->pctx, node); } if (match_initial_state && merged_state) { tc->pctx->move_state = merged_state; } else if (!merged_state) { tc->pctx->move_state = match_initial_state; } tc->is_unreachable = all_unreachable; } break; case NODE_STRUCT: case NODE_ENUM: case NODE_TYPE_ALIAS: if (node->kind == NODE_STRUCT) { misra_check_reserved_identifier(tc->pctx, node->strct.name, node->token); misra_check_struct_decl(tc->pctx, node); if (node->strct.is_union) { misra_check_union(tc->pctx, node->token); } } else if (node->kind == NODE_ENUM) { misra_check_reserved_identifier(tc->pctx, node->enm.name, node->token); } else if (node->kind == NODE_TYPE_ALIAS) { misra_check_reserved_identifier(tc->pctx, node->type_alias.alias, node->token); } break; case NODE_WHILE: case NODE_FOR: check_loop_passes(tc, node, depth + 1); break; case NODE_EXPR_BINARY: check_expr_binary(tc, node, depth + 1); break; case NODE_EXPR_UNARY: check_expr_unary(tc, node, depth + 1); break; case NODE_EXPR_CALL: check_expr_call(tc, node, depth + 1); break; case NODE_EXPR_INDEX: check_node(tc, node->index.array, depth + 1); check_node(tc, node->index.index, depth + 1); if (node->index.array->type_info) { Type *t = node->index.array->type_info; int is_ptr = 0; if (t->kind == TYPE_POINTER && t->inner && t->inner->kind == TYPE_STRUCT) { t = t->inner; is_ptr = 1; } // Pointers must use direct array indexing (base[i]), not the // __index/__get operator overload: taking the address of the // overload's return value is not an lvalue. This matches the // parser/codegen policy ("Pointers should use array indexing by // default, not operator overload"). if (t->kind == TYPE_STRUCT && t->name && !is_ptr) { size_t tname_len = strlen(t->name); char *mangled_idx = xmalloc(tname_len + sizeof("__index")); snprintf(mangled_idx, tname_len + sizeof("__index"), "%s__index", t->name); char *mangled_get = xmalloc(tname_len + sizeof("__get")); snprintf(mangled_get, tname_len + sizeof("__get"), "%s__get", t->name); FuncSig *sig = find_func(tc->pctx, mangled_idx); char *method_name = NULL; if (sig) { method_name = "index"; } else { sig = find_func(tc->pctx, mangled_get); if (sig) { method_name = "get"; } } if (method_name) { ASTNode *array = node->index.array; ASTNode *idx = node->index.index; node->kind = NODE_EXPR_CALL; memset(&node->call, 0, sizeof(node->call)); ASTNode *callee = ast_create(NODE_EXPR_MEMBER); callee->token = node->token; callee->member.target = array; callee->member.field = xstrdup(method_name); callee->member.is_pointer_access = is_ptr; node->call.callee = callee; node->call.args = idx; check_expr_call(tc, node, depth + 1); zfree(mangled_idx); zfree(mangled_get); break; } zfree(mangled_idx); zfree(mangled_get); } // The overload lookup above looks through a pointer to a struct; // the element type comes from the array/pointer type itself. if (is_ptr) { t = node->index.array->type_info; } if (t->kind == TYPE_ARRAY || t->kind == TYPE_POINTER || t->kind == TYPE_VECTOR) { if (t->kind == TYPE_VECTOR && !t->inner && t->name) { ASTNode *def = find_struct_def(tc->pctx, t->name); if (def && def->kind == NODE_STRUCT && def->strct.fields) { t->inner = def->strct.fields->type_info; } } // Propagate lifetime from array/slice to the indexed element node->type_info = type_clone(t->inner); if (node->type_info && node->index.array->type_info) { node->type_info->lifetime_depth = node->index.array->type_info->lifetime_depth; } } } // Validate index is integer if (node->index.index && node->index.index->type_info) { if (!is_integer_type(node->index.index->type_info)) { const char *hints[] = {"Array indices must be integers", NULL}; tc_error_with_hints(tc, node->index.index->token, "Non-integer array index", hints); } } break; case NODE_EXPR_MEMBER: if (node->member.field && strcmp(node->member.field, "forget") == 0) { // .forget() is a consuming operation: it is valid on a value that // was already moved into a container (e.g. `vec.push(x); x.forget();`), // so the use-after-move report is suppressed for the receiver. tc->is_forget_receiver = 1; check_node(tc, node->member.target, depth + 1); tc->is_forget_receiver = 0; } else { check_node(tc, node->member.target, depth + 1); } if (node->member.target && node->member.target->type_info) { Type *target_type = get_inner_type(node->member.target->type_info); if (target_type->kind == TYPE_STRUCT && target_type->name) { if (tc->pctx->config->misra_mode) { ZenSymbol *struct_sym = symbol_lookup_kind(tc->pctx->global_scope, target_type->name, SYM_STRUCT); if (struct_sym) { struct_sym->is_dereferenced = 1; } } ASTNode *struct_def = find_struct_def(tc->pctx, target_type->name); if (struct_def) { ASTNode *field = struct_def->strct.fields; while (field) { if (field->kind == NODE_FIELD && field->field.name && strcmp(field->field.name, node->member.field) == 0) { // Propagate lifetime from struct container to the member access result node->type_info = type_clone(field->type_info); if (node->type_info && node->member.target->type_info) { // Depth must be at least that of the container. // (If field itself is static/global, it will be 0, but container's // depth will override) node->type_info->lifetime_depth = node->member.target->type_info->lifetime_depth; } break; } field = field->next; } } } if (!node->type_info) { int is_ptr = 0; char *alloc_name = NULL; char *struct_name = resolve_struct_name_from_type(tc->pctx, target_type, &is_ptr, &alloc_name); if (struct_name) { char buf[MAX_ERROR_MSG_LEN]; snprintf(buf, sizeof(buf), "%s__%s", struct_name, node->member.field); char *mangled = merge_underscores(buf); FuncSig *sig = find_func(tc->pctx, mangled); if (sig) { node->type_info = sig->ret_type; } zfree(mangled); } if (alloc_name) { zfree(alloc_name); } } } if (!node->type_info) { // Fallback for failed lookups node->type_info = type_new(TYPE_UNKNOWN); } if (!tc->is_assign_lhs) { check_use_validity(tc, node); } break; case NODE_DEFER: // Check the deferred statement check_node(tc, node->defer_stmt.stmt, depth + 1); break; case NODE_EXPECT: case NODE_ASSERT: // Check assert/expect condition { int old_stmt_ctx = tc->is_stmt_context; tc->is_stmt_context = 0; check_node(tc, node->assert_stmt.condition, depth + 1); tc->is_stmt_context = old_stmt_ctx; } if (node->assert_stmt.condition && node->assert_stmt.condition->type_info) { Type *cond_type = resolve_alias(node->assert_stmt.condition->type_info); if (tc->pctx->config->misra_mode) { misra_check_condition_boolean(tc->pctx, node->assert_stmt.condition->type_info, node->assert_stmt.condition->token); } else if (cond_type->kind != TYPE_BOOL && !is_integer_type(cond_type) && cond_type->kind != TYPE_POINTER && cond_type->kind != TYPE_STRING) { const char *hints[] = { "Assert/expect conditions must be boolean, integer, or pointer", NULL}; tc_error_with_hints(tc, node->assert_stmt.condition->token, "Assert/expect condition must be a truthy type", hints); } } break; case NODE_TEST: { MoveState *prev_move_state = tc->pctx->move_state; tc->pctx->move_state = move_state_create(NULL); check_node(tc, node->test_stmt.body, depth + 1); move_state_free(tc->pctx->move_state); tc->pctx->move_state = prev_move_state; break; } case NODE_EXPR_CAST: // Check the expression being cast check_node(tc, node->cast.expr, depth + 1); // Could add cast safety checks here (e.g., narrowing, pointer-to-int) if (node->cast.expr && node->cast.expr->type_info && node->cast.target_type) { Type *source_type = resolve_alias(node->cast.expr->type_info); Type *target_type = type_from_string_helper(tc->pctx, node->cast.target_type); if (tc->pctx->config->misra_mode && target_type) { misra_check_cast(tc->pctx, target_type, source_type, node->token, is_composite_expression(node->cast.expr)); misra_check_pointer_conversion(tc->pctx, target_type, source_type, node->token); misra_check_void_ptr_cast(tc->pctx, target_type, source_type, node->token); if (target_type->kind == TYPE_POINTER) { misra_check_null_pointer_constant(tc->pctx, node, node->token); } } // Warn on pointer-to-integer casts (potential data loss) if (source_type->kind == TYPE_POINTER) { const char *target = node->cast.target_type; if (strcmp(target, "i8") == 0 || strcmp(target, "i16") == 0 || strcmp(target, "u8") == 0 || strcmp(target, "u16") == 0) { const char *hints[] = {"Pointer-to-small-integer casts may lose address bits", NULL}; tc_error_with_hints(tc, node->token, "Potentially unsafe pointer cast", hints); } } node->type_info = target_type; mark_type_as_used(tc, target_type); } break; case NODE_EXPR_ARRAY_LITERAL: { misra_check_initializer_side_effects(tc->pctx, node); ASTNode *elem = node->array_literal.elements; Type *elem_type = NULL; int count = 0; while (elem) { check_node(tc, elem, depth + 1); if (!elem_type && elem->type_info && elem->type_info->kind != TYPE_UNKNOWN) { elem_type = elem->type_info; } count++; elem = elem->next; } if (elem_type) { node->type_info = type_new_array(elem_type, count); } else { node->type_info = type_new_array(type_new(TYPE_UNKNOWN), count); } } break; case NODE_EXPR_TUPLE_LITERAL: { misra_check_initializer_side_effects(tc->pctx, node); ASTNode *elem = node->tuple_literal.elements; while (elem) { check_node(tc, elem, depth + 1); elem = elem->next; } } break; case NODE_EXPR_STRUCT_INIT: misra_check_initializer_side_effects(tc->pctx, node); check_struct_init(tc, node, depth + 1); break; case NODE_LOOP: check_loop_passes(tc, node, depth + 1); break; case NODE_TERNARY: check_node(tc, node->ternary.cond, depth + 1); check_node(tc, node->ternary.true_expr, depth + 1); check_node(tc, node->ternary.false_expr, depth + 1); // Validate condition if (node->ternary.cond && node->ternary.cond->type_info) { Type *t = node->ternary.cond->type_info; if (tc->pctx->config->misra_mode) { misra_check_condition_boolean(tc->pctx, node->ternary.cond->type_info, node->ternary.cond->token); int inv; if (is_expression_invariant(tc, node->ternary.cond, &inv)) { misra_check_invariant_condition(tc->pctx, node->ternary.cond->token); } } else if (t->kind != TYPE_BOOL && !is_integer_type(t) && t->kind != TYPE_POINTER) { tc_error(tc, node->ternary.cond->token, "Ternary condition must be truthy"); } } // Validate branch compatibility if (node->ternary.true_expr && node->ternary.false_expr) { Type *t1 = node->ternary.true_expr->type_info; Type *t2 = node->ternary.false_expr->type_info; if (t1 && t2) { // Loose compatibility check if (!check_type_compatibility(tc, t1, t2, node->token, NULL, 0)) { // Error reported by check_type_compatibility } else { node->type_info = t1; // Inherit type } } } break; case NODE_ASM: for (int i = 0; i < node->asm_stmt.output_count; i++) { ZenSymbol *sym = tc_lookup(tc, node->asm_stmt.outputs[i]); if (!sym) { char msg[MAX_SHORT_MSG_LEN]; if (tc->pctx->config->misra_mode) { snprintf(msg, sizeof(msg), "Undefined output variable in inline assembly: '%s' (MISRA Rule 17.3)", node->asm_stmt.outputs[i]); } else { snprintf(msg, sizeof(msg), "Undefined output variable in inline assembly: '%s'", node->asm_stmt.outputs[i]); } tc_error(tc, node->token, msg); } else if (sym->type_info) { int width = get_asm_register_size(sym->type_info); if (width > node->asm_stmt.register_size) { node->asm_stmt.register_size = width; } } } for (int i = 0; i < node->asm_stmt.input_count; i++) { ZenSymbol *sym = tc_lookup(tc, node->asm_stmt.inputs[i]); if (!sym) { char msg[MAX_SHORT_MSG_LEN]; if (tc->pctx->config->misra_mode) { snprintf(msg, sizeof(msg), "Undefined input variable in inline assembly: '%s' (MISRA Rule 17.3)", node->asm_stmt.inputs[i]); } else { snprintf(msg, sizeof(msg), "Undefined input variable in inline assembly: '%s'", node->asm_stmt.inputs[i]); } tc_error(tc, node->token, msg); } else if (sym->type_info) { int width = get_asm_register_size(sym->type_info); if (width > node->asm_stmt.register_size) { node->asm_stmt.register_size = width; } } } if (node->asm_stmt.register_size > 64) { char msg[MAX_SHORT_MSG_LEN]; snprintf(msg, sizeof(msg), "Unsupported register size is required in inline assembly: %i bits", node->asm_stmt.register_size); tc_error(tc, node->token, msg); } break; case NODE_LAMBDA: check_expr_lambda(tc, node, depth + 1); break; case NODE_EXPR_SIZEOF: if (node->size_of.expr) { check_node(tc, node->size_of.expr, depth + 1); if (tc->pctx->config->misra_mode && tc_expr_has_side_effects(node->size_of.expr)) { misra_check_side_effects_sizeof(tc->pctx, node->size_of.expr); } } node->type_info = type_new(TYPE_I32); break; case NODE_FOR_RANGE: check_loop_passes(tc, node, depth + 1); break; case NODE_EXPR_SLICE: // Check slice target and indices check_node(tc, node->slice.array, depth + 1); check_node(tc, node->slice.start, depth + 1); check_node(tc, node->slice.end, depth + 1); break; case NODE_DESTRUCT_VAR: if (node->destruct.init_expr) { check_node(tc, node->destruct.init_expr, depth + 1); } break; case NODE_BREAK: if (tc->loop_break_count > 0) { misra_check_iteration_termination(tc->pctx, node->token); } tc->loop_break_count++; if (tc->move_checks_only) { // No-op } else if (tc->pctx->move_state) { move_state_merge_into(&tc->loop_break_state, tc->pctx->move_state); } tc->is_unreachable = 1; break; case NODE_GOTO: if (tc->pctx->config->misra_mode) { ZenSymbol *lbl = tc_lookup(tc, node->goto_stmt.label_name); if (lbl && lbl->decl_token.line != 0) { misra_check_goto_constraint(tc->pctx, node->token, lbl->decl_token); } } misra_check_goto(tc->pctx, node->token); tc->is_unreachable = 1; break; case NODE_CONTINUE: if (tc->pctx->move_state) { move_state_merge_into(&tc->loop_continue_state, tc->pctx->move_state); } tc->is_unreachable = 1; break; case NODE_VA_START: case NODE_VA_END: case NODE_VA_COPY: case NODE_VA_ARG: misra_check_stdarg(tc->pctx, node->token); break; case NODE_RAW_STMT: misra_check_raw_block(tc->pctx, node->token); if (node->raw_stmt.interp_expr_count > 0) { // The `{...}` expressions of an f-string were parsed and typed when the // string was; check them only for moves (use after move, consumed values). int prev_move_checks_only = tc->move_checks_only; tc->move_checks_only = 1; ASTNode **expr = node->raw_stmt.interp_exprs; ASTNode **end = expr + node->raw_stmt.interp_expr_count; for (; expr < end; expr++) { check_node(tc, *expr, depth + 1); } tc->move_checks_only = prev_move_checks_only; } break; case NODE_PREPROC_DIRECTIVE: // Rule Zen 1.4 is already handled by parser_audit_preprocessor break; case NODE_PLUGIN: misra_check_plugin_block(tc->pctx, node->token); break; case NODE_LABEL: if (tc->pctx->config->misra_mode) { ZenSymbol *lbl = symbol_add(tc->pctx->current_scope, node->label_stmt.label_name, SYM_LABEL); if (lbl) { lbl->decl_token = node->token; } } break; case NODE_COMPTIME: { // Register comptime builtins for the body register_comptime_builtins(tc->pctx); // Type-check the comptime body ASTNode *stmt = node->comptime.body; while (stmt) { check_node(tc, stmt, depth + 1); stmt = stmt->next; } // Interpret the comptime body char *output = interpret_comptime(tc->pctx, node->comptime.body, tc->pctx->current_filename); if (!output) { break; } // Parse generated source code if (output[0]) { if (node->comptime.in_block) { // Inside a function the output is statements, not declarations. size_t len = strlen(output); char *wrapped = xmalloc(len + 5); snprintf(wrapped, len + 5, "{ %s }", output); Lexer out_l; lexer_init(&out_l, wrapped, tc->pctx->config, tc->pctx->current_filename); ASTNode *block = parse_block(tc->pctx, &out_l); node->comptime.generated = block ? block->block.statements : NULL; } else { Lexer out_l; lexer_init(&out_l, output, tc->pctx->config, tc->pctx->current_filename); node->comptime.generated = parse_program_nodes(tc->pctx, &out_l); } // Type-check generated nodes ASTNode *gen = node->comptime.generated; while (gen) { check_node(tc, gen, depth + 1); gen = gen->next; } } zfree(output); break; } default: // Generic recursion for lists and other nodes. // Special case for Return to trigger move? if (node->kind == NODE_RETURN && node->ret.value) { // If returning a value, check if it can be moved. check_move_for_rvalue(tc, node->ret.value); } break; } RECURSION_EXIT(tc->pctx); } static void infer_node_lifetime(TypeChecker *tc, ASTNode *node) { if (!node || node->kind != NODE_FUNCTION) { return; } FuncSig *fsig = find_func(tc->pctx, node->func.name); if (!fsig) { return; } // Default to local argument scope (depth 1) int inferred_depth = 1; int ptr_param_count = 0; int self_depth = -1; int elide_idx = -1; for (int i = 0; i < fsig->total_args; i++) { Type *t = (fsig->arg_types && fsig->arg_types[i]) ? fsig->arg_types[i] : NULL; if (t && t->kind == TYPE_POINTER) { ptr_param_count++; // Parameters are always at least depth 1 (argument scope) if (t->lifetime_depth == 0) { t->lifetime_depth = 1; } if (node->func.param_names && node->func.param_names[i] && strcmp(node->func.param_names[i], "self") == 0) { self_depth = t->lifetime_depth; elide_idx = i; } } } if (self_depth != -1) { inferred_depth = self_depth; } else if (ptr_param_count == 1) { for (int i = 0; i < fsig->total_args; i++) { Type *t = (fsig->arg_types && fsig->arg_types[i]) ? fsig->arg_types[i] : NULL; if (t && t->kind == TYPE_POINTER) { inferred_depth = t->lifetime_depth; elide_idx = i; break; } } } node->func.elide_from_idx = elide_idx; fsig->elide_from_idx = elide_idx; // Update the return type depth in the signature if (fsig->ret_type && fsig->ret_type->kind == TYPE_POINTER) { fsig->ret_type->lifetime_depth = inferred_depth; } // Also update AST node if types are already resolved there if (node->func.ret_type_info && node->func.ret_type_info->kind == TYPE_POINTER) { node->func.ret_type_info->lifetime_depth = inferred_depth; } } static void check_program_prepass(TypeChecker *tc, ASTNode *root, int depth) { if (!root || root->kind != NODE_ROOT) { return; } RECURSION_GUARD_TOKEN(tc->pctx, root->token, ); if (depth > 64) { RECURSION_EXIT(tc->pctx); return; } ASTNode *n = root->root.children; while (n) { if (n->kind == NODE_ROOT) { check_program_prepass(tc, n, depth + 1); } else if (n->kind == NODE_FUNCTION) { infer_node_lifetime(tc, n); } else if (n->kind == NODE_IMPL) { ASTNode *method = n->impl.methods; while (method) { if (method->kind == NODE_FUNCTION) { infer_node_lifetime(tc, method); } method = method->next; } } else if (n->kind == NODE_IMPL_TRAIT) { ASTNode *method = n->impl_trait.methods; while (method) { if (method->kind == NODE_FUNCTION) { infer_node_lifetime(tc, method); } method = method->next; } } else if (n->kind == NODE_IMPORT) { // Imports are conceptually ROOTs of their own module check_program_prepass(tc, n->import_stmt.module_root, depth + 1); } n = n->next; } RECURSION_EXIT(tc->pctx); } // ** Entry Point ** int check_program(ParserContext *ctx, ASTNode *root) { TypeChecker tc = {0}; tc.pctx = ctx; ctx->current_scope = ctx->global_scope; if (!ctx->move_state) { ctx->move_state = move_state_create(NULL); } check_program_prepass(&tc, root, 0); check_node(&tc, root, 0); root->is_checked = 1; // Fixed-point iteration to handle secondary instantiations int changed = 1; while (changed) { changed = 0; ASTNode *inst_func = ctx->instantiated_funcs; while (inst_func) { if (!inst_func->is_checked) { check_node(&tc, inst_func, 0); inst_func->is_checked = 1; changed = 1; // Restart from head to catch newly added instantiations that might be before us break; } inst_func = inst_func->next; } } if (ctx->move_state) { move_state_free(ctx->move_state); ctx->move_state = NULL; } if (tc.pctx->config->misra_mode) { misra_audit_unused_symbols(tc.pctx); misra_audit_block_scope(tc.pctx); misra_audit_identifier_uniqueness(tc.pctx); } if (ctx->compiler->error_count > 0) { fprintf(stderr, COLOR_BOLD COLOR_RED " error" COLOR_RESET ": semantic analysis found %d error%s\n", ctx->compiler->error_count, ctx->compiler->error_count == 1 ? "" : "s"); return 1; } return 0; } int check_moves_only(ParserContext *ctx, ASTNode *root) { TypeChecker tc = {0}; tc.pctx = ctx; tc.move_checks_only = 1; ctx->current_scope = ctx->global_scope; if (!ctx->move_state) { ctx->move_state = move_state_create(NULL); } check_node(&tc, root, 0); if (ctx->move_state) { move_state_free(ctx->move_state); ctx->move_state = NULL; } return ctx->compiler->error_count; }