Skip to content

fix: exclude PHP scalar type-hint/cast keywords from ast --kind string#1965

Open
carlos-alm wants to merge 1 commit into
fix/issue-1819-exported-consts-with-non-recognizedfrom
fix/issue-1821-codegraph-ast-kind-string-misclassifies-php
Open

fix: exclude PHP scalar type-hint/cast keywords from ast --kind string#1965
carlos-alm wants to merge 1 commit into
fix/issue-1819-exported-consts-with-non-recognizedfrom
fix/issue-1821-codegraph-ast-kind-string-misclassifies-php

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

Closes #1821

Verification (non-tautological)

Per the #1729/#1822 precedent, I verified the new tests actually exercise the fix rather than passing vacuously:

  • Reverted the WASM guard (removed php from REQUIRES_NAMED_NODE), rebuilt dist/, reran the PHP WASM tests → 3 of them failed, showing 7 spurious string rows instead of 3 genuine literals.
  • Reverted the native guard (requires_named_node: false on PHP_AST_CONFIG), rebuilt + codesigned the native addon, reran the PHP native tests → same 3 failures, same 7-vs-3 mismatch.
  • Restored both fixes, rebuilt both sides again — all tests pass.

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 and ast_nodes row-count parity: php
  • npm test — full suite green: 3738 passed, 30 skipped, 2 todo, 0 failed
  • npm run lint — clean
  • cargo clippy --release — no new warnings (checked helpers.rs diff against pre-existing warnings on the base branch)
  • Native addon rebuilt via napi build --platform --release and codesigned; native tests ran against the rebuilt binary, not a stale one
  • codegraph 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.

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-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a false-positive in codegraph ast --kind string for PHP, where tree-sitter-php's primitive_type (scalar type-hints like string $x, : string return types) and cast_type ((string)) productions lex the string keyword as an anonymous grammar token whose kind() string is identical to the named string literal node — the same collision previously fixed for TypeScript/JS in #1729.

  • Native engine: adds requires_named_node: bool to LangAstConfig and gates classify_ast_node on node.is_named() when set; only PHP_AST_CONFIG sets it true, all 29 other language configs explicitly set false.
  • WASM/TS engine: extends REQUIRES_NAMED_NODE (previously JS_REQUIRES_NAMED_NODE) to include 'php', reusing the existing resolveAstKind() guard already threaded through all production call sites.
  • Tests: adds WASM and native PHP regression suites (text-content and exact-count assertions) plus parity and multi-lang tests covering parameter type-hints, return types, and casts.

Confidence Score: 4/5

The 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 does not misclassify (string) cast assertion checks line 12, which is a blank line in the fixture — the actual cast lives on line 14 — so that particular test passes vacuously even without the fix. The remaining tests (text-content guards and the exact-count assertion) do catch the regression, so there is no correctness gap in the shipped fix, but the cast-specific test does not exercise what its comment describes.

tests/parsers/ast-nodes.test.ts — the cast-keyword test checks the wrong line number.

Important Files Changed

Filename Overview
crates/codegraph-core/src/extractors/helpers.rs Adds requires_named_node: bool to LangAstConfig, threads the Node reference into classify_ast_node, and gates classification on node.is_named() when set. Only PHP_AST_CONFIG sets this true; all 29 other configs explicitly set false to make future additions intentional. Logic is correct and well-scoped.
src/ast-analysis/rules/index.ts Renames JS_REQUIRES_NAMED_NODE to REQUIRES_NAMED_NODE and adds 'php' to the set, extending the existing TypeScript/JS guard to PHP. Comment is accurate and well-documented.
src/ast-analysis/visitors/ast-store-visitor.ts Comment-only update to resolveAstKind's doc block, extending the issue reference to include PHP (#1821). No logic changes.
tests/engines/ast-parity.test.ts Extends the PHP parity fixture to include a typed parameter (string $label), a return type annotation, and a (string) cast, making the parity test exercise both engine paths against the new guard.
tests/parsers/ast-all-langs.test.ts Adds a native multi-language regression test asserting that no ast_nodes row with kind='string' from the PHP fixture has unquoted text='string', correctly verifying the fix end-to-end.
tests/parsers/ast-nodes.test.ts Adds WASM and native PHP describe blocks with 5 tests each covering parameter type-hints, return type-hints, casts, genuine literals, and exact counts. The does not misclassify (string) cast test checks line 12 (blank) instead of line 14 where the cast actually lives, making that specific assertion vacuously true — the other tests still catch the regression.

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
Loading
%%{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
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix: exclude PHP scalar type-hint/cast k..." | Re-trigger Greptile

Comment on lines +623 to +627
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);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Fix in Claude Code

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

1 functions changed9 callers affected across 3 files

  • astRequiresNamedNode in src/ast-analysis/rules/index.ts:417 (9 transitive callers)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant