Files
codegraph/__tests__/rust-path-super-glob.test.ts
T
Colby MchenryandClaude Opus 5.5 cc17978174 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>
2026-10-30 13:21:12 +00:00

87 lines
2.7 KiB
TypeScript

/**
* Regression: #[path]-included test modules with `use super::*` must resolve
* bare callees through the declaring parent module, not a same-named symbol
* elsewhere in the workspace (ContextualWisdomLab/fast-mlsirm#1837). Case
* from PR #1892 by @seonghobae.
*/
import { afterEach, beforeEach, expect, it } from 'vitest';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { CodeGraph } from '../src';
let root: string;
let cg: CodeGraph | undefined;
beforeEach(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-rust-path-super-'));
});
afterEach(() => {
cg?.close();
cg = undefined;
fs.rmSync(root, { recursive: true, force: true });
});
async function indexWorkspace(layout: Record<string, string>) {
fs.writeFileSync(
path.join(root, 'Cargo.toml'),
'[workspace]\nmembers = ["crates/core", "crates/py"]\nresolver = "2"\n'
);
fs.mkdirSync(path.join(root, 'crates/core/src'), { recursive: true });
fs.mkdirSync(path.join(root, 'crates/py/src'), { recursive: true });
fs.mkdirSync(path.join(root, 'tests/unit'), { recursive: true });
fs.writeFileSync(
path.join(root, 'crates/core/Cargo.toml'),
'[package]\nname = "core-crate"\nversion = "0.1.0"\nedition = "2021"\n'
);
fs.writeFileSync(
path.join(root, 'crates/py/Cargo.toml'),
'[package]\nname = "py-crate"\nversion = "0.1.0"\nedition = "2021"\n'
);
for (const [file, text] of Object.entries(layout)) {
const dest = path.join(root, file);
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.writeFileSync(dest, text);
}
cg = await CodeGraph.init(root, { index: true });
}
function callTargets(testFn: string): string[] {
const caller = cg!
.getNodesByKind('function')
.find((n) => n.filePath === 'tests/unit/scaling_tests.rs' && n.name === testFn);
expect(caller).toBeDefined();
return cg!
.getOutgoingEdges(caller!.id)
.filter((e) => e.kind === 'calls')
.map((e) => {
const target = cg!.getNode(e.target)!;
return `${target.filePath}:${target.qualifiedName ?? target.name}`;
});
}
it('resolves use super::* callee through #[path] parent, not a same-named decoy (#1837)', async () => {
await indexWorkspace({
'crates/core/src/lib.rs': 'pub mod scaling;',
'crates/core/src/scaling.rs': `pub fn predict_rating_multi(x: i32) -> i32 { x + 1 }
#[cfg(test)]
#[path = "../../../tests/unit/scaling_tests.rs"]
mod tests;
`,
'crates/py/src/lib.rs': 'pub fn predict_rating_multi(x: i32) -> i32 { x + 99 }',
'tests/unit/scaling_tests.rs': `use super::*;
#[test]
fn pr_elom_rowmean() {
let _ = predict_rating_multi(1);
}
`,
});
expect(callTargets('pr_elom_rowmean')).toEqual([
'crates/core/src/scaling.rs:predict_rating_multi',
]);
});