Skip to content

fix(rust): generic impl methods source their own calls (#763 Rust share) - #2685

Merged
DeusData merged 7 commits into
mainfrom
fix/rust-generic-impl-call-scope
Oct 18, 2026
Merged

DeusData merged 7 commits into
mainfrom
fix/rust-generic-impl-call-scope

Conversation

@DeusData

Copy link
Copy Markdown
Owner

What does this PR do?

This is the Rust share of #763: calls inside the methods of a generic impl were sourced from the File node.

For impl<T> Gen<T> and impl<'a> Life<'a>, the extractor's call scope kept the type arguments (Gen<T>.in_generic). The def side strips them (Gen.in_generic, as extract_rust_impl does). As a result, no node carried the scope QN, and every call in those methods fell back to the File node. Plain impls, trait impls, free functions and inline modules were already correct.

The first commit strips the generic arguments from a Rust impl_item name in the class-scope QN (extract_unified.c).

A second commit, which the corpus proof forced

The Rust LSP also built an impl's self type from the raw text Gen<T>. Before the first commit, both sides used the same wrong name, so the LSP rows still joined their calls (onto the File node). After it, the LSP caller QN Gen<T>.in_generic no longer matched the fixed scope Gen.in_generic.

On 9 Rust repos, the first commit alone turned 1,927 LSP-resolved edges into registry name guesses (lsp_direct 518, lsp_short_name_unique 973, lsp_ufcs 182, lsp_constructor 114, lsp_method_dispatch 108 and others).

The same mismatch was already broken on main, independently of the first commit. Inside a generic impl, self.other() got only unique_name, and Self::assoc() had no edge at all. In a plain impl, the same calls get lsp_method_dispatch and lsp_direct. The return-type harvest (Phase B2) also never matched generic-impl methods, so chained calls on them stayed untyped.

The second commit adds rust_strip_impl_type_args, used in rust_process_impl and in Phase B2. A third commit reshapes the empty-name check so that cppcheck 2.20 accepts it; behaviour is unchanged.

Evidence

Base 9f85f104 vs this branch (head fed5cbf9, current main merged in). Setup: same machine, production binaries, 9 pinned Rust repos (diesel, helix, meilisearch, nushell, qdrant, cargo, tauri, tokio, typst). Every repo was indexed twice with the parallel resolver, and both runs were identical for every edge type.

#763 metric: Rust CALLS sourced at a File/Module node while the call sits inside a function span go from 22,287 to 1,572 (−93%). Total Rust CALLS go from 274,055 to 283,478. That rise is collapsed File edges splitting into one edge per calling method.

LSP accounting (same call sites, base -> this PR): the first commit's loss is gone.

strategy removed added
lsp_method_dispatch 109 3,084
lsp_direct 527 946
lsp_ufcs 182 225
lsp_trait_dispatch 12 195
lsp_constructor 114 164
lsp_short_name_unique 982 784

Independent oracle (rust-analyzer SCIP, all 9 repos): recall rises on 9 of 9 repos, and precision rises or stays equal on 9 of 9.

repo precision base -> PR recall base -> PR
diesel 0.704 -> 0.707 0.577 -> 0.661
helix 0.737 -> 0.740 0.750 -> 0.816
meilisearch 0.707 -> 0.707 0.666 -> 0.763
nushell 0.764 -> 0.768 0.576 -> 0.601
qdrant 0.703 -> 0.708 0.558 -> 0.688
cargo 0.619 -> 0.622 0.516 -> 0.542
tauri 0.609 -> 0.663 0.570 -> 0.719
tokio 0.642 -> 0.668 0.431 -> 0.521
typst 0.691 -> 0.693 0.534 -> 0.684
all 9 (micro) 0.696 -> 0.702 0.559 -> 0.629

The 1,572 File-sourced calls that remain are a separate shape, not investigated here. Many are multi-line chained callees, such as diesel's transaction_manager\n.status\n.set_....

Tests

  • extract_rust_generic_impl_call_scope_matches_def: the call scope of a generic and of a lifetime impl method equals its def QN.
  • pipeline_rust_generic_impl_method_sources_its_calls (sequential and parallel):
    • each method sources its own call, and the File node sources none;
    • the calls inside the generic impl carry an lsp_* strategy (helper_a, self.other_g(), self.other_l());
    • Self::assoc_g() has an edge.

All three fail with the production change reverted, for the asserted reason, and pass with it. Each half was also shown separately with production binaries on a fixture:

  • With only the first commit, the calls inside a generic impl get unique_name.
  • With the second commit, they get lsp_constructor, lsp_method_dispatch and lsp_direct, as in a plain impl.

Note for merging

This branch and #2681 both add Rust tests at the same spot in tests/test_pipeline.c. Whichever lands second must merge main again and keep both blocks. Nothing else overlaps; rust_lsp.c merges cleanly.

Checklist

  • Every commit is signed off (git commit -s)
  • Affected suites pass locally (macOS): rust_lsp, pipeline, parallel, extraction, lsp_resolution_probe, complexity, c_lsp, convergence_probe, doc_mentions_rs and pipeline_conventions, 2,387 passed and 0 failed
  • Lint: cppcheck 2.20 and clang-format clean on the linted file extract_unified.c; memory-core linter passes
  • New behaviour is covered by a test (reproduce-first)

The def side names a Rust impl method after the implementing type with
its generic arguments stripped (`impl<T> Gen<T>` -> `Gen`, extract_rust_impl).
The call-scope QN in extract_unified.c used the impl's type text verbatim,
`Gen<T>` / `Life<'a>`. So every method of a generic or lifetime-parameterised
impl had a scope QN that no node carries, and every call in its body was
sourced from the File node instead of the method. Base census, 9f85f10 par
(lane L7's #763 measurement): 87,855 Rust CALLS start at a File/Module node,
84,331 of them (96%) on a line inside a function span. Go is 0%.

Strip the generic arguments on the call side exactly as the def side does.
Plain impls, trait impls, free functions and inline modules were already
consistent (checked on a fixture with the base binary).

Test: extract_rust_generic_impl_call_scope_matches_def (plain, type-generic
and lifetime-generic impls; def QN == call scope).

Refs #763

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…h resolvers)

Sequential and parallel pipeline twin of extract_rust_generic_impl_call_scope_matches_def: the calls in a type-generic and a lifetime-generic impl method are sourced from the method, not from the File node.

Refs #763

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…-call-scope

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The extractor fix above names a generic impl's methods `Gen.in_generic`
on both the def and the call-scope side. The Rust LSP still built its
self type from the impl's raw text, `Gen<T>`. That broke three things:

- its caller QN (`Gen<T>.in_generic`) no longer joined the pipeline's
  call scope, so every LSP row inside a generic or lifetime impl was
  dropped for a registry guess. On 9 Rust repos that turned 1,927
  lsp_* edges into suffix_match/unique_name;
- `self.` and `Self::` dispatch never found the impl's own methods;
  `Self::assoc_g()` had no edge at all, on main as well;
- the return-type harvest never matched those methods, so chained
  calls on them stayed untyped.

Strip the generic arguments from the impl's self type in both places.
Text that starts with `<` (a qualified path) is left alone.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
cppcheck 2.20 reports the post-cut `if (!name[0])` as always false
(knownConditionTrueFalse): it cannot see that writing through `lt`
may empty `name`. The name is non-empty here, so the cut empties it
only when `<` is the first character. Test exactly that before the
cut. The behaviour is unchanged.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…Smart CI selection fix #2684)

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…-call-scope

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>

# Conflicts:
#	tests/test_pipeline.c
@DeusData
DeusData merged commit 658639e into main Oct 18, 2026
28 checks passed
DeusData added a commit to ilyabrykau-orca/codebase-memory-mcp that referenced this pull request Oct 19, 2026
Brings in main 658639e (DeusData#2685, Rust generic-impl call scope). One
conflict in tests/test_extraction.c: main's new F-R4 test and its
helper sit right before the flag-exempt test, whose comment this
branch rewrote (Go now sets is_method, so the guarded language is
Rust). Both are kept: main's block, then this branch's comment.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
DeusData added a commit to ilyabrykau-orca/codebase-memory-mcp that referenced this pull request Oct 19, 2026
Brings in the base branch with main 658639e merged (DeusData#2685, Rust
generic-impl call scope), so this branch merges cleanly into main
again. No conflicts.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant