Skip to content

Rust: a grouped use a::{B, C} becomes one import with local name {B, C}, so calls through it never use the import map #2922

Closed

Description

@sortakool

Version: codebase-memory-mcp v0.12.1 (pkg/go/v0.12.1, a7158ac). main at 612eee2 has the
same parser (internal/cbm/extract_imports.c:1900, identical body) and the same lookup
(src/pipeline/registry.c:1859, called at :2321).

What I did

I indexed jdx/mise v2026.11.2 and compared its CALLS edges with a rust-analyzer SCIP index of
the same checkout. src/cli/run.rs:27 imports the handler in a group:

use crate::task::task_output_handler::{OutputHandler, SummaryOutcome};

and src/cli/run.rs:1808 calls OutputHandler::new(output_config). SCIP links the call to the
one OutputHandler.new in the project; cbm has no edge. Of mise's 5,172 Rust IMPORTS edges,
1,912 have a local_name that starts with {.

To isolate it I wrote a probe crate (mode full, cbm 0.12.1):

// src/h.rs
pub struct Handler { pub n: u32 }
impl Handler { pub fn new() -> Self { Handler { n: 1 } } }
pub struct Other;

// src/a.rs
use crate::h::{Handler, Other};
pub fn grouped() -> u32 { let _o = Other; Handler::new().n }

// src/b.rs
use crate::h::Handler;
pub fn single() -> u32 { Handler::new().n }

I indexed it twice: with src/many.rs empty, and with many.rs holding 300
pub struct S{i}; impl S{i} { pub fn new() -> Self { S{i} } }. The second run pushes the bare
name new past the registry's 256-candidate cap, so the name lookup can't cover for the import map.

What happened

file IMPORTS local_name Handler::new(), many.rs empty with 300 more news
src/b.rs Handler edge, import_map edge, import_map
src/a.rs {Handler, Other} edge, unique_name no edge

a.rs also gets only one IMPORTS edge, to Handler. The import of Other is lost.

Why

parse_rust_imports (internal/cbm/extract_imports.c:1099-1127) makes one CBMImport per
top-level use_declaration. module_path is the whole text after use , and
local_name = path_last(full). path_last (:99-113) cuts after the last /, ., : or \,
so for crate::h::{Handler, Other} the local name is {Handler, Other}.

The IMPORTS edge stores that local name (src/pipeline/pass_parallel.c:1862-1866). The call-time
import map is built from those edges (src/pipeline/pass_lsp_cross.c:1007, read at :1054-1066).
resolve_import_map (src/pipeline/registry.c:1350) looks the callee's first segment up by exact
key (:1360-1368). Handler never equals {Handler, Other}, so the lookup is empty (:1370-1371)
and resolution falls through to the name lookup.

Proposal

  • Walk the use_list and scoped_use_list children and push one import per member:
    • each with its own local name: the member's last segment, or its alias for as;
    • each with its full path, the group's prefix joined to the member;
    • self in a group (use a::b::{self, C}) binds b.
  • Do the same for nested groups (use a::{b::{C, D}, E}).
  • Add an extractor test: use crate::h::{Handler, Other as O}; gives two imports, Handler →
    crate::h::Handler and O → crate::h::Other.

Activity

  1. DeusData commented on Nov 10, 2026

    @DeusData
    Owner

    Fixed by #2932, merged in d40ebf6. Grouped use declarations are now expanded into one import per member (nested groups, self, renames and globs included), so a call like Handler::new() behind use crate::h::{Handler, Other}; resolves through the import map. Thank you, @sortakool, for the precise report and the minimal reproduction: the 300-new probe went straight into the regression test, and your mise numbers (5,172 imports, 1,912 with a { local name) matched our measurement exactly. And thank you, @kasheeesh08, for the fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions