fix(rust): a module a #[path] attribute loads is the module that declares it (#3064)

A Rust module loaded with `#[path = "…"]` was found by its file's directory:
fast-mlsirm's `#[path = "../../../tests/unit/scaling_tests.rs"] mod tests;`
in `crates/core/src/scaling.rs` made `tests/unit/scaling_tests.rs`'s
`use super::*` walk to a `tests/unit` module that doesn't exist, so its
`predict_rating_multi(1)` linked to nothing, or to a same-named function of
another crate; its `crate::` had no crate, and the parent's private items
looked out of its reach. From the declaring side, a path through such a
module (`utils::mock_event(…)` under `#[path = "./utils.rs"] mod utils;`)
reached nothing.

The fix reads each file's `#[path]` declarations as the Rust Reference
reads the attribute (from the declaring file's directory, or inside an
inline `mod` from that module's own; a path that leaves the project, by
escapesRoot, or that a `cfg_attr` sets, names no file):
- rustSubmodule: a module the module declares once with a `#[path]` is
  the file it names, whatever else loads that file too (a helper several
  tests load is each test's); cfg alternatives that name a file each stay
  unknown, and a path's 2015 crate-root fallback no longer reaches past
  such a module to a namesake at the crate root.
- rustPathClaim: a file the regular rules don't place is the module of the
  one `#[path]` declaration that loads it, if exactly one does. Placed
  (rustPlacedByName) are crate roots, files with a `fn main` or
  `#![no_main]` of their own (trybuild cases, fuzz targets), and files a
  plain `mod name;` declares in the module owning their directory or in an
  inline module of one above it (dioxus's `mod components { pub mod home; }`
  in main.rs). Only for a file left over are the project's Rust files that
  write `path =` read (a byte check first), once per resolver. With a
  claim, rustSuperModule is the declaring module, rustFileModule owns the
  file's directory (rustc reads every `#[path]` file as a mod.rs),
  rustCrateRootModule is the declaring file's crate, and rustModuleWithin,
  isRustItemVisibleFrom and isVisibleAcrossFiles see the file inside the
  declaring module, so `use super::*` brings in its private items.
- rustCrateGraph: `[[example]]`, `[[test]]` and `[[bench]]` paths are
  target roots, as `[lib]`/`[[bin]]` ones are since PR #3014, and such an
  example is its own crate's root (dioxus's
  `examples/07-fullstack/handling_errors.rs`).

