| 1733 | } |
| 1734 | |
| 1735 | static int parse_narrowing_one(PHPLSPContext *ctx, TSNode cond, php_narrowing_t *out) { |
| 1736 | if (ts_node_is_null(cond)) |
| 1737 | return 0; |
| 1738 | const char *kind = ts_node_type(cond); |
| 1739 | if (strcmp(kind, "parenthesized_expression") == 0) { |
| 1740 | uint32_t nc = ts_node_child_count(cond); |
| 1741 | for (uint32_t i = 0; i < nc; i++) { |
| 1742 | TSNode c = ts_node_child(cond, i); |
| 1743 | if (!ts_node_is_null(c) && ts_node_is_named(c)) { |
| 1744 | return parse_narrowing_one(ctx, c, out); |
| 1745 | } |
| 1746 | } |
| 1747 | return 0; |
| 1748 | } |
| 1749 | |
| 1750 | /* `$x instanceof Foo` — emitted as binary_expression with operator |
| 1751 | * "instanceof" in tree-sitter-php. */ |
| 1752 | if (strcmp(kind, "binary_expression") == 0) { |
| 1753 | TSNode left = ts_node_child_by_field_name(cond, "left", 4); |
| 1754 | TSNode op = ts_node_child_by_field_name(cond, "operator", 8); |
| 1755 | TSNode right = ts_node_child_by_field_name(cond, "right", 5); |
| 1756 | if (ts_node_is_null(left) || ts_node_is_null(right)) |
| 1757 | return false; |
| 1758 | char *opt = !ts_node_is_null(op) ? php_node_text(ctx, op) : NULL; |
| 1759 | if (opt && strcmp(opt, "instanceof") == 0) { |
| 1760 | if (!node_is(left, "variable_name")) |
| 1761 | return false; |
| 1762 | char *vt = php_node_text(ctx, left); |
| 1763 | if (!vt) |
| 1764 | return false; |
| 1765 | const char *vname = (vt[0] == '$') ? vt + 1 : vt; |
| 1766 | char *rt = php_node_text(ctx, right); |
| 1767 | if (!rt) |
| 1768 | return false; |
| 1769 | const char *resolved = php_resolve_class_name(ctx, rt); |
| 1770 | if (!resolved) |
| 1771 | return false; |
| 1772 | out->var_name = cbm_arena_strdup(ctx->arena, vname); |
| 1773 | out->type = cbm_type_named(ctx->arena, resolved); |
| 1774 | return true; |
| 1775 | } |
| 1776 | /* `$x !== null` / `$x != null` / `null !== $x` — narrow to "non-null": |
| 1777 | * we keep the existing scope type, so this is a no-op for type |
| 1778 | * purposes but suppresses null branches downstream. We don't |
| 1779 | * subtract nullable so just return false here. */ |
| 1780 | } |
| 1781 | |
| 1782 | /* `is_string($x)` / `is_int($x)` / `array_key_exists(...)` / |
| 1783 | * `method_exists($x, 'foo')` / `is_a($x, Foo::class)` / ... */ |
| 1784 | if (strcmp(kind, "function_call_expression") == 0) { |
| 1785 | TSNode fn = ts_node_child_by_field_name(cond, "function", 8); |
| 1786 | TSNode args = ts_node_child_by_field_name(cond, "arguments", 9); |
| 1787 | if (ts_node_is_null(fn)) |
| 1788 | return false; |
| 1789 | char *name = php_node_text(ctx, fn); |
| 1790 | if (!name) |
| 1791 | return false; |
| 1792 | /* `is_a($x, Foo::class)` and `is_a($x, 'Foo')` narrow $x to Foo. */ |
no test coverage detected