Skip to content

fix(cpp): preserve template ownership and guarded initializer calls - #5115

Closed
xRedLeader wants to merge 6 commits into
Graphify-Labs:v8from
xRedLeader:codex/cpp-template-conditionals-v105
Closed

xRedLeader wants to merge 6 commits into
Graphify-Labs:v8from
xRedLeader:codex/cpp-template-conditionals-v105

Conversation

@xRedLeader

@xRedLeader xRedLeader commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

C++ template wrappers can lose member ownership/reference-returning declarations, while directives inside initializer or argument lists can leave otherwise valid source partially extracted. This preserves template member families, matches narrowly proven same-file primary-template owners, and recovers bounded branch-wise syntax with original source positions and diagnostics. Dependent, unknown and ambiguous receivers remain unresolved.

Recovered call guards survive later resolution, deferred cross-repository calls and linking. This builds on #5098 and preserves its 0.9.105 follow-up.

Earlier verification

  • Regressions reproduce missing ownership, parse failures, lost provenance and false dependent-type bindings before their fixes.
  • Python 3.10.11 and 3.13.15: broad targeted C++/C#/cross-repository run 499 passed, 96 skipped each; final template module 61 passed each. Final assembled branch: 138 passed for template, conditional and cross-repository modules on 3.13.
  • Language fixtures: 502 passed, 22 skipped; the existing fixed-path Swift cache test was excluded from that sandbox run.
  • Separate local 32-extraction matrix: complete graph records agree across Python versions, hash seeds, input ordering, one/two workers and cold/warm caches. Caller-ordered source inventory is checked separately.
  • Ruff/whitespace pass. Targeted Pyright retains the same 167 upstream diagnostics, none added; not a clean type-check claim.
  • Required graph self-update completed. Full suite was not run.

Commands included:

python -m pytest tests/test_cpp_template_members.py tests/test_cpp_conditional_initializers.py tests/test_cross_repo_member_calls.py -q
python -m pytest tests/test_cpp_template_members.py -q
python -m pytest tests/test_languages.py -q -k "not swift_extension_across_files_merges_into_canonical_type"
python -m pyright --pythonpath <venv-python> graphify/extract.py graphify/extractors/engine.py graphify/cross_repo_calls.py --outputjson
python -m graphify update . --no-cluster

The broader run enumerated all test_cpp*.py files alongside the C# and cross-repository control modules.

Review follow-up

  • Verified the upstream repository, codex/cpp-template-conditionals-v105 head b3133327, and v8 base before editing. Follow-up b68d9d8e adds tests only.
  • extract_cpp() adds one direct helper dependency to the five already present. The recovery helper owns bounded branch parsing, validation, and merging; its callees reuse the parser and existing extractor. No forwarding layers or changes to the coupling threshold were introduced.
  • Added rejection controls for conflicting branch-local symbol identities and the 128-group limit. They compare the complete returned result with the original extraction, permitting only the explicit recovery diagnostic. Disabling either guard in an isolated in-memory mutation makes its test fail.
  • Fresh Python 3.13 review run: 274 targeted C++/cross-repository tests passed. Coverage confirms all eight functions the review verifier could not exercise were executed with actual parser inputs; this is test evidence, not formal proof.
  • Final changed-area run on Python 3.10.11 and 3.13.15: 93 passed each. Ruff and whitespace checks pass.
  • CI on the reviewed production revision passed all seven jobs, including Python 3.10/3.12/3.13/3.14. The review tool's “tests not run” applies to its own pass.
  • The static report's clean summary conflicts with its detailed coupling warnings. Treat those counts as maintenance signals, not an independently demonstrated failure or slowdown. Its broad impact count is not a count of directly edited functions.

Boundaries

AST cache schema 44 retires stale records; rebuild existing graphs with graphify extract . --code-only --force. Reconcile this bump if another extractor PR lands first.

No macro evaluation, compiler preprocessing, build-configuration enumeration, overload/specialization resolution or runtime-dispatch claims. Recovery is capped at 128 groups/32 views; malformed, conflicting or unattributable cases retain errors. Primary-owner normalization supports only the documented exact, nondependent, parameterless signatures.

