Skip to content

fix(cpp): remap recovered definitions to original lines - #949

Merged
DeusData merged 1 commit into
DeusData:mainfrom
blankanswer:agent/fix-cpp-preproc-signature-gap
Jul 15, 2026
Merged

fix(cpp): remap recovered definitions to original lines#949
DeusData merged 1 commit into
DeusData:mainfrom
blankanswer:agent/fix-cpp-preproc-signature-gap

Conversation

@blankanswer

@blankanswer blankanswer commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #1050 for #946/#961. The recovery already on main remains authoritative; this PR adds explicit simplecpp expanded-to-original line mapping and the broader regression fixtures requested in review.

What changed

Validation

  • ./build/c/test-runner extraction: all 215 functional extraction tests passed; the only suite failure was the pre-existing wide-flat wall-clock threshold under this 894 MiB/swap-constrained runner.
  • ./build/c/test-runner parse_coverage: 9 passed.
  • make -f Makefile.cbm lint-format: passed.
  • git diff --check: passed.

The previous six-commit implementation was backed up locally and replaced with this single additive commit on current main.

@blankanswer
blankanswer requested a review from DeusData as a code owner July 8, 2026 04:38
@DeusData DeusData added bug Something isn't working parsing/quality Graph extraction bugs, false positives, missing edges priority/normal Standard review queue; useful PR with ordinary maintainer urgency. labels Jul 8, 2026
@DeusData DeusData mentioned this pull request Jul 8, 2026
2 tasks
@DeusData

DeusData commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Thanks for the focused C++ parser fix. Triage: parser recovery bug for #946, normal priority. Review focus is ownership of recovered callables, avoiding duplicate definitions across #ifdef branches, and keeping unrelated extraction behavior unchanged.

@DeusData DeusData added this to the 0.9.1-rc milestone Jul 8, 2026
@DeusData

DeusData commented Jul 9, 2026

Copy link
Copy Markdown
Owner

