mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-24 19:30:10 +08:00
fix(rust): a name a glob use brings in is an item the globbed module passes on (#2714)
dioxus's examples write `use dioxus::prelude::*;` and then `Element` or
`use_signal(...)`, which the prelude re-exports from dioxus-core and
dioxus-hooks: `pub use dioxus_core::{Element, ...}`, and `pub use
crate::hooks::*` through `pub use dioxus_hooks as hooks;`. The glob rule
(rustGlobCovers) only took an item declared in a file named like the glob's
last segment, so all of those went unlinked, while `use super::*` took
anything in the parent's directory and a glob over another crate's `types`
or `error` module took this crate's file of that name.
A name only globs bring in now goes through the module walk of #2702:
rustGlobItems walks each of the file's globs from the module it is written
in and looks the name up there. Two additions to the walk:
- a step through a module its parent passes on under that name
(`pub use dioxus_hooks as hooks;`, `pub use dioxus_signals::{self, *}`);
- Rust's visibility for globs: seen from inside a module (`use super::*`) a
glob takes everything; from anywhere else only what the module declares
`pub`, `pub use`s or `pub` globs. dioxus-hooks' `pub use use_effect::*`
does not pass on that module's own `use dioxus_core::*`, so the prelude
never brings in dioxus-core's `Ok` function; without this rule 146 std
`Ok(...)` calls in dioxus's examples linked to it.
A glob that brings the name in settles it, so one the walk can't follow
(a macro's module, a #[path] module) matters only when none does; then the
old module-name rule answers as before.
A/B with fresh indexes, main 505c355b and again on 818e7803, every changed
edge triaged: gleam 2053 links added (its `use crate::ast::*` and
`pretty_arena::*` names), 32 moved to the right item, 44 wrong ones gone
(std `Result` taken for the crate's alias through a test module's
`use super::*`, a `compile` parameter); dioxus 2329 added (the prelude's
hooks, signals, stores and rsx/html items), 24 dispatch fan-outs updated by
new implements edges, 7 wrong ones gone; tokio 6 added (`tokio::time::*`),
3 moved to a same-file enum; axum 2 added, 1 wrong gone; serde, ripgrep and
clap only confidences or nothing. No right link lost; express, gin, flask
and leveldb byte-identical. Cost: about 0.24 s of CPU per full index of
dioxus, 0.02 s on gleam.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e2aa5d205d
commit
f5158fac02
@@ -231,6 +231,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- In Rust, a method called on what `.as_mut()` or `.as_ref()` hands back now links by the type its receiver is declared with: `self.delay.as_mut().reset(…)` on a `Pin<Box<Sleep>>` field reaches `Sleep::reset`, a field pin-project hands out reaches its own type's method, and a `Box` or `NonNull` lends what it holds. A method on an `Option` reached this way, like `self.child.as_mut().map(…)`, or on a generic type no longer links to an unrelated project method that shares its name. Before, these calls were matched by name alone, so tokio's `self.delay.as_mut().reset(…)` linked to the calling `Interval::reset`, and dioxus's `Option` calls to its own `map` and `filter` helpers. Re-index Rust projects after upgrading.
|
||||
- In Rust, a type alias, struct, enum or function declared without `pub` now links only from its own module and the modules inside it, and a name a `use` brings in from one of your workspace's crates, like `use tokio_test::task::spawn` or `use crate::error::Error`, links only to an item of that crate or of a crate it depends on. Before, a file's `use crate::Result` could link its `Result<…>` types to another module's private `type Result`, as in dioxus's CLI, and a call to an imported function could link to another crate's function of the same name, such as tokio's test `spawn(…)` calls to one in its examples, or to nothing because a private function of that name was in the way. Re-index Rust projects after upgrading.
|
||||
- In Rust, a name a `use` brings in, like `use crate::sync::watch::Receiver` or `use tokio::task::yield_now`, now links to the item of that name in the module its path leads to, found the way Rust finds it: declared there, in a module declared inline with `mod name { … }`, or passed on by that module's own `pub use`, including one written inside a macro like tokio's `cfg_sync! { … }`. Before, any item of that name in the crate could take it, so tokio's `Receiver<()>` linked to the `Receiver` of `net/unix/pipe.rs`, `crate::runtime::Handle` to the runtime driver's `Handle`, and `crate::runtime::task::JoinHandle` to a test mock. A name whose item is declared by a macro, which the index doesn't see, now links to nothing rather than to a same-named item elsewhere. Re-index Rust projects after upgrading.
|
||||
- In Rust, a name only a glob `use` brings in, like `Element` or `use_signal(…)` after dioxus's `use dioxus::prelude::*`, or the syntax-tree types gleam's compiler reaches through `use crate::ast::*`, now links to the item that module passes on, following its own re-exports into the modules and crates it is built from. A glob brings in only what a module makes public, so a module's private `use … ::*` of its own stays its own, and `use super::*` brings in the parent module's items and imports, not a sibling module's. Before, a glob brought in only items declared in a file named like the module it names, so most such names linked to nothing, a `use super::*` could link a name to a sibling module's item, and a glob over a crate's `types` or `error` module could link to another crate's file of that name. Re-index Rust projects after upgrading.
|
||||
- In Rust, a name written on its own, like an `Err(…)` call or a `Response` return type, or through a module, like `io::Error` or `use crate::de::Error`, no longer links to a `type` or `const` declared inside an `impl`, such as a `FromStr` impl's `type Err` or a `Service` impl's `type Response`: only a path like `Self::Err` names those, and they are now listed under their type, as `ValueHint::Err`. Before, serde's and ripgrep's `Ok(…)` and `Err(…)` calls linked to a serializer's or parser's `type Ok` or `type Err`, axum's handlers' `Response` to a `Service` impl's, and ripgrep's `impl Captures for RegexCaptures` to another impl's `type Captures` instead of the `Captures` trait. Re-index Rust projects after upgrading.
|
||||
- In Rust, a name written through a path, like `io::Error`, `std::ffi::OsStr`, `mio::Interest`, `ast::Pattern` or `Source::Record { … }`, now links to what that path names, or to nothing when the path leads into the standard library or another crate, and no longer to an item of the same name that merely sits in the same file. Before, clap's `std::ffi::OsStr` and `io::Error` linked to its own `OsStr` and `Error`, serde's tests' `Token::Struct { … }` to their own `Struct`, and gleam's `ast::Pattern` to the `Pattern` enum beside it. Re-index Rust projects after upgrading.
|
||||
- In Rust, a trait's associated type written as `<T as Trait>::Name`, or bound in a trait's arguments like `Iterator<Item = &str>`, now links only to what it can name: through `<Self as Trait>` the `impl`'s own, through a type that type's `impl` of the trait, and nothing through a generic parameter or from a binding. Before, axum's `<L as Listener>::Io` linked to one listener's `type Io`, its `Iterator<Item = &str>` to an unrelated `enum Item`, and dioxus's `Storage = …` bindings to a `Storage` trait. Re-index Rust projects after upgrading.
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* A name only a Rust glob brings in is an item of the module the glob walks
|
||||
* to. dioxus's examples write `use dioxus::prelude::*;` and then `Element` or
|
||||
* `use_signal(…)`, and the prelude re-exports them from dioxus-core and
|
||||
* dioxus-hooks — `pub use dioxus_core::{Element, …}`, `pub use crate::hooks::*`
|
||||
* through `pub use dioxus_hooks as hooks;`. Only an item declared in a file
|
||||
* named like the glob's last segment read as brought in, so all of those went
|
||||
* unlinked, while a glob over another crate's `types` module brought in this
|
||||
* crate's `types.rs` too.
|
||||
*/
|
||||
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';
|
||||
|
||||
/** Writes `files` into a fresh temp dir and indexes it. */
|
||||
async function indexProject(prefix: string, files: Record<string, string>): Promise<{ root: string; cg: CodeGraph }> {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
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);
|
||||
}
|
||||
return { root, cg: await CodeGraph.init(root, { index: true }) };
|
||||
}
|
||||
|
||||
/** What `name`, written in `file`, links to, as `file@line` of each target, sorted. */
|
||||
const targetsOf = (cg: CodeGraph, file: string, name: string, kinds: string[]): string[] => {
|
||||
const out: string[] = [];
|
||||
for (const node of cg.getNodesInFile(file)) {
|
||||
for (const edge of cg.getOutgoingEdges(node.id)) {
|
||||
if (!kinds.includes(edge.kind)) continue;
|
||||
const target = cg.getNode(edge.target)!;
|
||||
if (target.name === name) out.push(`${target.filePath}@${target.startLine}`);
|
||||
}
|
||||
}
|
||||
return [...new Set(out)].sort();
|
||||
};
|
||||
|
||||
describe('a name a Rust glob brings in, by the module the glob walks', () => {
|
||||
let root = '';
|
||||
let cg: CodeGraph;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ root, cg } = await indexProject('cg-rust-glob-', {
|
||||
'Cargo.toml': '[workspace]\nmembers = ["app", "core", "dioxus", "hooks", "rsx"]\n',
|
||||
'dioxus/Cargo.toml': '[package]\nname = "dioxus"\nversion = "0.1.0"\n\n[dependencies]\ndioxus-core = { path = "../core" }\ndioxus-hooks = { path = "../hooks" }\n',
|
||||
'dioxus/src/lib.rs': [
|
||||
'pub use dioxus_core::Ok;',
|
||||
'pub use dioxus_hooks as hooks;',
|
||||
'',
|
||||
'pub mod error;',
|
||||
'pub mod types;',
|
||||
'',
|
||||
'pub mod prelude {',
|
||||
' pub use crate::hooks::*;',
|
||||
' pub use crate::macros::*;',
|
||||
' pub use dioxus_core::{Element, ScopeId};',
|
||||
'}',
|
||||
'',
|
||||
].join('\n'),
|
||||
'dioxus/src/error.rs': 'pub type Result<T> = std::result::Result<T, String>;\n',
|
||||
'dioxus/src/types.rs': 'pub struct Config;\n',
|
||||
'core/Cargo.toml': '[package]\nname = "dioxus-core"\nversion = "0.1.0"\n',
|
||||
'core/src/lib.rs': 'pub type Element = Option<u8>;\n\npub struct ScopeId(pub usize);\n\n#[allow(non_snake_case)]\npub fn Ok<T>(value: T) -> Result<T, ()> {\n Result::Ok(value)\n}\n',
|
||||
'hooks/Cargo.toml': '[package]\nname = "dioxus-hooks"\nversion = "0.1.0"\n\n[dependencies]\ndioxus-core = { path = "../core" }\n',
|
||||
// A module's own glob is its business: `pub use use_effect::*` passes on `use_effect`, not `Ok`.
|
||||
'hooks/src/lib.rs': 'mod use_effect;\nmod use_signal;\n\npub use use_effect::*;\npub use use_signal::*;\n',
|
||||
'hooks/src/use_effect.rs': 'use dioxus_core::*;\n\npub fn use_effect() {}\n',
|
||||
'hooks/src/use_signal.rs': 'pub fn use_signal<T>(init: T) -> T {\n init\n}\n',
|
||||
// Namesakes nothing brings in.
|
||||
'rsx/Cargo.toml': '[package]\nname = "dioxus-rsx"\nversion = "0.1.0"\n',
|
||||
'rsx/src/lib.rs': 'pub struct Element;\n\npub struct ScopeId;\n\npub fn use_signal() {}\n',
|
||||
'app/Cargo.toml': '[package]\nname = "app"\nversion = "0.1.0"\n\n[dependencies]\ndioxus = { path = "../dioxus" }\n',
|
||||
'app/src/main.rs': [
|
||||
'mod a;',
|
||||
'mod error;',
|
||||
'mod run;',
|
||||
'mod types;',
|
||||
'',
|
||||
'use dioxus::prelude::*;',
|
||||
'',
|
||||
'fn app(scope: ScopeId) -> Element {',
|
||||
' let count = use_signal(0);',
|
||||
' use_effect();',
|
||||
' None',
|
||||
'}',
|
||||
'',
|
||||
'fn parse() -> Result<u8, ()> {',
|
||||
' Ok(1)',
|
||||
'}',
|
||||
'',
|
||||
'fn main() {}',
|
||||
'',
|
||||
].join('\n'),
|
||||
'app/src/a/mod.rs': 'mod b;\nmod c;\n\npub fn helper() {}\n',
|
||||
'app/src/a/b.rs': 'use super::*;\n\npub fn run() {\n helper();\n}\n',
|
||||
// A sibling's function: `use super::*` brings in the module `c`, not what it declares.
|
||||
'app/src/a/c.rs': 'pub fn helper() {}\n',
|
||||
'app/src/error.rs': 'pub type Result<T> = std::result::Result<T, ()>;\n',
|
||||
'app/src/types.rs': 'pub struct Config;\n',
|
||||
'app/src/run.rs': 'use crate::error::*;\nuse dioxus::types::*;\n\npub fn go(config: Config) -> Result<()> {\n Ok(())\n}\n',
|
||||
}));
|
||||
}, 60_000);
|
||||
|
||||
afterAll(() => {
|
||||
cg?.close();
|
||||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('`use dioxus::prelude::*` brings in what the prelude re-exports from another crate', () => {
|
||||
expect(targetsOf(cg, 'app/src/main.rs', 'Element', ['references'])).toEqual(['core/src/lib.rs@1']);
|
||||
expect(targetsOf(cg, 'app/src/main.rs', 'ScopeId', ['references'])).toEqual(['core/src/lib.rs@3']);
|
||||
});
|
||||
|
||||
it('and what it globs in through a crate it passes on as a module', () => {
|
||||
expect(targetsOf(cg, 'app/src/main.rs', 'use_signal', ['calls'])).toEqual(['hooks/src/use_signal.rs@1']);
|
||||
expect(targetsOf(cg, 'app/src/main.rs', 'use_effect', ['calls'])).toEqual(['hooks/src/use_effect.rs@3']);
|
||||
});
|
||||
|
||||
it('but only what each module passes on: a module\'s own `use … ::*` stays its own', () => {
|
||||
// `Ok(1)` is std's: the prelude never re-exports dioxus-core's `Ok`, only the crate root does.
|
||||
expect(targetsOf(cg, 'app/src/main.rs', 'Ok', ['calls'])).toEqual([]);
|
||||
});
|
||||
|
||||
it('`use super::*` brings in the parent\'s items, not a sibling module\'s', () => {
|
||||
expect(targetsOf(cg, 'app/src/a/b.rs', 'helper', ['calls'])).toEqual(['app/src/a/mod.rs@4']);
|
||||
});
|
||||
|
||||
it('a glob over another crate\'s module is that module, not a namesake of this crate\'s', () => {
|
||||
expect(targetsOf(cg, 'app/src/run.rs', 'Config', ['references'])).toEqual(['dioxus/src/types.rs@1']);
|
||||
expect(targetsOf(cg, 'app/src/run.rs', 'Result', ['references'])).toEqual(['app/src/error.rs@1']);
|
||||
});
|
||||
});
|
||||
+113
-28
@@ -7901,6 +7901,8 @@ interface RustUsePath {
|
||||
segs: string[];
|
||||
/** The offset of its `use` in the file, which decides the module the path starts from. */
|
||||
at: number;
|
||||
/** A `pub use` (of any reach), which a glob over its module passes on. */
|
||||
pub: boolean;
|
||||
}
|
||||
|
||||
/** An inline `mod name { … }`, by the offsets of its braces. */
|
||||
@@ -8150,10 +8152,12 @@ function rustUsesOf(filePath: string, context: ResolutionContext): RustUses {
|
||||
const outside = root === 'std' || root === 'core' || root === 'alloc' || (root !== '' && dependencies.has(root));
|
||||
// Every path, outside ones too: walked, a path dependency on a crate the
|
||||
// workspace doesn't list is the project's (a facade's `pub use shared::Thing`).
|
||||
const kw = m.index! + m[0].indexOf('use');
|
||||
const pub = /\bpub(?:\s*\([^()]*\))?\s*$/.test(text.slice(Math.max(0, kw - 48), kw));
|
||||
for (const { segs, local } of rustUseTreeBindings(tree)) {
|
||||
// A macro's own `use $crate::…` names no module of this file's.
|
||||
if (segs.length === 0 || segs[0]!.startsWith('$') || RUST_STD_ROOTS.has(segs[0]!)) continue;
|
||||
const path: RustUsePath = { segs, at: m.index! };
|
||||
const path: RustUsePath = { segs, at: m.index!, pub };
|
||||
if (local === null) uses.globPaths.push(path);
|
||||
else if (uses.paths.has(local)) uses.paths.get(local)!.push(path);
|
||||
else uses.paths.set(local, [path]);
|
||||
@@ -8398,11 +8402,12 @@ 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. 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.
|
||||
* `<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.
|
||||
*/
|
||||
function rustSubmodule(m: RustModule, name: string, context: ResolutionContext): RustModule | null {
|
||||
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;
|
||||
@@ -8411,6 +8416,12 @@ function rustSubmodule(m: RustModule, name: string, context: ResolutionContext):
|
||||
for (const file of [`${dir}.rs`, `${dir}/mod.rs`]) {
|
||||
if (context.fileExists(file)) return { file, dir, block: null, root: false };
|
||||
}
|
||||
if (depth >= 8) return null;
|
||||
for (const p of layout.paths.get(name) ?? []) {
|
||||
if (rustBlockAt(layout.blocks, p.at) !== m.block) continue;
|
||||
const passed = rustWalkModules(p.segs, m, context, depth + 1);
|
||||
if (passed !== null && passed !== 'outside') return passed;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -8435,7 +8446,7 @@ function rustSuperModule(m: RustModule, context: ResolutionContext): RustModule
|
||||
* 'outside' for the standard library and every other crate; null when a step
|
||||
* finds no module the project holds.
|
||||
*/
|
||||
function rustWalkModules(segs: readonly string[], from: RustModule, context: ResolutionContext): RustModule | 'outside' | null {
|
||||
function rustWalkModules(segs: readonly string[], from: RustModule, context: ResolutionContext, depth = 0): RustModule | 'outside' | null {
|
||||
const [first, ...rest] = segs;
|
||||
if (first === undefined) return from;
|
||||
let at: RustModule | null;
|
||||
@@ -8444,7 +8455,7 @@ function rustWalkModules(segs: readonly string[], from: RustModule, context: Res
|
||||
else if (first === 'super') at = rustSuperModule(from, context);
|
||||
else if (RUST_STD_ROOTS.has(first)) return 'outside';
|
||||
else {
|
||||
at = rustSubmodule(from, first, context);
|
||||
at = rustSubmodule(from, first, context, depth);
|
||||
if (at === null) {
|
||||
const graph = rustCrateGraph(context);
|
||||
const own = rustCrateDirOf(from.file, graph);
|
||||
@@ -8456,13 +8467,17 @@ function rustWalkModules(segs: readonly string[], from: RustModule, context: Res
|
||||
}
|
||||
for (const seg of rest) {
|
||||
if (at === null) return null;
|
||||
at = seg === 'self' ? at : seg === 'super' ? rustSuperModule(at, context) : rustSubmodule(at, seg, context);
|
||||
at = seg === 'self' ? at : seg === 'super' ? rustSuperModule(at, context) : rustSubmodule(at, seg, context, depth);
|
||||
}
|
||||
return at;
|
||||
}
|
||||
|
||||
/** The items `m` declares as `name` at its own level: not in a module it declares inline, nor inside a function. */
|
||||
function rustDeclaredItems(m: RustModule, name: string, context: ResolutionContext): Node[] {
|
||||
/**
|
||||
* The items `m` declares as `name` at its own level: not in a module it
|
||||
* declares inline, nor inside a function. With `publicOnly`, only its `pub`
|
||||
* ones (a trait or const records no visibility, and counts).
|
||||
*/
|
||||
function rustDeclaredItems(m: RustModule, name: string, context: ResolutionContext, publicOnly = false): Node[] {
|
||||
const out: Node[] = [];
|
||||
for (const file of m.alt ? [m.file, m.alt] : [m.file]) {
|
||||
const layout = rustUsesOf(file, context);
|
||||
@@ -8470,6 +8485,7 @@ function rustDeclaredItems(m: RustModule, name: string, context: ResolutionConte
|
||||
for (const n of context.getNodesInFileNamed?.(file, name) ?? context.getNodesInFile(file).filter((c) => c.name === name)) {
|
||||
// An item in a function body is qualified by it (`outer::Inner`); an inline module adds nothing.
|
||||
if (n.language !== 'rust' || n.qualifiedName !== name || !RUST_MODULE_ITEM_KINDS.has(n.kind)) continue;
|
||||
if (publicOnly && n.visibility === 'private') continue;
|
||||
const at = (layout.lineStarts[n.startLine - 1] ?? 0) + Math.max(0, n.startColumn ?? 0);
|
||||
if (rustBlockAt(layout.blocks, at) === block) out.push(n);
|
||||
}
|
||||
@@ -8484,20 +8500,22 @@ function rustDeclaredItems(m: RustModule, name: string, context: ResolutionConte
|
||||
* are — else the ones its globs do. An outside crate's add nothing. null when
|
||||
* one of them can't be followed. `seen` holds the lookups under way, so
|
||||
* modules that glob each other (`use super::*` below a `pub use child::*`)
|
||||
* end the walk.
|
||||
* end the walk. `publicOnly` is a glob's view from outside the module: only
|
||||
* what it declares `pub` or passes on with `pub use` — dioxus-hooks' `pub use
|
||||
* use_effect::*` doesn't pass on that module's own `use dioxus_core::*`.
|
||||
*/
|
||||
function rustModuleItems(m: RustModule, name: string, context: ResolutionContext, seen: Set<string>): Node[] | null {
|
||||
const key = `${m.file}\0${m.block?.open ?? -1}\0${name}`;
|
||||
function rustModuleItems(m: RustModule, name: string, context: ResolutionContext, seen: Set<string>, publicOnly = false): Node[] | null {
|
||||
const key = `${m.file}\0${m.block?.open ?? -1}\0${name}\0${publicOnly ? 1 : 0}`;
|
||||
if (seen.has(key)) return [];
|
||||
if (seen.size >= 32) return null;
|
||||
const uses = rustUsesOf(m.file, context);
|
||||
if (!uses.balanced) return null;
|
||||
seen.add(key);
|
||||
try {
|
||||
const declared = rustDeclaredItems(m, name, context);
|
||||
const declared = rustDeclaredItems(m, name, context, publicOnly);
|
||||
if (declared.length > 0) return declared;
|
||||
const here = (at: number): boolean => rustBlockAt(uses.blocks, at) === m.block;
|
||||
const named = (uses.paths.get(name) ?? []).filter((p) => here(p.at));
|
||||
const here = (p: RustUsePath): boolean => (p.pub || !publicOnly) && rustBlockAt(uses.blocks, p.at) === m.block;
|
||||
const named = (uses.paths.get(name) ?? []).filter(here);
|
||||
const found: Node[] = [];
|
||||
for (const p of named) {
|
||||
const items = rustUsePathItems(p.segs, m, context, seen);
|
||||
@@ -8508,20 +8526,53 @@ function rustModuleItems(m: RustModule, name: string, context: ResolutionContext
|
||||
// `pub use clap_derive::ValueEnum` is a derive macro, and the trait
|
||||
// `ValueEnum` still comes in through its `pub use clap_builder::*`.
|
||||
if (found.length > 0) return found;
|
||||
for (const g of uses.globPaths) {
|
||||
if (!here(g.at)) continue;
|
||||
const mod = rustWalkModules(g.segs, m, context);
|
||||
if (mod === 'outside') continue;
|
||||
const items = mod === null ? null : rustModuleItems(mod, name, context, seen);
|
||||
if (items === null) return null;
|
||||
found.push(...items);
|
||||
}
|
||||
return found;
|
||||
const globbed = rustGlobbedItems(uses.globPaths.filter(here), () => m, name, context, seen);
|
||||
return globbed === null ? null : [...found, ...globbed];
|
||||
} finally {
|
||||
seen.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The items named `name` that globs bring in, each walked from the module
|
||||
* `fromOf` gives for it — what the module a glob names lets the importer see:
|
||||
* all of it from inside (`use super::*`), its public part from anywhere else.
|
||||
* A glob that brings the name in settles it — two globs bringing in two items
|
||||
* of one name don't compile — so one the walk can't follow (dioxus's
|
||||
* `elements::prelude::*`, a macro's module) matters only when none does; then
|
||||
* the answer is null.
|
||||
*/
|
||||
function rustGlobbedItems(
|
||||
globs: readonly RustUsePath[],
|
||||
fromOf: (glob: RustUsePath) => RustModule,
|
||||
name: string,
|
||||
context: ResolutionContext,
|
||||
seen: Set<string>,
|
||||
): Node[] | null {
|
||||
const found: Node[] = [];
|
||||
let unknown = false;
|
||||
for (const g of globs) {
|
||||
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));
|
||||
if (items === null) unknown = true;
|
||||
else found.push(...items);
|
||||
}
|
||||
return found.length > 0 || !unknown ? found : null;
|
||||
}
|
||||
|
||||
/** 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 {
|
||||
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);
|
||||
}
|
||||
|
||||
/** The items a `use` path names from `from`: its last segment's, in the module the rest walks to. */
|
||||
function rustUsePathItems(segs: readonly string[], from: RustModule, context: ResolutionContext, seen: Set<string>): Node[] | null {
|
||||
if (segs.length < 2) return null;
|
||||
@@ -8560,6 +8611,39 @@ function rustUseItems(name: string, filePath: string, context: ResolutionContext
|
||||
return ids;
|
||||
}
|
||||
|
||||
const RUST_GLOB_ITEMS = new WeakMap<ResolutionContext, Map<string, Set<string> | null>>();
|
||||
|
||||
/**
|
||||
* The ids of the items named `name` that the globs of `filePath` bring in —
|
||||
* `use dioxus::prelude::*` brings in what that module re-exports from the
|
||||
* crates it is built from. null when the file has no globs or they can't be
|
||||
* followed, and the module names they end in say what they bring instead.
|
||||
*/
|
||||
function rustGlobItems(name: string, filePath: string, context: ResolutionContext): Set<string> | null {
|
||||
let memo = RUST_GLOB_ITEMS.get(context);
|
||||
if (!memo) RUST_GLOB_ITEMS.set(context, (memo = new Map()));
|
||||
const key = `${filePath}\0${name}`;
|
||||
const hit = memo.get(key);
|
||||
if (hit !== undefined) return hit;
|
||||
const uses = rustUsesOf(filePath, context);
|
||||
const items = uses.balanced && uses.globPaths.length > 0
|
||||
? rustGlobbedItems(uses.globPaths, (g) => rustModuleAt(filePath, g.at, context), name, context, new Set())
|
||||
: null;
|
||||
const ids = items === null ? null : new Set(items.map((n) => n.id));
|
||||
memo.set(key, ids);
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether one of the file's globs brings this candidate in: by the modules
|
||||
* they walk to, when those can be followed; else by the module names they end
|
||||
* in (rustGlobCovers).
|
||||
*/
|
||||
function rustGlobScope(name: string, candidate: Node, uses: RustUses, ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
const ids = RUST_MODULE_ITEM_KINDS.has(candidate.kind) ? rustGlobItems(name, ref.filePath, context) : null;
|
||||
return ids === null ? rustGlobCovers(uses, candidate, ref) : ids.has(candidate.id);
|
||||
}
|
||||
|
||||
/** The module a Rust file is: `src/glob.rs` → `glob`, `src/walk/mod.rs` → `walk`. */
|
||||
function rustModuleName(filePath: string): string {
|
||||
const parts = filePath.split('/');
|
||||
@@ -8733,14 +8817,14 @@ export function isRustNameInScope(candidate: Node, ref: UnresolvedRef, context:
|
||||
}
|
||||
if (!RUST_PRELUDE.has(name)) {
|
||||
// Another file's item — a type, a function — is in scope only through a
|
||||
// `use` that binds it or a glob over its module: tokio's `Context<'_>` is
|
||||
// `use` that binds it or a glob that brings it in: tokio's `Context<'_>` is
|
||||
// not `runtime::task::trace`'s `Context` unless the file brings that one
|
||||
// in. A method is reached through a value, never a `use`.
|
||||
if (TYPE_MEMBER_KINDS.has(candidate.kind) || ref.referenceKind === 'imports' ||
|
||||
candidate.kind === 'file' || candidate.kind === 'module' || candidate.kind === 'namespace') return true;
|
||||
return uses.bound.has(name) || rustGlobCovers(uses, candidate, ref);
|
||||
return uses.bound.has(name) || rustGlobScope(name, candidate, uses, ref, context);
|
||||
}
|
||||
return uses.names.has(name) || rustGlobCovers(uses, candidate, ref);
|
||||
return uses.names.has(name) || rustGlobScope(name, candidate, uses, ref, context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15225,6 +15309,7 @@ export function clearNameMatcherMemos(context: ResolutionContext): void {
|
||||
RUST_TRAIT_IMPL_MEMO.delete(context);
|
||||
RUST_USES.delete(context);
|
||||
RUST_USE_ITEMS.delete(context);
|
||||
RUST_GLOB_ITEMS.delete(context);
|
||||
RUST_CRATES.delete(context);
|
||||
RUST_DEPENDENCIES.delete(context);
|
||||
RUST_CRATE_GRAPHS.delete(context);
|
||||
|
||||
Reference in New Issue
Block a user