mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-16 20:53:57 +08:00
ddc03f7c79
Found by the README-wide sweep: ripgrep bound 469 `Some(x)` calls to its own `EncodingMode::Some` variant and 536 `Ok(x)` to `ParseResult::Ok`; serde bound its `Ok(…)`/`Result<…>` to the `struct Ok` / `struct Result` its macro-hygiene test declares (845 on one); tokio 924 `Ok` onto `TransitionToIdle::Ok`, 290 `drop(x)` onto a `drop` method. For a name that is bare IN THE SOURCE (a `::` path is kept by its last segment and is not a prelude lookup — checked at the reference's column): - an enum variant is in scope only through a `use` of it (and its enum) or of its enum's `*`, and never names a type; - a prelude name (Ok/Err/Some/None/Result/Option/Box/Vec/String/drop/…) reaches a project item in another file only through a project `use` of that name or a glob of that item's own module (`use crate::glob::*`, `use super::*` for the parent) — `use std::…` trees don't count, and the `use` scan runs on comment-stripped text (a doc comment's "…use the Option…" once read as an import); - fuzzy matching is case-exact for Rust (`Bytes` ≠ method `bytes`, `Ok` ≠ fn `ok`). Applied as a candidate filter (so the in-scope item can win) and at resolveOne's seam — a Rust framework resolver's `Ok(x)` → `struct Ok` construction bypassed name matching. A/B vs main (edge diffs, every change classified): ripgrep −1,462/+0, serde −2,072/+40, tokio −1,820/+2, clap −752/+7, axum −397/+4, bat −45/+0 — losses are Ok/Some/Err/Result/Option/Vec/drop and same-named variants pinned on project items; gains are pre-existing same-name ambiguity (serde's two `Content` enums). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
2.8 KiB
TypeScript
80 lines
2.8 KiB
TypeScript
/**
|
|
* A bare Rust name reaches only what is in scope. `Some(x)` / `Ok(x)` are the
|
|
* prelude's: ripgrep bound every one to its own `EncodingMode::Some` and
|
|
* `ParseResult::Ok` variants, serde its `Ok`/`Result` to the structs its
|
|
* macro-hygiene test declares. A variant needs a `use` of it or its enum's `*`.
|
|
*/
|
|
import { describe, it, expect, afterAll } from 'vitest';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { CodeGraph } from '../src';
|
|
|
|
const roots: string[] = [];
|
|
afterAll(() => {
|
|
for (const r of roots.splice(0)) fs.rmSync(r, { recursive: true, force: true });
|
|
});
|
|
|
|
const FILES: Record<string, string> = {
|
|
'Cargo.toml': '[package]\nname = "app"\nversion = "0.1.0"\n',
|
|
'src/lowargs.rs': `pub enum EncodingMode {
|
|
Auto,
|
|
Some(u8),
|
|
Disabled,
|
|
}
|
|
pub enum ParseResult<T> {
|
|
Ok(T),
|
|
Err(String),
|
|
}
|
|
`,
|
|
'src/hygiene.rs': `struct Ok;
|
|
struct Result;
|
|
fn local() -> Ok { Ok }
|
|
`,
|
|
'src/decompress.rs': `pub fn find(cmd: u8) -> Option<u8> {
|
|
if cmd > 0 { return Some(cmd); }
|
|
let r: std::result::Result<u8, ()> = Ok(cmd);
|
|
None
|
|
}
|
|
`,
|
|
'src/error.rs': `pub struct Error;
|
|
pub type Result<T> = std::result::Result<T, Error>;
|
|
`,
|
|
'src/api.rs': `pub fn load() -> crate::error::Result<u8> { unimplemented!() }
|
|
`,
|
|
'src/modes.rs': `use crate::lowargs::EncodingMode::*;
|
|
pub fn pick() -> crate::lowargs::EncodingMode { Some(1) }
|
|
`,
|
|
};
|
|
|
|
describe('Rust: a bare name reaches what is in scope', () => {
|
|
it('the prelude, a used variant, a same-file item', async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-rust-prelude-'));
|
|
roots.push(root);
|
|
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);
|
|
}
|
|
const cg = await CodeGraph.init(root, { index: true });
|
|
try {
|
|
const targets = (fn: string): string[] => {
|
|
const from = cg.getNodesByName(fn).filter((n) => n.kind === 'function');
|
|
return cg
|
|
.getOutgoingEdgesFrom(from.map((n) => n.id), ['calls', 'references', 'instantiates'])
|
|
.map((e) => cg.getNode(e.target)!)
|
|
.map((n) => `${n.kind}:${n.qualifiedName}`);
|
|
};
|
|
// Prelude `Some` / `Ok`, not the project's variants or structs.
|
|
expect(targets('find').filter((t) => /Some|Ok|Result/.test(t))).toEqual([]);
|
|
// `use EncodingMode::*` brings the variant in.
|
|
expect(targets('pick')).toContain('enum_member:EncodingMode::Some');
|
|
// A path (`crate::error::Result`) is not a prelude lookup, so it still links.
|
|
expect(targets('load').some((t) => t.endsWith(':Result'))).toBe(true);
|
|
// A same-file struct still shadows the prelude.
|
|
expect(targets('local')).toContain('struct:Ok');
|
|
} finally {
|
|
cg.close();
|
|
}
|
|
});
|
|
});
|