| 1256 | /* ── statement processing ───────────────────────────────────────── */ |
| 1257 | |
| 1258 | static void cs_process_local_decl(CSLSPContext *ctx, TSNode node) { |
| 1259 | /* local_declaration_statement -> variable_declaration */ |
| 1260 | TSNode vd = cs_child_named_kind(node, "variable_declaration"); |
| 1261 | if (ts_node_is_null(vd)) return; |
| 1262 | TSNode tnode = ts_node_child_by_field_name(vd, "type", 4); |
| 1263 | if (ts_node_is_null(tnode)) tnode = cs_first_named_child(vd); |
| 1264 | /* For each declarator, bind the variable to (rhs_type or declared type). */ |
| 1265 | uint32_t nc = ts_node_child_count(vd); |
| 1266 | bool is_var = false; |
| 1267 | if (!ts_node_is_null(tnode)) { |
| 1268 | char *tt = cs_node_text(ctx, tnode); |
| 1269 | if (tt && (strcmp(tt, "var") == 0 || strcmp(ts_node_type(tnode), "implicit_type") == 0)) { |
| 1270 | is_var = true; |
| 1271 | } |
| 1272 | } |
| 1273 | const CBMType *declared_type = ts_node_is_null(tnode) ? cbm_type_unknown() |
| 1274 | : cs_parse_type_node(ctx, tnode); |
| 1275 | for (uint32_t i = 0; i < nc; i++) { |
| 1276 | TSNode c = ts_node_child(vd, i); |
| 1277 | if (ts_node_is_null(c) || !ts_node_is_named(c)) continue; |
| 1278 | const char *k = ts_node_type(c); |
| 1279 | if (strcmp(k, "variable_declarator") != 0) continue; |
| 1280 | TSNode nm = cs_child_named_kind(c, "identifier"); |
| 1281 | if (ts_node_is_null(nm)) { |
| 1282 | nm = ts_node_child_by_field_name(c, "name", 4); |
| 1283 | } |
| 1284 | if (ts_node_is_null(nm)) continue; |
| 1285 | char *vname = cs_node_text(ctx, nm); |
| 1286 | if (!vname) continue; |
| 1287 | /* Find initializer (= rhs). variable_declarator children depend on |
| 1288 | * the grammar: tree-sitter-c-sharp emits `_identifier_or_global` |
| 1289 | * (the name) + optional `=` token + value-expression; the value |
| 1290 | * may also be wrapped in equals_value_clause in some grammar |
| 1291 | * variants. Try the field-name path first, then fall back to |
| 1292 | * walking named children for any expression after the identifier. */ |
| 1293 | TSNode init = ts_node_child_by_field_name(c, "value", 5); |
| 1294 | const CBMType *rhs_t = NULL; |
| 1295 | if (!ts_node_is_null(init)) { |
| 1296 | if (strcmp(ts_node_type(init), "equals_value_clause") == 0) { |
| 1297 | TSNode rhs = cs_first_named_child(init); |
| 1298 | if (!ts_node_is_null(rhs)) rhs_t = cs_eval_expr_type(ctx, rhs); |
| 1299 | } else { |
| 1300 | rhs_t = cs_eval_expr_type(ctx, init); |
| 1301 | } |
| 1302 | } |
| 1303 | if (!rhs_t || rhs_t->kind == CBM_TYPE_UNKNOWN) { |
| 1304 | /* Walk named children: skip identifier (the name), evaluate |
| 1305 | * the next named child as the rhs expression. Also catch the |
| 1306 | * legacy equals_value_clause shape. */ |
| 1307 | uint32_t cc = ts_node_child_count(c); |
| 1308 | int seen_named = 0; |
| 1309 | for (uint32_t j = 0; j < cc; j++) { |
| 1310 | TSNode cn = ts_node_child(c, j); |
| 1311 | if (ts_node_is_null(cn) || !ts_node_is_named(cn)) continue; |
| 1312 | const char *ck = ts_node_type(cn); |
| 1313 | if (strcmp(ck, "equals_value_clause") == 0) { |
| 1314 | TSNode rhs = cs_first_named_child(cn); |
| 1315 | if (!ts_node_is_null(rhs)) { |
no test coverage detected