| 3126 | } |
| 3127 | |
| 3128 | static void process_node(TSLSPContext *ctx, TSNode node) { |
| 3129 | if (!ctx || ts_node_is_null(node)) |
| 3130 | return; |
| 3131 | const char *kind = ts_node_type(node); |
| 3132 | |
| 3133 | /* A nested statement block owns `let`/`const` declarations. Function-body |
| 3134 | * blocks are intentionally unwrapped by process_function_body(), so this |
| 3135 | * branch applies only to nested lexical blocks and cannot hide parameters. */ |
| 3136 | if (strcmp(kind, "statement_block") == 0) { |
| 3137 | CBMScope *saved = ctx->current_scope; |
| 3138 | ctx->current_scope = cbm_scope_push(ctx->arena, saved); |
| 3139 | TSTreeCursor cursor = ts_tree_cursor_new(node); |
| 3140 | if (ts_tree_cursor_goto_first_child(&cursor)) { |
| 3141 | do { |
| 3142 | process_node(ctx, ts_tree_cursor_current_node(&cursor)); |
| 3143 | } while (ts_tree_cursor_goto_next_sibling(&cursor)); |
| 3144 | } |
| 3145 | ts_tree_cursor_delete(&cursor); |
| 3146 | ctx->current_scope = saved; |
| 3147 | return; |
| 3148 | } |
| 3149 | |
| 3150 | // Scope-affecting statements bind first, then we recurse. |
| 3151 | ts_process_statement(ctx, node); |
| 3152 | |
| 3153 | if (strcmp(kind, "call_expression") == 0) { |
| 3154 | resolve_call_at(ctx, node); |
| 3155 | resolve_value_references_at(ctx, node); |
| 3156 | |
| 3157 | // Contextual callback typing: when an arg is an arrow_function and the |
| 3158 | // corresponding param of the called function is itself a FUNC, propagate the |
| 3159 | // expected callback param types into the arrow's body before walking it. |
| 3160 | // Handle arg processing manually for this path to avoid double-walking via |
| 3161 | // default recurse below. |
| 3162 | do { |
| 3163 | TSNode fn_node = |
| 3164 | ts_node_child_by_field_name(node, "function", TS_LSP_FIELD_LEN("function")); |
| 3165 | if (ts_node_is_null(fn_node)) |
| 3166 | break; |
| 3167 | TSNode args = |
| 3168 | ts_node_child_by_field_name(node, "arguments", TS_LSP_FIELD_LEN("arguments")); |
| 3169 | if (ts_node_is_null(args)) |
| 3170 | break; |
| 3171 | const CBMType *fn_type = ts_signature_for_call(ctx, fn_node, args); |
| 3172 | if (!fn_type || fn_type->kind != CBM_TYPE_FUNC) |
| 3173 | break; |
| 3174 | if (!fn_type->data.func.param_types) |
| 3175 | break; |
| 3176 | |
| 3177 | // Process the function child for nested call resolution. |
| 3178 | process_node(ctx, fn_node); |
| 3179 | |
| 3180 | // param_types is NULL-terminated (no count field). Measure its |
| 3181 | // length so we never index past the terminator: a call may pass |
| 3182 | // more args than the function declares params (e.g. excess/variadic |
| 3183 | // args), and the extra args simply have no expected type. Indexing |
| 3184 | // param_types[i] by the raw arg count read out of bounds → garbage |
| 3185 | // CBMType* → crash on expected->kind. |
no test coverage detected