Skip to content

fix(cpp): type member-call receivers from parameters and class fields (#3215) - #3240

Open
abhay-codes07 wants to merge 1 commit into
Graphify-Labs:v8from
abhay-codes07:fix/cpp-receiver-parameters
Open

fix(cpp): type member-call receivers from parameters and class fields (#3215)#3240
abhay-codes07 wants to merge 1 commit into
Graphify-Labs:v8from
abhay-codes07:fix/cpp-receiver-parameters

Conversation

@abhay-codes07

Copy link
Copy Markdown
Contributor

Closes #3215.

The problem

_resolve_cpp_member_calls types a member-call receiver from cpp_type_table — which was populated only from local variable declarations. Function parameters never entered it, so a call through const Thing& T / Thing* P / Thing V (the dominant C++ idiom for passing state) was silently skipped while the byte-identical call through a local resolved. Class-field receivers had the same gap. TS/JS have an explicit augmentation pass for exactly this; C++ had none.

There was a second, compounding half the issue's table exposes: even with the parameter in the table, the resolver's capitalized-receiver arm treated T.IsOk() as an explicit type name (Foo::bar() tier) and bailed when no type called T exists — and capitalized parameter names (T, V, Inner) are everywhere in C++.

The change

Per-file table (engine): the table now folds in, in C++'s own shadowing order enforced by its first-write-wins —

  1. local declarations (unchanged),
  2. the enclosing function's parameters (_cpp_parameter_types — same precision rules: class-like type nodes only, qualified names keyed by their simple tail),
  3. class/struct member declarations (_cpp_field_types, lowest precedence).

_cpp_declarator_name learns field_identifier (fields use it where locals use identifier).

Resolver (corpus): when the capitalized-receiver arm finds no unique type of that name, it falls back to the file's var→type table exactly like the lowercase arm — mirroring what the C# resolver already does for Pascal-cased locals. A table miss still bails; nothing is ever guessed, and chained receivers (B.Inner.IsOk()) stay deferred.

Tests

tests/test_cpp_receiver_parameters.py — 6 tests built on the issue's exact repro: all four receiver forms (local, const&, *, by-value) resolve like the local does; a class-field receiver resolves; a shadowing local can never bind through the parameter's type; builtin-typed parameters contribute nothing; chained receivers emit nothing wrong; and the pre-existing tiers (scoped call, local var) are unchanged. With the fix reverted, 3 of 6 fail. The C/C++ and language suites are unchanged (450 passed, the one exception being the known Windows-only markdown-unicode baseline failure); the full suite matches the fresh v8 (0.9.53) baseline.

…Graphify-Labs#3215)

_resolve_cpp_member_calls types a receiver from cpp_type_table, which was
populated ONLY from local variable declarations - parameters never entered
it, so a call through `const Thing& T` / `Thing* P` / `Thing V` (the
dominant C++ idiom for passing state) was silently skipped while the
identical call through a local resolved. TS/JS have an explicit
augmentation pass for exactly this; C++ had none.

Two halves:

  * the per-file table now folds in the enclosing function's parameters
    (_cpp_parameter_types) and class/struct member declarations
    (_cpp_field_types), in C++'s own shadowing order - a local beats a
    parameter beats a field, enforced by the table's first-write-wins;
    the same precision rules as locals apply (class-like type nodes only,
    qualified names keyed by their simple tail, multi-declarator and
    builtin-typed entries contribute nothing);
  * the resolver's capitalized-receiver arm treated `T.IsOk()` as an
    explicit type name and bailed when no type called T exists - but a
    capitalized receiver is very often a VARIABLE (parameter T, field
    Inner). When the explicit-type lookup finds no unique type, it now
    falls back to the file's var->type table exactly like the lowercase
    arm, mirroring what the C# resolver already does for Pascal-cased
    locals; a table miss still bails rather than guessing.

_cpp_declarator_name learns field_identifier (fields use it where locals
use identifier). Chained receivers (B.Inner.IsOk()) stay deferred.
Copilot AI lite review requested due to automatic review settings August 31, 2026 11:50

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Resolves C++ member calls made through parameter and field receivers (#3215), so idioms like T.IsOk() on a const Thing& T parameter or Inner.IsOk() on a class field now emit calls edges instead of being silently dropped — previously only local-variable receivers were typed. Adds _cpp_parameter_types and _cpp_field_types to augment the type table in first-write-wins order (local beats parameter beats field, matching C++ name lookup), and teaches _cpp_declarator_name to read field_identifier nodes. Loosens _resolve_cpp_member_calls so a capitalized receiver with no unique type definition falls back to the file's var→type table like the lowercase arm, still bailing on ambiguity rather than guessing.

Worth a look

  • C++ field receiver types leak across classesgraphify/extractors/engine.py:1642 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2015 functions depend on the 442 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 522 callers, 43 callees
  • new: _rebuild_code() — 113 callers, 50 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 123 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 33 more — each is listed as a finding

Verification — 2015 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: 1850 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify \_resolve\_cpp\_member\_calls.

The verifier did not have enough to check \_resolve\_cpp\_member\_calls, 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: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

Could not verify: Could not verify \_cpp\_declarator\_name.

The verifier did not have enough to check \_cpp\_declarator\_name, 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: not verifiable: all 189 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: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 41 more finding(s) on lines outside this diff (see the check run).

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

Labels

None yet

Projects

None yet

2 participants