Validation:
- rust-path-super-glob.test.ts (PR 1892's case, credited) and
  rust-path-attribute-modules.test.ts (7 cases): on main all 8 fail; all
  pass. Every rust-* suite passes.
- A/B against main, native kernel: fast-mlsirm (its Rust files) +2,796
  links (1,839 calls, 427 references, 334 constructions, 196 imports), 71
  retargeted (exposure_tests.rs's `Lcg::next_f64` from cdm_tests.rs's
  private `Lcg` to exposure.rs's, the declaring module's helpers instead
  of bifactor_grm.rs's), 87 reaching the same item through the declaring
  module (confidence 0.4 to 0.9), 0 removed; dioxus +27 (each headless
  test's `utils::mock_event(…)` into the helper five tests load); tokio +2
  (`use super::AtomicU64` in the files atomic_u64_as_mutex.rs loads);
  ripgrep, axum, clap, serde, gleam, express and flask unchanged.
- Cost, native kernel, medians of 3 interleaved runs: fast-mlsirm CPU 15.3
  -> 15.8 s, wall 4.2 -> 5.2 s, peak RSS 825 -> 882 MB; tokio CPU 26.1 ->
  26.7 s, wall 10.4 -> 11.2 s, peak RSS 1,023 -> 1,028 MB; dioxus CPU 34.8
  -> 35.6 s, wall 10.3 -> 11.2 s, peak RSS 1,125 -> 1,120 MB; gleam CPU
  24.1 -> 23.8 s, wall 6.6 -> 6.7 s, peak RSS 1,027 -> 1,023 MB.

Builds on PR #1892 by @seonghobae.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-30 13:21:12 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent ed6b88e53a
commit cc17978174
5 changed files with 550 additions and 27 deletions
+10 -7
View File
@@ -159,22 +159,25 @@ export function parsePackageName(cargoToml: string): string | null {
/**
* The target roots a manifest names with a `path` of their own: `[lib] path =
* "…"` and each `[[bin]] path = "…"` (ripgrep's `crates/core/main.rs`), as
* written — relative to the manifest's directory.
* "…"`, each `[[bin]] path = "…"` (ripgrep's `crates/core/main.rs`), and each
* `[[example]]`, `[[test]]` and `[[bench]]` one (dioxus's
* `examples/07-fullstack/handling_errors.rs`), as written — relative to the
* manifest's directory.
*/
export function parseTargetPaths(cargoToml: string): { lib?: string; bins: string[] } {
const out: { lib?: string; bins: string[] } = { bins: [] };
let table: 'lib' | 'bin' | null = null;
export function parseTargetPaths(cargoToml: string): { lib?: string; bins: string[]; others: string[] } {
const out: { lib?: string; bins: string[]; others: string[] } = { bins: [], others: [] };
let table: 'lib' | 'bin' | 'other' | null = null;
for (const raw of cargoToml.split(/\r?\n/)) {
const line = raw.replace(/#.*$/, '').trim();
if (line.startsWith('[')) {
table = line === '[lib]' ? 'lib' : line === '[[bin]]' ? 'bin' : null;
table = line === '[lib]' ? 'lib' : line === '[[bin]]' ? 'bin' : /^\[\[(?:example|test|bench)\]\]$/.test(line) ? 'other' : null;
continue;
}
const at = table && /^path\s*=\s*["']([^"'\n]+)["']/.exec(line)?.[1]?.replace(/\\/g, '/');
if (!at) continue;
if (table === 'lib') out.lib = at;
else out.bins.push(at);
else if (table === 'bin') out.bins.push(at);
else out.others.push(at);
}
return out;
}
+305 -20
View File
@@ -2861,6 +2861,24 @@ function rustModuleDir(filePath: string): string {
return path.posix.join(dir, base.replace(/\.rs$/, ''));
}
/**
* Whether a Rust file is inside the module whose file is `ownerFile` and whose
* submodules live under `ownerDir`: below that directory, or loaded by a
* `#[path]` declaration written inside it (fast-mlsirm's
* `tests/unit/scaling_tests.rs` is `scaling.rs`'s `mod tests`, and sees its
* private items).
*/
function rustFileUnder(file: string, ownerDir: string, ownerFile: string, context: ResolutionContext): boolean {
let f = file;
for (let i = 0; i < 8; i++) {
if (f.startsWith(`${ownerDir}/`) || (i > 0 && f === ownerFile)) return true;
const claim = rustPathClaim(f, context);
if (!claim) return false;
f = claim.file;
}
return false;
}
/**
* Standard-library package names (and kubernetes' aliases of a few of its own
* util packages). A dotted Go reference through one is taken for a call into
@@ -3957,8 +3975,7 @@ export function isVisibleAcrossFiles(candidate: Node, ref: UnresolvedRef, contex
// trait's, reached through a value whose type the call's receiver
// declares (matchRustTypedReceiverCall). A guess by its name alone stays out.
if (candidate.kind === 'method' && RUST_TYPED_REFS.get(context)?.has(ref) && rustTraitOf(candidate, context)?.visibility !== 'private') return true;
const owner = rustModuleDir(candidate.filePath);
return ref.filePath.startsWith(owner + '/');
return rustFileUnder(ref.filePath, rustModuleDir(candidate.filePath), candidate.filePath, context);
}
// A Dart `_name` is its library's, which spans the library file and its
// parts: riverpod_generator's `class Counter extends _$Counter` names the
@@ -8732,6 +8749,10 @@ interface RustUses {
blocks: RustModBlock[];
/** Modules it declares with a `#[path = "…"]` of their own (often one per cfg), whose file can't be told from their name. */
pathMods: Set<string>;
/** Modules it declares out of line with no `#[path]` (`mod name;`): the files the regular rules name. */
mods: Set<string>;
/** Whether it starts a binary of its own: a top-level `fn main` or `#![no_main]` (a trybuild case, a fuzz target). */
main: boolean;
/** Whether every brace closes, so `blocks` can be trusted to say what is inside them. */
balanced: boolean;
/** The offset each line starts at. */
@@ -8852,6 +8873,8 @@ interface RustCrate {
lib?: string;
/** The binaries' roots its manifest gives a `path` (ripgrep's `crates/core/main.rs`), as project files. */
bins: string[];
/** The examples', tests' and benches' roots its manifest gives a `path`, as project files: each its own crate. */
others: string[];
}
interface RustCrateGraph {
@@ -8892,8 +8915,8 @@ function rustCrateGraph(context: ResolutionContext): RustCrateGraph {
if (!name) return false;
const targets = parseTargetPaths(text!);
const lib = targets.lib === undefined ? null : join(dir, targets.lib);
const bins = targets.bins.map((bin) => join(dir, bin)).filter((bin): bin is string => bin !== null);
crates.set(dir, { name, keys: new Map(), deps: new Set(), lib: lib ?? undefined, bins });
const files = (rels: string[]): string[] => rels.map((rel) => join(dir, rel)).filter((file): file is string => file !== null);
crates.set(dir, { name, keys: new Map(), deps: new Set(), lib: lib ?? undefined, bins: files(targets.bins), others: files(targets.others) });
manifests.set(dir, text!);
if (!byName.has(name)) byName.set(name, dir);
return true;
@@ -9000,7 +9023,7 @@ function rustUsesOf(filePath: string, context: ResolutionContext): RustUses {
if (hit) return hit;
const uses: RustUses = {
names: new Set(), globs: new Set(), external: new Set(), bound: new Set(), boundFrom: new Map(),
paths: new Map(), stdPaths: new Map(), globPaths: [], blocks: [], pathMods: new Set(), balanced: true, lineStarts: [],
paths: new Map(), stdPaths: new Map(), globPaths: [], blocks: [], pathMods: new Set(), mods: new Set(), main: false, balanced: true, lineStarts: [],
};
const leaves = (tree: string): string[] => [
...[...tree.matchAll(/([A-Za-z_]\w*)\s*(?=[,}]|$|\s+as\b)|\bas\s+([A-Za-z_]\w*)/g)]
@@ -9067,9 +9090,16 @@ function rustUsesOf(filePath: string, context: ResolutionContext): RustUses {
for (const g of tree.matchAll(/(\w+)\s*::\s*(?:\{[^}]*)?\*/g)) uses.globs.add(g[1]!);
});
// `#[path = "atomic_u64_native.rs"] mod imp;`, `#[cfg_attr(docsrs, path = "…")] pub mod de;`
const pathModAt = new Set<number>();
for (const m of text.matchAll(/#\[\s*(?:cfg_attr\s*\([^\]]*\bpath|path)\s*=[^\]]*\]\s*(?:#!?\[[^\]]*\]\s*)*(?:pub(?:\([^)]*\))?\s+)?mod\s+(?:r#)?([A-Za-z_]\w*)/g)) {
uses.pathMods.add(m[1]!);
pathModAt.add(m.index! + m[0].search(/\bmod\s+(?:r#)?[A-Za-z_]\w*$/));
}
// `mod name;` with no `#[path]` of its own.
for (const m of text.matchAll(/\bmod\s+(?:r#)?([A-Za-z_]\w*)\s*;/g)) {
if (!pathModAt.has(m.index!)) uses.mods.add(m[1]!);
}
uses.main = /^(?:pub\s+)?(?:async\s+)?fn\s+main\s*\(|^[ \t]*#!\[\s*no_main\s*\]/m.test(text);
for (let at = 0; at !== -1; at = text.indexOf('\n', at) + 1 || -1) uses.lineStarts.push(at);
memo.set(filePath, uses);
return uses;
@@ -9283,13 +9313,239 @@ function isRustTargetRoot(filePath: string, context: ResolutionContext): boolean
const dir = rustCrateDirOf(filePath, graph);
if (dir === null) return /(?:^|\/)(?:lib|main)\.rs$/.test(filePath);
const crate = graph.crates.get(dir);
if (crate && (crate.lib === filePath || crate.bins.includes(filePath))) return true;
if (crate && (crate.lib === filePath || crate.bins.includes(filePath) || crate.others.includes(filePath))) return true;
const rel = dir === '' ? filePath : filePath.slice(dir.length + 1);
return /^(?:src\/(?:lib|main)\.rs|src\/bin\/[^/]+(?:\.rs|\/main\.rs)|(?:tests|examples|benches)\/[^/]+(?:\.rs|\/main\.rs)|build\.rs)$/.test(rel);
}
/** A `#[path = "…"] mod name;` declaration. */
interface RustPathDecl {
/** The file it is written in, and the offset of its `mod`: the module it is declared in. */
file: string;
at: number;
name: string;
/**
* The file it loads, from the project's root, read as the Rust Reference
* reads the attribute; null for one a `cfg_attr` sets, which the build's
* cfg decides, or one that leaves the project.
*/
target: string | null;
}
const RUST_PATH_DECLS = new WeakMap<ResolutionContext, Map<string, readonly RustPathDecl[]>>();
const RUST_PATH_LOADS = new WeakMap<ResolutionContext, Map<string, RustPathDecl[]>>();
const RUST_PATH_LOADERS = new WeakMap<ResolutionContext, Map<string, readonly RustPathDecl[]>>();
const RUST_FILES_BY_DIR = new WeakMap<ResolutionContext, Map<string, string[]>>();
const NO_RUST_PATH_DECLS: readonly RustPathDecl[] = [];
/** An out-of-line module declaration, `pub(crate) mod name;`, from its visibility. */
const RUST_MOD_DECL = /(?:\bpub(?:\s*\([^()]*\))?\s+)?\bmod\s+(?:r#)?([A-Za-z_]\w*)\s*;/g;
/**
* A file's own `#[path = "…"] mod name;` declarations. The attribute's path
* is relative to the directory of the file it is written in — or, inside an
* inline `mod`, to that module's own directory: the file's for a `mod.rs` or
* a crate root, else the directory named after the file, then the inline
* modules' names (the Rust Reference, "The path attribute").
*/
function rustPathDeclsIn(file: string, context: ResolutionContext): readonly RustPathDecl[] {
let memo = RUST_PATH_DECLS.get(context);
if (!memo) RUST_PATH_DECLS.set(context, (memo = new Map()));
let decls = memo.get(file);
if (!decls) memo.set(file, (decls = rustReadPathDecls(file, context)));
return decls;
}
function rustReadPathDecls(file: string, context: ResolutionContext): readonly RustPathDecl[] {
let decls: RustPathDecl[] | undefined;
// Both forms write `path =`: `#[path = "…"]`, `#[cfg_attr(…, path = "…")]`.
// The bytes are checked first, so a file without the word is never decoded.
if (!file.endsWith('.rs') || (context.fileContains && !context.fileContains(file, 'path'))) return NO_RUST_PATH_DECLS;
const src = context.readFile(file);
if (!src || !/\bpath\s*=/.test(src)) return NO_RUST_PATH_DECLS;
const code = blankRustCode(src);
let blocks: RustModBlock[] | undefined;
for (const m of code.matchAll(RUST_MOD_DECL)) {
let target: string | null | undefined;
for (const [from, to] of rustAttributesBefore(code, m.index!)) {
const raw = src.slice(from, to);
const plain = /^#\s*\[\s*path\s*=\s*"([^"]*)"\s*\]$/.exec(raw);
if (plain) target = plain[1]!.replace(/\\/g, '/');
else if (target === undefined && /^#\s*\[\s*cfg_attr\b[\s\S]*\bpath\s*=/.test(raw)) target = null;
}
if (target === undefined) continue;
// The `mod` keyword's offset (`mod modules;` holds a second `mod`).
const at = m.index! + /\bmod\s/.exec(m[0])!.index;
// At the file's top level it is the file's own; only one inside braces
// (an inline module's, or a macro's) needs the file's block layout.
const block = rustBraceDepth(code, at) === 0 ? null : rustBlockAt((blocks ??= rustUsesOf(file, context).blocks), at);
// From the file's directory; in an inline module, from that module's.
let base = rustParentDir(file);
if (block) {
base = isRustTargetRoot(file, context) || /(?:^|\/)mod\.rs$/.test(file) ? base : file.replace(/\.rs$/, '');
const names: string[] = [];
for (let b: RustModBlock | null = block; b; b = b.parent) names.unshift(b.name);
for (const name of names) base = rustJoin(base, name);
}
let loads: string | null = null;
if (target !== null && !/^(?:\/|[A-Za-z]:)/.test(target)) {
const joined = path.posix.normalize(rustJoin(base, target));
loads = escapesRoot(joined) ? null : joined;
}
(decls ??= []).push({ file, at, name: m[1]!, target: loads });
}
return decls ?? NO_RUST_PATH_DECLS;
}
/** How many braces are open at `at` in blanked code. */
function rustBraceDepth(code: string, at: number): number {
let depth = 0;
for (let i = 0; i < at; i++) {
const c = code.charCodeAt(i);
if (c === 123) depth++;
else if (c === 125 && depth > 0) depth--;
}
return depth;
}
/** Each file a `#[path]` of the project loads → the declarations that load it: every Rust file's, read once. */
function rustPathLoads(context: ResolutionContext): Map<string, RustPathDecl[]> {
const hit = RUST_PATH_LOADS.get(context);
if (hit) return hit;
const out = new Map<string, RustPathDecl[]>();
RUST_PATH_LOADS.set(context, out);
for (const file of context.getAllFiles()) {
for (const decl of rustPathDeclsIn(file, context)) {
if (decl.target === null) continue;
const list = out.get(decl.target);
if (list) list.push(decl);
else out.set(decl.target, [decl]);
}
}
return out;
}
/** The project's Rust files directly in `dir`. */
function rustFilesIn(dir: string, context: ResolutionContext): readonly string[] {
let index = RUST_FILES_BY_DIR.get(context);
if (!index) {
index = new Map();
for (const file of context.getAllFiles()) {
if (!file.endsWith('.rs')) continue;
const parent = rustParentDir(file);
const list = index.get(parent);
if (list) list.push(file);
else index.set(parent, [file]);
}
RUST_FILES_BY_DIR.set(context, index);
}
return index.get(dir) ?? [];
}
/**
* Whether the regular rules place a Rust file: a crate root (one with a
* `fn main` of its own too, as a trybuild case is), or `name.rs` /
* `name/mod.rs` that a plain `mod name;` declares in the module owning its
* directory (that directory's `mod.rs`, the file named after it, or a crate
* root in it) — or in an inline module of the module owning a directory
* above it, whose names make up the rest of the path (dioxus's
* ecommerce-site: `mod components { pub mod home; }` in `src/main.rs` places
* `src/components/home.rs`). Such a file is that module, whatever else loads it.
*/
function rustPlacedByName(file: string, context: ResolutionContext): boolean {
if (isRustTargetRoot(file, context) || rustUsesOf(file, context).main) return true;
const dir = rustParentDir(file);
const isMod = file === 'mod.rs' || file.endsWith('/mod.rs');
if (isMod && dir === '') return false;
const name = path.posix.basename(isMod ? dir : file, '.rs');
let parent = isMod ? rustParentDir(dir) : dir;
// The inline modules, outermost first, between the owner's directory and the file's.
const inline: string[] = [];
for (let depth = 0; depth < 4; depth++) {
const owners = [rustJoin(parent, 'mod.rs'), ...(parent === '' ? [] : [`${parent}.rs`])];
for (const f of rustFilesIn(parent, context)) if (isRustTargetRoot(f, context)) owners.push(f);
for (const owner of owners) {
if (owner === file || !context.fileExists(owner)) continue;
const uses = rustUsesOf(owner, context);
if (uses.mods.has(name) && (inline.length === 0 || rustHasInlineModules(uses.blocks, inline))) return true;
}
if (parent === '') break;
inline.unshift(path.posix.basename(parent));
parent = rustParentDir(parent);
}
return false;
}
/** Whether a file declares the inline modules `names` (outermost first) nested in one another from its top level. */
function rustHasInlineModules(blocks: readonly RustModBlock[], names: readonly string[]): boolean {
return blocks.some((block) => {
let b: RustModBlock | null = block;
for (let i = names.length - 1; i >= 0; i--) {
if (!b || b.name !== names[i]) return false;
b = b.parent;
}
return b === null;
});
}
/**
* The `#[path]` declarations that load `file`, for a file the regular rules
* don't place (rustPlacedByName); none for one they do. Only then is every
* Rust file of the project read for its declarations.
*/
function rustPathLoaders(file: string, context: ResolutionContext): readonly RustPathDecl[] {
let memo = RUST_PATH_LOADERS.get(context);
if (!memo) RUST_PATH_LOADERS.set(context, (memo = new Map()));
const hit = memo.get(file);
if (hit) return hit;
const loaders = !file.endsWith('.rs') || rustPlacedByName(file, context)
? NO_RUST_PATH_DECLS
: rustPathLoads(context).get(file) ?? NO_RUST_PATH_DECLS;
memo.set(file, loaders);
return loaders;
}
/** The spans of the outer attributes (`#[…]`) written right before `at` in blanked code, nearest first. */
function rustAttributesBefore(code: string, at: number): Array<[number, number]> {
const spans: Array<[number, number]> = [];
let p = at;
for (;;) {
let q = p - 1;
while (q >= 0 && /\s/.test(code[q]!)) q--;
if (code[q] !== ']') break;
let depth = 0;
let open = -1;
for (let i = q; i >= 0; i--) {
if (code[i] === ']') depth++;
else if (code[i] === '[' && --depth === 0) {
open = i;
break;
}
}
let hash = open - 1;
while (hash >= 0 && /\s/.test(code[hash]!)) hash--;
if (open < 0 || code[hash] !== '#') break;
spans.push([hash, q + 1]);
p = hash;
}
return spans;
}
/**
* The one `#[path]` declaration that loads `file` as a module, or null: a file
* the regular rules place, one no such declaration loads, or one several do
* (tests that each load a shared helper; then which module it is depends on
* the crate being built).
*/
function rustPathClaim(file: string, context: ResolutionContext): RustPathDecl | null {
const loaders = rustPathLoaders(file, context);
return loaders.length === 1 ? loaders[0]! : null;
}
/** The module a Rust file is, at its own top level. */
function rustFileModule(filePath: string, context: ResolutionContext): RustModule {
// A file a `#[path]` loads owns its directory, as a `mod.rs` does: rustc
// reads every `#[path]` file so.
if (rustPathClaim(filePath, context)) return { file: filePath, dir: rustParentDir(filePath), block: null, root: false };
const root = isRustTargetRoot(filePath, context);
const dir = root || filePath.endsWith('/mod.rs') || filePath === 'mod.rs' ? rustParentDir(filePath) : filePath.replace(/\.rs$/, '');
return { file: filePath, dir, block: null, root };
@@ -9314,10 +9570,15 @@ function rustModuleAt(filePath: string, offset: number, context: ResolutionConte
* `main.rs` above it. null when no single root is the file's: a
* `tests/support/` module serves every test that declares it.
*/
function rustCrateRootModule(filePath: string, context: ResolutionContext): RustModule | null {
function rustCrateRootModule(filePath: string, context: ResolutionContext, depth = 0): RustModule | null {
// A file a `#[path]` loads is in the crate of the file that declares it.
const claim = depth < 8 ? rustPathClaim(filePath, context) : null;
if (claim) return rustCrateRootModule(claim.file, context, depth + 1);
const graph = rustCrateGraph(context);
const dir = rustCrateDirOf(filePath, graph);
if (dir === null) return rustNearestRootModule(filePath, context);
// An example, test or bench the manifest places with a path of its own is its own crate's root.
if (graph.crates.get(dir)?.others.includes(filePath)) return { file: filePath, dir: rustParentDir(filePath), block: null, root: true };
const base = dir === '' ? '' : `${dir}/`;
const rel = filePath.slice(base.length);
const target = /^(src\/bin|tests|examples|benches)\/([^/]+?)(\.rs$|\/)/.exec(rel);
@@ -9370,14 +9631,21 @@ function rustLibModule(crateDir: string, context: ResolutionContext): RustModule
/**
* The module `name` that `m` declares: inline (`mod name { … }`), else
* `<name>.rs` or `<name>/mod.rs` in its directory, else one it passes on under
* that name — dioxus's `pub use dioxus_hooks as hooks;`. null for one a
* `#[path]` puts elsewhere — tokio's `process::imp` is `unix/mod.rs` or
* `windows.rs` by cfg — as for one with no file.
* that name — dioxus's `pub use dioxus_hooks as hooks;`. One a `#[path]`
* puts elsewhere is the file it names when `m` declares it once, whatever
* else loads that file too (a helper several tests load is each test's);
* null when cfg alternatives name a file each (tokio's `process::imp` is
* `unix/mod.rs` or `windows.rs`) or a `cfg_attr` sets the path, as for one
* with no file.
*/
function rustSubmodule(m: RustModule, name: string, context: ResolutionContext, depth = 0): RustModule | null {
const dir = rustJoin(m.dir, name);
const layout = rustUsesOf(m.file, context);
if (layout.pathMods.has(name)) return null;
if (layout.pathMods.has(name)) {
const decls = rustPathDeclsIn(m.file, context).filter((d) => d.name === name && rustBlockAt(layout.blocks, d.at) === m.block);
const target = decls.length === 1 ? decls[0]!.target : null;
return target !== null && context.fileExists(target) ? { file: target, dir: rustParentDir(target), block: null, root: false } : null;
}
const block = layout.blocks.find((b) => b.name === name && b.parent === m.block);
if (block) return { file: m.file, dir, block, root: false };
for (const file of [`${dir}.rs`, `${dir}/mod.rs`]) {
@@ -9392,11 +9660,18 @@ function rustSubmodule(m: RustModule, name: string, context: ResolutionContext,
return null;
}
/** The module `m` is declared in — what `super::` names — or null above a crate's root. */
/**
* The module `m` is declared in — what `super::` names — or null above a
* crate's root. A file a `#[path]` loads is declared where that attribute is,
* wherever the file lies (fast-mlsirm's `tests/unit/scaling_tests.rs` is
* `crates/core/src/scaling.rs`'s `mod tests`).
*/
function rustSuperModule(m: RustModule, context: ResolutionContext): RustModule | null {
if (m.block) {
return m.block.parent ? { file: m.file, dir: rustParentDir(m.dir), block: m.block.parent, root: false } : rustFileModule(m.file, context);
}
const claim = rustPathClaim(m.file, context);
if (claim) return rustModuleAt(claim.file, claim.at, context);
if (m.root) return null;
const dir = rustParentDir(m.dir);
for (const file of [rustJoin(dir, 'mod.rs'), ...(dir === '' ? [] : [`${dir}.rs`]), rustJoin(dir, 'lib.rs'), rustJoin(dir, 'main.rs')]) {
@@ -9523,7 +9798,7 @@ function rustGlobbedItems(
const from = fromOf(g);
const mod = rustWalkModules(g.segs, from, context);
if (mod === 'outside') continue;
const items = mod === null ? null : rustModuleItems(mod, name, context, seen, !rustModuleWithin(from, mod));
const items = mod === null ? null : rustModuleItems(mod, name, context, seen, !rustModuleWithin(from, mod, context));
if (items === null) unknown = true;
else found.push(...items);
}
@@ -9531,14 +9806,17 @@ function rustGlobbedItems(
}
/** Whether module `inner` is `outer` or one declared inside it, inline or in a file below its directory. */
function rustModuleWithin(inner: RustModule, outer: RustModule): boolean {
function rustModuleWithin(inner: RustModule, outer: RustModule, context?: ResolutionContext, depth = 0): boolean {
if (inner.file === outer.file) {
for (let b: RustModBlock | null = inner.block; ; b = b.parent) {
if (b === outer.block) return true;
if (b === null) return false;
}
}
return inner.file.startsWith(outer.dir === '' ? '' : `${outer.dir}/`) && !(outer.dir === '' && outer.block !== null);
if (inner.file.startsWith(outer.dir === '' ? '' : `${outer.dir}/`) && !(outer.dir === '' && outer.block !== null)) return true;
// A file a `#[path]` loads is inside the module that declares it.
const claim = context && depth < 8 && inner.block === null ? rustPathClaim(inner.file, context) : null;
return claim !== null && rustModuleWithin(rustModuleAt(claim.file, claim.at, context!), outer, context, depth + 1);
}
/**
@@ -9716,9 +9994,9 @@ const RUST_MODULE_ITEM_KINDS = new Set(['function', 'struct', 'enum', 'union', '
* same rule isVisibleAcrossFiles applies to the pipeline's pick, here ahead of
* the ranking, so the crate's own `pub(crate) type Result` can win instead.
*/
function isRustItemVisibleFrom(candidate: Node, ref: UnresolvedRef): boolean {
function isRustItemVisibleFrom(candidate: Node, ref: UnresolvedRef, context: ResolutionContext): boolean {
if (candidate.language !== 'rust' || candidate.visibility !== 'private' || !RUST_MODULE_ITEM_KINDS.has(candidate.kind)) return true;
return candidate.filePath === ref.filePath || ref.filePath.startsWith(`${rustModuleDir(candidate.filePath)}/`);
return candidate.filePath === ref.filePath || rustFileUnder(ref.filePath, rustModuleDir(candidate.filePath), candidate.filePath, context);
}
/** What a Rust `impl` declares besides its functions: `type Err = …;`, `const MAX: usize = …;`. */
@@ -9761,7 +10039,7 @@ function rustGlobCovers(uses: RustUses, candidate: Node, ref: UnresolvedRef): bo
*/
export function isRustNameInScope(candidate: Node, ref: UnresolvedRef, context: ResolutionContext): boolean {
if (RUST_SCOPED_REFS.get(context)?.has(ref)) return true;
if (!isRustItemVisibleFrom(candidate, ref)) return false;
if (!isRustItemVisibleFrom(candidate, ref, context)) return false;
const name = ref.referenceName;
// Bare in the SOURCE: the index keeps `crate::error::Result` by its last
// segment, and a path is not a prelude lookup.
@@ -10088,7 +10366,10 @@ export function rustPathTarget(ref: UnresolvedRef, context: ResolutionContext):
return ids === null ? null : [...ids].map((id) => context.getNodeById?.(id)).filter((n): n is Node => !!n);
}
let items = rustUsePathItems(path, from, context, new Set());
if (items === null && bare) {
// As a 2015 path, from the crate's root — unless the module declares the
// head itself, where a `#[path]` puts it (a cfg's choice): another module
// of that name at the root is not it.
if (items === null && bare && !rustUsesOf(from.file, context).pathMods.has(head)) {
const root = rustCrateRootModule(ref.filePath, context);
if (root) items = rustUsePathItems(path, root, context, new Set());
}
@@ -11127,7 +11408,7 @@ function rustTypeLookup(name: string, writtenPath: string[], file: string, conte
if (segments.length === 0 && (RUST_STD_TYPES.has(real) || RUST_PRELUDE.has(real))) return outside;
return { nodes: [], outside: false, undeclared: true, name: real };
}
let nodes = all.filter((n) => isRustItemVisibleFrom(n, fileRef));
let nodes = all.filter((n) => isRustItemVisibleFrom(n, fileRef, context));
// A path the project's modules can be walked along, written or the one a
// `use` binds, names the item of the module it walks to, as a `use` does in
// isRustNameInScope: tokio's `crate::runtime::Handle` is the one
@@ -20643,6 +20924,10 @@ export function clearNameMatcherMemos(context: ResolutionContext): void {
RUST_VISIBLE_ITEMS.delete(context);
RUST_PATH_TARGETS.delete(context);
RUST_TYPE_MEMBERS.delete(context);
RUST_PATH_DECLS.delete(context);
RUST_PATH_LOADS.delete(context);
RUST_PATH_LOADERS.delete(context);
RUST_FILES_BY_DIR.delete(context);
RUST_SCOPED_REFS.delete(context);
RUST_GLOB_ITEMS.delete(context);
RUST_CRATES.delete(context);