Skip to content

fix(cpp): don't mint sourceless god nodes for Metal/CUDA GPU qualifiers - #4554

Open
rajatnagda45 wants to merge 1 commit into
Graphify-Labs:v8from
rajatnagda45:fix/gpu-dialect-qualifier-phantoms
Open

rajatnagda45 wants to merge 1 commit into
Graphify-Labs:v8from
rajatnagda45:fix/gpu-dialect-qualifier-phantoms

Conversation

@rajatnagda45

Copy link
Copy Markdown
Contributor

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:

  • 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). That is a provenance violation (a node with no source_file), and the hubs pollute god-node rankings in analyze.

Implementation

  • Add a fixed, per-extension set of GPU-dialect qualifier keywords (_CPP_DIALECT_TYPE_QUALIFIERS, keyed by .metal / .cu / .cuh).
  • Thread a dialect_quals skip set into _cpp_collect_type_refs (the single choke point for every type_identifier emission) and compute it once per file from the extension.
  • Plain .cpp/.cc/.h 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.

Verification

  • New regressions in 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* p buries the real Foo type inside the ERROR node, so Foo is 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).

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.
@github-actions

Copy link
Copy Markdown

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.

@github-actions github-actions Bot added the contributor Pull request from a returning community contributor label Oct 18, 2026

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 — impact
  • tests/test_build.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_ejs_extractor.py — impact
  • tests/test_ext_tagged_id_clash.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_php_closures.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_indirect_call_block_scoped_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_java_member_calls.py — impact
  • tests/test_js_exported_scalar_bindings.py — impact
  • tests/test_js_member_collisions.py — impact
  • tests/test_languages.py — impact, changed-test
  • tests/test_multilang.py — impact
  • tests/test_php_unparsed_casts.py — impact
  • tests/test_python_inherited_method_calls.py — impact
  • tests/test_python_property_reads.py — impact
  • tests/test_python_pytest_fixture_receivers.py — impact
  • tests/test_python_underscore_resolution.py — impact
  • tests/test_rationale.py — impact
  • tests/test_ruby_resolution.py — impact
  • tests/test_scala_context_bounds.py — impact
  • tests/test_scala_given_instances.py — impact
  • tests/test_scala_self_type.py — impact
  • tests/test_scala_top_level_binding.py — impact
  • tests/test_scala_type_definition.py — impact
  • tests/test_svelte_extraction.py — impact
  • tests/test_swift_computed_properties.py — impact
  • tests/test_swift_protocol_requirements.py — impact
  • tests/test_trailing_newline_not_a_syntax_error.py — impact
  • tests/test_ts_new_expression_calls.py — impact
  • tests/test_typescript_module_extensions.py — impact
  • tests/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)

…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).

This branch has not been deployed

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

Labels

contributor Pull request from a returning community contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Metal/CUDA GPU qualifiers (kernel, __global__, device, ...) mint sourceless god nodes

1 participant