Walk an `if_statement` (or `else_if_clause`) honoring narrowing. * * Tree-sitter-php's `if_statement` emits children as: an optional `if` * keyword, a parenthesized_expression (condition), and one or more * statement-shaped bodies (the if-body, optionally followed by * else_if_clause / else_clause). We narrow on the condition then walk * EVERY non-condition named child as the body — only the
| 2012 | * scope sequentially for the remainder of the enclosing function. |
| 2013 | */ |
| 2014 | static void process_if_statement(PHPLSPContext *ctx, TSNode node) { |
| 2015 | /* Find condition: first parenthesized_expression child. */ |
| 2016 | TSNode cond; |
| 2017 | memset(&cond, 0, sizeof(cond)); |
| 2018 | uint32_t nc = ts_node_child_count(node); |
| 2019 | for (uint32_t i = 0; i < nc; i++) { |
| 2020 | TSNode c = ts_node_child(node, i); |
| 2021 | if (ts_node_is_null(c) || !ts_node_is_named(c)) |
| 2022 | continue; |
| 2023 | if (strcmp(ts_node_type(c), "parenthesized_expression") == 0) { |
| 2024 | cond = c; |
| 2025 | break; |
| 2026 | } |
| 2027 | } |
| 2028 | /* Collect all conjunctive narrowings: `$x instanceof Foo && is_int($y)` |
| 2029 | * narrows BOTH $x and $y in the if-body. */ |
| 2030 | php_narrowing_t nws[8] = {{0}}; |
| 2031 | int nw_count = ts_node_is_null(cond) ? 0 : parse_narrowing_collect(ctx, cond, nws, 8); |
| 2032 | bool has_nw = nw_count > 0; |
| 2033 | |
| 2034 | /* Detect negative narrowing: condition is `!P` and P is a narrowing |
| 2035 | * predicate. We unwrap one level of unary `!`. */ |
| 2036 | php_narrowing_t neg_nw = {0}; |
| 2037 | bool has_neg_nw = false; |
| 2038 | if (!ts_node_is_null(cond)) { |
| 2039 | TSNode inner; |
| 2040 | memset(&inner, 0, sizeof(inner)); |
| 2041 | /* Unwrap parenthesized_expression. */ |
| 2042 | TSNode probe = cond; |
| 2043 | for (int loop = 0; loop < 4; loop++) { |
| 2044 | if (strcmp(ts_node_type(probe), "parenthesized_expression") == 0) { |
| 2045 | uint32_t pnc = ts_node_child_count(probe); |
| 2046 | TSNode next; |
| 2047 | memset(&next, 0, sizeof(next)); |
| 2048 | for (uint32_t i = 0; i < pnc; i++) { |
| 2049 | TSNode c = ts_node_child(probe, i); |
| 2050 | if (!ts_node_is_null(c) && ts_node_is_named(c)) { |
| 2051 | next = c; |
| 2052 | break; |
| 2053 | } |
| 2054 | } |
| 2055 | if (ts_node_is_null(next)) |
| 2056 | break; |
| 2057 | probe = next; |
| 2058 | } else { |
| 2059 | break; |
| 2060 | } |
| 2061 | } |
| 2062 | if (strcmp(ts_node_type(probe), "unary_op_expression") == 0) { |
| 2063 | /* Find the operator child; if it's `!`, parse the inner expr. */ |
| 2064 | TSNode op = ts_node_child_by_field_name(probe, "operator", 8); |
| 2065 | char *opt = !ts_node_is_null(op) ? php_node_text(ctx, op) : NULL; |
| 2066 | if (opt && strcmp(opt, "!") == 0) { |
| 2067 | TSNode operand = ts_node_child_by_field_name(probe, "operand", 7); |
| 2068 | if (ts_node_is_null(operand)) { |
| 2069 | uint32_t unc = ts_node_child_count(probe); |
| 2070 | for (uint32_t i = 0; i < unc; i++) { |
| 2071 | TSNode c = ts_node_child(probe, i); |
no test coverage detected