Skip to content

fix(rust-lsp): Rust calls never bind definitions of other languages - #2681

Merged
DeusData merged 6 commits into
mainfrom
fix/rust-registry-rust-defs-only
Oct 18, 2026
Merged

DeusData merged 6 commits into
mainfrom
fix/rust-registry-rust-defs-only

Conversation

@DeusData

Copy link
Copy Markdown
Owner

What does this PR do?

The Rust LSP built its registries from the pipeline's mixed-language definition set. The shared Tier-2 build (cbm_rust_build_cross_registry) and the per-file conversion (pxc_lspdefs_to_rust) copied every def, so a Perl sub new, a TypeScript function or a C function became a Rust free function.

The lsp_short_name_unique fallback then bound Rust calls to them at confidence 0.90. Its "crate scope" check compares only the first module-QN segment, which is the project name, so it accepts anything in the project. Example: use crate::store::Store; Store::new() bound to perlsrc.Lib.new on both resolvers.

The fix registers only Rust defs at both conversion points, exactly as the Go builder filters to Go. Rust impl-relation records already carry CBM_LANG_RUST. As a side effect, the shared Rust build no longer registers the C defs of a mixed C+Rust tree.

The filter keys on CBMLSPDef.lang, so every production source of those defs must set it. All of them do:

  • Shared build: the only production caller of cbm_rust_build_cross_registry is the lazy getter in pass_parallel.c:3984, which reads rc->all_defs. Those defs come from one of two places:
    • Full index: cbm_pxc_collect_all_defs (pipeline.c:2592).
    • Incremental index: the stored surfaces plus cbm_pxc_collect_all_defs for the re-parsed files (pipeline_incremental.c:1867, :1893-1903).
  • Per-file path: pxc_lspdefs_to_rust (pass_lsp_cross.c:1596) receives defs from the same collector: the sequential path (pass_lsp_cross.c:1982), the incremental legacy path (pipeline_incremental.c:1950) and the surface probe (:2278).
  • The collector: cbm_pxc_collect_all_defs passes each file's files[fi].language to pxc_build_lsp_def (pass_lsp_cross.c:819), which sets dst->lang (:476). Rust impl relations set CBM_LANG_RUST (:715).
  • Stored surfaces: they round-trip lang under the "lg" key (lsp_surface.c:389 encode, :664 decode). The key has been in the codec since its first version.
  • cbm_batch_rust_lsp_cross has no caller.

A second commit, which the corpus proof forced

The first version (2ac9c33 alone) regressed true edges. Without the cross-language namesakes, many short names became unique among Rust free functions, and the same fallback bound type-qualified calls to them. Examples: OpenOptions::write(..) landed on a free cache_file::write, and Vector4::zeros() on a free response_body::zeros. That LSP row pre-empts the registry, which had the right answer.

On 7 Rust repos, this replaced 516 correct qualified_suffix edges and 84 correct import_map edges. 1,137 of the 1,606 added lsp_short_name_unique edges were type-qualified. The bug already exists on main: in a one-crate fixture, Opts::write(true) binds a free cache::write there too. The namesakes only hid it more often.

ec4f0ea: a call whose qualifying segment names a type (ASCII-uppercase initial, Self, or a <T as Tr>:: path) never takes either free-function fallback. Module segments are snake_case, so a lowercase cache::write keeps its fallback.

Evidence

Base 9f85f104 vs this branch, measured at ec4f0eaa with current main merged in; the head adds only the test-fixture commit. Setup: same machine, production binaries, 7 pinned Rust repos (qdrant, tauri, helix, typst, deno, swc, wasmtime). Every repo was indexed twice with the parallel resolver, and both runs were identical for every edge type.

metric base this PR
Rust-sourced CALLS onto non-Rust files 3,121 835
of which lsp_short_name_unique 2,274 0
Rust-sourced CALLS total 284,718 283,767

Of the 835 remaining non-Rust targets, 834 come from the pipeline registry's import_map strategy (deno 615, swc 129, wasmtime 78), not from the Rust LSP. They are outside this change and are tracked separately as a cross-language veto for the registry.

CALLS diff, base -> this PR:

strategy removed added
lsp_short_name_unique 3,933 (2,274 cross-language, 1,659 type-qualified Rust->Rust) 267
qualified_suffix 0 1,048
import_map 14 250
suffix_match 77 1,490
lsp_trait_dispatch 0 7
same_module 0 11

I hand-judged a stratified sample (4 per stratum, with source snippets):

  • Removed: 12 of 12 were false. Examples: TranspileOptions::default() -> a JS default, Func::wrap(..) -> a free test function wrap, std::cmp::min -> BaseHistogram.min.
  • Added qualified_suffix, import_map and lsp_trait_dispatch: 12 of 12 were true. Examples: Transaction::change, LocalShard::load, self.realpath_sync() -> DenoRtSys.
  • Added suffix_match, all at confidence <= 0.28: 4 of 4 were false. Example: fs::write -> FileLock.write. These are the generic registry fallback reaching call sites whose 0.90 LSP edge was also false. They are a separate follow-up, as are std::/dependency-crate paths such as std::ptr::write.

