src/analysis/typecheck_utils.c
Rama main · réplica de lectura del repositorio original.
// 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 <ctype.h>
#include <string.h>
// External helpers from parser
int tc_expr_has_side_effects(ASTNode *node)
{
if (!node)
{
return 0;
}
switch (node->kind)
{
case NODE_EXPR_CALL:
// Function calls are always considered to have potential side effects in MISRA
return 1;
case NODE_EXPR_BINARY:
// Assignment operators are side effects
if (node->binary.op && strstr(node->binary.op, "="))
{
return 1;
}
return tc_expr_has_side_effects(node->binary.left) ||
tc_expr_has_side_effects(node->binary.right);
case NODE_EXPR_UNARY:
// Increment/Decrement are side effects (prefix and postfix)
if (node->unary.op &&
(strcmp(node->unary.op, "++") == 0 || strcmp(node->unary.op, "--") == 0 ||
strcmp(node->unary.op, "_post++") == 0 || strcmp(node->unary.op, "_post--") == 0))
{
return 1;
}
return tc_expr_has_side_effects(node->unary.operand);
case NODE_RAW_STMT:
// Inline assembly always has potential side effects
return 1;
case NODE_EXPR_STRUCT_INIT:
{
ASTNode *f = node->struct_init.fields;
while (f)
{
if (tc_expr_has_side_effects(f->var_decl.init_expr))
{
return 1;
}
f = f->next;
}
return 0;
}
case NODE_EXPR_ARRAY_LITERAL:
{
ASTNode *e = node->array_literal.elements;
while (e)
{
if (tc_expr_has_side_effects(e))
{
return 1;
}
e = e->next;
}
return 0;
}
case NODE_EXPR_TUPLE_LITERAL:
{
ASTNode *e = node->tuple_literal.elements;
while (e)
{
if (tc_expr_has_side_effects(e))
{
return 1;
}
e = e->next;
}
return 0;
}
case NODE_EXPR_MEMBER:
return tc_expr_has_side_effects(node->member.target);
case NODE_EXPR_INDEX:
return tc_expr_has_side_effects(node->index.array) ||
tc_expr_has_side_effects(node->index.index);
case NODE_EXPR_CAST:
return tc_expr_has_side_effects(node->cast.expr);
case NODE_EXPR_SLICE:
return tc_expr_has_side_effects(node->slice.array) ||
tc_expr_has_side_effects(node->slice.start) ||
tc_expr_has_side_effects(node->slice.end);
default:
// Most other nodes (LITERAL, VAR, SIZEOF itself if nested) are side-effect free
return 0;
}
}
void collect_symbols(ASTNode *node, SymbolSet *reads, SymbolSet *writes)
{
if (!node)
{
return;
}
switch (node->kind)
{
case NODE_EXPR_BINARY:
if (node->binary.op && strstr(node->binary.op, "="))
{
// LHS is a write
if (node->binary.left->kind == NODE_EXPR_VAR)
{
if (writes->count < 32)
{
writes->syms[writes->count++] = node->binary.left->var_ref.symbol;
}
}
collect_symbols(node->binary.left, reads, writes); // In case of complex LHS
collect_symbols(node->binary.right, reads, writes);
}
else
{
collect_symbols(node->binary.left, reads, writes);
collect_symbols(node->binary.right, reads, writes);
}
break;
case NODE_EXPR_UNARY:
if (node->unary.op &&
(strcmp(node->unary.op, "++") == 0 || strcmp(node->unary.op, "--") == 0 ||
strcmp(node->unary.op, "_post++") == 0 || strcmp(node->unary.op, "_post--") == 0))
{
if (node->unary.operand->kind == NODE_EXPR_VAR)
{
if (writes->count < 32)
{
writes->syms[writes->count++] = node->unary.operand->var_ref.symbol;
}
}
}
collect_symbols(node->unary.operand, reads, writes);
break;
case NODE_EXPR_VAR:
if (reads->count < 32)
{
reads->syms[reads->count++] = node->var_ref.symbol;
}
break;
case NODE_EXPR_CALL:
collect_symbols(node->call.callee, reads, writes);
ASTNode *arg = node->call.args;
while (arg)
{
collect_symbols(arg, reads, writes);
arg = arg->next;
}
break;
default:
// Generic traversal? For now just handle these.
break;
}
}
void check_side_effect_collision(TypeChecker *tc, ASTNode *left, ASTNode *right, Token token)
{
CompilerConfig *cfg = &tc->pctx->compiler->config;
if (!cfg->misra_mode || !left || !right)
{
return;
}
SymbolSet l_reads = {0}, l_writes = {0};
SymbolSet r_reads = {0}, r_writes = {0};
collect_symbols(left, &l_reads, &l_writes);
collect_symbols(right, &r_reads, &r_writes);
// Rule 13.2: Modification collision
for (int i = 0; i < l_writes.count; i++)
{
ZenSymbol *s = l_writes.syms[i];
if (!s)
{
continue;
}
// Check against other writes
for (int j = 0; j < r_writes.count; j++)
{
if (s == r_writes.syms[j])
{
tc_error(tc, token, "MISRA Rule 13.2: symbol modified in multiple sub-expressions");
return;
}
}
// Check against other reads
for (int j = 0; j < r_reads.count; j++)
{
if (s == r_reads.syms[j])
{
tc_error(tc, token,
"MISRA Rule 13.2: symbol both read and modified in same expression");
return;
}
}
}
// Vice versa for r_writes against l_reads
for (int i = 0; i < r_writes.count; i++)
{
ZenSymbol *s = r_writes.syms[i];
if (!s)
{
continue;
}
for (int j = 0; j < l_reads.count; j++)
{
if (s == l_reads.syms[j])
{
tc_error(tc, token,
"MISRA Rule 13.2: symbol both read and modified in same expression");
return;
}
}
}
}
void check_all_args_side_effects(TypeChecker *tc, ASTNode *receiver, ASTNode *args, Token token)
{
CompilerConfig *cfg = &tc->pctx->compiler->config;
if (!cfg->misra_mode)
{
return;
}
SymbolSet reads = {0}, writes = {0};
if (receiver)
{
collect_symbols(receiver, &reads, &writes);
}
ASTNode *arg = args;
while (arg)
{
SymbolSet next_reads = {0}, next_writes = {0};
collect_symbols(arg, &next_reads, &next_writes);
// Check against cumulative sets
for (int i = 0; i < next_writes.count; i++)
{
ZenSymbol *s = next_writes.syms[i];
for (int r = 0; r < reads.count; r++)
{
if (s == reads.syms[r])
{
tc_error(tc, token, "MISRA Rule 13.2: argument read and modified in same call");
return;
}
}
for (int w = 0; w < writes.count; w++)
{
if (s == writes.syms[w])
{
tc_error(tc, token, "MISRA Rule 13.2: symbol modified in multiple arguments");
return;
}
}
}
for (int i = 0; i < next_reads.count; i++)
{
ZenSymbol *s = next_reads.syms[i];
for (int w = 0; w < writes.count; w++)
{
if (s == writes.syms[w])
{
tc_error(tc, token, "MISRA Rule 13.2: argument read and modified in same call");
return;
}
}
}
// Add to cumulative sets
for (int i = 0; i < next_reads.count && reads.count < 32; i++)
{
reads.syms[reads.count++] = next_reads.syms[i];
}
for (int i = 0; i < next_writes.count && writes.count < 32; i++)
{
writes.syms[writes.count++] = next_writes.syms[i];
}
arg = arg->next;
}
}
// Internal MISRA helpers moved to platform/misra.c
void tc_error(TypeChecker *tc, Token t, const char *msg)
{
if (tc->move_checks_only)
{
return;
}
zerror_at(t, "%s", msg);
}
int is_expression_invariant(TypeChecker *tc, ASTNode *node, int *val)
{
if (!node)
{
return 0;
}
long long out;
if (eval_const_int_expr(node, tc->pctx, &out))
{
if (val)
{
*val = (int)out;
}
return 1;
}
return 0;
}
// Global recursion guard
void tc_error_with_hints(TypeChecker *tc, Token t, const char *msg, const char *const *hints)
{
if (tc->move_checks_only)
{
return;
}
zerror_with_hints(t, msg, hints);
}
void tc_ctrl_flow_error(TypeChecker *tc, Token t, const char *msg, const char *const *hints)
{
(void)tc;
// Control flow errors (missing return, unreachable code, etc.)
// are reported even during move-only checking.
zerror_with_hints(t, msg, hints);
}
void tc_move_error_with_hints(TypeChecker *tc, Token t, const char *msg, const char *const *hints)
{
(void)tc;
zerror_with_hints(t, msg, hints);
}
int is_char_type(Type *t)
{
if (!t)
{
return 0;
}
Type *res = resolve_alias(t);
return (res->kind == TYPE_CHAR || res->kind == TYPE_C_CHAR || res->kind == TYPE_C_UCHAR);
}
// tc_check_misra_10_4 moved to misra_check_binary_op_essential_types in misra.c
void tc_enter_scope(TypeChecker *tc)
{
tc->current_depth++;
enter_scope(tc->pctx);
}
void tc_exit_scope(TypeChecker *tc)
{
if (tc->current_depth > 0)
{
tc->current_depth--;
}
exit_scope(tc->pctx);
}
void tc_add_symbol(TypeChecker *tc, const char *name, Type *type, Token t, int is_immutable)
{
CompilerConfig *cfg = &tc->pctx->compiler->config;
if (cfg->misra_mode)
{
misra_check_shadowing(tc->pctx, name, t);
misra_check_typographic_ambiguity(tc->pctx, name, t);
}
add_symbol_with_token(tc->pctx, name, NULL, type, t, 0);
ZenSymbol *sym = symbol_lookup(tc->pctx->current_scope, name);
if (sym)
{
sym->is_immutable = is_immutable;
sym->scope_depth = tc->current_depth;
}
}
ZenSymbol *tc_lookup(TypeChecker *tc, const char *name)
{
ZenSymbol *sym = symbol_lookup(tc->pctx->current_scope, name);
if (sym)
{
sym->is_used = 1;
}
return sym;
}
void mark_type_as_used(TypeChecker *tc, Type *t)
{
if (!t)
{
return;
}
// Unroll pointers, arrays, vectors
Type *curr = t;
while (curr &&
(curr->kind == TYPE_POINTER || curr->kind == TYPE_ARRAY || curr->kind == TYPE_VECTOR))
{
curr = curr->inner;
}
if (!curr)
{
return;
}
if (curr->kind == TYPE_STRUCT || curr->kind == TYPE_ENUM)
{
if (curr->name)
{
ZenSymbol *sym =
symbol_lookup_kind(tc->pctx->global_scope, curr->name,
(curr->kind == TYPE_STRUCT) ? SYM_STRUCT : SYM_ENUM);
if (sym)
{
sym->is_used = 1;
}
}
}
else if (curr->kind == TYPE_ALIAS)
{
if (curr->name)
{
ZenSymbol *sym = symbol_lookup_kind(tc->pctx->global_scope, curr->name, SYM_ALIAS);
if (sym)
{
sym->is_used = 1;
}
}
}
// Generic arguments
for (int i = 0; curr->args && i < curr->count; i++)
{
mark_type_as_used(tc, curr->args[i]);
}
// Function type return and args
if (curr->kind == TYPE_FUNCTION)
{
mark_type_as_used(tc, curr->inner);
for (int i = 0; curr->args && i < curr->count; i++)
{
mark_type_as_used(tc, curr->args[i]);
}
}
}
// Internal MISRA helpers moved to platform/misra.c
int get_asm_register_size(Type *t)
{
if (!t)
{
return 0;
}
if (t->kind == TYPE_F64 || t->kind == TYPE_I64 || t->kind == TYPE_U64 ||
(t->kind == TYPE_STRUCT && t->name &&
(0 == strcmp(t->name, "int64_t") || 0 == strcmp(t->name, "uint64_t"))))
{
return 64;
}
if (t->kind == TYPE_I128 || t->kind == TYPE_U128)
{
return 128;
}
return 32;
}
int integer_type_width(Type *t)
{
if (!t)
{
return 0;
}
switch (t->kind)
{
case TYPE_I8:
case TYPE_U8:
case TYPE_BYTE:
case TYPE_C_CHAR:
case TYPE_C_UCHAR:
return 8;
case TYPE_I16:
case TYPE_U16:
case TYPE_C_SHORT:
case TYPE_C_USHORT:
return 16;
case TYPE_I32:
case TYPE_U32:
case TYPE_INT:
case TYPE_UINT:
case TYPE_RUNE:
case TYPE_C_INT:
case TYPE_C_UINT:
return 32;
case TYPE_I64:
case TYPE_U64:
case TYPE_ISIZE:
case TYPE_USIZE:
case TYPE_C_LONG:
case TYPE_C_ULONG:
case TYPE_C_LONGLONG:
case TYPE_C_ULONGLONG:
return 64;
case TYPE_I128:
case TYPE_U128:
return 128;
default:
return 0;
}
}
// EXPRESSION CHECKERS