| 2210 | /* ── statement processing: bind from assignments, for-loops, with-as ──── */ |
| 2211 | |
| 2212 | static void py_process_statement(PyLSPContext *ctx, TSNode node) { |
| 2213 | if (!ctx || ts_node_is_null(node)) |
| 2214 | return; |
| 2215 | const char *k = ts_node_type(node); |
| 2216 | |
| 2217 | if (strcmp(k, "assignment") == 0) { |
| 2218 | TSNode left = ts_node_child_by_field_name(node, "left", 4); |
| 2219 | TSNode right = ts_node_child_by_field_name(node, "right", 5); |
| 2220 | TSNode ann = ts_node_child_by_field_name(node, "type", 4); |
| 2221 | |
| 2222 | const CBMType *rhs_type = |
| 2223 | ts_node_is_null(right) ? cbm_type_unknown() : py_eval_expr_type(ctx, right); |
| 2224 | bool rhs_lexical_alias = false; |
| 2225 | const char *rhs_callable = |
| 2226 | ts_node_is_null(right) |
| 2227 | ? NULL |
| 2228 | : py_exact_callable_target_ex(ctx, right, NULL, &rhs_lexical_alias); |
| 2229 | /* A proven callable value on the right of an assignment is a reference |
| 2230 | * to that callable, exactly as it would be as a call argument -- the |
| 2231 | * policy is proven exact value => CALL_REFERENCE, and `cb = handler` |
| 2232 | * proves it or the alias binding below could not be created. Bare |
| 2233 | * identifier RHS only, in lockstep with the usage-carrier rule in |
| 2234 | * extract_usages.c; the strategy distinction mirrors the argument path |
| 2235 | * so a shadowed source name still satisfies the local-shadow guard. */ |
| 2236 | if (rhs_callable && strcmp(ts_node_type(right), "identifier") == 0) { |
| 2237 | char *rhs_name = py_node_text(ctx, right); |
| 2238 | py_emit_resolved_reference(ctx, rhs_callable, rhs_name, right, |
| 2239 | rhs_lexical_alias ? "lsp_callable_alias" |
| 2240 | : "lsp_callable_value_reference"); |
| 2241 | } |
| 2242 | |
| 2243 | // Annotated assignment: x: T = expr — annotation wins. |
| 2244 | bool has_annotation = !ts_node_is_null(ann); |
| 2245 | if (has_annotation) { |
| 2246 | char *ann_text = py_node_text(ctx, ann); |
| 2247 | if (ann_text && ann_text[0]) { |
| 2248 | rhs_type = py_resolve_annotation(ctx, ann_text); |
| 2249 | } |
| 2250 | } |
| 2251 | |
| 2252 | if (ts_node_is_null(left)) |
| 2253 | return; |
| 2254 | const char *lk = ts_node_type(left); |
| 2255 | // Tuple/list pattern unpacking: a, b = f() / [a, b] = f() / |
| 2256 | // (a, b) = f(). Each LHS element binds to the corresponding |
| 2257 | // element of the RHS tuple. *rest binds the remaining elements |
| 2258 | // as a list (best-effort: list of element type). |
| 2259 | if (strcmp(lk, "pattern_list") == 0 || strcmp(lk, "tuple_pattern") == 0 || |
| 2260 | strcmp(lk, "list_pattern") == 0 || strcmp(lk, "expression_list") == 0) { |
| 2261 | uint32_t lc = ts_node_named_child_count(left); |
| 2262 | // Collect RHS element types. |
| 2263 | const CBMType *const *rhs_elems = NULL; |
| 2264 | int rhs_count = 0; |
| 2265 | if (rhs_type) { |
| 2266 | if (rhs_type->kind == CBM_TYPE_TUPLE) { |
| 2267 | rhs_elems = rhs_type->data.tuple.elems; |
| 2268 | rhs_count = rhs_type->data.tuple.count; |
| 2269 | } else if (rhs_type->kind == CBM_TYPE_TEMPLATE && |
no test coverage detected