Repository navigation
fix(rust): a module-path call links the function its module declares - #2955
Merged
Merged
Conversation
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>
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>
colbymchenry
added a commit
that referenced
this pull request
Oct 29, 2026
…he crate's modules
A bare Rust name took the file's `use`s all at once: a `use` inside one
function (`fn first() { use crate::util::take; take() }`) bound `take` for
every other function too, and for the file's own `fn take`; a renamed one
(`use crate::util::take as consume; consume()`) linked nothing, since no
item carries the alias; a parameter or closure of the function's name
(`fn run(take: fn()) { take() }`, `let take = || {};`) linked to the
imported function; and `use crate::models::E::Good; Good(1)` took any
`E::Good`. Paths through modules (`resolveRustPathReference`) walked the
directories instead of the modules: `super::helper()` and `crate::config()`
with no module between linked nothing, `super` inside an inline
`mod tests { }` went to the parent directory's module, and an item a module
passes on (`pub use`, a glob) or another workspace crate's was not found.
The fix keeps one `use` reader, rustUsesOf:
- each `use` keeps the block it is written in (a function's body or a
block; an inline `mod` and a macro's braces, `cfg_if!`'s nested ones
included, are the module's), and std `use`s are kept by position;
- a reference reads the binding of its innermost scope (rustVisibleUse);
isRustNameInScope uses it, so a block's `use` shadows the file's own item
and a variant `use` names its enum's variant (rustUsePathItems' enum tail);
- matchRustUseBoundName, ahead of the pre-filter, resolves a rename to the
item its path names, and a call to a name a local binds (rust-scopes'
bindings, nearer than any `use`) to nothing, unless the local is a `let`
holding a function by its path (serde's `let test = test::<i8>;`);
- resolveRustPathReference is rustWalkModules + rustModuleItems from the
module the path is written in (rustPathTarget), with today's crate-root
fallback; resolveRustModuleFile, collectRustUseBindings and
rustItemPlacement are gone, and isBoundToOutOfRepoImport reads
rustUsesOf's std `use`s;
- `use`s are read with strings blanked (blankRustCode), so a string's
"use crate::x;" binds nothing;
- the crate graph reads a package's name by its section (axum-core writes
`categories = [..]` first) and a `[lib]`/`[[bin]]` path of its own
(ripgrep's `crates/core/main.rs`), which `crate::` then starts from; a
file no manifest covers starts it from the nearest lib.rs or main.rs
above it, as the directory walk did.
Validation:
- rust-use-call.test.ts (PR 2308's 15 cases), rust-use-scopes.test.ts,
rust-crate-roots.test.ts and 5 new rust-module-path-call cases: on main's
source 18 of 32 fail; all pass. Every rust-* suite passes.
- A/B on main 01e0ac3, 7 repos, the same sites on the kernel and on wasm:
tokio +2,434 sites (1,354 calls, 1,053 imports), 159 removed (152 locals
called or passed as values, 6 `Result`s std's, 1 import through a path
attribute module); ripgrep +248 / -149; axum +710 / -60; clap +730 / -112;
serde +155 / -207 (192 calls to a closure named `test`); dioxus
+2,950 / -272; gleam +1,573 / -359. Groups and samples inspected, every
changed site's crate checked against the manifests' dependencies;
express and flask byte-identical.
- Cost (kernel, medians of 3 interleaved): tokio CPU 34.5 -> 33.0 s, peak
RSS 991 -> 1,007 MB; gleam 32.5 -> 30.6 s, 1,022 -> 1,012 MB; dioxus
45.4 -> 45.4 s, 1,101 -> 1,107 MB.
- Original PR #2308 by @danusha2345; its module-path part landed as PR #2955.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 29, 2026
…he crate's modules
A bare Rust name took the file's `use`s all at once: a `use` inside one
function (`fn first() { use crate::util::take; take() }`) bound `take` for
every other function too, and for the file's own `fn take`; a renamed one
(`use crate::util::take as consume; consume()`) linked nothing, since no
item carries the alias; a parameter or closure of the function's name
(`fn run(take: fn()) { take() }`, `let take = || {};`) linked to the
imported function; and `use crate::models::E::Good; Good(1)` took any
`E::Good`. Paths through modules (`resolveRustPathReference`) walked the
directories instead of the modules: `super::helper()` and `crate::config()`
with no module between linked nothing, `super` inside an inline
`mod tests { }` went to the parent directory's module, and an item a module
passes on (`pub use`, a glob) or another workspace crate's was not found.
The fix keeps one `use` reader, rustUsesOf:
- each `use` keeps the block it is written in (a function's body or a
block; an inline `mod` and a macro's braces, `cfg_if!`'s nested ones
included, are the module's), and std `use`s are kept by position;
- a reference reads the binding of its innermost scope (rustVisibleUse);
isRustNameInScope uses it, so a block's `use` shadows the file's own item
and a variant `use` names its enum's variant (rustUsePathItems' enum tail);
- matchRustUseBoundName, ahead of the pre-filter, resolves a rename to the
item its path names, and a call to a name a local binds (rust-scopes'
bindings, nearer than any `use`) to nothing, unless the local is a `let`
holding a function by its path (serde's `let test = test::<i8>;`);
- resolveRustPathReference is rustWalkModules + rustModuleItems from the
module the path is written in (rustPathTarget), with today's crate-root
fallback; resolveRustModuleFile, collectRustUseBindings and
rustItemPlacement are gone, and isBoundToOutOfRepoImport reads
rustUsesOf's std `use`s;
- `use`s are read with strings blanked (blankRustCode), so a string's
"use crate::x;" binds nothing;
- the crate graph reads a package's name by its section (axum-core writes
`categories = [..]` first) and a `[lib]`/`[[bin]]` path of its own
(ripgrep's `crates/core/main.rs`), which `crate::` then starts from; a
file no manifest covers starts it from the nearest lib.rs or main.rs
above it, as the directory walk did.
Validation:
- rust-use-call.test.ts (PR 2308's 15 cases), rust-use-scopes.test.ts,
rust-crate-roots.test.ts and 5 new rust-module-path-call cases: on main's
source 18 of 32 fail; all pass. Every rust-* suite passes.
- A/B on main 01e0ac3, 7 repos, the same sites on the kernel and on wasm:
tokio +2,434 sites (1,354 calls, 1,053 imports), 159 removed (152 locals
called or passed as values, 6 `Result`s std's, 1 import through a path
attribute module); ripgrep +248 / -149; axum +710 / -60; clap +730 / -112;
serde +155 / -207 (192 calls to a closure named `test`); dioxus
+2,950 / -272; gleam +1,573 / -359. Groups and samples inspected, every
changed site's crate checked against the manifests' dependencies;
express and flask byte-identical.
- Cost (kernel, medians of 3 interleaved): tokio CPU 34.5 -> 33.0 s, peak
RSS 991 -> 1,007 MB; gleam 32.5 -> 30.6 s, 1,022 -> 1,012 MB; dioxus
45.4 -> 45.4 s, 1,101 -> 1,107 MB.
- Original PR #2308 by @danusha2345; its module-path part landed as PR #2955.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 29, 2026
…he crate's modules
A bare Rust name took the file's `use`s all at once: a `use` inside one
function (`fn first() { use crate::util::take; take() }`) bound `take` for
every other function too, and for the file's own `fn take`; a renamed one
(`use crate::util::take as consume; consume()`) linked nothing, since no
item carries the alias; a parameter or closure of the function's name
(`fn run(take: fn()) { take() }`, `let take = || {};`) linked to the
imported function; and `use crate::models::E::Good; Good(1)` took any
`E::Good`. Paths through modules (`resolveRustPathReference`) walked the
directories instead of the modules: `super::helper()` and `crate::config()`
with no module between linked nothing, `super` inside an inline
`mod tests { }` went to the parent directory's module, and an item a module
passes on (`pub use`, a glob) or another workspace crate's was not found.
The fix keeps one `use` reader, rustUsesOf:
- each `use` keeps the block it is written in (a function's body or a
block; an inline `mod` and a macro's braces, `cfg_if!`'s nested ones
included, are the module's), and std `use`s are kept by position;
- a reference reads the binding of its innermost scope (rustVisibleUse);
isRustNameInScope uses it, so a block's `use` shadows the file's own item
and a variant `use` names its enum's variant (rustUsePathItems' enum tail);
- matchRustUseBoundName, ahead of the pre-filter, resolves a rename to the
item its path names, and a call to a name a local binds (rust-scopes'
bindings, nearer than any `use`) to nothing, unless the local is a `let`
holding a function by its path (serde's `let test = test::<i8>;`);
- resolveRustPathReference is rustWalkModules + rustModuleItems from the
module the path is written in (rustPathTarget), with today's crate-root
fallback; resolveRustModuleFile, collectRustUseBindings and
rustItemPlacement are gone, and isBoundToOutOfRepoImport reads
rustUsesOf's std `use`s;
- `use`s are read with strings blanked (blankRustCode), so a string's
"use crate::x;" binds nothing;
- the crate graph reads a package's name by its section (axum-core writes
`categories = [..]` first) and a `[lib]`/`[[bin]]` path of its own
(ripgrep's `crates/core/main.rs`), which `crate::` then starts from; a
file no manifest covers starts it from the nearest lib.rs or main.rs
above it, as the directory walk did.
Validation:
- rust-use-call.test.ts (PR 2308's 15 cases), rust-use-scopes.test.ts,
rust-crate-roots.test.ts and 5 new rust-module-path-call cases: on main's
source 18 of 32 fail; all pass. Every rust-* suite passes.
- A/B on main 01e0ac3, 7 repos, the same sites on the kernel and on wasm:
tokio +2,434 sites (1,354 calls, 1,053 imports), 159 removed (152 locals
called or passed as values, 6 `Result`s std's, 1 import through a path
attribute module); ripgrep +248 / -149; axum +710 / -60; clap +730 / -112;
serde +155 / -207 (192 calls to a closure named `test`); dioxus
+2,950 / -272; gleam +1,573 / -359. Groups and samples inspected, every
changed site's crate checked against the manifests' dependencies;
express and flask byte-identical.
- Cost (kernel, medians of 3 interleaved): tokio CPU 34.5 -> 33.0 s, peak
RSS 991 -> 1,007 MB; gleam 32.5 -> 30.6 s, 1,022 -> 1,012 MB; dioxus
45.4 -> 45.4 s, 1,101 -> 1,107 MB.
- Original PR #2308 by @danusha2345; its module-path part landed as PR #2955.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 29, 2026
…he crate's modules (#3014) A bare Rust name took the file's `use`s all at once: a `use` inside one function (`fn first() { use crate::util::take; take() }`) bound `take` for every other function too, and for the file's own `fn take`; a renamed one (`use crate::util::take as consume; consume()`) linked nothing, since no item carries the alias; a parameter or closure of the function's name (`fn run(take: fn()) { take() }`, `let take = || {};`) linked to the imported function; and `use crate::models::E::Good; Good(1)` took any `E::Good`. Paths through modules (`resolveRustPathReference`) walked the directories instead of the modules: `super::helper()` and `crate::config()` with no module between linked nothing, `super` inside an inline `mod tests { }` went to the parent directory's module, and an item a module passes on (`pub use`, a glob) or another workspace crate's was not found. The fix keeps one `use` reader, rustUsesOf: - each `use` keeps the block it is written in (a function's body or a block; an inline `mod` and a macro's braces, `cfg_if!`'s nested ones included, are the module's), and std `use`s are kept by position; - a reference reads the binding of its innermost scope (rustVisibleUse); isRustNameInScope uses it, so a block's `use` shadows the file's own item and a variant `use` names its enum's variant (rustUsePathItems' enum tail); - matchRustUseBoundName, ahead of the pre-filter, resolves a rename to the item its path names, and a call to a name a local binds (rust-scopes' bindings, nearer than any `use`) to nothing, unless the local is a `let` holding a function by its path (serde's `let test = test::<i8>;`); - resolveRustPathReference is rustWalkModules + rustModuleItems from the module the path is written in (rustPathTarget), with today's crate-root fallback; resolveRustModuleFile, collectRustUseBindings and rustItemPlacement are gone, and isBoundToOutOfRepoImport reads rustUsesOf's std `use`s; - `use`s are read with strings blanked (blankRustCode), so a string's "use crate::x;" binds nothing; - the crate graph reads a package's name by its section (axum-core writes `categories = [..]` first) and a `[lib]`/`[[bin]]` path of its own (ripgrep's `crates/core/main.rs`), which `crate::` then starts from; a file no manifest covers starts it from the nearest lib.rs or main.rs above it, as the directory walk did. Validation: - rust-use-call.test.ts (PR 2308's 15 cases), rust-use-scopes.test.ts, rust-crate-roots.test.ts and 5 new rust-module-path-call cases: on main's source 18 of 32 fail; all pass. Every rust-* suite passes. - A/B on main 01e0ac3, 7 repos, the same sites on the kernel and on wasm: tokio +2,434 sites (1,354 calls, 1,053 imports), 159 removed (152 locals called or passed as values, 6 `Result`s std's, 1 import through a path attribute module); ripgrep +248 / -149; axum +710 / -60; clap +730 / -112; serde +155 / -207 (192 calls to a closure named `test`); dioxus +2,950 / -272; gleam +1,573 / -359. Groups and samples inspected, every changed site's crate checked against the manifests' dependencies; express and flask byte-identical. - Cost (kernel, medians of 3 interleaved): tokio CPU 34.5 -> 33.0 s, peak RSS 991 -> 1,007 MB; gleam 32.5 -> 30.6 s, 1,022 -> 1,012 MB; dioxus 45.4 -> 45.4 s, 1,101 -> 1,107 MB. - Original PR #2308 by @danusha2345; its module-path part landed as PR #2955. Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A Rust path into a module, like
crate::util::take(1),super::util::take(1),util::take(1)oruse 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 animpl Buf { fn take }written abovepub fn takecaught every such call.Root cause
resolveRustPathReferencemapped the path's module to a file and tookgetNodesInFile(file).find(n => n.name === leaf && …), withmethodamong the accepted kinds. The first same-named node won: a method of animplabove the free function, a function written inside another function, or an item of amod tests { … }above the module's own. A path into the module's own file was refused outright, and a path whose head ausebinds from the standard library (fs::readunderuse 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 ausebinds 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:
useparser the PR adds (rust-use-bindings.ts). Here they use what main already has: a smallrustItemPlacementhelper over main'srustUsesOf/rustBlockAt(the same testrustDeclaredItemsmakes), and main'sisBoundToOutOfRepoImportfor the std-bound head (the PR's rule, minus the source positions).asaliases, block-localuse, shadowing by locals, enum variants through ause), the new parser and its unit test, and the file-walker changes that depend on it. main'susewalk (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.mod testsbefore the module's item, an inline module's item reached through its re-export from its own file, andfs::readunderuse std::fs;beside a projectsrc/fs/mod.rs.Validation
__tests__/rust-module-path-call.test.ts: 8/8. On main, 7 fail:method Buf::take,Deep::get,Only::grab, noimportsedge foruse crate::util::take;, nothing for the inline-module namesake and the re-export, andfs::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):
fs::write(…),crate::fs::mkdir(…)move fromimpl … for ProjectIO's methods to the free functions incompiler-cli/src/fs.rs(plus 2erlang::modulefromGenerator::module); 4type_::list(…)→Type::listedges are gone (the real one istype_/prelude.rs::list, throughpub use prelude::*); 15uses of a file's own items inside its test modules linkuses), 1 more target on a shared columncrate::io::uring::open::open(…)moves fromOp::opento the freeopen; 4 links (crate::sync::watch::channelfrom watch.rs itself, test-moduleuses)use crate::{…, dir::IgnoreBuilder, …}inside dir.rs linksEvery 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):
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