src/analysis/typecheck_call.c
Rama main · réplica de lectura del repositorio original.
// SPDX-License-Identifier: MIT
#include "typecheck_internal.h"
#include "../utils/utils.h"
void check_expr_call(TypeChecker *tc, ASTNode *node, int depth)
{
check_node(tc, node->call.callee, depth + 1);
const char *func_name = NULL;
FuncSig *sig = NULL;
// Check if the function exists (for simple direct calls)
if (node->call.callee && node->call.callee->kind == NODE_EXPR_VAR)
{
func_name = node->call.callee->var_ref.name;
// Look up function signature
sig = find_func(tc->pctx, func_name);
if (tc->pctx->config->misra_mode)
{
Token t = node->call.callee->token;
if (t.line == 0)
{
t = node->token;
}
misra_check_banned_function(tc->pctx, func_name, t);
}
if (tc->pctx->config->misra_mode && tc->current_func)
{
if (strcmp(func_name, tc->current_func->func.name) == 0)
{
Token t = node->call.callee->token;
if (t.line == 0)
{
t = node->token;
}
misra_check_recursion(tc->pctx, t);
}
}
if (!sig)
{
// Check if it's a built-in macro injected by the compiler
if (strcmp(func_name, "_z_str") == 0)
{
// _z_str is a generic format macro from ZC_C_GENERIC_STR
check_node(tc, node->call.args, depth + 1); // Still check the argument
node->type_info = type_new(TYPE_STRING);
return;
}
// Check local scope first, then global symbols
ZenSymbol *sym = tc_lookup(tc, func_name);
if (!sym)
{
ZenSymbol *global_sym = find_symbol_in_all(tc->pctx, func_name);
if (!global_sym && !should_suppress_undef_warning(tc->pctx, func_name))
{
char msg[MAX_SHORT_MSG_LEN];
if (tc->pctx->config->misra_mode)
{
snprintf(msg, sizeof(msg), "Undefined function '%s' (MISRA Rule 17.3)",
func_name);
}
else
{
snprintf(msg, sizeof(msg), "Undefined function '%s'", func_name);
}
const char *hints[] = {"Check if the function is defined or imported", NULL};
tc_error_with_hints(tc, node->call.callee->token, msg, hints);
}
}
}
}
else if (node->call.callee && node->call.callee->kind == NODE_EXPR_MEMBER)
{
if (node->call.callee->type_info && node->call.callee->type_info->name)
{
func_name = node->call.callee->type_info->name;
sig = find_func(tc->pctx, func_name);
}
// Trait method resolution fallback
if (!sig && node->call.callee->member.target && node->call.callee->member.target->type_info)
{
Type *target_type = get_inner_type(node->call.callee->member.target->type_info);
if (target_type->name && is_trait(target_type->name))
{
ASTNode *trait_def = find_trait_def(tc->pctx, target_type->name);
if (trait_def)
{
ASTNode *method = trait_def->trait.methods;
while (method)
{
if (strcmp(method->func.name, node->call.callee->member.field) == 0)
{
// Correctly resolve return type for trait method
node->type_info = method->func.ret_type_info;
break;
}
method = method->next;
}
}
}
}
}
// Count arguments
int count = 0;
ASTNode *arg = node->call.args;
while (arg)
{
count++;
arg = arg->next;
}
// Member call (a.b()) counts as +1 arg (the receiver)
if (node->call.callee && node->call.callee->kind == NODE_EXPR_MEMBER)
{
count++;
}
// Enforce @pure constraint
if (tc->current_func && tc->current_func->func.pure)
{
if (!sig || !sig->is_pure)
{
// Allow _z_str? Wait, _z_str is a compiler macro, it's not strictly "pure", but it's
// safe.
if (!func_name || strcmp(func_name, "_z_str") != 0)
{
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg),
"Pure function '%s' cannot call non-pure or dynamic function '%s'",
tc->current_func->func.name, func_name ? func_name : "unknown");
const char *hints[] = {
"Mark the called function as @pure, or remove @pure from the caller", NULL};
tc_error_with_hints(tc, node->call.callee->token, msg, hints);
}
}
}
// Validate argument count
if (sig)
{
int min_args = sig->total_args;
if (sig->defaults)
{
min_args = 0;
for (int i = 0; i < sig->total_args; i++)
{
if (!sig->defaults[i])
{
min_args++;
}
}
}
if (count < min_args)
{
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg), "Too few arguments: '%s' expects at least %d, got %d",
func_name, min_args, count);
const char *hints[] = {"Check the function signature for required parameters", NULL};
tc_ctrl_flow_error(tc, node->token, msg, hints);
}
else if (count > sig->total_args && !sig->is_varargs)
{
char msg[MAX_SHORT_MSG_LEN];
snprintf(msg, sizeof(msg), "Too many arguments: '%s' expects %d, got %d", func_name,
sig->total_args, count);
const char *hints[] = {
"Remove extra arguments or check if you meant to call a different function", NULL};
tc_ctrl_flow_error(tc, node->token, msg, hints);
}
}
// Check argument types
arg = node->call.args;
int sig_arg_idx = 0;
// For member calls, the first signature argument is the receiver
if (node->call.callee && node->call.callee->kind == NODE_EXPR_MEMBER)
{
if (sig && sig->total_args > 0 && sig->arg_types && sig->arg_types[0])
{
Type *expected_rec = sig->arg_types[0];
Type *actual_rec = node->call.callee->member.target->type_info;
// Allow T to T* for method receivers
if (expected_rec->kind == TYPE_POINTER && actual_rec &&
actual_rec->kind != TYPE_POINTER && type_eq(expected_rec->inner, actual_rec))
{
// OK: Compiler will take address
}
else
{
check_type_compatibility(tc, expected_rec, actual_rec, node->call.callee->token,
NULL, 1);
}
}
sig_arg_idx = 1;
}
// Method calls are lowered to direct calls with the receiver as the first
// argument. .forget() is allowed on an already-moved receiver, so suppress
// the use-after-move report for that call's arguments.
size_t fn_len = func_name ? strlen(func_name) : 0;
int forget_receiver_arg =
func_name && ((fn_len >= 8 && strcmp(func_name + fn_len - 8, "__forget") == 0) ||
strcmp(func_name, "forget") == 0);
if (forget_receiver_arg && node->call.callee && node->call.callee->kind != NODE_EXPR_MEMBER &&
node->call.args)
{
tc->is_forget_receiver = 1;
}
while (arg)
{
Type *expected = NULL;
if (sig && sig_arg_idx < sig->total_args && sig->arg_types && sig->arg_types[sig_arg_idx])
{
expected = sig->arg_types[sig_arg_idx];
}
else if (!sig && node->call.callee->type_info)
{
Type *callee_t = get_inner_type(node->call.callee->type_info);
if (callee_t->kind == TYPE_FUNCTION && sig_arg_idx < callee_t->count && callee_t->args)
{
expected = callee_t->args[sig_arg_idx];
}
}
// Propagate expected type to lambda for inference
if (arg->kind == NODE_LAMBDA && expected)
{
arg->type_info = expected;
}
check_node(tc, arg, depth + 1);
// Validate type against signature
Type *actual = arg->type_info;
if (expected && actual)
{
Type *e_resolved = get_inner_type(expected);
Type *a_resolved = get_inner_type(actual);
if (e_resolved->kind == TYPE_UNKNOWN && a_resolved->kind != TYPE_UNKNOWN)
{
// Backward type inference: we passed an actual type to a lambda taking unknown
*e_resolved = *a_resolved;
}
else if (e_resolved->kind == TYPE_FUNCTION && a_resolved->kind == TYPE_FUNCTION)
{
for (int j = 0; j < e_resolved->count && j < a_resolved->count; j++)
{
if (a_resolved->args && a_resolved->args[j] &&
a_resolved->args[j]->kind == TYPE_UNKNOWN && e_resolved->args &&
e_resolved->args[j] && e_resolved->args[j]->kind != TYPE_UNKNOWN)
{
*a_resolved->args[j] = *e_resolved->args[j];
}
}
if (a_resolved->inner && a_resolved->inner->kind == TYPE_UNKNOWN &&
e_resolved->inner)
{
*a_resolved->inner = *e_resolved->inner;
}
}
// Rule 17.5: Array parameter sizes must match.
if (tc->pctx->config->misra_mode && e_resolved->kind == TYPE_ARRAY &&
a_resolved->kind == TYPE_ARRAY)
{
if (e_resolved->array_size != a_resolved->array_size)
{
misra_check_array_param_size(tc->pctx, e_resolved->array_size,
a_resolved->array_size, arg->token);
}
}
check_type_compatibility(tc, expected, actual, arg->token, arg, 1);
}
// If argument is passed by VALUE, check if it can be moved.
check_move_for_rvalue(tc, arg);
arg = arg->next;
sig_arg_idx++;
}
tc->is_forget_receiver = 0;
// Propagate return type from function signature
if (sig && sig->ret_type)
{
if (!node->type_info)
{
// Deep clone return type to ensure caller doesn't modify callee's metadata
node->type_info = type_clone(sig->ret_type);
}
// Apply Lifetime Elision
if (sig->elide_from_idx != -1 && node->type_info->kind == TYPE_POINTER)
{
int target_depth = 0; // Default to escaping if not found
if (node->call.callee && node->call.callee->kind == NODE_EXPR_MEMBER &&
sig->elide_from_idx == 0)
{
if (node->call.callee->member.target->type_info)
{
target_depth = node->call.callee->member.target->type_info->lifetime_depth;
}
}
else
{
int current_idx =
(node->call.callee && node->call.callee->kind == NODE_EXPR_MEMBER) ? 1 : 0;
ASTNode *a = node->call.args;
while (a)
{
if (current_idx == sig->elide_from_idx)
{
if (a->type_info)
{
target_depth = a->type_info->lifetime_depth;
}
break;
}
current_idx++;
a = a->next;
}
}
node->type_info->lifetime_depth = target_depth;
}
else
{
// Function results always have depth 0 (static/heap/escaping) by default
node->type_info->lifetime_depth = 0;
}
}
else if (!node->type_info && node->call.callee && node->call.callee->type_info)
{
Type *callee_t = get_inner_type(node->call.callee->type_info);
if (callee_t->kind == TYPE_FUNCTION && callee_t->inner)
{
node->type_info = callee_t->inner;
}
}
// Rule 17.7: Unused return values
if (tc->pctx->config->misra_mode && tc->is_stmt_context && node->type_info)
{
misra_check_function_return_usage(tc->pctx, node);
}
// Rule 13.2: Side effect collision detection in arguments
ASTNode *receiver = (node->call.callee && node->call.callee->kind == NODE_EXPR_MEMBER)
? node->call.callee->member.target
: NULL;
check_all_args_side_effects(tc, receiver, node->call.args, node->token);
// Evaluation order check: function call arguments should not have conflicting side effects
if (tc->pctx->config->misra_mode)
{
misra_check_evaluation_order(tc->pctx, node);
}
}
// STATEMENT / BLOCK CHECKERS