This is the fix we most wanted from the 0.9.1-rc bug queue — the coverage signal we shipped this week (#963/#968) flagged exactly this class on our own codebase (#961: cbm_path_within_root, handle_process_kill — both in-function #ifdef splits), and recovering missing callables from the already-existing preprocessor second pass is the right systemic answer: raw parse stays primary, expanded AST only fills verified gaps, deduped by label+QN. The defs-only walker entry point is a clean seam.

Two things before merge:

  1. Line coordinates of recovered defs. The merged callables carry EXPANDED-source line numbers, but get_code_snippet (and the UI's GitHub deep-links) read the ORIGINAL file at start_line..end_line — a drifted range shows the wrong code for exactly the methods this PR rescues. Please either remap to original coordinates where feasible, or mark recovered defs (e.g. a recovered_from_preprocessed property / reduced confidence) and document the drift — plus one test pinning whatever behavior you choose, so snippet consumers aren't silently wrong.

  2. Rebase onto current main — the end-of-extraction region of internal/cbm/cbm.c changed in feat(index): best-effort parse-coverage signal — flag not-fully-indexed files #968: the parse-coverage signal now computes error regions AFTER extraction and subtracts recovered definitions. Your merge hook lands in the same neighborhood (conflict likely), and there's a pleasant interaction to verify once rebased: callables you recover should automatically UN-flag the corresponding parse_partial ranges (a region fully covered by recovered defs is dropped). The regression test tests/test_parse_coverage.c shows the mechanics; a one-line assertion in your test that the fixture is no longer parse_partial-flagged would prove the two features compose.

Genuinely excited about this one — with the rebase + the line-coordinate decision it merges.

@blankanswer
blankanswer force-pushed the agent/fix-cpp-preproc-signature-gap branch 2 times, most recently from 56da8cf to eee5a31 Compare July 10, 2026 05:45
@blankanswer

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — both requested items are addressed:

  • Rebased onto current main.
  • Recovered callables now map expanded lines back to original main-file coordinates and fail closed when mapping is unreliable.
  • Raw definitions remain primary/fill-only; successfully remapped callables are used for parse-coverage, and the bug for parse c++ #946 regression verifies both coordinates and cleared parse_incomplete/error ranges.

I also added coverage for both preprocessor branches, remap failure, header ownership, call-site ambiguity, and more than 256 recoveries. All CI checks are green. Ready for another look.

@blankanswer
blankanswer force-pushed the agent/fix-cpp-preproc-signature-gap branch from 5293205 to 69e4255 Compare July 12, 2026 08:25
@DeusData

Copy link
Copy Markdown
Owner

An update you should see before investing more here, with an apology attached: while working the twin issue #961 (same root cause — #ifdef-split braces → ERROR region → dropped defs), I landed a minimal version of the recovery on main in #1050 (65a176e) without cross-linking it to this PR first. That overlap is on me — you had the fix candidate in flight for #946 and had just addressed review.

What #1050 does (deliberately minimal): re-runs the defs walk on the existing simplecpp second pass and adopts only defs that (a) intersect a raw-tree ERROR region, (b) have their name visible on the corresponding original source line, and (c) are absent by qualified name. It relies on simplecpp's same-file line alignment rather than explicit mapping. #946's SurfaceFlinger shape verifies fixed with it.

Reading your latest revision, two things look like genuine improvements over what landed:

  1. Explicit expanded→original line mapping with fail-closed semantics — my name-on-line check rejects misaligned defs but can't correct them; your mapping potentially recovers defs whose lines shift (e.g. resolved includes above the def), which fix(extract): recover C/C++ defs lost to #ifdef-split brace ERROR regions #1050 silently skips.
  2. Your fixture set — broader than the two shapes fix(extract): recover C/C++ defs lost to #ifdef-split brace ERROR regions #1050 pinned.

If you're willing, I'd welcome a rebase of exactly those two deltas on top of current main as the follow-up: the line-map hardening (with a case #1050 provably skips — that would be its RED test) plus your fixtures. Smaller PR, clearly additive, and it keeps your authorship on the part that outlives both versions. If you'd rather not re-cut it, say so and we'll handle the salvage ourselves with credit to this PR either way.

Sorry again for the collision — and thank you for the careful iteration on this; the fail-closed mapping idea is the right instinct.

Signed-off-by: Blank_Answer <97771966+blankanswer@users.noreply.github.com>
@blankanswer
blankanswer force-pushed the agent/fix-cpp-preproc-signature-gap branch from 69e4255 to 99d3919 Compare July 13, 2026 05:22
@blankanswer blankanswer changed the title fix(cpp): recover callables after preprocessor-gated signatures fix(cpp): remap recovered definitions to original lines Jul 13, 2026
@blankanswer

Copy link
Copy Markdown
Contributor Author

Re-cut as requested on current main in 99d3919.

This is now a single additive follow-up to #1050: explicit expanded→original line/file ownership mapping, fail-closed original-span validation, and the salvaged fixtures. The new include-shift test is the requested case #1050 provably skips: a 40-line resolved header shifts the expanded def outside the raw ERROR coordinates; mapping recovers it at original lines 3–12, excludes header defs, and clears the fully recovered parse range.

Also retained the real #946 SurfaceFlinger shape for both branches and a macro-generated-callable rejection guard. parse_coverage is 9/9 and all 215 functional extraction tests pass locally; only the unrelated wide-flat wall-clock guard failed under this 894 MiB/swap-constrained environment. Ready for another look.

@DeusData
DeusData merged commit ef14822 into DeusData:main Jul 15, 2026
20 checks passed
@DeusData

Copy link
Copy Markdown
Owner

Merged (ef14822). This is exactly the clean additive follow-up I hoped for — explicit expanded→original line/file mapping with fail-closed span validation, layered on top of #1050's recovery rather than replacing it. Verified: my #1050 #961 guards still pass unchanged, your new include-shift test passes (the case #1050's name-on-line heuristic couldn't handle — where resolved includes shift the expanded line numbers), 249/249 extraction green, lint clean. Thanks for the graceful re-cut after the overlap; the fail-closed mapping was the right instinct and the codebase is better for it.

xi-guan pushed a commit to xi-guan/codebase-memory-mcp that referenced this pull request Jul 16, 2026
A function-like macro invocation whose argument is a type token — e.g.
ALLOC(int, n) — makes tree-sitter's C/C++ grammar emit an ERROR node (it parses
the type in expression position), which cbm_collect_error_regions recorded as a
parse_partial coverage gap. But the macro is #defined in the same file and the
call sits inside an already-extracted function body, so nothing is actually
missing from the graph — it's a benign call the grammar can't parse without the
preprocessor (DeusData#1071, systematic across allocation-macro-style codebases).

Subtract an error region only when it both (a) contains an invocation of a
file-defined function-like macro and (b) is fully enclosed by an extracted
Function/Method body. Condition (b) keeps a TOP-LEVEL macro invocation that
expands to a definition still flagged (DeusData#949: the generated def isn't in the
original span), and fails safe — a benign top-level call stays flagged rather
than a real gap being hidden.

Tests: a type-arg macro call inside a function no longer reports parse_partial;
a real in-body syntax error still does; DeusData#949/DeusData#946 preserved.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
CaptainMittens added a commit to CaptainMittens/codebase-memory-mcp that referenced this pull request Aug 31, 2026
…eusData#963)

src/cli/cli.c reported an error range of 1-13047 — the whole file. The file
indexed fine; the report was wrong. Three #ifndef _WIN32 blocks split a brace
(two `if` headers, one closing brace), so the raw tree-sitter parse cannot
resync at file scope, the root node becomes ERROR, and cbm.c takes its
whole-file branch.

The pipeline already parses these files a second time after preprocessing, and
that parse is clean. The report just never consulted it.

Build one byte per original line from the preprocessed pass, then cut each raw
error range down to the runs of lines the second parse could not vouch for.

Three rules, all found by running it and all load-bearing:

- An expanded line only vouches for its original line when it HAS TEXT. The
  preprocessor emits a blank line where it dropped a branch; treating that
  blank as proof suppressed every C range in the suite.
- Preprocessor directive lines (with backslash continuations) never count as
  missing code — the preprocessor consumes them, so the second parse can never
  vouch for one. Without this every #include block reported as a miss. Known
  cost: a #define the raw parse really dropped no longer shows up on its own.
- A TOP-LEVEL macro invocation line never counts as vouched-for even when the
  expanded line parses clean. The macro can expand to a whole definition that
  the recovery walker deliberately refuses to adopt (DeusData#949), so a clean second
  parse there proves nothing. An in-body invocation is the benign DeusData#1071 case
  and is left to the existing macro subtraction.

The order of the three coverage steps is now settled by where each one's
evidence lives:

  recovery subtraction  -> before the refinement; its evidence is a whole
                           definition that STARTS inside the range, so it must
                           be asked while the range still matches the construct
  the refinement        -> middle
  DeusData#1071 macro rule      -> after the refinement; its evidence is per-line, so a
                           narrow range points at the call itself

Measured on this repo: src/cli/cli.c goes from one whole-file range to 64
ranges over ~9.8% of the file, tests/test_cli.c from 48.6% to ~2.9%,
src/cli/activation_transaction.c from 38% to 7.5%. What survives is honest —
the biggest remaining ranges in cli.c are genuinely discarded #ifdef _WIN32
and #ifdef CBM_CLI_ENABLE_TEST_API blocks, absent from the graph on this
platform.

Both percentages above are floors, not measurements: cli.c and test_cli.c now
land on exactly 64 ranges, which is CBM_MAX_ERROR_REGIONS. That cap drops
regions with no signal, and a follow-up raises it and adds a truncation marker.

Five tests, all red before the change: the range narrows to the dropped
branch; lines the preprocessor explained are excluded; a range never starts or
ends on a directive; real garbage beside a split brace stays flagged; a clean
file stays unflagged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
CaptainMittens added a commit to CaptainMittens/codebase-memory-mcp that referenced this pull request Sep 1, 2026
…eusData#963)

src/cli/cli.c reported an error range of 1-13047 — the whole file. The file
indexed fine; the report was wrong. Three #ifndef _WIN32 blocks split a brace
(two `if` headers, one closing brace), so the raw tree-sitter parse cannot
resync at file scope, the root node becomes ERROR, and cbm.c takes its
whole-file branch.

The pipeline already parses these files a second time after preprocessing, and
that parse is clean. The report just never consulted it.

Build one byte per original line from the preprocessed pass, then cut each raw
error range down to the runs of lines the second parse could not vouch for.

Three rules, all found by running it and all load-bearing:

- An expanded line only vouches for its original line when it HAS TEXT. The
  preprocessor emits a blank line where it dropped a branch; treating that
  blank as proof suppressed every C range in the suite.
- Preprocessor directive lines (with backslash continuations) never count as
  missing code — the preprocessor consumes them, so the second parse can never
  vouch for one. Without this every #include block reported as a miss. Known
  cost: a #define the raw parse really dropped no longer shows up on its own.
- A TOP-LEVEL macro invocation line never counts as vouched-for even when the
  expanded line parses clean. The macro can expand to a whole definition that
  the recovery walker deliberately refuses to adopt (DeusData#949), so a clean second
  parse there proves nothing. An in-body invocation is the benign DeusData#1071 case
  and is left to the existing macro subtraction.

The order of the three coverage steps is now settled by where each one's
evidence lives:

  recovery subtraction  -> before the refinement; its evidence is a whole
                           definition that STARTS inside the range, so it must
                           be asked while the range still matches the construct
  the refinement        -> middle
  DeusData#1071 macro rule      -> after the refinement; its evidence is per-line, so a
                           narrow range points at the call itself

Measured on this repo: src/cli/cli.c goes from one whole-file range to 64
ranges over ~9.8% of the file, tests/test_cli.c from 48.6% to ~2.9%,
src/cli/activation_transaction.c from 38% to 7.5%. What survives is honest —
the biggest remaining ranges in cli.c are genuinely discarded #ifdef _WIN32
and #ifdef CBM_CLI_ENABLE_TEST_API blocks, absent from the graph on this
platform.

Both percentages above are floors, not measurements: cli.c and test_cli.c now
land on exactly 64 ranges, which is CBM_MAX_ERROR_REGIONS. That cap drops
regions with no signal, and a follow-up raises it and adds a truncation marker.

Five tests, all red before the change: the range narrows to the dropped
branch; lines the preprocessor explained are excluded; a range never starts or
ends on a directive; real garbage beside a split brace stays flagged; a clean
file stays unflagged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
CaptainMittens added a commit to CaptainMittens/codebase-memory-mcp that referenced this pull request Sep 2, 2026
…eusData#963)

src/cli/cli.c reported an error range of 1-13047 — the whole file. The file
indexed fine; the report was wrong. Three #ifndef _WIN32 blocks split a brace
(two `if` headers, one closing brace), so the raw tree-sitter parse cannot
resync at file scope, the root node becomes ERROR, and cbm.c takes its
whole-file branch.

The pipeline already parses these files a second time after preprocessing, and
that parse is clean. The report just never consulted it.

Build one byte per original line from the preprocessed pass, then cut each raw
error range down to the runs of lines the second parse could not vouch for.

Three rules, all found by running it and all load-bearing:

- An expanded line only vouches for its original line when it HAS TEXT. The
  preprocessor emits a blank line where it dropped a branch; treating that
  blank as proof suppressed every C range in the suite.
- Preprocessor directive lines (with backslash continuations) never count as
  missing code — the preprocessor consumes them, so the second parse can never
  vouch for one. Without this every #include block reported as a miss. Known
  cost: a #define the raw parse really dropped no longer shows up on its own.
- A TOP-LEVEL macro invocation line never counts as vouched-for even when the
  expanded line parses clean. The macro can expand to a whole definition that
  the recovery walker deliberately refuses to adopt (DeusData#949), so a clean second
  parse there proves nothing. An in-body invocation is the benign DeusData#1071 case
  and is left to the existing macro subtraction.

The order of the three coverage steps is now settled by where each one's
evidence lives:

  recovery subtraction  -> before the refinement; its evidence is a whole
                           definition that STARTS inside the range, so it must
                           be asked while the range still matches the construct
  the refinement        -> middle
  DeusData#1071 macro rule      -> after the refinement; its evidence is per-line, so a
                           narrow range points at the call itself

Measured on this repo: src/cli/cli.c goes from one whole-file range to 64
ranges over ~9.8% of the file, tests/test_cli.c from 48.6% to ~2.9%,
src/cli/activation_transaction.c from 38% to 7.5%. What survives is honest —
the biggest remaining ranges in cli.c are genuinely discarded #ifdef _WIN32
and #ifdef CBM_CLI_ENABLE_TEST_API blocks, absent from the graph on this
platform.

Both percentages above are floors, not measurements: cli.c and test_cli.c now
land on exactly 64 ranges, which is CBM_MAX_ERROR_REGIONS. That cap drops
regions with no signal, and a follow-up raises it and adds a truncation marker.

Five tests, all red before the change: the range narrows to the dropped
branch; lines the preprocessor explained are excluded; a range never starts or
ends on a directive; real garbage beside a split brace stays flagged; a clean
file stays unflagged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working parsing/quality Graph extraction bugs, false positives, missing edges priority/normal Standard review queue; useful PR with ordinary maintainer urgency.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants