src/analysis/typecheck_expr.c
Rama main · réplica de lectura del repositorio original.
// SPDX-License-Identifier: MIT
#include "typecheck_internal.h"
#include "../utils/utils.h"
void check_move_for_rvalue(TypeChecker *tc, ASTNode *rvalue)
{
if (!rvalue || !rvalue->type_info)
{
return;
}
if (is_type_copy(tc->pctx, rvalue->type_info))
{
return;
}
if (rvalue->kind == NODE_EXPR_VAR)
{
ZenSymbol *sym = tc_lookup(tc, rvalue->var_ref.name);
if (sym)
{
mark_symbol_moved(tc->pctx, sym, rvalue);
}
}
else if (rvalue->kind == NODE_EXPR_UNARY && strcmp(rvalue->unary.op, "*") == 0)
{
const char *hints[] = {"This type owns resources and cannot be implicitly copied",
"Consider borrowing value via references or implementing Copy",
NULL};
tc_move_error_with_hints(tc, rvalue->token, "Cannot move out of a borrowed reference",
hints);
}
else if (rvalue->kind == NODE_EXPR_MEMBER)
{
// Now allowed, but will be tracked by path
mark_symbol_moved(tc->pctx, NULL, rvalue);
}
else if (rvalue->kind == NODE_EXPR_INDEX)
{
const char *hints[] = {"Cannot move an element out of an array or slice.", NULL};
tc_move_error_with_hints(tc, rvalue->token, "Cannot move out of an index expression",
hints);
}
}
// Follows `alias A = B;` by name until it reaches a name that is not an alias
// (the parser stores the target already mangled: `Handle<K>` -> `Handle__K`).
// Opaque aliases stop the chain: they must stay distinct types.
static const char *resolve_alias_name(TypeChecker *tc, const char *name)
{
for (int depth = 0; name && depth < 16; depth++)
{
TypeAlias *ta = find_type_alias_node(tc->pctx, name);
if (ta && ta->is_opaque)
{
return name;
}
const char *next = find_type_alias(tc->pctx, name);
if (!next || strcmp(next, name) == 0)
{
return name;
}
name = next;
}
return name;
}
// Canonical spelling of a type for alias-insensitive comparison: aliases
// resolved by name, pointers spelled outside (`alias P = T*;` makes `P*` and
// `T**` the same). Returns an allocated string, or NULL when there is nothing
// to resolve.
static char *canonical_type_name(TypeChecker *tc, Type *t, int depth)
{
if (!t || depth > 16)
{
return NULL;
}
t = resolve_alias(t);
if (t->kind == TYPE_POINTER)
{
char *inner = canonical_type_name(tc, t->inner, depth + 1);
if (!inner)
{
return NULL;
}
size_t len = strlen(inner);
char *out = xmalloc(len + 2);
memcpy(out, inner, len);
out[len] = '*';
out[len + 1] = 0;
zfree(inner);
return out;
}
if (!t->name)
{
return NULL;
}
const char *name = resolve_alias_name(tc, t->name);
// Template instantiation spells a pointer argument as `<Name>Ptr`
// (`Vec<DirEntry*>` substitutes T by `DirEntryPtr`): read it back as
// `<Name>*` when `<Name>Ptr` is not a type of its own.
size_t name_len = strlen(name);
if (name_len > 3 && strcmp(name + name_len - 3, "Ptr") == 0 &&
!find_struct_def(tc->pctx, name) && !find_type_alias(tc->pctx, name))
{
char *base_name = xstrdup(name);
base_name[name_len - 3] = 0;
Type base = {0};
base.kind = TYPE_STRUCT;
base.name = base_name;
char *inner = canonical_type_name(tc, &base, depth + 1);
zfree(base_name);
if (inner)
{
size_t len = strlen(inner);
char *out = xmalloc(len + 2);
memcpy(out, inner, len);
out[len] = '*';
out[len + 1] = 0;
zfree(inner);
return out;
}
}
// Drop spaces so "DirEntry *" and "DirEntry*" compare equal.
char *out = xmalloc(strlen(name) + 1);
char *w = out;
for (const char *r = name; *r; r++)
{
if (*r != ' ')
{
*w++ = *r;
}
}
*w = 0;
return out;
}
Type *resolve_alias(Type *t)
{
while (t && t->kind == TYPE_ALIAS && t->inner)
{
t = t->inner;
}
return t;
}
void check_expr_unary(TypeChecker *tc, ASTNode *node, int depth)
{
check_node(tc, node->unary.operand, depth + 1);
Type *operand_type = node->unary.operand->type_info;
const char *op = node->unary.op;
if (!operand_type)
{
return;
}
// Logical NOT: !
if (strcmp(op, "!") == 0)
{
node->type_info = type_new(TYPE_BOOL);
return;
}
// Numeric negation: -
if (strcmp(op, "-") == 0)
{
if (!is_integer_type(operand_type) && !is_float_type(operand_type))
{
const char *hints[] = {"Negation requires a numeric operand", NULL};
tc_error_with_hints(tc, node->token, "Cannot negate non-numeric type", hints);
}
else
{
node->type_info = operand_type;
}
return;
}
// Dereference: *
if (strcmp(op, "*") == 0)
{
if (operand_type->kind == TYPE_UNKNOWN)
{
node->type_info = type_new(TYPE_UNKNOWN);
return;
}
Type *resolved = resolve_alias(operand_type);
if (resolved->kind != TYPE_POINTER && resolved->kind != TYPE_STRING)
{
const char *hints[] = {"Only pointers can be dereferenced", NULL};
tc_error_with_hints(tc, node->token, "Cannot dereference non-pointer type", hints);
}
else if (resolved->kind == TYPE_STRING)
{
node->type_info = type_new(TYPE_CHAR);
}
else if (resolved->inner)
{
node->type_info = resolved->inner;
CompilerConfig *cfg = &tc->pctx->compiler->config;
if (cfg->misra_mode)
{
misra_check_file_dereference(tc->pctx, operand_type, node->token);
}
}
return;
}
// Bitwise NOT: ~
if (strcmp(op, "~") == 0)
{
if (!is_integer_type(operand_type))
{
const char *hints[] = {"Bitwise NOT requires an integer operand", NULL};
tc_error_with_hints(tc, node->token, "Cannot apply ~ to non-integer type", hints);
}
else
{
misra_check_bitwise_operand(tc->pctx, node->unary.operand->type_info, node->token);
node->type_info = operand_type;
}
return;
}
if (strcmp(op, "++") == 0 || strcmp(op, "--") == 0 || strcmp(op, "_post++") == 0 ||
strcmp(op, "_post--") == 0)
{
misra_check_inc_dec_result_used(tc->pctx, node->token);
// Track as a write
if (node->unary.operand->kind == NODE_EXPR_VAR)
{
ZenSymbol *s = tc_lookup(tc, node->unary.operand->var_ref.name);
if (s)
{
s->is_written_to = 1;
}
}
else if (node->unary.operand->kind == NODE_EXPR_UNARY &&
strcmp(node->unary.operand->unary.op, "*") == 0)
{
ASTNode *inner = node->unary.operand->unary.operand;
if (inner->kind == NODE_EXPR_VAR)
{
ZenSymbol *s = tc_lookup(tc, inner->var_ref.name);
if (s)
{
s->is_written_to = 1;
}
}
}
if (!is_integer_type(operand_type) && !is_float_type(operand_type) &&
operand_type->kind != TYPE_POINTER)
{
tc_error(tc, node->token, "Increment/decrement requires a numeric or pointer operand");
}
node->type_info = operand_type;
return;
}
// Address-of: &
if (strcmp(op, "&") == 0 || strcmp(op, "&_rval") == 0)
{
node->type_info = type_new_ptr(operand_type);
// Record provenance depth for escape analysis
if (node->unary.operand->kind == NODE_EXPR_VAR)
{
ZenSymbol *sym = tc_lookup(tc, node->unary.operand->var_ref.name);
if (sym)
{
node->type_info->lifetime_depth = sym->scope_depth;
}
}
else if (strcmp(op, "&_rval") == 0)
{
// R-values are temporaries, their life is the current code block
if (node->unary.operand->type_info)
{
node->type_info->lifetime_depth = node->unary.operand->type_info->lifetime_depth;
}
else
{
node->type_info->lifetime_depth = tc->current_depth;
}
}
else
{
// For complex targets (e.g. &array[0]), inherit from the operand if it has a lifetime
if (operand_type)
{
node->type_info->lifetime_depth = operand_type->lifetime_depth;
}
}
return;
}
}
void check_expr_binary(TypeChecker *tc, ASTNode *node, int depth)
{
const char *op = node->binary.op;
Type *contextual_type = node->type_info;
if (strcmp(op, "=") == 0 ||
(strlen(op) > 1 && op[strlen(op) - 1] == '=' && strcmp(op, "==") != 0 &&
strcmp(op, "!=") != 0 && strcmp(op, "<=") != 0 && strcmp(op, ">=") != 0))
{
if (!tc->is_stmt_context)
{
misra_check_assignment_result_used(tc->pctx, node->token);
}
int old_is_assign_lhs = tc->is_assign_lhs;
int old_stmt_ctx = tc->is_stmt_context;
tc->is_assign_lhs = 1;
tc->is_stmt_context = 0;
check_node(tc, node->binary.left, depth + 1);
tc->is_assign_lhs = old_is_assign_lhs;
if (node->binary.left->type_info && node->binary.right->kind == NODE_LAMBDA)
{
node->binary.right->type_info = node->binary.left->type_info;
}
check_node(tc, node->binary.right, depth + 1);
tc->is_stmt_context = old_stmt_ctx;
// Rule 13.2: Side effect collision detection for assignments
if (tc->pctx->config->misra_mode)
{
SymbolSet l_reads = {0}, l_writes = {0};
SymbolSet r_reads = {0}, r_writes = {0};
collect_symbols(node->binary.left, &l_reads, &l_writes);
collect_symbols(node->binary.right, &r_reads, &r_writes);
// Treat LHS of assignment as a write
if (node->binary.left->kind == NODE_EXPR_VAR)
{
int already_in = 0;
for (int k = 0; k < l_writes.count; k++)
{
if (l_writes.syms[k] == node->binary.left->var_ref.symbol)
{
already_in = 1;
break;
}
}
if (!already_in && l_writes.count < 32)
{
l_writes.syms[l_writes.count++] = node->binary.left->var_ref.symbol;
}
}
// Double write collision: i = i++
int match = 0;
for (int i = 0; i < l_writes.count; i++)
{
ZenSymbol *s = l_writes.syms[i];
if (!s)
{
continue;
}
for (int j = 0; j < r_writes.count; j++)
{
if (s == r_writes.syms[j])
{
tc_error(tc, node->token,
"MISRA Rule 13.2: symbol modified multiple times in assignment");
match = 1;
break;
}
}
if (match)
{
break;
}
// Also check if LHS write conflicts with RHS reads (if RHS also writes it)
// e.g. i = i++ is already covered by double write.
// But what about i = i + (i++)?
// r_writes={i}, r_reads={i, i}.
}
}
}
else
{
int old_stmt_ctx = tc->is_stmt_context;
tc->is_stmt_context = 0;
check_node(tc, node->binary.left, depth + 1);
check_node(tc, node->binary.right, depth + 1);
tc->is_stmt_context = old_stmt_ctx;
// Rule 13.2: Side effect collision detection for binary operators
if (strcmp(op, "&&") != 0 && strcmp(op, "||") != 0 && strcmp(op, ",") != 0)
{
check_side_effect_collision(tc, node->binary.left, node->binary.right, node->token);
}
else if (tc->pctx->config->misra_mode && (strcmp(op, "&&") == 0 || strcmp(op, "||") == 0))
{
// Rule 13.5: The RHS of && or || shall not contain persistent side effects
SymbolSet r_reads = {0}, r_writes = {0};
collect_symbols(node->binary.right, &r_reads, &r_writes);
if (r_writes.count > 0)
{
tc_error(tc, node->binary.right->token,
"MISRA Rule 13.5: persistent side effect in logical RHS");
}
}
}
// Rule 19.1: Check for self-assignment (x = x)
if (tc->pctx->config->misra_mode && strcmp(op, "=") == 0)
{
misra_check_assignment_overlap(tc->pctx, node->binary.left, node->binary.right,
node->token);
}
// Malformed input can produce binary expressions with NULL children
if (!node->binary.left || !node->binary.right)
{
return;
}
Type *left_type = node->binary.left->type_info;
Type *right_type = node->binary.right->type_info;
// Assignment Logic for Moves (and type compatibility)
if (strcmp(op, "=") == 0)
{
// Check type compatibility for assignment
if (left_type && right_type)
{
apply_implicit_struct_pointer_conversions(tc, &node->binary.right, left_type);
right_type = node->binary.right->type_info;
check_type_compatibility(tc, left_type, right_type, node->binary.right->token,
node->binary.right, 0);
// Mark LHS as written to
if (node->binary.left->kind == NODE_EXPR_VAR)
{
ZenSymbol *s = tc_lookup(tc, node->binary.left->var_ref.name);
if (s)
{
s->is_written_to = 1;
}
}
else if (node->binary.left->kind == NODE_EXPR_UNARY &&
strcmp(node->binary.left->unary.op, "*") == 0)
{
ASTNode *inner = node->binary.left->unary.operand;
if (inner->kind == NODE_EXPR_VAR)
{
ZenSymbol *s = tc_lookup(tc, inner->var_ref.name);
if (s)
{
s->is_written_to = 1;
}
}
}
// Also handle array indexing as write
else if (node->binary.left->kind == NODE_EXPR_INDEX)
{
ASTNode *arr = node->binary.left->index.array;
if (arr->kind == NODE_EXPR_VAR)
{
ZenSymbol *s = tc_lookup(tc, arr->var_ref.name);
if (s)
{
s->is_written_to = 1;
}
}
}
// Also handle member access as write
else if (node->binary.left->kind == NODE_EXPR_MEMBER)
{
ASTNode *target = node->binary.left->member.target;
// Follow the member chain to the base variable
while (target && target->kind == NODE_EXPR_MEMBER)
{
target = target->member.target;
}
if (target && target->kind == NODE_EXPR_VAR)
{
ZenSymbol *s = tc_lookup(tc, target->var_ref.name);
if (s)
{
s->is_written_to = 1;
}
}
}
}
// If RHS is moving a non-copy value, check validity and mark moved
check_move_for_rvalue(tc, node->binary.right);
// LHS is being (re-)initialized, so it becomes Valid.
if (node->binary.left->kind == NODE_EXPR_VAR)
{
ZenSymbol *lhs_sym = tc_lookup(tc, node->binary.left->var_ref.name);
if (lhs_sym)
{
if (tc->pctx->config->misra_mode && node->binary.left &&
node->binary.left->kind == NODE_EXPR_VAR)
{
misra_check_param_modified(tc->current_func, node->binary.left,
node->binary.left->token);
}
if (lhs_sym->is_immutable)
{
tc_error(tc, node->binary.left->token, "Cannot assign to immutable variable");
}
if (tc->pctx->config->misra_mode)
{
misra_check_pointer_conversion(tc->pctx, left_type, right_type, node->token);
}
mark_symbol_valid(tc->pctx, lhs_sym, node->binary.left);
}
}
// Result type is same as LHS
node->type_info = left_type;
return;
}
// Arithmetic operators: +, -, *, /, %
if (strcmp(op, "+") == 0 || strcmp(op, "-") == 0 || strcmp(op, "*") == 0 ||
strcmp(op, "/") == 0 || strcmp(op, "%") == 0)
{
// Division by zero detection for / and %
if ((strcmp(op, "/") == 0 || strcmp(op, "%") == 0) && node->binary.right &&
node->binary.right->kind == NODE_EXPR_LITERAL)
{
LiteralKind kind = node->binary.right->literal.kind;
if (kind == LITERAL_INT && node->binary.right->literal.int_val == 0)
{
const char *hints[] = {"Division by zero is undefined behavior", NULL};
tc_error_with_hints(tc, node->binary.right->token, "Division by zero detected",
hints);
}
else if (kind == LITERAL_FLOAT && node->binary.right->literal.float_val >= -0.0 &&
node->binary.right->literal.float_val <= 0.0)
{
const char *hints[] = {"Division by zero results in infinity or NaN", NULL};
tc_error_with_hints(tc, node->binary.right->token, "Division by zero detected",
hints);
}
}
if (left_type && right_type)
{
Type *lhs_resolved = resolve_alias(left_type);
Type *rhs_resolved = resolve_alias(right_type);
// Pointer Arithmetic
if (lhs_resolved->kind == TYPE_POINTER || lhs_resolved->kind == TYPE_STRING)
{
misra_check_pointer_arithmetic(tc->pctx, lhs_resolved, node->token);
// Ptr - Ptr -> isize
if (strcmp(op, "-") == 0 &&
(rhs_resolved->kind == TYPE_POINTER || rhs_resolved->kind == TYPE_STRING))
{
node->type_info = type_new(TYPE_ISIZE);
return;
}
// Ptr + Int -> Ptr
// Ptr - Int -> Ptr
if ((strcmp(op, "+") == 0 || strcmp(op, "-") == 0) && is_integer_type(rhs_resolved))
{
node->type_info = left_type;
return;
}
}
// Int + Ptr -> Ptr
if (strcmp(op, "+") == 0 && is_integer_type(lhs_resolved) &&
(rhs_resolved->kind == TYPE_POINTER || rhs_resolved->kind == TYPE_STRING))
{
misra_check_pointer_arithmetic(tc->pctx, rhs_resolved, node->token);
node->type_info = right_type;
return;
}
int left_numeric = is_integer_type(left_type) || is_float_type(left_type) ||
left_type->kind == TYPE_VECTOR;
int right_numeric = is_integer_type(right_type) || is_float_type(right_type) ||
right_type->kind == TYPE_VECTOR;
if (!left_numeric || !right_numeric)
{
if (left_type->kind == TYPE_UNKNOWN || right_type->kind == TYPE_UNKNOWN)
{
node->type_info = type_new(TYPE_UNKNOWN);
return;
}
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg), "Operator '%s' requires numeric operands", op);
const char *hints[] = {
"Arithmetic operators can only be used with integer, float, or vector types",
NULL};
tc_error_with_hints(tc, node->token, msg, hints);
}
else if (left_type->kind == TYPE_VECTOR || right_type->kind == TYPE_VECTOR)
{
if (left_type->kind != right_type->kind || !type_eq(left_type, right_type))
{
tc_error(tc, node->token,
"Vector operation requires operands of same vector type");
}
node->type_info = left_type;
return;
}
else
{
// Rule 10.4: Balancing
misra_check_binary_op_essential_types(tc->pctx, node->binary.left,
node->binary.right, node->token);
// MISRA Rule 12.4: Evaluation of constant expressions shall not lead to unsigned
// wrap Use the contextually pushed down type (stored in node->type_info before this
// function) or fall back to the inferred operand type.
Type *target_type = contextual_type ? contextual_type : left_type;
if (tc->pctx->config->misra_mode && target_type && is_integer_type(target_type))
{
long long lval, rval;
if (eval_const_int_expr(node->binary.left, tc->pctx, &lval) &&
eval_const_int_expr(node->binary.right, tc->pctx, &rval))
{
long long res = 0;
if (strcmp(op, "+") == 0)
{
res = lval + rval;
}
else if (strcmp(op, "-") == 0)
{
res = lval - rval;
}
else if (strcmp(op, "*") == 0)
{
res = lval * rval;
}
if (strcmp(op, "+") == 0 || strcmp(op, "-") == 0 || strcmp(op, "*") == 0)
{
misra_check_unsigned_wrap(tc->pctx, op, lval, rval, res, target_type,
node->token);
}
}
}
// Rule 10.2: Character arithmetic
misra_check_char_arithmetic(tc->pctx, left_type, right_type, op, node->token);
// Result type: Only infer if not already set by context
if (!node->type_info)
{
if (is_float_type(left_type) || is_float_type(right_type))
{
node->type_info = type_new(TYPE_F64);
}
else
{
node->type_info = left_type;
}
}
}
}
return;
}
// Comparison operators: ==, !=, <, >, <=, >=
if (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0 || strcmp(op, "<") == 0 ||
strcmp(op, ">") == 0 || strcmp(op, "<=") == 0 || strcmp(op, ">=") == 0)
{
// Result is always bool
node->type_info = type_new(TYPE_BOOL);
// Operands should be comparable
if (left_type && right_type)
{
// Rule 10.4: Balancing
misra_check_binary_op_essential_types(tc->pctx, node->binary.left, node->binary.right,
node->token);
// Rule 18.3: Relational operators on pointers (>, <, >=, <=)
if (tc->pctx->config->misra_mode && (strcmp(op, "<") == 0 || strcmp(op, ">") == 0 ||
strcmp(op, "<=") == 0 || strcmp(op, ">=") == 0))
{
Type *l_resolved = resolve_alias(left_type);
Type *r_resolved = resolve_alias(right_type);
if (l_resolved->kind == TYPE_POINTER || r_resolved->kind == TYPE_POINTER)
{
tc_error(
tc, node->token,
"MISRA Rule 18.3: relational operator shall not be applied to pointers");
}
}
misra_check_string_compare(tc->pctx, left_type, right_type, node->token);
if (!type_eq(left_type, right_type))
{
// Allow comparison between numeric types
int left_numeric = is_integer_type(left_type) || is_float_type(left_type);
int right_numeric = is_integer_type(right_type) || is_float_type(right_type);
if (!left_numeric || !right_numeric)
{
if ((left_type && left_type->kind == TYPE_UNKNOWN) ||
(right_type && right_type->kind == TYPE_UNKNOWN))
{
node->type_info = type_new(TYPE_BOOL);
return;
}
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg), "Cannot compare '%s' with incompatible types", op);
const char *hints[] = {"Ensure both operands have the same or compatible types",
NULL};
tc_error_with_hints(tc, node->token, msg, hints);
}
}
}
return;
}
// Logical operators: &&, ||
if (strcmp(op, "&&") == 0 || strcmp(op, "||") == 0)
{
node->type_info = type_new(TYPE_BOOL);
// Could validate that operands are boolean-like, but C is lax here
return;
}
// Bitwise operators: &, |, ^, <<, >>
if (strcmp(op, "&") == 0 || strcmp(op, "|") == 0 || strcmp(op, "^") == 0 ||
strcmp(op, "<<") == 0 || strcmp(op, ">>") == 0)
{
// MISRA Rule 12.2: Shift amount validation for << and >>
if (tc->pctx->config->misra_mode && (strcmp(op, "<<") == 0 || strcmp(op, ">>") == 0))
{
long long shift_amt;
if (eval_const_int_expr(node->binary.right, tc->pctx, &shift_amt))
{
int width = integer_type_width(left_type);
misra_check_shift_amount(tc->pctx, shift_amt, width, node->token);
}
}
else if ((strcmp(op, "<<") == 0 || strcmp(op, ">>") == 0) && node->binary.right &&
node->binary.right->kind == NODE_EXPR_LITERAL &&
node->binary.right->literal.kind == LITERAL_INT)
{
// Legacy/Non-MISRA warnings
unsigned long long shift_amt = node->binary.right->literal.int_val;
if (shift_amt >= 64)
{
const char *hints[] = {"Shift amount exceeds bit width, result is undefined", NULL};
tc_error_with_hints(tc, node->binary.right->token, "Shift amount too large", hints);
}
else if (shift_amt >= 32 && left_type &&
(left_type->kind == TYPE_INT || left_type->kind == TYPE_UINT ||
left_type->kind == TYPE_I32 || left_type->kind == TYPE_U32 ||
left_type->kind == TYPE_C_INT || left_type->kind == TYPE_C_UINT))
{
const char *hints[] = {
"Shift amount >= 32 is undefined behavior for 32-bit integers", NULL};
tc_error_with_hints(tc, node->binary.right->token,
"Shift amount exceeds 32-bit type width", hints);
}
}
if (left_type && right_type)
{
if ((!is_integer_type(left_type) && left_type->kind != TYPE_VECTOR) ||
(!is_integer_type(right_type) && right_type->kind != TYPE_VECTOR))
{
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg),
"Bitwise operator '%s' requires integer or vector operands", op);
const char *hints[] = {"Bitwise operators only work on integer or vector types",
NULL};
tc_error_with_hints(tc, node->token, msg, hints);
}
else if (left_type->kind == TYPE_VECTOR || right_type->kind == TYPE_VECTOR)
{
if (left_type->kind != right_type->kind || !type_eq(left_type, right_type))
{
tc_error(tc, node->token, "Vector bitwise operation requires same vector type");
}
node->type_info = left_type;
}
else
{
if (tc->pctx->config->misra_mode)
{
misra_check_bitwise_operand(tc->pctx, left_type, node->token);
misra_check_bitwise_operand(tc->pctx, right_type, node->token);
// Rule 10.4: Balancing for &, |, ^
if (strcmp(op, "&") == 0 || strcmp(op, "|") == 0 || strcmp(op, "^") == 0)
{
misra_check_binary_op_essential_types(tc->pctx, node->binary.left,
node->binary.right, node->token);
}
}
node->type_info = left_type;
}
}
return;
}
}
void extract_base_name(const char *full_name, char *base_buf, size_t max_len)
{
if (!full_name)
{
base_buf[0] = '\0';
return;
}
size_t i = 0;
while (full_name[i] && full_name[i] != '<' && full_name[i] != '_' && i < max_len - 1)
{
base_buf[i] = full_name[i];
i++;
}
base_buf[i] = '\0';
}
int is_struct_base_match(Type *base, Type *instantiated)
{
if (!base || !base->name || !instantiated || !instantiated->name)
{
return 0;
}
if (strcmp(base->name, instantiated->name) == 0)
{
return 1;
}
char base_str[MAX_TYPE_NAME_LEN];
char inst_str[MAX_TYPE_NAME_LEN];
extract_base_name(base->name, base_str, sizeof(base_str));
extract_base_name(instantiated->name, inst_str, sizeof(inst_str));
if (base_str[0] != '\0' && strcmp(base_str, inst_str) == 0)
{
return 1;
}
return 0;
}
void apply_implicit_struct_pointer_conversions(TypeChecker *tc, ASTNode **expr_ptr,
Type *expected_type)
{
if (!expr_ptr || !*expr_ptr || !expected_type)
{
return;
}
ASTNode *expr = *expr_ptr;
Type *actual_type = expr->type_info;
if (!actual_type)
{
return;
}
Type *e_res = get_inner_type(expected_type);
Type *a_res = get_inner_type(actual_type);
// T* (actual) -> T (expected) => Implicit Dereference *
// This allows `return self` to return the struct value when self is a pointer.
// We only do this if the type is Copy, otherwise it would trigger a
// move-from-borrowed-reference error.
if (a_res->kind == TYPE_POINTER && a_res->inner &&
(a_res->inner->kind == TYPE_STRUCT || a_res->inner->kind == TYPE_ENUM) &&
(type_eq(a_res->inner, e_res) || is_struct_base_match(a_res->inner, e_res)) &&
is_type_copy(tc->pctx, a_res->inner))
{
ASTNode *deref = ast_create(NODE_EXPR_UNARY);
deref->unary.op = xstrdup("*");
deref->unary.operand = expr;
deref->type_info = a_res->inner;
deref->token = expr->token;
deref->next = expr->next;
expr->next = NULL;
*expr_ptr = deref;
}
// T (actual) -> T* (expected) => Implicit Address-Of &
else if (e_res->kind == TYPE_POINTER && e_res->inner &&
(a_res->kind == TYPE_STRUCT || a_res->kind == TYPE_ENUM) &&
(type_eq(a_res, e_res->inner) || is_struct_base_match(a_res, e_res->inner)))
{
ASTNode *addr = ast_create(NODE_EXPR_UNARY);
int is_rvalue = expr_is_rvalue(tc->pctx, expr);
addr->unary.op = is_rvalue ? xstrdup("&_rval") : xstrdup("&");
addr->unary.operand = expr;
addr->type_info = e_res;
addr->token = expr->token;
addr->next = expr->next;
expr->next = NULL;
*expr_ptr = addr;
}
}
int check_type_compatibility(TypeChecker *tc, Type *target, Type *value, Token t,
ASTNode *value_node, int is_call_arg)
{
if (!target || !value)
{
return 1; // Can't check incomplete types
}
Type *resolved_target = resolve_alias(target);
Type *resolved_value = resolve_alias(value);
// MISRA Pointer & Constant Checks (Rules 11.5, 11.9, etc.)
if (tc->pctx->config->misra_mode && resolved_target->kind == TYPE_POINTER)
{
misra_check_null_pointer_constant(tc->pctx, value_node, t);
misra_check_void_ptr_cast(tc->pctx, target, value, t);
misra_check_pointer_conversion(tc->pctx, target, value, t);
}
// A literal 0 is a null pointer constant, as in C. MISRA mode reports it
// above (Rule 11.9); otherwise it is accepted for any pointer.
if (resolved_target->kind == TYPE_POINTER && value_node)
{
ASTNode *expr = value_node;
while (expr && expr->kind == NODE_EXPR_CAST)
{
expr = expr->cast.expr;
}
if (expr && expr->kind == NODE_EXPR_LITERAL && expr->literal.kind == LITERAL_INT &&
expr->literal.int_val == 0)
{
return 1;
}
}
// Resolution of Integer compatibility (Rule 10.3)
// This MUST happen before type_eq fast-path because type_eq is lax for integers.
if (is_integer_type(resolved_target) && is_integer_type(resolved_value))
{
int target_width = integer_type_width(resolved_target);
int value_width = integer_type_width(resolved_value);
if (tc->pctx->config->misra_mode)
{
misra_check_implicit_conversion(tc->pctx, target, value, value_node, t);
}
else
{
// Warn on narrowing conversions in non-MISRA mode
if (target_width > 0 && value_width > 0 && target_width < value_width)
{
char *t_str = type_to_string(target);
char *v_str = type_to_string(value);
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg),
"Implicit narrowing conversion from '%s' (%d-bit) to '%s' (%d-bit)", v_str,
value_width, t_str, target_width);
zwarn_at(t, "%s", msg);
if (tc)
{
tc->warning_count++;
}
zfree(t_str);
zfree(v_str);
}
}
return 1; // All integer pairs compatible (modulo MISRA/Warning checks above)
}
if (!is_call_arg && resolved_target->kind == TYPE_POINTER &&
resolved_value->kind == TYPE_POINTER)
{
// Higher depth = shorter scope.
if (resolved_target->lifetime_depth < resolved_value->lifetime_depth)
{
char msg[MAX_ERROR_MSG_LEN];
snprintf(msg, sizeof(msg),
"Escape analysis error: pointer assigned to a location that outlives it");
const char *hints[] = {"The source pointer belongs to a child scope and will become "
"invalid when that scope ends.",
"Consider copying the value instead of taking a reference.",
NULL};
tc_error_with_hints(tc, t, msg, hints);
return 0;
}
}
// Types that are aliases of one another (`alias UserId = Handle<UserKind>`,
// `alias EntryPtr = Entry*` making `EntryPtr*` and `Entry**` equal): an alias
// may also reach here as a struct named after it.
{
char *target_name = canonical_type_name(tc, target, 0);
char *value_name = canonical_type_name(tc, value, 0);
int same = target_name && value_name && strcmp(target_name, value_name) == 0;
zfree(target_name);
zfree(value_name);
if (same)
{
return 1;
}
}
// Fast path: exact match
if (type_eq(target, value))
{
// MISRA Rule 11.8: Check const qualification during pointer assignment
if (tc->pctx->config->misra_mode && resolved_target->kind == TYPE_POINTER &&
resolved_value->kind == TYPE_POINTER)
{
misra_check_pointer_conversion(tc->pctx, target, value, t);
}
return 1;
}
if (tc->pctx->config->misra_mode)
{
// Remaining pointer nesting and array param size checks are modularized
// in their respective node visitors.
}
// Resolve type aliases (str -> string, etc.) for non-integer types
// (Integer aliases handled by resolve_alias above)
if (target->kind == TYPE_ALIAS && target->name)
{
const char *alias = find_type_alias(tc->pctx, target->name);
if (alias)
{
// Check if resolved names match
if (value->name && strcmp(alias, value->name) == 0)
{
return 1;
}
}
}
if (value->kind == TYPE_ALIAS && value->name)
{
const char *alias = find_type_alias(tc->pctx, value->name);
if (alias)
{
if (target->name && strcmp(alias, target->name) == 0)
{
return 1;
}
}
}
// String types: str, string, *char are compatible
if ((target->kind == TYPE_STRING || (target->name && strcmp(target->name, "str") == 0)) &&
(value->kind == TYPE_STRING || (value->name && strcmp(value->name, "string") == 0)))
{
return 1;
}
// void* is generic pointer
if (resolved_target->kind == TYPE_POINTER && resolved_target->inner &&
resolved_target->inner->kind == TYPE_VOID)
{
return 1;
}
if (resolved_value->kind == TYPE_POINTER && resolved_value->inner &&
resolve_alias(resolved_value->inner)->kind == TYPE_VOID)
{
return 1;
}
// Array decay: Array[T] -> T*
// This allows passing a fixed-size array where a pointer is expected.
if (resolved_target->kind == TYPE_POINTER && resolved_value->kind == TYPE_ARRAY)
{
if (resolved_target->inner && resolved_value->inner)
{
// Recursive check for inner types (e.g. char* <- char[10])
if (type_eq(resolved_target->inner, resolved_value->inner))
{
return 1;
}
// Allow char* <- char[N] explicitly if type_eq is too strict
if (is_char_type(resolved_target->inner) && is_char_type(resolved_value->inner))
{
return 1;
}
}
}
if (is_integer_type(resolved_target) && is_integer_type(resolved_value))
{
return 1;
}
// Float compatibility
if (is_float_type(resolved_target) && is_float_type(resolved_value))
{
return 1;
}
// Trait object compatibility: Struct -> Trait
if (resolved_target->name && is_trait(resolved_target->name) &&
resolved_value->kind == TYPE_STRUCT && resolved_value->name)
{
if (check_impl(tc->pctx, resolved_target->name, resolved_value->name))
{
return 1;
}
}
// Type mismatch - report error
char *t_str = type_to_string(target);
char *v_str = type_to_string(value);
char msg[MAX_ERROR_MSG_LEN];
snprintf(msg, sizeof(msg), "Type mismatch: expected '%s', but found '%s'", t_str, v_str);
const char *hints[] = {
"Check if you need an explicit cast",
"Ensure the types match exactly (no implicit conversions for strict types)", NULL};
tc_error_with_hints(tc, t, msg, hints);
zfree(t_str);
zfree(v_str);
return 0;
}
void check_expr_var(TypeChecker *tc, ASTNode *node)
{
// Check if it's an enum variant FIRST, prioritizing value over function constructor if no
// payload
EnumVariantReg *ev = find_enum_variant(tc->pctx, node->var_ref.name);
if (ev)
{
FuncSig *sig = find_func(tc->pctx, node->var_ref.name);
// If it's a no-payload variant, treat it as an enum value (TYPE_ENUM)
// If it has payloads, it's a constructor function (TYPE_FUNCTION)
if (!sig || sig->total_args == 0)
{
Type *et = type_new(TYPE_ENUM);
et->name = xstrdup(ev->enum_name);
node->type_info = et;
return;
}
}
ZenSymbol *sym = tc_lookup(tc, node->var_ref.name);
node->var_ref.symbol = sym; // Store for MISRA audits
if (sym && sym->type_info)
{
// Clone the type to keep metadata (like lifetime_depth) isolated per usage site
node->type_info = type_clone(sym->type_info);
// Rule 8.9 tracking: Identify globals used in only one function
if (!sym->is_local && sym->kind == SYM_VARIABLE && tc->current_func)
{
ZenSymbol *orig = sym->original ? sym->original : sym;
if (orig->first_using_func == NULL)
{
orig->first_using_func = tc->current_func;
}
else if (orig->first_using_func != tc->current_func)
{
orig->multi_func_use = 1;
}
}
}
else
{
// Fallback: Check if it's a mangled function name (e.g. from :: operator or enum
// constructor)
FuncSig *sig = find_func(tc->pctx, node->var_ref.name);
if (sig)
{
Type *fn_type = type_new(TYPE_FUNCTION);
fn_type->is_raw = 1;
fn_type->inner = sig->ret_type ? sig->ret_type : type_new(TYPE_VOID);
fn_type->count = sig->total_args;
if (sig->total_args > 0)
{
fn_type->args = xmalloc(sizeof(Type *) * (size_t)(sig->total_args));
for (int i = 0; i < sig->total_args; i++)
{
fn_type->args[i] = sig->arg_types[i];
}
}
node->type_info = fn_type;
}
else if (tc->pctx->config->misra_mode)
{
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg), "Undefined variable '%s' (MISRA Rule 17.3)",
node->var_ref.name);
tc_error(tc, node->token, msg);
}
}
if (!tc->is_assign_lhs)
{
check_use_validity(tc, node);
}
}
void check_expr_literal(TypeChecker *tc, ASTNode *node)
{
(void)tc;
if (node->type_info && node->type_info->kind != TYPE_UNKNOWN)
{
return;
}
switch (node->literal.kind)
{
case LITERAL_INT:
node->type_info = type_new(TYPE_I32); // Default to i32
break;
case LITERAL_FLOAT:
node->type_info = type_new(TYPE_F64); // Default to f64
break;
case LITERAL_STRING:
case LITERAL_RAW_STRING:
node->type_info = type_new(TYPE_STRING);
break;
case LITERAL_CHAR:
node->type_info = type_new(TYPE_CHAR);
break;
default:
break;
}
}
void check_struct_init(TypeChecker *tc, ASTNode *node, int depth)
{
if (!node)
{
return;
}
RECURSION_GUARD_TOKEN(tc->pctx, node->token, );
// MISRA: Mark struct type as used
mark_type_as_used(tc, node->type_info);
if (tc->pctx->config->misra_mode)
{
ZenSymbol *struct_sym =
symbol_lookup_kind(tc->pctx->global_scope, node->struct_init.struct_name, SYM_STRUCT);
if (struct_sym)
{
struct_sym->is_dereferenced = 1;
}
}
// Find struct definition (the literal may name it through an alias)
ASTNode *def = find_struct_def(tc->pctx, node->struct_init.struct_name);
if (!def)
{
def = find_struct_def(tc->pctx, resolve_alias_name(tc, node->struct_init.struct_name));
}
if (!def)
{
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg), "Unknown struct '%s'", node->struct_init.struct_name);
tc_error(tc, node->token, msg);
RECURSION_EXIT(tc->pctx);
return;
}
// Iterate provided fields
ASTNode *field_init = node->struct_init.fields;
while (field_init)
{
// Rule 9.4: Double initialization check
if (tc->pctx->config->misra_mode)
{
ASTNode *prev = node->struct_init.fields;
while (prev != field_init)
{
if (strcmp(prev->var_decl.name, field_init->var_decl.name) == 0)
{
misra_check_double_initialization(tc->pctx, field_init->var_decl.name,
field_init->token);
break;
}
prev = prev->next;
}
}
// Find corresponding field in definition
ASTNode *def_field = def->strct.fields;
Type *expected_type = NULL;
int found = 0;
while (def_field)
{
if (def_field->kind == NODE_FIELD &&
strcmp(def_field->field.name, field_init->var_decl.name) == 0)
{
found = 1;
expected_type = def_field->type_info;
break;
}
def_field = def_field->next;
}
if (found)
{
field_init->type_info = expected_type;
}
if (found && expected_type && field_init->var_decl.init_expr->kind == NODE_LAMBDA)
{
field_init->var_decl.init_expr->type_info = expected_type;
}
// Check the initialization expression
check_node(tc, field_init->var_decl.init_expr, depth + 1);
if (!found)
{
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg), "Struct '%s' has no field named '%s'",
node->struct_init.struct_name, field_init->var_decl.name);
tc_error(tc, field_init->token, msg);
}
else if (expected_type && field_init->var_decl.init_expr->type_info)
{
// Localize expected field type depth for initialization check.
// A local struct's fields expect lifetimes compatible with the struct's own scope.
Type *localized_expected = type_clone(expected_type);
if (localized_expected && tc->current_func)
{
localized_expected->lifetime_depth = tc->current_depth;
}
check_type_compatibility(tc, localized_expected,
field_init->var_decl.init_expr->type_info, field_init->token,
field_init->var_decl.init_expr, 0);
}
// Move Analysis: Check if the initializer moves a non-copy value.
check_move_for_rvalue(tc, field_init->var_decl.init_expr);
field_init = field_init->next;
}
// Fields left out of a struct literal are zero-initialised (the C designated
// initializer the build emits), so they are only reported in MISRA mode,
// which requires explicit initialization.
ASTNode *def_field = tc->pctx->config->misra_mode ? def->strct.fields : NULL;
while (def_field)
{
if (def_field->kind == NODE_FIELD && def_field->field.name)
{
int provided = 0;
ASTNode *fi = node->struct_init.fields;
while (fi)
{
if (fi->var_decl.name && strcmp(fi->var_decl.name, def_field->field.name) == 0)
{
provided = 1;
break;
}
fi = fi->next;
}
if (!provided)
{
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg), "Missing field '%s' in initializer for struct '%s'",
def_field->field.name, node->struct_init.struct_name);
const char *hints[] = {"All struct fields must be initialized", NULL};
tc_error_with_hints(tc, node->token, msg, hints);
}
}
def_field = def_field->next;
}
node->type_info = def->type_info;
RECURSION_EXIT(tc->pctx);
}
void check_expr_lambda(TypeChecker *tc, ASTNode *node, int depth)
{
Type *expected = get_inner_type(node->type_info);
if (expected && expected->kind == TYPE_FUNCTION && expected->is_raw)
{
if (node->lambda.capture_count == 0)
{
node->lambda.is_bare = 1;
}
else
{
const char *hints[] = {
"Only non-capturing lambdas can be converted to raw function pointers", NULL};
tc_error_with_hints(tc, node->token,
"Cannot convert capturing lambda to raw function pointer", hints);
}
}
if (node->lambda.captured_vars)
{
for (int i = 0; i < node->lambda.capture_count; i++)
{
char *var_name = node->lambda.captured_vars[i];
int mode = node->lambda.capture_modes ? node->lambda.capture_modes[i]
: node->lambda.default_capture_mode;
ZenSymbol *sym = tc_lookup(tc, var_name);
if (!sym)
{
continue;
}
check_path_validity(tc, var_name, node->token);
if (mode == 0)
{
Type *t = sym->type_info;
if (!is_type_copy(tc->pctx, t))
{
mark_symbol_moved(tc->pctx, sym, node);
}
}
}
}
tc_enter_scope(tc);
for (int i = 0; i < node->lambda.count; i++)
{
char *pname = node->lambda.param_names[i];
Type *ptype = NULL;
Type *node_ti = get_inner_type(node->type_info);
if (node_ti && node_ti->kind == TYPE_FUNCTION && node_ti->args)
{
if (i < node_ti->count)
{
ptype = node_ti->args[i];
}
}
tc_add_symbol(tc, pname, ptype, node->token, 0);
}
// Add captured variables to the scope to ensure visibility and immutability inside the lambda.
if (node->lambda.captured_vars)
{
int saved_silent = tc->pctx->silent_warnings;
tc->pctx->silent_warnings = 1;
for (int i = 0; i < node->lambda.capture_count; i++)
{
char *var_name = node->lambda.captured_vars[i];
int mode = node->lambda.capture_modes ? node->lambda.capture_modes[i]
: node->lambda.default_capture_mode;
// Lookup original symbol to get its type
ZenSymbol *orig_sym = tc_lookup(tc, var_name);
if (orig_sym)
{
// Shadow the original variable in the lambda scope
// Value captures are immutable.
tc_add_symbol(tc, var_name, orig_sym->type_info, node->token, (mode == 0));
}
}
tc->pctx->silent_warnings = saved_silent;
}
MoveState *prev_move_state = tc->pctx->move_state;
tc->pctx->move_state = move_state_create(NULL);
int prev_unreachable = tc->is_unreachable;
tc->is_unreachable = 0;
if (node->lambda.body)
{
if (node->lambda.body->kind == NODE_BLOCK)
{
check_block(tc, node->lambda.body, depth + 1);
}
else
{
check_node(tc, node->lambda.body, depth + 1);
}
}
move_state_free(tc->pctx->move_state);
tc->pctx->move_state = prev_move_state;
tc->is_unreachable = prev_unreachable;
tc_exit_scope(tc);
}
// INFERENCE & ENTRY POINTS