fix: exclude PHP scalar type-hint/cast keywords from ast --kind string#1965
Conversation
tree-sitter-php's primitive_type production (scalar type-hints like `string $x` and `: string` return types) and cast_type production (`(string) $x`) both lex the `string` keyword as an anonymous grammar token whose type string is identical to the named `string` literal node type -- the same collision as TypeScript's predefined_type (#1729). Without a named-node guard, `codegraph ast --kind string` misclassified these keyword occurrences as string literals. Fixes both engines: - Native: add `requires_named_node` to `LangAstConfig`, checked in `classify_ast_node` via `node.is_named()`; set true only for PHP. - WASM/TS: extend `astRequiresNamedNode()` (already scoped to JS/TS/TSX from #1729) to also cover `php`, reusing the existing `resolveAstKind()` guard threaded through all three production call sites. Verified non-tautological: reverting each side's guard and rebuilding reproduces the false positives (7 rows instead of 3) that the new tests catch, on both the WASM and native paths. Impact: 1 functions changed, 9 affected
Greptile SummaryThis PR fixes a false-positive in
Confidence Score: 4/5The fix is safe to merge; the core guard logic in both engines is correct and does not touch any language other than PHP. The Rust and WASM fixes are correct and symmetric. The only wrinkle is in the new PHP test suite: the tests/parsers/ast-nodes.test.ts — the cast-keyword test checks the wrong line number. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[tree-sitter walks PHP AST node] --> B{requires_named_node?}
B -- false --> D[classify by kind string]
B -- true --> C{node.is_named?}
C -- false --> E[return None — skip anonymous token]
C -- true --> D
D --> F{kind matches config?}
F -- string_types --> G[emit AstNode kind=string]
F -- new_types --> H[emit AstNode kind=new]
F -- throw_types --> I[emit AstNode kind=throw]
F -- no match --> J[skip]
subgraph WASM side
K[resolveAstKind] --> L{astRequiresNamedNode langId?}
L -- php/ts/tsx/js --> M{node.isNamed?}
M -- false --> N[skip anonymous token]
M -- true --> O[emit AstNode]
L -- other lang --> O
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[tree-sitter walks PHP AST node] --> B{requires_named_node?}
B -- false --> D[classify by kind string]
B -- true --> C{node.is_named?}
C -- false --> E[return None — skip anonymous token]
C -- true --> D
D --> F{kind matches config?}
F -- string_types --> G[emit AstNode kind=string]
F -- new_types --> H[emit AstNode kind=new]
F -- throw_types --> I[emit AstNode kind=throw]
F -- no match --> J[skip]
subgraph WASM side
K[resolveAstKind] --> L{astRequiresNamedNode langId?}
L -- php/ts/tsx/js --> M{node.isNamed?}
M -- false --> N[skip anonymous token]
M -- true --> O[emit AstNode]
L -- other lang --> O
end
Reviews (1): Last reviewed commit: "fix: exclude PHP scalar type-hint/cast k..." | Re-trigger Greptile |
| test('does not misclassify (string) cast as kind:string', () => { | ||
| // `(string) $value` (line 12) must not contribute a bare "string" row. | ||
| const nodes = queryPhpAstNodes('string'); | ||
| expect(nodes.some((n) => n.line === 12)).toBe(false); | ||
| }); |
There was a problem hiding this comment.
Cast-test assertion checks a blank line, not the cast line
PHP_FIXTURE_CODE places return (string) $value; on line 14 (counting from the first line of the template literal: <?php → 1, blank → 2, class Greeter { → 3, greet(...) → 4, …, normalize body → 14). Line 12 is a blank line, so nodes.some((n) => n.line === 12) is always false regardless of whether the fix is in place. The cast false-positive (when present) appears at line 14, so this specific assertion would pass vacuously even with the guard reverted. The other tests in the suite (text === 'string' guards and the exact-count test) still catch the regression, so correctness is not broken — but this test does not verify what its comment claims.
Codegraph Impact Analysis1 functions changed → 9 callers affected across 3 files
|
Summary
codegraph ast --kind stringmatched PHP AST nodes bynode.kind()string alone. tree-sitter-php'sprimitive_typeproduction (scalar type-hints likestring $xand: stringreturn types) andcast_typeproduction ((string) $x) both lex thestringkeyword as an anonymous grammar token whosekind()string is identical to the namedstringliteral node type — the same collision as TypeScript'spredefined_typeproduction fixed in codegraph ast --kind string misclassifies TypeScript primitive-type annotations as string literals #1729/fix: exclude primitive type keywords from ast --kind string matching #1822.requires_named_node: booltoLangAstConfig(crates/codegraph-core/src/extractors/helpers.rs), checked vianode.is_named()inclassify_ast_node. Settrueonly forPHP_AST_CONFIG; all other 29 language configs getfalseexplicitly (no behavior change for them).astRequiresNamedNode()insrc/ast-analysis/rules/index.ts— already scoped tojavascript/typescript/tsxfrom codegraph ast --kind string misclassifies TypeScript primitive-type annotations as string literals #1729 — to also coverphp. This reuses the existingresolveAstKind()guard inast-store-visitor.ts, already threaded through all three production call sites (features/ast.ts,ast-analysis/engine.ts,domain/wasm-worker-entry.ts).Closes #1821
Verification (non-tautological)
Per the #1729/#1822 precedent, I verified the new tests actually exercise the fix rather than passing vacuously:
phpfromREQUIRES_NAMED_NODE), rebuiltdist/, reran the PHP WASM tests → 3 of them failed, showing 7 spuriousstringrows instead of 3 genuine literals.requires_named_node: falseonPHP_AST_CONFIG), rebuilt + codesigned the native addon, reran the PHP native tests → same 3 failures, same 7-vs-3 mismatch.Test plan
npx vitest run tests/parsers/ast-nodes.test.ts tests/parsers/ast-all-langs.test.ts tests/engines/ast-parity.test.ts— 87 passed, including new PHP WASM + native regression tests andast_nodes row-count parity: phpnpm test— full suite green: 3738 passed, 30 skipped, 2 todo, 0 failednpm run lint— cleancargo clippy --release— no new warnings (checkedhelpers.rsdiff against pre-existing warnings on the base branch)napi build --platform --releaseand codesigned; native tests ran against the rebuilt binary, not a stale onecodegraph diff-impact --staged -T— 1 function changed (astRequiresNamedNode), 9 transitive callers across 3 files, all expected (the three production call sites + their test coverage)Stacked on #1819/#1820 (base branch
fix/issue-1819-exported-consts-with-non-recognized) — only the ast-analysis/extractor/test diff is this issue's change.