| 3266 | } |
| 3267 | |
| 3268 | static void process_node(TSLSPContext *ctx, TSNode node) { |
| 3269 | if (!ctx || ts_node_is_null(node)) |
| 3270 | return; |
| 3271 | const char *kind = ts_node_type(node); |
| 3272 | |
| 3273 | /* A nested statement block owns `let`/`const` declarations. Function-body |
| 3274 | * blocks are intentionally unwrapped by process_function_body(), so this |
| 3275 | * branch applies only to nested lexical blocks and cannot hide parameters. */ |
| 3276 | if (strcmp(kind, "statement_block") == 0) { |
| 3277 | CBMScope *saved = ctx->current_scope; |
| 3278 | ctx->current_scope = cbm_scope_push(ctx->arena, saved); |
| 3279 | TSTreeCursor cursor = ts_tree_cursor_new(node); |
| 3280 | if (ts_tree_cursor_goto_first_child(&cursor)) { |
| 3281 | do { |
| 3282 | process_node(ctx, ts_tree_cursor_current_node(&cursor)); |
| 3283 | } while (ts_tree_cursor_goto_next_sibling(&cursor)); |
| 3284 | } |
| 3285 | ts_tree_cursor_delete(&cursor); |
| 3286 | ctx->current_scope = saved; |
| 3287 | return; |
| 3288 | } |
| 3289 | |
| 3290 | // Scope-affecting statements bind first, then we recurse. |
| 3291 | ts_process_statement(ctx, node); |
| 3292 | |
| 3293 | if (strcmp(kind, "call_expression") == 0) { |
| 3294 | resolve_call_at(ctx, node); |
| 3295 | resolve_value_references_at(ctx, node); |
| 3296 | |
| 3297 | // Contextual callback typing: when an arg is an arrow_function and the |
| 3298 | // corresponding param of the called function is itself a FUNC, propagate the |
| 3299 | // expected callback param types into the arrow's body before walking it. |
| 3300 | // Handle arg processing manually for this path to avoid double-walking via |
| 3301 | // default recurse below. |
| 3302 | do { |
| 3303 | TSNode fn_node = |
| 3304 | ts_node_child_by_field_name(node, "function", TS_LSP_FIELD_LEN("function")); |
| 3305 | if (ts_node_is_null(fn_node)) |
| 3306 | break; |
| 3307 | TSNode args = |
| 3308 | ts_node_child_by_field_name(node, "arguments", TS_LSP_FIELD_LEN("arguments")); |
| 3309 | if (ts_node_is_null(args)) |
| 3310 | break; |
| 3311 | const CBMType *fn_type = ts_signature_for_call(ctx, fn_node, args); |
| 3312 | if (!fn_type || fn_type->kind != CBM_TYPE_FUNC) |
| 3313 | break; |
| 3314 | if (!fn_type->data.func.param_types) |
| 3315 | break; |
| 3316 | |
| 3317 | // Process the function child for nested call resolution. |
| 3318 | process_node(ctx, fn_node); |
| 3319 | |
| 3320 | // param_types is NULL-terminated (no count field). Measure its |
| 3321 | // length so we never index past the terminator: a call may pass |
| 3322 | // more args than the function declares params (e.g. excess/variadic |
| 3323 | // args), and the extra args simply have no expected type. Indexing |
| 3324 | // param_types[i] by the raw arg count read out of bounds → garbage |
| 3325 | // CBMType* → crash on expected->kind. |
no test coverage detected