Conditional-recovery repair and node audit

  • 9eea9f81 fixes a confirmed failure with repeated calls in multiple conditional initializer lists: pair deduplication selected different first call sites across branch views, causing valid guards to fail validation and later declarations to disappear. Recovery now preserves each call site through the existing strict validation; ordinary extraction keeps its prior deduplication behavior.
  • Two new production-path regressions fail before the repair and pass afterward, requiring exactly five nodes, all expected guarded calls, and identical cold/warm cache results. Split-name, conflicting-identity and branch-budget rejection controls remain intact.
  • Fresh targeted tests: 280 passed on Python 3.13; the same 280 passed on 3.10 across 270 initial passes and a 10-test rerun with a writable sandbox temp directory. Scoped Ruff, whitespace checks and graph self-update pass. No full suite was run locally.
  • Independent bounded audit: all 9 fixtures, 36 head extractions and 12 Clang checks pass against unchanged hand-written symbol expectations and compiler declaration locations. Complete records match across Python 3.10/3.13 and cold/warm caches. The two formerly failing fixtures now each contain exactly five nodes. The audit's old single-group guard assumption was replaced with exact source-derived call-site/guard sets; no fixture or node expectation changed.
  • The hash-verified 25-file real-source snapshot remains at 2,890 nodes (2,386 located / 504 unlocated); this repair adds/removes zero node IDs. No duplicate located (file, line, label) records were found. Unlocated placeholders are not definitions, and bounded fixture/source audits are not a general proof for arbitrary C++.

Reuse a same-file declared member only for unique primary template owners with direct ordered type-parameter correspondence and exact parameterless primitive signatures. Keep specialized, overloaded, dependent and shadowed forms separate instead of stripping template arguments.
Apply recovered source ranges to resolver-created edges and parked C++ callsites, then carry their conditional evidence through cross-repository linking. Preserve existing parking deduplication and isolate the metadata transfer from other languages.
Track template-template parameter names without leaking their inner parameters. Preserve dependent local/parameter bindings as unknown during in-file call selection and defer unknown owner fields, preventing unrelated same-named methods from being selected while preserving auto receiver behavior.
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

Thanks for the pull request, @xRedLeader. 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 Nov 9, 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 9 changed function(s) (1 sampled, not proven, 8 not verified).

Formal verification. PR-changed functions: 1/9 verified (0 proven, 1 may-equivalent, 0 distinguished) · 8 not verified (7 vacuous, 1 unsupported).

Not verified on this run: extract (vacuous: never exercised), extract\_cpp (vacuous: never exercised), \_cpp\_declared\_receiver\_type (vacuous: never exercised), \_cpp\_local\_binding\_ranges (vacuous: never exercised), \_cpp\_receiver\_chain (vacuous: never exercised), \_cpp\_type\_parameters\_in\_scope (vacuous: never exercised), \_extract\_generic (unsupported), \_get\_cpp\_func\_name (vacuous: never exercised).


Graphify review — findings

Adds C++ template-wrapper member ownership, so calls like worker.Tick<int>() and same-file qualified definitions such as Box<T>::Run() resolve through the existing declared-receiver rules. Dependent, unknown and ambiguous receivers, specializations and overloads stay unresolved rather than getting guessed targets. Adds bounded recovery for C++ initializers and argument lists broken by conditional directives, parsing separate byte-preserving branch views without evaluating defines or running a preprocessor. Recovery is capped at 128 conditional groups and 32 branch views, and malformed, conflicting or over-budget input keeps its original parse errors; recovered branches are recorded in metadata.cpp_conditional_recovery and guarded records carry metadata.cpp_conditions. Bumps the AST cache schema to 43 so older template and conditional-recovery records re-extract, and stops cross-repo member calls from binding across languages (a TypeScript call no longer binds to a Python declaration).

Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 5525 functions depend on the 1043 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: extract() — 1178 callers, 72 callees
  • worse: _extract_generic() — 21 callers, 54 callees
  • worse: link_cross_repo_member_calls() — 37 callers, 11 callees
  • worse: extract_cpp() — 41 callers, 6 callees
  • worse: walk() — 1 callers, 86 callees
  • worse: cache_dir() — 28 callers, 3 callees
  • worse: walk_calls() — 1 callers, 42 callees
  • new: _recover_cpp_conditional_initializers() — 1 callers, 7 callees

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

Test selection

Test selection

