Skip to content

fix(rust): calls through a type a path or a use names reach that type's own function - #3025

Merged
colbymchenry merged 1 commit into
mainfrom
claude/rust-cross-crate-paths
Oct 30, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/rust-cross-crate-paths

Conversation

@colbymchenry

@colbymchenry colbymchenry commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

Builds on #2631 by @BGamboa13, on top of #3014's module walk: a Rust call through a type that a path or a use names now reaches that type's own function or variant, across the crates of a Cargo workspace too.

  • sim::items::ItemPool::for_def(…) into another workspace crate, core_sim::items::… through a renamed path dependency, vendored::helper(…) through one outside the members.
  • ItemPool::for_def(…) / Rng::new(…) under use sim::items::ItemPool; / use sim::Rng; (a re-export), Handle::current() under use crate::runtime::Handle;.
  • crate::models::Mode::Fast(1): a variant through a module path; Codec::encode(&x): a trait's own method.
  • use sim::RANK_COUNT; / use crate::ledger::{KEY_RANK_COUNT, LIMIT};: module-level pub const/static items.
  • h: &scheduler::\n Handle: a type rustfmt wrapped below its path links by that path, not by a use of its last name.
  • Manifest paths stay inside the project: a workspace member listed outside it (../shared, an absolute path) is no longer read or listed, and a [lib]/[[bin]] path that leaves it is no crate root.

