| 1455 | } |
| 1456 | |
| 1457 | static void process_foreach(PHPLSPContext *ctx, TSNode node) { |
| 1458 | /* Try to extract the iterable expression's element type. tree-sitter-php |
| 1459 | * emits the iterable as the first sibling expression after `foreach (`, |
| 1460 | * and the loop var is the variable_name child after `as`. |
| 1461 | * |
| 1462 | * Element-type resolution rules: |
| 1463 | * - iterable is array<T> / list<T> / iterable<T> (TEMPLATE) → T |
| 1464 | * - iterable is array<K, V> (TEMPLATE arg_count=2) → V (value) |
| 1465 | * - iterable is a NAMED collection-like type whose `current()` method |
| 1466 | * has a known return type (e.g. Iterator) → that |
| 1467 | * - otherwise → UNKNOWN |
| 1468 | */ |
| 1469 | TSNode iterable; |
| 1470 | memset(&iterable, 0, sizeof(iterable)); |
| 1471 | TSNode loop_vars[4]; |
| 1472 | int loop_var_count = 0; |
| 1473 | memset(loop_vars, 0, sizeof(loop_vars)); |
| 1474 | |
| 1475 | /* tree-sitter-php emits foreach_statement children in source order: |
| 1476 | * <iterable expression> ... `as` ... <loop var(s)> ... <body> |
| 1477 | * The iterable can be ANY expression: variable_name, member_call, |
| 1478 | * function_call_expression, member_access_expression, etc. The loop |
| 1479 | * vars are always variable_name. The body is compound_statement / |
| 1480 | * colon_block / a statement. |
| 1481 | * |
| 1482 | * We find the iterable as the first named child whose kind is an |
| 1483 | * expression-shape; subsequent variable_name children before the body |
| 1484 | * are the loop vars. */ |
| 1485 | uint32_t nc = ts_node_child_count(node); |
| 1486 | for (uint32_t i = 0; i < nc; i++) { |
| 1487 | TSNode c = ts_node_child(node, i); |
| 1488 | if (ts_node_is_null(c) || !ts_node_is_named(c)) |
| 1489 | continue; |
| 1490 | const char *k = ts_node_type(c); |
| 1491 | if (strcmp(k, "compound_statement") == 0 || strcmp(k, "colon_block") == 0 || |
| 1492 | strcmp(k, "expression_statement") == 0 || strcmp(k, "echo_statement") == 0 || |
| 1493 | strcmp(k, "return_statement") == 0 || strcmp(k, "if_statement") == 0 || |
| 1494 | strcmp(k, "for_statement") == 0 || strcmp(k, "while_statement") == 0 || |
| 1495 | strcmp(k, "switch_statement") == 0 || strcmp(k, "try_statement") == 0) { |
| 1496 | break; /* reached body */ |
| 1497 | } |
| 1498 | if (ts_node_is_null(iterable)) { |
| 1499 | iterable = c; |
| 1500 | } else if (strcmp(k, "variable_name") == 0 && loop_var_count < 4) { |
| 1501 | loop_vars[loop_var_count++] = c; |
| 1502 | } else if (strcmp(k, "pair") == 0) { |
| 1503 | /* `$k => $v` — extract both variable_names; the second is the |
| 1504 | * value, gets the elem_type. */ |
| 1505 | uint32_t pnc = ts_node_child_count(c); |
| 1506 | for (uint32_t j = 0; j < pnc; j++) { |
| 1507 | TSNode pc = ts_node_child(c, j); |
| 1508 | if (!ts_node_is_null(pc) && ts_node_is_named(pc) && |
| 1509 | strcmp(ts_node_type(pc), "variable_name") == 0 && loop_var_count < 4) { |
| 1510 | loop_vars[loop_var_count++] = pc; |
| 1511 | } |
| 1512 | } |
| 1513 | } |
| 1514 | } |
no test coverage detected