543 of 543 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety (noncode-change, uncovered-code) — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/pi.test.ts — full-run-safety
  • tests/test_4874_jvm_resolution.py — impact, full-run-safety
  • tests/test_4909_reference_binding_scale.py — full-run-safety
  • tests/test_4978_reconcile_cost.py — impact, full-run-safety
  • tests/test_4992_ts_import_types.py — impact, full-run-safety
  • tests/test_5072_splittable_under_cap_retry.py — full-run-safety
  • tests/test_affected_cli.py — full-run-safety
  • tests/test_affected_defines.py — full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_affected_overrides.py — impact, full-run-safety
  • tests/test_affected_path_file_node.py — full-run-safety
  • tests/test_affected_seed_reference_forms.py — full-run-safety
  • tests/test_agents_platform.py — full-run-safety
  • tests/test_alias_folding.py — impact, full-run-safety
  • tests/test_analyze.py — impact, full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — impact, full-run-safety
  • tests/test_astro_import_ids.py — impact, full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_backend_opt_in.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_benchmark_real_query.py — full-run-safety
  • tests/test_binary_media_words.py — impact, full-run-safety
  • tests/test_blade_extractor.py — impact, full-run-safety
  • tests/test_build.py — impact, full-run-safety
  • tests/test_build_located_semantic_identity.py — impact, full-run-safety
  • tests/test_build_merge_dedup_scope.py — impact, full-run-safety
  • tests/test_build_merge_ghost_fold_4955.py — impact, full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — impact, full-run-safety
  • tests/test_build_merge_shrink_guard.py — impact, full-run-safety
  • tests/test_builtin_global_type_refs.py — impact, full-run-safety
  • tests/test_c_include_resolution.py — impact, full-run-safety
  • tests/test_cache.py — impact, full-run-safety
  • tests/test_cache_long_paths.py — impact, full-run-safety
  • tests/test_cache_stale_import_target.py — impact, full-run-safety
  • tests/test_cache_windows_case_4925.py — impact, full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_cargo_missing_manifest.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — impact, full-run-safety
  • tests/test_case_only_decl_ids_4904.py — impact, full-run-safety
  • tests/test_case_sensitive_resolution.py — impact, full-run-safety
  • … and 493 more

non-code file(s) changed (ARCHITECTURE.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (ARCHITECTURE.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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

No difference found (not proven): No behavior difference found in link\_cross\_repo\_member\_calls (not a proof).

The verifier ran both versions of link\_cross\_repo\_member\_calls on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, 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 90 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly TypeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify extract\_cpp.

The verifier did not have enough to check extract\_cpp, 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 9 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_cpp\_declared\_receiver\_type.

The verifier did not have enough to check \_cpp\_declared\_receiver\_type, 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 220 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 \_cpp\_local\_binding\_ranges.

The verifier did not have enough to check \_cpp\_local\_binding\_ranges, 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 190 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 \_cpp\_receiver\_chain.

The verifier did not have enough to check \_cpp\_receiver\_chain, 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 190 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 \_cpp\_type\_parameters\_in\_scope.

The verifier did not have enough to check \_cpp\_type\_parameters\_in\_scope, 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 190 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

Could not verify: Could not verify \_get\_cpp\_func\_name.

The verifier did not have enough to check \_get\_cpp\_func\_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: no capturable inputs from the test suite; property tier: not verifiable: all 190 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)

· 2 grounded finding(s) anchored inline below; 6 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extract.py
return selected


def _recover_cpp_conditional_initializers(path: Path, source: bytes, original: dict) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — _recover_cpp_conditional_initializers()

fans out to 7 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/extract.py
stamp(entry, "file", "line")


def extract_cpp(path: Path) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — extract_cpp()

fans out to 6 callees (efferent coupling); 41 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

safishamsi added a commit that referenced this pull request Nov 10, 2026
Co-landing the scoped-alias/inherited-receiver work (#5118) with the template/
guarded-initializer work (#5115) required merging their _cpp_local_binding_ranges
tuple: #5118 appends the lexical lookup scope, #5115 appends the qualified name
and a dependent-type flag. Carry all three (receiver_type, lookup_scope,
qualified_name, dependent), widen the type annotations to the 6-tuple, and move
the dependent-flag read in the member-defer predicate to its new index. Full C++
suite (443 tests incl. #5098/#4916/#4946) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
safishamsi added a commit that referenced this pull request Nov 10, 2026
… trio

#5113 (Python subscript generic-arg count) and #5117/#5118/#5115 (C++ decl/def
reconcile, scoped aliases + inherited receivers, template ownership + guarded
initializers) all change per-file extractor output and land in one window, so
they share a single bump to 43 chained over prior (42).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@safishamsi

Copy link
Copy Markdown
Member

Landed on v8 and shipped in 0.9.106 (now on PyPI), cherry-picked with -x so your commits keep their authorship. This C++ trio co-landed in order #5117 -> #5118 -> #5115; merge-time adjustments were the shared schema bump to 43 (your branch targeted a higher number) and, where the three overlapped, a reconciliation of the engine binding core: the _cpp_local_binding_ranges tuple now carries both #5118's lexical lookup scope and #5115's qualified name + dependent flag, with the readers updated, plus a dedupe of _cpp_is_header_guard and a union of the persisted C++ marker lists. Verified green afterward: the full C++ suite (443 tests, including the #5098/#4916/#4946 regression files) passes. Thanks @xRedLeader.

@safishamsi safishamsi closed this Nov 10, 2026
@xRedLeader
xRedLeader deleted the codex/cpp-template-conditionals-v105 branch November 10, 2026 23:46
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.

2 participants