From b57434f41da6e74b1f74c407f7dd4880ae04882c Mon Sep 17 00:00:00 2001 From: "bob-codedaptive.com" Date: Sun, 1 Nov 2026 00:22:16 -0500 Subject: [PATCH] fix(rust): a call through a type a path names reaches that type's own function, not a namesake's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A call written through a type's path, `crate::a::Cache::open()`, took the function of any type of that name in the crate: rustTypeMember matched by qualified name and crate alone. A namesake type in another inline module of the same file, one a function declares in its own body, and an `impl` for a reference to the type (`impl Open for &Cache`, the reference's, not the type's) could each be linked instead of the type the path walks to. The walk itself also took a head wrongly: a `use` in a function's body that binds it (`use crate::b as a;`) was ignored, and so was a `mod a { … }` the function declares, which rustSubmodule also took for the module's own `a` when it was written before the module's `pub mod a`. - rustTypeMember takes a function, `const` or variant of a struct, enum or union only from an `impl` whose header names that type (rustImplSelfItems, walked from the module the `impl` is in); a trait's own declaration is still the trait body's. - rustImplSelfItems reads the `impl` header with the new rustImplSelfType, which rustImplSelfArgs shares; a header naming a reference to the type, or a bare name in an `impl` inside a function's body, names no type here. - rustSubmodule skips a `mod` that sits in a function's body or another block (RustModBlock.scope, read with rustUseScopes), which is no item of its module. - rustPathTarget reads a head that a `use` in the body binds as the path that `use` names, and declines one a `mod` in the body declares. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + __tests__/rust-path-call-edge-cases.test.ts | 285 ++++++++++++++++++++ src/resolution/name-matcher.ts | 140 ++++++++-- 3 files changed, 411 insertions(+), 15 deletions(-) create mode 100644 __tests__/rust-path-call-edge-cases.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f9854bde3..0823a677b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -192,6 +192,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - In Rust, a call written through a module path, like `crate::util::take(3)`, `super::util::take(3)` or `util::take(3)`, and a `use` of a function, like `use crate::util::take;`, now link to the function that module declares. Before, they could land on a method of the same name declared above it in that file, like an `impl Buf`'s `take`, or on a function written inside another function, and a call through a module the standard library provides, like `fs::read(…)` under `use std::fs;`, could link to your own `fs` module's `read`. Re-index Rust projects after upgrading. Thanks @danusha2345. (#2308) - In Rust, a name a `use` renames, like `consume()` after `use crate::util::take as consume;`, now links to the item its path names, and a `use` written inside a function or a block counts only there, ahead of the file's own `use`s and items, so `fn first() { use crate::util::take; take() }` reaches util's `take` and the next function's own `use` its own. A call to a parameter, a closure or a local that shares a function's name, like `take()` inside `fn run(take: fn())` or after `let take = || {};`, no longer links to that function, while `let test = test::;` still calls `test`, and a `use` of an enum's variant, like `use crate::models::Mode::Fast;`, links `Fast(…)` to that enum's variant. A path through a module, like `super::helper()`, `crate::config()`, `super::shared::helper()` inside a `mod tests { … }` or `tokio::task::yield_now()`, now reaches the item that module declares or passes on with a `pub use`, in your own crate or another crate of the workspace, and its `use` links to it; that includes binaries whose manifest gives them a path of their own, like ripgrep's, and packages whose manifest lists `name` after other fields, like axum-core's. A `use` written inside a string no longer counts. Re-index Rust projects after upgrading. Thanks @danusha2345. (#2308) - In Rust, a call through a type that a path or a `use` names, like `sim::items::ItemPool::for_def(…)` into another crate of your workspace, `ItemPool::for_def(…)` under `use sim::items::ItemPool;` or `crate::models::Mode::Fast(1)`, now links to that type's own function or variant, even when a type of the same name in another crate or module has one too, and a `use` of a `pub const` or `static`, like `use sim::RANK_COUNT;`, links to it. A type written on the line below its module path, as rustfmt wraps a long signature (`scheduler::` then `Handle`), now links by that path rather than by a `use` of its name. A workspace member listed outside the project, like `../shared`, is no longer read. Re-index Rust projects after upgrading. Thanks @BGamboa13. (#2631) +- In Rust, a call through a type that a path names, like `crate::a::Cache::open()`, now links only to that type's own function. Before, it could land on the function of a type of the same name in another module of the same file, on one a function declares inside its own body, or on an `impl` written for a reference to the type like `impl Open for &Cache`, and a path through a name that a `use` inside a function binds, like `use crate::b as a;`, or through a module declared inside a function could land on a module of that name elsewhere in the file. Re-index Rust projects after upgrading. - In Rust, a module loaded with `#[path = "…"]`, like tests kept in another folder as `#[path = "../../../tests/unit/scaling_tests.rs"] mod tests;`, is now part of the module that declares it: its `use super::*` brings in that module's items, private ones included, `super::` and `crate::` start from there, and the path is read as Rust reads it, from the declaring file's folder or an inline module's own. A call through such a module, like `support::make()` in each test that loads a shared helper, reaches the file it names. A module whose `#[cfg]` alternatives each name a file of their own, or whose path a `cfg_attr` sets, depends on the build and is left alone. An example, test or bench that `Cargo.toml` places at a path of its own, like `examples/07-fullstack/handling_errors.rs`, is now its own crate, so its `crate::` starts from it. A name only one of a module's `cfg` alternatives holds now links through it, like `crate::process::unix::GlobalOrphanQueue` through tokio's `#[path]` modules for Unix and Windows. Re-index Rust projects after upgrading. Thanks @seonghobae. (#1892) - Axum routes now link to their handler when rustfmt wraps the `.route(` call so the handler sits on a later line, and when the handler's name also appears elsewhere on the line, as in `delete(handlers::user::delete)` or `.route("/login", post(handlers::user::login))`; Actix's `web::resource(…)` and `.route(…)` registrations get the same fix. A call inside a closure handler is no longer mistaken for a route, and an Actix resource no longer takes the next route's handler as its own. Thanks @mg-mg-mg for the report and @danusha2345. (#2326) - In Play projects, a route or a call like `MediaType.parse(...)` now links to a method of the class or object it names, and a call to an overloaded one to the overload its arguments fit. Before, it went to the first method of that name anywhere in the class's file, often a nested or sibling class's. When a Scala and a Java type share a name, as Play's own `RequestAuthority` does, each language's calls now reach their own. Thanks @danusha2345. diff --git a/__tests__/rust-path-call-edge-cases.test.ts b/__tests__/rust-path-call-edge-cases.test.ts new file mode 100644 index 000000000..5c08e95e1 --- /dev/null +++ b/__tests__/rust-path-call-edge-cases.test.ts @@ -0,0 +1,285 @@ +/** + * A Rust call through a type a path names — `crate::a::Cache::open()` — is to + * the associated function of the type that path walks to, and no other's: + * - not a namesake type's, in another inline module of the same file or one a + * function declares in its own body; + * - not an `impl` for a reference to the type (`impl Open for &Cache`), which + * is the reference type's. + * A path whose head a `use` in the function's body binds (`use crate::b as + * a;`) is the path that `use` names, not the module `a` beside it; a `mod a { + * … }` the function declares is no module of the file, so neither its call + * nor a later `pub mod a` takes the other's items. + */ +import { afterAll, beforeAll, describe, expect, it } 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-path-edge-')); + const files: Record = { + 'Cargo.toml': '[package]\nname = "app"\nversion = "0.1.0"\nedition = "2021"\n', + 'src/lib.rs': 'pub mod inline;\npub mod local;\npub mod shadow;\npub mod order;\npub mod refimpl;\npub mod traits;\npub mod shapes;\npub mod store;\npub mod split;\n', + // a's Cache has only a trait's provided `open`; b's has an inherent one. + 'src/inline.rs': `pub mod a { + pub struct Cache; + pub trait Open { + fn open() -> usize { + 1 + } + } + impl Open for Cache {} +} + +pub mod b { + pub struct Cache; + impl Cache { + pub fn open() -> usize { + 2 + } + } +} + +pub fn through_a() -> usize { + crate::inline::a::Cache::open() +} + +pub fn through_b() -> usize { + crate::inline::b::Cache::open() +} +`, + // The root's Cache has only the trait's provided `open`; a function + // declares a Cache of its own, whose inherent `open` is not the root's. + 'src/local.rs': `pub struct Cache; + +pub trait Open { + fn open() -> usize { + 1 + } +} + +impl Open for Cache {} + +pub fn build_local() -> usize { + struct Cache; + impl Cache { + pub fn open() -> usize { + 2 + } + } + 0 +} + +pub fn through_root() -> usize { + crate::local::Cache::open() +} +`, + 'src/shadow.rs': `pub mod a { + pub struct Cache; + impl Cache { + pub fn open() -> usize { + 1 + } + } + pub fn make() -> usize { + 1 + } +} + +pub mod b { + pub struct Cache; + impl Cache { + pub fn open() -> usize { + 2 + } + } + pub fn make() -> usize { + 2 + } +} + +pub fn through_module() -> usize { + a::Cache::open() +} + +pub fn through_use() -> usize { + use crate::shadow::b as a; + a::Cache::open() +} + +pub fn through_use_fn() -> usize { + use crate::shadow::b as a; + a::make() +} + +pub fn through_local_mod() -> usize { + mod a { + pub use crate::shadow::b::Cache; + } + a::Cache::open() +} + +pub fn through_local_mod_fn() -> usize { + mod a { + pub fn make() -> usize { + 3 + } + } + a::make() +} +`, + // b comes first in the file, and a function before `pub mod a` declares a + // `mod a` of its own: the module's own `a` is still the one after it. + 'src/order.rs': `pub mod b { + pub struct Cache; + impl Cache { + pub fn open() -> usize { + 2 + } + } +} + +pub fn decoy() { + mod a { + pub use crate::order::b::Cache; + } +} + +pub mod a { + pub struct Cache; + impl Cache { + pub fn open() -> usize { + 1 + } + } +} + +pub fn via_order() -> usize { + a::Cache::open() +} +`, + // Cache takes the trait's provided `open`; the impl for `&Cache` writes its own. + 'src/refimpl.rs': `pub struct Cache; + +pub trait Open { + fn open() -> usize { + 1 + } +} + +impl Open for Cache {} + +impl Open for &crate::refimpl::Cache { + fn open() -> usize { + 3 + } +} + +pub fn through_cache() -> usize { + crate::refimpl::Cache::open() +} +`, + // A trait, and a struct of the same name in another module with an inherent method of the trait's method's name. + 'src/traits.rs': `pub trait Region { + fn size() -> usize; +} +`, + 'src/shapes.rs': `pub struct Region; + +impl Region { + pub fn size() -> usize { + 0 + } +} + +pub fn through_trait() -> usize { + crate::traits::Region::size() +} +`, + // An impl written in another file than its type: by path, and by a bare name a `use` brings in. + 'src/store.rs': `pub struct Store; +pub struct Vault; +`, + 'src/split.rs': `use crate::store::Vault; + +impl crate::store::Store { + pub fn drop_all() -> usize { + 1 + } +} + +impl Vault { + pub fn seal() -> usize { + 2 + } +} + +pub fn through_path() -> usize { + crate::store::Store::drop_all() +} + +pub fn through_use() -> usize { + crate::store::Vault::seal() +} +`, + }; + for (const [relative, content] of Object.entries(files)) { + fs.mkdirSync(path.dirname(path.join(root, relative)), { recursive: true }); + fs.writeFileSync(path.join(root, relative), content); + } + cg = await CodeGraph.init(root, { index: true }); +}); + +afterAll(() => { + cg?.close(); + if (root) fs.rmSync(root, { recursive: true, force: true }); +}); + +/** What the function `name` in `file` calls, as `kind qualifiedName file:line`. */ +const calls = (file: string, name: string): string[] => { + const fn = cg.getNodesInFile(file).find((n) => n.name === name && n.kind === 'function'); + expect(fn, `${name} in ${file}`).toBeDefined(); + return cg.getOutgoingEdgesFrom([fn!.id]).filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.target)!).map((t) => `${t.kind} ${t.qualifiedName} ${t.filePath}:${t.startLine}`).sort(); +}; + +describe('Rust calls through a type a path names', () => { + it('never reach a namesake type\'s function in another inline module of the file', () => { + expect(calls('src/inline.rs', 'through_a')).toEqual([]); + expect(calls('src/inline.rs', 'through_b')).toEqual(['method Cache::open src/inline.rs:14']); + // Controls: an impl written in another file than its type is still the + // type's, by its path or by a name a `use` brings in; and a trait's own + // declaration is still reached through the trait, not a namesake struct's. + expect(calls('src/split.rs', 'through_path')).toEqual(['method Store::drop_all src/split.rs:4']); + expect(calls('src/split.rs', 'through_use')).toEqual(['method Vault::seal src/split.rs:10']); + expect(calls('src/shapes.rs', 'through_trait')).toEqual(['method Region::size src/traits.rs:2']); + }); + + it('never reach the function of a namesake type a function declares in its own body', () => { + expect(calls('src/local.rs', 'through_root')).toEqual([]); + }); + + it('never reach the function of an impl for a reference to the type', () => { + expect(calls('src/refimpl.rs', 'through_cache')).toEqual([]); + }); + + it('read a head a `use` in the function\'s body binds as the path that `use` names', () => { + expect(calls('src/shadow.rs', 'through_use')).toEqual(['method Cache::open src/shadow.rs:16']); + expect(calls('src/shadow.rs', 'through_use_fn')).toEqual(['function make src/shadow.rs:20']); + // Where no `use` in the body binds `a`, it is the module `a` (control). + expect(calls('src/shadow.rs', 'through_module')).toEqual(['method Cache::open src/shadow.rs:4']); + }); + + it('never reach module a\'s item through a module `a` the function declares in its own body', () => { + expect(calls('src/shadow.rs', 'through_local_mod')).not.toContain('method Cache::open src/shadow.rs:4'); + expect(calls('src/shadow.rs', 'through_local_mod_fn')).not.toContain('function make src/shadow.rs:8'); + }); + + it('keep the module\'s own `mod a` when a function before it declares a `mod a` of its own', () => { + expect(calls('src/order.rs', 'via_order')).toEqual(['method Cache::open src/order.rs:19']); + }); +}); diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index 5d4e0cc8e..a5beda444 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -9511,6 +9511,13 @@ interface RustModBlock { close: number; /** The inline module it is declared in, or null for the file's own top level. */ parent: RustModBlock | null; + /** + * The braces of the function body, or other block, it is declared in + * (`fn f() { mod a { … } }`): the module is `a` there alone and is no item + * of the module around it. Undefined for a module's own `mod`, in a macro's + * braces too. + */ + scope?: RustUseScope; } const RUST_CRATES = new WeakMap>(); @@ -9923,9 +9930,16 @@ function rustCodeBraces(text: string): number[] { /** The inline `mod name { … }` blocks of a file's text (comments already blanked), in source order. */ function rustModBlocks(text: string, codeBraces: () => readonly number[] = () => rustCodeBraces(text)): { blocks: RustModBlock[]; balanced: boolean } { const heads = new Map(); - for (const m of text.matchAll(/\bmod\s+(?:r#)?([A-Za-z_]\w*)\s*\{/g)) heads.set(m.index! + m[0].length - 1, m[1]!); + const kws: number[] = []; + for (const m of text.matchAll(/\bmod\s+(?:r#)?([A-Za-z_]\w*)\s*\{/g)) { + heads.set(m.index! + m[0].length - 1, m[1]!); + kws.push(m.index!); + } const blocks: RustModBlock[] = []; if (heads.size === 0) return { blocks, balanced: true }; + // The block each `mod` is written in, if it is in a block rather than a module (rustUseScopes). + const scopes = rustUseScopes(text, kws, codeBraces(), new Set(heads.keys())); + const scopeOf = new Map([...heads.keys()].map((at, i) => [at, scopes?.[i]])); const open: Array = []; let inner: RustModBlock | null = null; let balanced = true; @@ -9933,7 +9947,9 @@ function rustModBlocks(text: string, codeBraces: () => readonly number[] = () => for (const at of codeBraces()) { if (text[at] === '{') { const name = heads.get(at); - const block: RustModBlock | null = name === undefined ? null : { name, open: at, close: text.length, parent: inner }; + const scope = scopeOf.get(at); + const block: RustModBlock | null = name === undefined ? null + : scope ? { name, open: at, close: text.length, parent: inner, scope } : { name, open: at, close: text.length, parent: inner }; if (block) { blocks.push(block); inner = block; @@ -10354,7 +10370,8 @@ function rustLibModule(crateDir: string, context: ResolutionContext): RustModule } /** - * The module `name` that `m` declares: inline (`mod name { … }`), else + * The module `name` that `m` declares: inline (`mod name { … }`, other than + * one a function declares in its own body), else * `.rs` or `/mod.rs` in its directory, else one it passes on under * 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 @@ -10376,7 +10393,8 @@ function rustSubmodule(m: RustModule, name: string, context: ResolutionContext, } return rustCfgChoice([...files].map((file) => ({ file, dir: rustParentDir(file), block: null, root: false }))); } - const block = layout.blocks.find((b) => b.name === name && b.parent === m.block); + // (A `mod` a function declares in its body is no item of `m`.) + const block = layout.blocks.find((b) => b.name === name && b.parent === m.block && !b.scope); if (block) return { file: m.file, dir, block, root: false }; for (const file of [`${dir}.rs`, `${dir}/mod.rs`]) { if (context.fileExists(file)) return { file, dir, block: null, root: false }; @@ -11120,10 +11138,13 @@ const RUST_PATH_TARGETS = new WeakMap /^[A-Za-z_]\w*$/.test(s))) return null; const variants = ref.referenceKind === 'imports'; const offset = rustRefOffset(ref, context); @@ -11137,11 +11158,28 @@ export function rustPathTarget(ref: UnresolvedRef, context: ResolutionContext): const ownerUse = typed && segs.length === 2 ? rustVisibleUse(owner, ref.filePath, offset, context) : undefined; if (typed && (ref.referenceKind !== 'calls' || owner === 'Self' || (segs.length === 2 && !ownerUse?.paths.length))) return null; const from = rustModuleAt(ref.filePath, offset, context); - const head = segs[0]!; - const bare = head !== 'crate' && head !== 'self' && head !== 'super'; + let head = segs[0]!; + let bare = head !== 'crate' && head !== 'self' && head !== 'super'; if (bare) { const bound = rustVisibleUse(head, ref.filePath, offset, context); if (bound && bound.std.length > 0 && bound.paths.length === 0) return null; + // A head a `use` in the function's body (or a block in it) binds is the + // path that `use` names there — `use crate::b as a;` makes `a::Cache::open()` + // `crate::b::Cache::open()` — never the module of that name the walk from + // the enclosing module would find. A head the `use`s name differently, or a + // type a `use` binds (`Cache::open()`, read below), is left to the walk. + if (bound && bound.block !== -1 && bound.paths.length > 0 && !variants && !(typed && segs.length === 2)) { + const via = bound.paths[0]!.segs; + if (bound.paths.some((p) => p.segs.join('::') !== via.join('::'))) return null; + segs = [...via, ...segs.slice(1)]; + head = segs[0]!; + bare = head !== 'crate' && head !== 'self' && head !== 'super'; + } + // So does a `mod` declared in the body, or in a block in it, around the + // reference: `a` is that module there, which the walk does not read + // (rustSubmodule takes a module's own `mod`s alone). + if (rustUsesOf(ref.filePath, context).blocks.some((b) => b.name === head && b.scope !== undefined && b.parent === from.block && + b.scope.open < offset && offset < b.scope.close)) return null; } let memo = RUST_PATH_TARGETS.get(context); if (!memo) RUST_PATH_TARGETS.set(context, (memo = new Map())); @@ -11189,8 +11227,11 @@ const RUST_TYPE_MEMBERS = new WeakMap type.kind !== 'trait' || - (n.filePath === type.filePath && n.startLine >= type.startLine && n.startLine <= (type.endLine ?? type.startLine)); + // A trait's own declaration is written in the trait's body. Any other + // type's item is written in an `impl` whose header names that type, walked + // from the module the `impl` is in: a namesake type's (in another inline + // module of the file, or one a function declares for itself) does not. + const within = (n: Node): boolean => type.kind === 'trait' + ? n.filePath === type.filePath && n.startLine >= type.startLine && n.startLine <= (type.endLine ?? type.startLine) + : rustImplSelfItems(n, context)?.has(type.id) === true; const all = context.getNodesByName(member).filter((n) => n.language === 'rust' && RUST_MEMBER_KINDS.has(n.kind) && n.qualifiedName === `${type.name}::${member}` && rustCrateDirOf(n.filePath, graph) === crate && within(n)); const inherent = all.filter((n) => !isRustTraitImplMethod(n, context)); @@ -13007,17 +13053,81 @@ function rustOwnedMethods(name: string, method: string, lookup: RustTypeLookup, return matches.filter((m) => files.has(m.filePath) || rustTypeLookup(name, [], m.filePath, context, rustNodeOffset(m, context)).nodes.some((n) => ids.has(n.id))); } -/** The text an `impl` header above a Rust method gives its implementing type's arguments: `BuildArgs` of `impl Overrides`. */ -function rustImplSelfArgs(method: Node, context: ResolutionContext): { args: string; generics: string; byRef: boolean } | null { +/** + * The ids of the items named by the type in the header of the `impl` around + * a Rust method (`impl Cache`, `impl Open for Cache`; rustImplSelfType), + * walked from the module the `impl` is written in. A path from `crate`, + * `self` or `super` names the same item wherever the `impl` is written: + * `impl crate::store::Cache` is store's. Any other path (a bare `impl + * Cache`) is walked only from an `impl` written directly in a module's body, + * the file's top level or an inline `mod`'s braces, where it is that + * module's own `Cache` (or the one its `use` brings in), never a namesake's + * in another inline module of the file. Null when the method is in no `impl` + * (a trait's own body, a macro the scopes don't read), when the header's + * type is no path or its path can't be walked, when the header names a + * reference to the type (`impl Open for &Cache`, `&'a mut Cache`): that + * `impl` is the reference type's, and its method is no method of `Cache` + * itself, which may take the trait's provided one instead; and for a path + * not from `crate`, `self` or `super` in an `impl` inside a function's body + * or another block: the block may declare a type of that name for itself + * (`fn f() { struct Cache; impl Cache { … } }`) or `use` one, which the + * module's walk does not see. + */ +function rustImplSelfItems(method: Node, context: ResolutionContext): Set | null { const scopes = rustScopesOf(method.filePath, context); if (!scopes) return null; const block = rustImplAt(scopes, rustNodeOffset(method, context)); if (!block || block.kind !== 'impl') return null; + const self = rustImplSelfType(scopes, block); + // `impl Open for &Cache`: the reference type's method, never `Cache`'s own. + if (!self || self.byRef) return null; + const type = self.type; + const head = type.segments[0]; + if (head !== 'crate' && head !== 'self' && head !== 'super') { + // In a module's body, the innermost braces around the `impl` are none + // (the file's top level) or that inline `mod`'s own. + const inner = rustInnerBlockAt(scopes, block.at); + const mod = rustBlockAt(rustUsesOf(method.filePath, context).blocks, block.at); + if (inner !== null && inner[0] !== mod?.open) return null; + } + // A bare name is looked up in the impl's own module, as `self::Cache`. + const segs = type.segments.length > 0 ? [...type.segments, type.name] : ['self', type.name]; + return rustWalkedItems(segs, method.filePath, block.at, context); +} + +/** + * The type an `impl` header names as the one it implements, as rustPathHead + * reads it — `crate::store::Cache` of `impl crate::store::Cache`, + * `Overrides` of `impl Overrides`, `Cache` of + * `impl Open for &'a mut Cache` — and whether the header names a + * reference to it. Read from the `impl` keyword at `block.at` to the body's + * `{` at `block.from`: its `where` clause and `impl<…>` generics dropped, the + * text after its first `for` when it has one, a leading `&'a mut` dropped. + * Null when that type is no path (a tuple, a slice, `dyn Trait`, a fn + * pointer). + */ +function rustImplSelfType(scopes: RustScopes, block: { at: number; from: number }): { type: { segments: string[]; name: string; args: string }; byRef: boolean } | null { const header = scopes.code.slice(block.at, block.from).replace(/\bwhere\b[\s\S]*$/, ''); const head = header.replace(/^impl\s*(?:<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/, ''); const self = /\bfor\s+([\s\S]+)$/.exec(head)?.[1] ?? head; const type = rustPathHead(self.trim().replace(/^&\s*(?:'\w+\s+)?(?:mut\s+)?/, '')); - return type ? { args: type.args, generics: block.generics, byRef: self.trim().startsWith('&') } : null; + return type ? { type, byRef: self.trim().startsWith('&') } : null; +} + +/** + * What the `impl` header above a Rust method says of its implementing type + * (rustImplSelfType): the type's arguments (`BuildArgs` of + * `impl Overrides`), the `impl<…>` generics, and whether the + * header names a reference to the type. Null when the method's file cannot + * be read, the method is in no `impl`, or the type is no path. + */ +function rustImplSelfArgs(method: Node, context: ResolutionContext): { args: string; generics: string; byRef: boolean } | null { + const scopes = rustScopesOf(method.filePath, context); + if (!scopes) return null; + const block = rustImplAt(scopes, rustNodeOffset(method, context)); + if (!block || block.kind !== 'impl') return null; + const self = rustImplSelfType(scopes, block); + return self ? { args: self.type.args, generics: block.generics, byRef: self.byRef } : null; } /**