Repository navigation
fix(cpp): don't mint sourceless god nodes for Metal/CUDA GPU qualifiers - #4554
rajatnagda45 wants to merge 1 commit into
Conversation
Metal (.metal) and CUDA (.cu/.cuh) are C++-derived GPU languages that route
through the C++ extractor, but their qualifiers are not part of the C++ grammar:
- Metal address-space / function qualifiers: `device`, `constant`,
`threadgroup`, `thread`, `kernel`, `vertex`, `fragment`, ...
- CUDA execution-space / memory qualifiers: `__global__`, `__device__`,
`__host__`, `__shared__`, `__constant__`, `__forceinline__`, ...
tree-sitter-cpp parses a leading qualifier as the declaration's
`type_identifier` (the real type is pushed into an ERROR node), and
`_cpp_collect_type_refs` emitted it as a type reference. Each qualifier then
became a sourceless, corpus-shared god node that every shader/kernel
`references` (e.g. `kernel`/`device`/`constant` on sample.metal,
`__global__`/`__device__`/`__host__` on sample.cu, the CUDA ones colliding on
ids `global`/`device`/`host`). That is a provenance violation — a node with no
source_file — and the hubs also pollute god-node rankings in analyze.
Skip a fixed per-extension set of dialect qualifier keywords in the C++
type-ref collector, keyed by file extension so plain `.cpp`/`.cc`/`.h` files
are untouched (there these words are legal identifiers and still resolve as
real user types). Genuine user types in the shader/kernel (the `Vec3` struct)
are preserved.
Documented limitation: `constant Foo* p` buries the real `Foo` type inside the
ERROR node, so `Foo` is not recovered; this change only removes the bogus
qualifier node.
|
Thanks for the pull request, @rajatnagda45. A maintainer will review it soon. Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions. A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic. |
There was a problem hiding this comment.
Graphify reviewed this change.
No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.
Not checked: tests were not run; formal verification proved 0 of 2 changed function(s) (2 not verified).
Formal verification. PR-changed functions: 0/2 verified (0 proven, 0 may-equivalent, 0 distinguished) · 2 not verified (1 vacuous, 1 unsupported).
Not verified on this run: \_cpp\_collect\_type\_refs (vacuous: never exercised), \_extract\_generic (unsupported).
Graphify review — findings
Stops Metal and CUDA qualifiers (kernel, device, __global__, __host__, etc.) from being emitted as type references, so they no longer become sourceless god nodes that every shader or kernel points at. tree-sitter-cpp misparses a leading qualifier as the declaration's type_identifier, so _cpp_collect_type_refs now takes a dialect_quals set and skips those names. The filter is chosen by file extension (.metal, .cu, .cuh) in _extract_generic, because these words are legal identifiers in plain C++. Plain .cpp, .cc and .h files are unaffected, so a real type named kernel or device there still resolves. Adds tests for both dialects that check no qualifier leaks as a sourceless node or references target, while the real Vec3 struct reference is preserved.
No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1376 functions depend on the 1017 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
extract_cpp()— 39 callers, 5 callees - worse:
walk()— 1 callers, 76 callees
Verification — 1376 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 1285 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
39 of 432 test file(s) selected (9%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_build.py— impacttests/test_cjs_module_extension.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_csharp_partial_classes.py— impacttests/test_dotnet.py— impacttests/test_ejs_extractor.py— impacttests/test_ext_tagged_id_clash.py— impacttests/test_extract.py— impacttests/test_extract_php_closures.py— impacttests/test_import_extension_resolution.py— impacttests/test_indirect_call_block_scoped_shadow.py— impacttests/test_indirect_dispatch.py— impacttests/test_indirect_dispatch_assign_return.py— impacttests/test_indirect_dispatch_getattr.py— impacttests/test_java_member_calls.py— impacttests/test_js_exported_scalar_bindings.py— impacttests/test_js_member_collisions.py— impacttests/test_languages.py— impact, changed-testtests/test_multilang.py— impacttests/test_php_unparsed_casts.py— impacttests/test_python_inherited_method_calls.py— impacttests/test_python_property_reads.py— impacttests/test_python_pytest_fixture_receivers.py— impacttests/test_python_underscore_resolution.py— impacttests/test_rationale.py— impacttests/test_ruby_resolution.py— impacttests/test_scala_context_bounds.py— impacttests/test_scala_given_instances.py— impacttests/test_scala_self_type.py— impacttests/test_scala_top_level_binding.py— impacttests/test_scala_type_definition.py— impacttests/test_svelte_extraction.py— impacttests/test_swift_computed_properties.py— impacttests/test_swift_protocol_requirements.py— impacttests/test_trailing_newline_not_a_syntax_error.py— impacttests/test_ts_new_expression_calls.py— impacttests/test_typescript_module_extensions.py— impacttests/test_vue_extraction.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Docs that may be stale (advisory)
graphify/skills/agents/references/query.md§ Step 1 — Traversal (lines 61-191): references changed symbolscollectgraphify/skills/amp/references/query.md§ Step 1 — Traversal (lines 61-191): references changed symbolscollectgraphify/skills/claude/references/query.md§ Step 1 — Traversal (lines 61-191): references changed symbolscollectgraphify/skills/claw/references/query.md§ Step 1 — Traversal (lines 61-191): references changed symbolscollectgraphify/skills/codex/references/query.md§ Step 1 — Traversal (lines 61-191): references changed symbolscollectgraphify/skills/copilot/references/query.md§ Step 1 — Traversal (lines 61-191): references changed symbolscollectgraphify/skills/droid/references/query.md§ Step 1 — Traversal (lines 61-191): references changed symbolscollectgraphify/skills/kilo/references/query.md§ Step 1 — Traversal (lines 61-191): references changed symbolscollectgraphify/skills/kiro/references/query.md§ Step 1 — Traversal (lines 61-191): references changed symbolscollectgraphify/skills/opencode/references/query.md§ Step 1 — Traversal (lines 61-191): references changed symbolscollect
…and 10 more.
Formal verification
Could not verify: Could not verify \_cpp\_collect\_type\_refs.
The verifier did not have enough to check \_cpp\_collect\_type\_refs, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: not verifiable: all 235 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `config` is annotated `LanguageConfig` — outside the synthesizable primitive/collection set
· 2 more finding(s) on lines outside this diff (see the check run).
Closes #4550.
Problem
Metal (
.metal) and CUDA (.cu/.cuh) are C++-derived GPU languages that route through the C++ extractor, but their qualifiers are not part of the C++ grammar:device,constant,threadgroup,thread,kernel,vertex,fragment, ...__global__,__device__,__host__,__shared__,__constant__,__forceinline__, ...tree-sitter-cpp parses a leading qualifier as the declaration's
type_identifier(the real type is pushed into anERRORnode), and_cpp_collect_type_refsemitted it as a type reference. Each qualifier then became a sourceless, corpus-shared god node that every shader/kernelreferences(e.g.kernel/device/constantonsample.metal,__global__/__device__/__host__onsample.cu). That is a provenance violation (a node with nosource_file), and the hubs pollute god-node rankings inanalyze.Implementation
_CPP_DIALECT_TYPE_QUALIFIERS, keyed by.metal/.cu/.cuh).dialect_qualsskip set into_cpp_collect_type_refs(the single choke point for everytype_identifieremission) and compute it once per file from the extension..cpp/.cc/.hare untouched — there these words are legal identifiers and still resolve as real user types.Genuine user types in the shader/kernel (the
Vec3struct) are preserved.Verification
tests/test_languages.py:test_metal_qualifiers_do_not_mint_sourceless_god_nodes,test_cuda_execution_space_qualifiers_do_not_mint_sourceless_god_nodes(both fail on pre-fix v8, pass after).uv/venv commands run:python -m pytest tests/ -q --ignore=tests/test_security.py→ 8331 passed, 15 skipped (deselecting the live-DNS security tests and the built-wheel test that need a network/packaged wheel).ruff check .→ clean.pyright graphify/extractors/engine.py→ unchanged from baseline (0 added).Limitations
constant Foo* pburies the realFootype inside theERRORnode, soFoois not recovered; this change only removes the bogus qualifier node, it does not add the lost type reference. Metal scalar vector types (uint,float4) are left as-is (they are types, not qualifiers).