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
| 2194 | * scope sequentially for the remainder of the enclosing function. |
| 2195 | */ |
| 2196 | static void process_if_statement(PHPLSPContext *ctx, TSNode node) { |
| 2197 | /* Find condition: first parenthesized_expression child. */ |
| 2198 | TSNode cond; |
| 2199 | memset(&cond, 0, sizeof(cond)); |
| 2200 | uint32_t nc = ts_node_child_count(node); |
| 2201 | for (uint32_t i = 0; i < nc; i++) { |
| 2202 | TSNode c = ts_node_child(node, i); |
| 2203 | if (ts_node_is_null(c) || !ts_node_is_named(c)) |
| 2204 | continue; |
| 2205 | if (strcmp(ts_node_type(c), "parenthesized_expression") == 0) { |
| 2206 | cond = c; |
| 2207 | break; |
| 2208 | } |
| 2209 | } |
| 2210 | /* Collect all conjunctive narrowings: `$x instanceof Foo && is_int($y)` |
| 2211 | * narrows BOTH $x and $y in the if-body. */ |
| 2212 | php_narrowing_t nws[8] = {{0}}; |
| 2213 | int nw_count = ts_node_is_null(cond) ? 0 : parse_narrowing_collect(ctx, cond, nws, 8); |
| 2214 | bool has_nw = nw_count > 0; |
| 2215 | |
| 2216 | /* Detect negative narrowing: condition is `!P` and P is a narrowing |
| 2217 | * predicate. We unwrap one level of unary `!`. */ |
| 2218 | php_narrowing_t neg_nw = {0}; |
| 2219 | bool has_neg_nw = false; |
| 2220 | if (!ts_node_is_null(cond)) { |
| 2221 | TSNode inner; |
| 2222 | memset(&inner, 0, sizeof(inner)); |
| 2223 | /* Unwrap parenthesized_expression. */ |
| 2224 | TSNode probe = cond; |
| 2225 | for (int loop = 0; loop < 4; loop++) { |
| 2226 | if (strcmp(ts_node_type(probe), "parenthesized_expression") == 0) { |
| 2227 | uint32_t pnc = ts_node_child_count(probe); |
| 2228 | TSNode next; |
| 2229 | memset(&next, 0, sizeof(next)); |
| 2230 | for (uint32_t i = 0; i < pnc; i++) { |
| 2231 | TSNode c = ts_node_child(probe, i); |
| 2232 | if (!ts_node_is_null(c) && ts_node_is_named(c)) { |
| 2233 | next = c; |
| 2234 | break; |
| 2235 | } |
| 2236 | } |
| 2237 | if (ts_node_is_null(next)) |
| 2238 | break; |
| 2239 | probe = next; |
| 2240 | } else { |
| 2241 | break; |
| 2242 | } |
| 2243 | } |
| 2244 | if (strcmp(ts_node_type(probe), "unary_op_expression") == 0) { |
| 2245 | /* Find the operator child; if it's `!`, parse the inner expr. */ |
| 2246 | TSNode op = ts_node_child_by_field_name(probe, "operator", 8); |
| 2247 | char *opt = !ts_node_is_null(op) ? php_node_text(ctx, op) : NULL; |
| 2248 | if (opt && strcmp(opt, "!") == 0) { |
| 2249 | TSNode operand = ts_node_child_by_field_name(probe, "operand", 7); |
| 2250 | if (ts_node_is_null(operand)) { |
| 2251 | uint32_t unc = ts_node_child_count(probe); |
| 2252 | for (uint32_t i = 0; i < unc; i++) { |
| 2253 | TSNode c = ts_node_child(probe, i); |
no test coverage detected