Root cause

  • The module walk (rustPathTarget) stopped at the type: Type::member paths fell to qualified-name matching, which takes any ItemPool::for_def, a decoy crate's included.
  • Module items left out variable, the kind a pub const/static is extracted as.
  • A qualifier on the line above a type was never read.
  • getCargoWorkspaceCrateMap read <member>/Cargo.toml (and listed a glob member's directories) for every member as written, ../outside included; only dependency paths were kept inside the project.

Fix

  • rustPathTarget: for a call whose second-to-last segment is a type (CamelCase), the type is the item the path's modules name (rustUsePathItems), or, written alone, the one the visible use of its name names (rustUseItemsAt). rustTypeMember then finds the member: an enum's variant; else the type's inherent function or const in the type's own crate (a trait impl's only when it is the one of that name, as matchRustSelfPath picks); a trait's own declared method. A type alias, Self, a type the module declares itself and type paths in other positions are left as they were.
  • RUST_PATH_ITEM_KINDS includes variable (module-level only: rustDeclaredItems never returns an impl's const).
  • matchRustUseBoundName reads a qualifier written on the line above a bare type (rust-scopes' blanked code, so // crate::scheduler:: is none) and resolves the whole path.
  • Memoized per path and per (type, member).
  • escapesRoot (fix(paths): a folder named like ..generated is inside the project, not outside it #3028's helper): expandMembers skips a member that leaves the project before reading or listing anything for it; rustCrateGraph passes members and [lib]/[[bin]] paths through the same join its dependency paths use, which now asks escapesRoot too and drops one that leaves the project. A member whose folder only starts with two dots (..generated/crate) is still read.

Validation

  • Tests: fix(rust): resolve paths into other crates of a Cargo workspace #2631's rust-cross-crate.test.ts (9 cases, credited), rust-type-member-paths.test.ts and rust-manifest-paths.test.ts: on main 10 of 15 fail; all pass. Every rust-* suite passes. On main, a workspace listing ../outside, ../vendor/* and /abs/crate read ../outside/Cargo.toml, ../vendor/x/Cargo.toml and /abs/crate/Cargo.toml and listed ../vendor; with the fix it reads only the project's manifests.

  • A/B against main (4955eac, with fix(rust): use bindings follow Rust's scopes, and module paths walk the crate's modules #3014; re-checked on 60b0d7a and cdfa31b, each with its own kernel), 7 Rust repos, native kernel; every site the same as against fix(rust): use bindings follow Rust's scopes, and module paths walk the crate's modules #3014's own commit:

    repo added retargeted additive same target, now by path removed
    tokio 205 139 230 1,178 0
    ripgrep 155 13 1 624 0
    axum 25 0 0 481 0
    clap 179 0 295 2,687 0
    serde 28 0 0 73 0
    dioxus 142 6 31 1,367 0
    gleam 114 47 0 1,194 0

    Retargets, all checked: tokio's Semaphore::new (66) from batch_semaphore.rs to the sync::Semaphore the code imports, RwLock/Mutex/Barrier::new (55) from loom mocks to tokio::sync's, a task Builder::new (9) from tokio-test's io builder, Wheel::new / ReusableBoxFuture::new / Elapsed::new from tokio-util's and tokio-stream's namesakes to tokio's own; ripgrep's Error::from from grep-cli's Error to ignore's own, RegexMatcherBuilder::new() from pcre2's to grep-regex's; dioxus's CssAssetOptions::default() to the inherent const fn default Rust picks over the Default impl; gleam's Printer::new (45) from docs/printer.rs to the type_::printer::Printer the files import. Additive sites are chains gaining their head call (Builder::new_current_thread().build(), clap::Arg::new("x").short(…)). Added sites sampled (all right) and every target's crate checked against the manifests. express and flask byte-identical. The manifest change moves nothing in these repos (none lists a member outside itself).

  • Cost (native kernel, medians of 3 interleaved runs; dioxus of 7, as its spread was widest):

    repo CPU peak RSS
    tokio 32.1 → 29.7 s 1,000 → 1,010 MB
    gleam 29.1 → 28.1 s 1,010 → 1,004 MB
    dioxus 38.7 → 40.1 s 1,111 → 1,115 MB

    Timed on one thread (resolver pool off), dioxus's rustPathTarget takes 1.32 s against main's 1.24 s over the same 17,198 calls, and rustTypeMember 15 ms; isRustNameInScope and matchRustUseBoundName are unchanged.

  • Full suite (native kernel rebuilt from main, CODEGRAPH_KERNEL_EXPECT=1, daemon allowed, 4 workers) on this commit, on cdfa31b: 805 files (799 passed, 6 skipped); 9,629 tests: 9,543 passed, 0 failed, 86 skipped.

Left out

  • Associated-const reads (Semaphore::MAX_PERMITS) are not extracted as path references at all, so they gain nothing here.
  • A Type::member path whose type the module declares itself (Foo::new() beside struct Foo) keeps main's resolution; so does a type alias's.
  • #[path] modules are fix(rust): resolve use super::* callees in #[path] modules #1892's follow-up, stacked on this one.

🤖 Generated with Claude Code

@colbymchenry
colbymchenry force-pushed the claude/rust-cross-crate-paths branch 10 times, most recently from 180c9bd to 6655ff5 Compare October 30, 2026 06:32
…'s own function

A Rust call through a type, `sim::items::ItemPool::for_def(…)` into another
crate of the workspace or `ItemPool::for_def(…)` under `use
sim::items::ItemPool;`, was matched by its qualified name alone, so a
namesake type's function in another crate or module could take it (a
workspace's `client` crate's `ItemPool::for_def`). The module walk stopped
at the type, a `use` of a `pub const` or `static` (`use sim::RANK_COUNT;`)
linked nothing, and a type rustfmt wrapped below its path (`h:
&scheduler::\n Handle`) linked by the file's `use` of its last name.

The fix, on the module walk:
- rustPathTarget reads `Type::member` calls: the type is the item the
  path's modules name, or the one the `use` that binds its name names
  (rustUseItemsAt), and rustTypeMember finds its member: an enum's variant;
  else the type's own (inherent) function or `const` in the type's crate,
  a trait impl's only when it is the one of that name; a trait's own
  declared method. A type alias, `Self`, a type the module declares itself
  and a type path in any other position are left as they were.
- Module items include `variable`s (a module-level `pub const`/`static`).
- matchRustUseBoundName reads a qualifier written on the line above a
  type (rust-scopes' blanked code, so a commented-out path is none) and
  resolves the whole path.
- Memoized per (type, member) and per path.
- Manifest paths stay inside the project: a workspace member that leaves
  it (`../sibling`, an absolute path) is neither read nor listed
  (escapesRoot, in expandMembers), and rustCrateGraph drops a
  `[lib]`/`[[bin]]` path that leaves it, as it already did a dependency's.

Validation:
- rust-cross-crate.test.ts (PR 2631's 9 cases, credited),
  rust-type-member-paths.test.ts and rust-manifest-paths.test.ts: on main
  10 of 15 fail; all pass. Every rust-* suite passes. On main the workspace
  map read `../outside/Cargo.toml`, `../vendor/x/Cargo.toml` and
  `/abs/crate/Cargo.toml` and listed `../vendor`; now only the project's.
- A/B against main, native kernel, 7 Rust repos (every site the same
  as against PR #3014's own commit; re-checked on 60b0d7a): tokio +205
  links / 139 retargeted / 230 chains gaining their head call, ripgrep
  +155 / 13 / 1, axum +25, clap +179 / 0 / 295, serde +28, dioxus +142 /
  6 / 31, gleam +114 / 47 / 0; 0 removed. Retargets go to the type the
  code imports or its own crate's (tokio's Semaphore::new and loom mocks
  to tokio::sync, ripgrep's Error::from, gleam's Printer::new); express
  and flask byte-identical.
- Cost, native kernel, medians of 3 interleaved runs (dioxus of 7):
  tokio CPU 32.1 -> 29.7 s, peak RSS 1,000 -> 1,010 MB; gleam 29.1 ->
  28.1 s, 1,010 -> 1,004 MB; dioxus 38.7 -> 40.1 s, 1,111 -> 1,115 MB
  (timed on one thread, rustPathTarget 1.24 -> 1.32 s over 17,198 calls).

Builds on PR #2631 by @BGamboa13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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