fix(cpp): type member-call receivers from parameters and class fields (#3215) - #3240
fix(cpp): type member-call receivers from parameters and class fields (#3215)#3240abhay-codes07 wants to merge 1 commit into
Conversation
…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.
There was a problem hiding this comment.
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 classes —
graphify/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).
Closes #3215.
The problem
_resolve_cpp_member_callstypes a member-call receiver fromcpp_type_table— which was populated only from local variable declarations. Function parameters never entered it, so a call throughconst 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 calledTexists — 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 —
_cpp_parameter_types— same precision rules: class-like type nodes only, qualified names keyed by their simple tail),_cpp_field_types, lowest precedence)._cpp_declarator_namelearnsfield_identifier(fields use it where locals useidentifier).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 freshv8(0.9.53) baseline.