mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-28 22:52:00 +08:00
fix(rust): a bare name is in scope only through its module or a use (#2214)
The Rust scope rule (#2124) only checked prelude names; every other bare name could mean any same-named project item, and a name after any path (`io::Result`) could mean any at all. The Rust framework resolver's PascalCase / handler / service patterns bound names with no scope check at 0.7-0.8. tokio's 1,221 `io::Result<...>` went to a private `runtime::task::Result` alias, 642 `Context<'_>` (from `use std::task::{Context, Poll}`) to `runtime::context::Context`. - A type or function from another file is in scope only through a `use` that binds it (its leaf, `as` alias, or `{self}`) or a glob over its module; methods and fields (reached through a value), import refs and module / file targets are unaffected. - A name a file imports from outside the project - `std` / `core` / `alloc`, or a crate its Cargo manifests declare as a dependency - is that outside item. (An inline `mod support { pub mod panic; }` is the project's.) - A path names its module: `seg::Name` reaches an item of module `seg`; a path rooted at std (`std::io::Error`) or at an outside import (`io::` under `use std::io`) reaches none; `crate::` / `self::` / `super::` and a project crate's name (`clap::Command`, re-exported) look it up as before. The path is read anywhere on the line, not only at the reference's column. - The framework resolver's patterns apply the same check. tokio -3008 +610, serde -792 +650, ripgrep -602 +247, axum -453 +186, clap -342 +32, bat -38 +9. Sampled losses: std / core / futures / syn / http / anyhow imports and paths, associated types (`S::Ok`, `S::Error`, `V::Value`, `Fut::Output`); gains are same-module items the old first match hid. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5f1a635714
commit
fa8d7284c5
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* A bare Rust type or function from another file is in scope only through a
|
||||
* `use` that binds it (or a glob over its module), and one the file imports
|
||||
* from outside the project — `use std::task::{Context, Poll}` — is that
|
||||
* outside item: tokio's 1,221 `io::Result<…>` bound to a private
|
||||
* `runtime::task::Result` alias, its 642 `Context<'_>` to
|
||||
* `runtime::context::Context`. A path names its module (`io::Result` is no
|
||||
* `task` alias; `std::io::Error` is std's), a project crate's name
|
||||
* (`clap::Command`) re-exports as `crate::` does, and an inline test module
|
||||
* (`mod support { pub mod panic; }`) is the project's own.
|
||||
*/
|
||||
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { CodeGraph } from '../src';
|
||||
|
||||
let root = '';
|
||||
let cg: CodeGraph;
|
||||
|
||||
beforeAll(async () => {
|
||||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-rust-scope-'));
|
||||
const files: Record<string, string> = {
|
||||
'Cargo.toml': `[package]\nname = "app"\nversion = "0.1.0"\n\n[dependencies]\nfutures = "0.3"\n`,
|
||||
'src/lib.rs': `pub mod runtime;\npub mod io;\npub mod net;\n`,
|
||||
'src/runtime/mod.rs': `pub mod context;\npub mod task;\npub mod driver;\n`,
|
||||
'src/runtime/context.rs': `pub struct Context {\n pub depth: u8,\n}\n`,
|
||||
'src/runtime/task/mod.rs': `pub type Result<T> = std::result::Result<T, ()>;\n`,
|
||||
'src/io/mod.rs': `pub mod copy;\n`,
|
||||
'src/io/copy.rs': `pub struct Error;\n`,
|
||||
'src/net.rs': `use std::io;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pub fn poll_ready(cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
let _ = cx;
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
pub fn fail() -> std::io::Error {
|
||||
std::io::Error::other("x")
|
||||
}
|
||||
`,
|
||||
'src/runtime/driver.rs': `use crate::runtime::context::Context;
|
||||
|
||||
pub fn enter(ctx: &Context) -> u8 {
|
||||
ctx.depth
|
||||
}
|
||||
`,
|
||||
'tests/support/panic.rs': `pub fn test_panic() {}\n`,
|
||||
'tests/io_panic.rs': `mod support {
|
||||
pub mod panic;
|
||||
}
|
||||
use support::panic::test_panic;
|
||||
|
||||
#[test]
|
||||
fn panics() {
|
||||
test_panic();
|
||||
}
|
||||
`,
|
||||
};
|
||||
for (const [rel, content] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, rel), content);
|
||||
}
|
||||
cg = await CodeGraph.init(root, { index: true });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cg?.close();
|
||||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const targetsFrom = (file: string) => {
|
||||
const ids = cg.getNodesInFile(file).map((n) => n.id);
|
||||
return cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind !== 'contains').map((e) => cg.getNode(e.target)!.filePath);
|
||||
};
|
||||
|
||||
describe('Rust names in scope', () => {
|
||||
it('an outside import, or a path through one, is never a project item', () => {
|
||||
const targets = targetsFrom('src/net.rs');
|
||||
expect(targets).not.toContain('src/runtime/context.rs');
|
||||
expect(targets).not.toContain('src/runtime/task/mod.rs');
|
||||
expect(targets).not.toContain('src/io/copy.rs');
|
||||
});
|
||||
|
||||
it('a crate-local use, and an inline test module, still reach the project item', () => {
|
||||
expect(targetsFrom('src/runtime/driver.rs')).toContain('src/runtime/context.rs');
|
||||
expect(targetsFrom('tests/io_panic.rs')).toContain('tests/support/panic.rs');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user