The accounting, weighted by stratum size:

  • Removed: about 4,020 edges, all false. Cross-language lsp_short_name_unique drops from 2,274 to 0. The second commit alone removes 2,998 type-qualified short-name hijacks.
  • Added true: about 1,430 edges (qualified_suffix, import_map, trait dispatch; 12 of 12 judged true).
  • Added false, low confidence: about 1,650 edges. Most are suffix_match at confidence <= 0.28 (4 of 4 judged false). Each sits at a call site whose old 0.90 LSP edge was also false. That makes it the same wrong answer at much lower confidence, not a new wrong edge.
  • Net: about −2,370 false edges and +1,430 true edges.

Against an independent oracle (rust-analyzer SCIP, covering 4 of these repos), precision and recall both rise on every repo:

repo precision base -> PR recall base -> PR
qdrant 0.703 -> 0.728 0.558 -> 0.575
helix 0.737 -> 0.745 0.750 -> 0.756
typst 0.691 -> 0.698 0.534 -> 0.538
tauri 0.609 -> 0.610 0.570 -> 0.571

Follow-ups, each a separate PR:

  • The remaining name guesses for std::/core::/dependency-crate paths, such as std::ptr::write (about 150 of the 1,650).
  • The Rust LSP does not resolve calls into sibling mod x; modules (cache::write, self::cache::write, Opts::write after use crate::fsx::Opts). Those sites fall back to suffix_match, which is 44% of Rust CALLS on main.

Tests

  • pipeline_rust_call_never_binds_another_language (sequential and parallel): no CALLS from the Rust caller into the Perl file, and the Rust associated function still binds.
  • pipeline_rust_assoc_call_never_binds_free_namesake (sequential and parallel): Opts::write(true) binds fsx.Opts.write, never the free cache.write. The module-qualified cache::write("x") still binds the free function.

A third commit tags two existing test_rust_lsp.c fixtures with lang = CBM_LANG_RUST. They fed the shared build with lang left at 0, which the pipeline never does. The other shared-registry fixtures in that file already set it.

All four new tests fail with the production change reverted, for the asserted reason, and pass with it. Each half was also shown separately with production binaries on the fixtures. With only the first commit, Opts::write still lands on cache.write. On main, Store::new() lands on the Perl new.

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,388 tests. The first run flagged the two lang-less fixtures; after the fixture fix, rust_lsp passes 522/522.
  • Lint: cppcheck 2.20 and clang-format clean on the linted file pass_lsp_cross.c; memory-core linter passes
  • New behaviour is covered by a test (reproduce-first)

Both Rust registries were built from the pipeline's mixed-language def
set. The shared Tier-2 build (cbm_rust_build_cross_registry) and the
per-file conversion (pxc_lspdefs_to_rust) copied every def, so a Perl
`sub new` or any C function became a Rust free function. The
lsp_short_name_unique fallback then bound `Store::new()` to the Perl
constructor at confidence 0.90 on both venues: its "crate scope" check
compares only the first module-QN segment, which is the project name.
(Found by the generic-engine lane on a 12-language fixture.)

Register Rust defs only, at both conversion points, exactly as the Go
builder filters to Go. Rust impl-relation records already carry
CBM_LANG_RUST. As a side effect, the shared Rust build no longer
registers the C defs of a mixed C+Rust tree.

Tests: pipeline_rust_call_never_binds_another_language (sequential and
parallel): no CALLS from the Rust caller into the Perl file, and the
Rust associated function still binds.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Formatting only, with the repo's clang-format (Homebrew LLVM).

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

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

`Opts::write(true)` is an associated function. When the Rust LSP cannot
place it, the registry can (import_map or qualified_suffix onto
fsx.Opts.write). The two free-function short-name fallbacks (the
cross-crate member lookup and the project-wide lsp_short_name_unique)
ran first, though. They bound the call to the project's only free
`write`, and that LSP row pre-empted the right answer.

While the Rust registry also held other languages' definitions, a
same-named JS or Perl function often made the free name ambiguous and
hid this. With Rust-only registries the hijack replaced 516
qualified_suffix and 84 import_map edges on 7 Rust repos.

A call whose qualifying segment names a type now skips both fallbacks.
That covers an ASCII-uppercase initial, `Self`, and a qualified
`<T as Tr>::f` path.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The shared Tier-2 build now registers Rust definitions only. Two
fixtures that feed cbm_rust_build_cross_registry left CBMLSPDef.lang
zero, which the pipeline never does (pass_lsp_cross.c tags every def
with its file's language). The other shared-registry fixtures in this
file already set CBM_LANG_RUST. Set it on these two as well.

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>
@DeusData
DeusData merged commit 08ee95a into main Oct 18, 2026
28 checks passed
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