Skip to content

fix(rust): a module-path call links the function its module declares - #2955

Merged
colbymchenry merged 1 commit into
mainfrom
claude/pr-2308-rust-module-path-calls
Oct 28, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/pr-2308-rust-module-path-calls

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Summary

A Rust path into a module, like crate::util::take(1), super::util::take(1), util::take(1) or use crate::util::take;, now links to the item that module declares. Before, it went to the first node of that name in the module's file, methods included, so an impl Buf { fn take } written above pub fn take caught every such call.

Root cause

resolveRustPathReference mapped the path's module to a file and took getNodesInFile(file).find(n => n.name === leaf && …), with method among the accepted kinds. The first same-named node won: a method of an impl above the free function, a function written inside another function, or an item of a mod tests { … } above the module's own. A path into the module's own file was refused outright, and a path whose head a use binds from the standard library (fs::read under use std::fs;) went to a project module of the same name.

Fix

The path's item is the module's own: never a method, an impl's or trait's associated item, or an item of a function body; an item written at the file's top level comes before one in a module the file declares inline, which the path reaches only through the module's re-export (#[cfg(unix)] mod imp { … } + pub use imp::*;). A head a use binds from the standard library leaves the path to it, and a path into the module's own file resolves.

From the contributor, and what we changed

From @danusha2345's PR #2308: the new candidate selection in resolveRustPathReference (no methods, lexically reachable, module-level first, inline-module fallback, same-file paths, the std-head rule) and __tests__/rust-module-path-call.test.ts.

What we changed:

  • Their module-level and inline-module checks, and the std-head rule, read a second Rust use parser the PR adds (rust-use-bindings.ts). Here they use what main already has: a small rustItemPlacement helper over main's rustUsesOf/rustBlockAt (the same test rustDeclaredItems makes), and main's isBoundToOutOfRepoImport for the std-bound head (the PR's rule, minus the source positions).
  • Left out: the bare-call bindings (as aliases, block-local use, shadowing by locals, enum variants through a use), the new parser and its unit test, and the file-walker changes that depend on it. main's use walk (rustUsesOf, rustWalkModules, rustModuleItems, fix(rust): a name a use brings in is the item of the module its path walks #2702/fix(rust): a name a glob use brings in is an item the globbed module passes on #2714) and scope reader (rust-scopes.ts, fix(rust): a call on a local reaches the method of the type it is declared with #2715) already derive all of that; folding those cases into them is proposed to the maintainer separately.
  • Added three cases to their test file: a namesake in an inline mod tests before the module's item, an inline module's item reached through its re-export from its own file, and fs::read under use std::fs; beside a project src/fs/mod.rs.
  • The CHANGELOG bullet is rewritten for this scope.

Validation

  • __tests__/rust-module-path-call.test.ts: 8/8. On main, 7 fail: method Buf::take, Deep::get, Only::grab, no imports edge for use crate::util::take;, nothing for the inline-module namesake and the re-export, and fs::read → src/fs/mod.rs::read.

  • A/B, main vs this branch, on 6 Rust repos (both arms on main 7cceba5, same kernel; after rebasing onto 2577601, express and flask were rechecked byte-identical and gleam's delta is unchanged):

    repo changed sites what moved
    gleam 152 133 calls like fs::write(…), crate::fs::mkdir(…) move from impl … for ProjectIO's methods to the free functions in compiler-cli/src/fs.rs (plus 2 erlang::module from Generator::module); 4 type_::list(…) → Type::list edges are gone (the real one is type_/prelude.rs::list, through pub use prelude::*); 15 uses of a file's own items inside its test modules link
    dioxus 8 7 links (same-file paths and uses), 1 more target on a shared column
    tokio 5 crate::io::uring::open::open(…) moves from Op::open to the free open; 4 links (crate::sync::watch::channel from watch.rs itself, test-module uses)
    ripgrep 1 use crate::{…, dir::IgnoreBuilder, …} inside dir.rs links
    clap 0
    serde 0

    Every changed site was inspected; all 162 are right. The 135 retargets were each checked to be a module-path call at the site. express and flask are byte-identical.

  • Kernel parity: not affected (resolution only; no extraction change).

  • Cost (medians of 3 interleaved index runs, both arms on main 7cceba5):

    repo CPU main → branch peak RSS main → branch
    tokio 36.6 s → 36.0 s 980 → 986 MB
    gleam 34.3 s → 35.2 s 1003 → 1012 MB

    Both differences are within the runs' spread. In a CPU profile of tokio the changed functions take under 0.1 s. The standard-library check reads the file's uses, so it is asked only when the path would otherwise link a project item; asked for every path it cost 0.3 s on tokio.

  • Full suite on the exact commit (Windows, kernel built, CODEGRAPH_KERNEL_EXPECT=1, --maxWorkers=4): 776 files, 9,175 passed, 0 failed, 85 skipped.

Lands #2308 by @danusha2345.

🤖 Generated with Claude Code

A Rust path into a module, `crate::util::take(1)`, `super::util::take(1)`,
`util::take(1)` or a `use crate::util::take;`, went to the first node of
that name in the module's file, methods included, so an `impl Buf { fn
take }` above `pub fn take` caught every such call. A function written
inside another function, or in a `mod tests { ... }` above the module's
own item, could take it too, a path into the module's own file linked
nothing, and a path through a module a `use` takes from the standard
library (`fs::read` under `use std::fs;`) went to the project's own `fs`
module.

The path's item is now the module's own: never a method, an impl's or a
trait's associated item, or an item of a function body; one written at
the file's top level before one in a module the file declares inline,
which the path reaches through the module's re-export (`#[cfg(unix)] mod
imp { ... }` + `pub use imp::*;`). A path whose head a `use` binds from
the standard library is left to it (main's isBoundToOutOfRepoImport rule
for bare names), and a path into the module's own file resolves.

Validation:
- rust-module-path-call.test.ts: 8/8; on main 7 fail (method Buf::take,
  Deep::get, Only::grab, no import edge, the inline-module namesake, the
  re-export from its own file, std `fs::read` -> src/fs/mod.rs).
- A/B on main 7cceba5, 6 Rust repos: 162 changed sites, all inspected and
  right. gleam: 133 calls like `fs::write(...)` / `crate::fs::mkdir(...)` move
  from `impl ... for ProjectIO`'s methods to compiler-cli's free functions,
  4 wrong `type_::list(...)` -> `Type::list` edges are gone, 15 `use`s link;
  tokio: `crate::io::uring::open::open(...)` moves from `Op::open` to the free
  `open`, +4; dioxus +7, 1 more; ripgrep +1; clap and serde unchanged.
  express and flask byte-identical.
- Cost (medians of 3 interleaved runs): tokio CPU 36.6 -> 36.0 s, gleam
  34.3 -> 35.2 s, both within the runs' spread; peak RSS unchanged. In a
  CPU profile of tokio the changed functions take under 0.1 s. The
  standard-library check reads the file's `use`s, so it runs only when the
  path would otherwise link a project item (asked for every path, it cost
  0.3 s on tokio).

This is the separable part of PR #2308. Its bare-call bindings (`as`
aliases, block-local `use`, shadowing by locals, enum variants through a
`use`) and the second `use` parser it adds are left out: main's `use` walk
(rustUsesOf, rustWalkModules, rustModuleItems) and scope reader
(rust-scopes.ts) cover the same ground, and folding those cases into them
is proposed separately.

Original PR #2308 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@colbymchenry
colbymchenry merged commit 3f8e8b0 into main Oct 28, 2026
@colbymchenry
colbymchenry deleted the claude/pr-2308-rust-module-path-calls branch October 28, 2026 00:34
danusha2345 pushed a commit to danusha2345/codegraph that referenced this pull request Oct 28, 2026
…mchenry#2972; 10 landed PRs yield to main; EXTRACTION_VERSION 56)

Main landed ten more of our PRs as our own commits with the maintainer's additions
(colbymchenry#2923 ← colbymchenry#1936, colbymchenry#2924 ← colbymchenry#1951, colbymchenry#2925 ← colbymchenry#1939, colbymchenry#2930 ← colbymchenry#2921, colbymchenry#2940 ← colbymchenry#1736,
colbymchenry#2942 ← colbymchenry#1947, colbymchenry#2950 ← colbymchenry#1952, colbymchenry#2961 ← colbymchenry#1954, colbymchenry#2965 ← colbymchenry#1942, colbymchenry#2972 ← colbymchenry#1933) and the
module-path half of colbymchenry#2308 (colbymchenry#2955). Where the integration carried our version, main's
wins: the enclosing-scope member pick and its tests, the Go and Java receiver typers,
the delegating-call self-loop rule, the duplicated-module copy confidence, the C #if
collapse, the module-path resolution of Rust paths, the trail/path hardening in
utils.ts. Kept beside main: colbymchenry#1875's Verilog hooks and status fields, colbymchenry#2839's
implementations command, colbymchenry#1923, colbymchenry#2922, colbymchenry#2258, colbymchenry#1761, colbymchenry#1583, the bare-call half of
colbymchenry#2308 (rust-use-bindings.ts). CHANGELOG is main's plus the open PRs' bullets.

EXTRACTION_VERSION 56: the C #if group handling (colbymchenry#2940) and the kernel change.

Co-Authored-By: Claude Fable 5.1 <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