fix(rust): a call on what as_mut() hands back reaches the receiver's declared type (#2710)

A Rust call written x.as_mut().m(..) or x.as_ref().m(..) reaches the
resolver by its bare name, so it was matched by name alone. tokio's
self.delay.as_mut().reset(now) on a Pin<Box<Sleep>> field linked to the
calling Interval::reset, its this.timer.as_mut().as_pin_mut() on an
Option to a stream's as_pin_mut, reader.as_mut().poll_read(..) on a
generic R to BufReader::poll_read, and dioxus's
self.child.as_mut().map(..) and self.server.as_mut().filter(..) on
Options to its own GenerationalRefMut::map and Store::filter.

Such a call is now read from the call's own chain and resolved on the
receiver's declared type as as_mut passes it on: a Pin<P> re-pins what
it points at (Rust's lookup through the Pin from the previous change),
a Box, NonNull, Rc or Arc lends what it holds, an Option stays an
Option, whose methods are std's, unless ? takes the value back out
(self.client.as_mut()?.next()), and a type parameter or another std or
dependency type gives no project method. A field is read from its
struct's declaration, also through pin-project's let me = self.project()
(a #[pin] field projects to Pin<&mut T>), even when a pin_project! macro
wraps the struct; a local or parameter from its own declaration, which
now also reads let x: T = .. annotations. A receiver whose type isn't
written where it can be read, a pin! local or a call's result, is left
to the name strategies as before.

A/B on tokio, axum, clap, serde, ripgrep, gleam and dioxus: 33 wrong
links removed, 7 moved to the right method, 13 right links added, none
lost, and 28 same-target links now resolved from the declared type
instead of by name. axum, clap, serde and the controls express, gin,
flask and leveldb are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-20 17:32:25 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent a31bf2f476
commit 5a7a4347a6
3 changed files with 471 additions and 17 deletions
+1
View File
@@ -227,6 +227,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In Rust, a call written `Self::new(…)`, `Self::from_std(…)` or `Self::Text(…)` now links to that function or variant of the type whose `impl` it is written in, even when other types have one of the same name. Before, such a call was linked only when nothing else in the project shared the name, so most `Self::new(…)` constructors and many helper calls were missing from callers and impact. Re-index Rust projects after upgrading.
- In Rust, a call like `String::from("…")`, `Vec::from([…])` or `NonNull::from(x)` no longer links to your project's own `impl From<YourType> for String` (or for `Vec`, `NonNull`, …) unless what it passes is a `YourType`. A method call on a standard-library value, like `files.iter()` or `buf.extend(…)`, links to your trait's implementation for that type only where the trait is imported. Before, every `String::from("…")` call in dioxus's CLI linked to one such conversion, and tokio's `vec.iter()` calls to an internal trait's `Vec` impl. Re-index Rust projects after upgrading.
- In Rust, a method called through `self: Pin<&mut Self>`, `self: Box<Self>`, `self: Rc<Self>` or `self: Arc<Self>`, or on a value declared `Pin<&mut …>`, now links the way Rust looks it up: to the type's own method, like `self.poll_flush(cx)` inside tokio's `impl AsyncWrite for File`, rather than to a catch-all `impl AsyncWrite for Pin<P>`, while `Pin`'s own methods, like `self.get_mut()`, stay unlinked. A type's own method now also comes before a trait's of the same name, so tokio's `Arc::from_raw(…)` calls are the standard library's rather than its internal `Link` trait's, and a method called on a tuple, like serde's `(self.ip(), self.port()).serialize(…)`, no longer links to an unrelated type's. Before, most calls made through such a `self` linked to nothing. Re-index Rust projects after upgrading.
- 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 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 C++, every name a declaration lists now has the declaration's type, like `strings_found` in google/breakpad's `ByteBuffer symbols_found, strings_found;` or `data_` in a class's `Table *index_, *data_;`, so calls on it link to that class's methods. Before, only the first name had its type read, and a call on another name linked to nothing or to the class of an earlier variable with the same name.
+248
View File
@@ -0,0 +1,248 @@
/**
* A Rust call on what `.as_mut()` or `.as_ref()` hands back, which the
* extractor records by its bare name, is a call on the receiver's declared
* type as `as_mut` passes it on: a `Pin<P>` re-pins what it points at, a
* `Box<T>` lends its `T`, an `Option<T>` stays an `Option`, whose methods are
* std's. By the name alone, tokio's `self.delay.as_mut().reset(now)` on a
* `Pin<Box<Sleep>>` field went to the calling `Interval::reset`, its
* `this.timer.as_mut().as_pin_mut()` on an `Option` to a stream's
* `as_pin_mut`, `reader.as_mut().poll_read(…)` on a generic `R` to
* `BufReader::poll_read`, and dioxus's `self.child.as_mut().map(…)` to a
* `GenerationalRefMut::map` that shared the word `mut`.
*/
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-as-mut-'));
const files: Record<string, string> = {
'Cargo.toml': '[package]\nname = "rt"\nversion = "0.1.0"\n',
'src/main.rs': 'mod copy;\nmod decoys;\nmod holder;\nmod interval;\nmod projected;\nmod pump;\nmod sleep;\n\nfn main() {}\n',
'src/sleep.rs': [
'use std::pin::Pin;',
'',
'pub struct Sleep {',
' deadline: u64,',
'}',
'',
'impl Sleep {',
' pub fn reset(self: Pin<&mut Self>, deadline: u64) {}',
'',
' pub fn deadline(&self) -> u64 {',
' self.deadline',
' }',
'}',
'',
].join('\n'),
// Methods by the names the calls below use, on types the calls never reach.
'src/decoys.rs': [
'use std::pin::Pin;',
'',
'pub struct Writer;',
'',
'impl Writer {',
' pub fn start_seek(self: Pin<&mut Self>, pos: u64) {}',
'}',
'',
'pub struct GuardMut;',
'',
'impl GuardMut {',
' pub fn map(self, f: u8) -> u8 {',
' f',
' }',
'}',
'',
'pub struct Lines;',
'',
'impl Lines {',
' pub fn as_pin_mut(self: Pin<&mut Self>) {}',
'}',
'',
].join('\n'),
// Fields declared `Pin<Box<T>>` and `Option<T>`.
'src/interval.rs': [
'use std::pin::Pin;',
'',
'use crate::sleep::Sleep;',
'',
'pub struct Interval {',
' delay: Pin<Box<Sleep>>,',
' timer: Option<Sleep>,',
'}',
'',
'impl Interval {',
' pub fn reset(&mut self, at: u64) {',
' self.delay.as_mut().reset(at);',
' }',
'',
' pub fn peek(&mut self) -> u64 {',
' self.delay.as_ref().deadline()',
' }',
'',
' pub fn stop(&mut self) {',
' self.timer.as_mut().map(|_| ());',
' }',
'}',
'',
].join('\n'),
// Fields declared `Box<T>` and `NonNull<T>` lend the `T`; `?` takes an `Option`'s back out.
'src/holder.rs': [
'use std::ptr::NonNull;',
'',
'use crate::sleep::Sleep;',
'',
'pub struct Holder {',
' inner: Box<Sleep>,',
'}',
'',
'impl Holder {',
' pub fn check(&mut self) -> u64 {',
' self.inner.as_mut().deadline()',
' }',
'}',
'',
'pub struct Shared {',
' entry: NonNull<Sleep>,',
' next: Option<Sleep>,',
'}',
'',
'impl Shared {',
' pub unsafe fn when(&self) -> u64 {',
' unsafe { self.entry.as_ref().deadline() }',
' }',
'',
' pub fn upcoming(&mut self) -> Option<u64> {',
' Some(self.next.as_mut()?.deadline())',
' }',
'}',
'',
].join('\n'),
// pin-project: a `#[pin]` field projects to `Pin<&mut T>`, another to `&mut T`.
'src/projected.rs': [
'use std::pin::Pin;',
'',
'use crate::sleep::Sleep;',
'',
'pin_project_lite::pin_project! {',
' pub struct Timeout<W> {',
' #[pin]',
' inner: W,',
' #[pin]',
' delay: Sleep,',
' timer: Option<Sleep>,',
' }',
'}',
'',
'impl<W> Timeout<W> {',
' pub fn poll_seek(self: Pin<&mut Self>, pos: u64) {',
' let mut me = self.project();',
' me.inner.as_mut().start_seek(pos);',
' me.delay.as_mut().reset(pos);',
' me.timer.as_mut().as_pin_mut();',
' }',
'}',
'',
].join('\n'),
// Values declared `Pin<&mut …>`.
'src/copy.rs': [
'use std::pin::Pin;',
'',
'use crate::sleep::Sleep;',
'',
'pub fn read_some<R: Unpin>(mut reader: Pin<&mut R>, deadline: Pin<&mut Sleep>) -> u64 {',
' reader.as_mut().start_seek(0);',
' deadline.as_ref().deadline()',
'}',
'',
].join('\n'),
// `self: Pin<&mut Self>`: `self.as_mut()` is that same `Pin`.
'src/pump.rs': [
'use std::pin::Pin;',
'',
'pub struct Pump;',
'',
'impl Pump {',
' fn step(self: Pin<&mut Self>) {}',
'',
' fn get_mut(&mut self) -> u8 {',
' 0',
' }',
'',
' pub fn run(mut self: Pin<&mut Self>) {',
' self.as_mut().step();',
' self.as_mut().get_mut();',
' }',
'}',
'',
].join('\n'),
};
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 });
}, 60_000);
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
/** What the calls on `line` of `file` link to, as `qualifiedName@file:line`, sorted. */
const callsAt = (file: string, line: number): string[] => {
const out: string[] = [];
for (const node of cg.getNodesInFile(file)) {
for (const edge of cg.getOutgoingEdges(node.id)) {
if (edge.kind !== 'calls' || edge.line !== line) continue;
const target = cg.getNode(edge.target)!;
out.push(`${target.qualifiedName}@${target.filePath}:${target.startLine}`);
}
}
return [...new Set(out)].sort();
};
describe('a Rust call on `.as_mut()` / `.as_ref()` of a field', () => {
it('declared `Pin<Box<T>>` is `T`\'s method', () => {
expect(callsAt('src/interval.rs', 12)).toEqual(['Sleep::reset@src/sleep.rs:8']);
expect(callsAt('src/interval.rs', 16)).toEqual(['Sleep::deadline@src/sleep.rs:10']);
});
it('declared `Option<T>` is the `Option`\'s own, no project method', () => {
expect(callsAt('src/interval.rs', 20)).toEqual([]);
});
it('declared `Box<T>` or `NonNull<T>` is `T`\'s method', () => {
expect(callsAt('src/holder.rs', 11)).toEqual(['Sleep::deadline@src/sleep.rs:10']);
expect(callsAt('src/holder.rs', 22)).toEqual(['Sleep::deadline@src/sleep.rs:10']);
});
it('declared `Option<T>`, through `?`, is `T`\'s method', () => {
expect(callsAt('src/holder.rs', 26)).toEqual(['Sleep::deadline@src/sleep.rs:10']);
});
it('projected by pin-project is the field\'s type, pinned or not', () => {
expect(callsAt('src/projected.rs', 18)).toEqual([]);
expect(callsAt('src/projected.rs', 19)).toEqual(['Sleep::reset@src/sleep.rs:8']);
expect(callsAt('src/projected.rs', 20)).toEqual([]);
});
});
describe('a Rust call on `.as_mut()` / `.as_ref()` of a value declared `Pin<&mut T>`', () => {
it('is `T`\'s method, and nothing for a type parameter', () => {
expect(callsAt('src/copy.rs', 6)).toEqual([]);
expect(callsAt('src/copy.rs', 7)).toEqual(['Sleep::deadline@src/sleep.rs:10']);
});
});
describe('a Rust call on `self.as_mut()` through `self: Pin<&mut Self>`', () => {
it('is the type\'s own method, or `Pin`\'s', () => {
expect(callsAt('src/pump.rs', 13)).toEqual(['Pump::step@src/pump.rs:6']);
expect(callsAt('src/pump.rs', 14)).toEqual([]);
});
});
+222 -17
View File
@@ -17679,38 +17679,80 @@ function rustImplFor(method: Node, inner: string | null, context: ResolutionCont
return impl !== null && impl.selfArgs.some((a) => impl.params.includes(a) || a === inner);
}
/** The type written at the start of `text`, up to what ends it at its own depth: `Pin<&mut R>, cx: …` → `Pin<&mut R>`. */
function rustTypeTextAt(text: string): string {
let depth = 0;
for (let i = 0; i < text.length; i++) {
const ch = text[i]!;
if (ch === '<' || ch === '(' || ch === '[') depth++;
else if (ch === '>' && text[i - 1] !== '-') depth = Math.max(0, depth - 1);
else if (ch === ')' || ch === ']') {
if (depth === 0) return text.slice(0, i).trim();
depth--;
} else if (depth === 0 && (ch === ',' || ch === '=' || ch === ';' || ch === '{')) return text.slice(0, i).trim();
}
return text.trim();
}
/**
* The type a Rust value declared `Pin<…>` pins — `read: Pin<&mut R>` → `R`,
* `fut: Pin<Box<dyn Future>>` → `Future`, `this: Pin<&mut Self>` → the impl's
* type — read off its nearest declaration in the calling function; null when
* that names none (`let fut = Pin::new(&mut f)`).
* The type a Rust local or parameter is written with — `reader: Pin<&mut
* R>`, `let timer: Option<Timer> = …` — read off its nearest declaration in
* the calling function; null when that declaration writes none (`let f =
* pin!(f)`).
*/
function rustPinnedType(receiver: string, ref: UnresolvedRef, context: ResolutionContext): string | null {
function rustDeclaredTypeText(name: string, ref: UnresolvedRef, context: ResolutionContext): string | null {
const lines = context.getFileLines?.(ref.filePath) ?? context.readFile(ref.filePath)?.split(/\r?\n/) ?? [];
const start = Math.max(0, enclosingScopeStartLine(ref, context) - 1);
const escaped = receiver.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const declared = new RegExp(`\\b${escaped}\\s*:(?!:)\\s*(.*)$|\\blet\\s+(?:mut\\s+)?${escaped}\\b`);
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const declared = new RegExp(`\\blet\\s+(?:mut\\s+)?${escaped}\\b(?:\\s*:(?!:)\\s*(.*))?|\\b${escaped}\\s*:(?!:)\\s*(.*)$`);
for (let i = Math.min(lines.length, ref.line) - 1; i >= start; i--) {
const m = declared.exec(lines[i] ?? '');
if (!m) continue;
const pinned = /^(?:&\s*(?:'\w+\s+)?(?:mut\s+)?)?(?:(?:std|core)\s*::\s*pin\s*::\s*)?Pin\s*<\s*(?:&\s*(?:'\w+\s+)?(?:mut\s+)?|(?:(?:std|alloc)\s*::\s*boxed\s*::\s*)?Box\s*<\s*)?(?:dyn\s+|impl\s+)?((?:[A-Za-z_]\w*\s*::\s*)*[A-Za-z_]\w*)/
.exec(m[1] ?? '');
const type = pinned?.[1]!.split('::').pop()!.trim() ?? null;
if (type !== 'Self') return type;
return rustSelfType(ref, context)?.type ?? null;
const written = m[1] ?? m[2];
return written === undefined ? null : rustTypeTextAt(written);
}
return null;
}
/**
* What a Rust `Pin<…>` type pins — `Pin<&mut R>` → `R`, `Pin<Box<dyn
* Future>>` → `Future`, `Pin<&mut Self>` → the impl's type; null for a type
* that is no `Pin`.
*/
function rustPinnedPointee(type: string, ref: UnresolvedRef, context: ResolutionContext): string | null {
const pinned = /^(?:&\s*(?:'\w+\s+)?(?:mut\s+)?)?(?:(?:std|core)\s*::\s*pin\s*::\s*)?Pin\s*<\s*(?:&\s*(?:'\w+\s+)?(?:mut\s+)?|(?:(?:std|alloc)\s*::\s*boxed\s*::\s*)?Box\s*<\s*)?(?:dyn\s+|impl\s+)?((?:[A-Za-z_]\w*\s*::\s*)*[A-Za-z_]\w*)/
.exec(type);
const name = pinned?.[1]!.split('::').pop()!.trim() ?? null;
return name === 'Self' ? rustSelfType(ref, context)?.type ?? null : name;
}
/**
* The type a Rust value declared `Pin<…>` pins — `read: Pin<&mut R>` → `R`,
* `this: Pin<&mut Self>` → the impl's type — read off its nearest
* declaration in the calling function; null when that names none (`let fut
* = Pin::new(&mut f)`).
*/
function rustPinnedType(receiver: string, ref: UnresolvedRef, context: ResolutionContext): string | null {
const type = rustDeclaredTypeText(receiver, ref, context);
return type === null ? null : rustPinnedPointee(type, ref, context);
}
/**
* A Rust call on a value declared `Pin<…>`: Rust's lookup through the `Pin`
* (rustThroughWrapper) on the type it pins. A type parameter's own methods
* are its bound's, which the call doesn't name: tokio's `read.poll_read(…)`
* on `read: Pin<&mut R>` went to its `impl AsyncRead for Pin<P>`.
* (rustThroughWrapper) on the type it pins.
*/
function matchRustPinnedCall(receiver: string, name: string, ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
const pinned = rustPinnedType(receiver, ref, context);
const inner = pinned !== null && !isRustTypeParam(pinned, ref, context) ? pinned : null;
return rustPinnedCallOn(rustPinnedType(receiver, ref, context), name, ref, context);
}
/**
* Rust's lookup through a `Pin` (rustThroughWrapper) for `name`, on the type
* it pins. A type parameter's own methods are its bound's, which the call
* doesn't name: tokio's `read.poll_read(…)` on `read: Pin<&mut R>` went to
* its `impl AsyncRead for Pin<P>`.
*/
function rustPinnedCallOn(pointee: string | null, name: string, ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
const inner = pointee !== null && !isRustTypeParam(pointee, ref, context) ? pointee : null;
const own = inner === null ? [] : context.getNodesByName(name).filter((n) => n.language === 'rust' && n.kind === 'method' &&
(n.qualifiedName === `${inner}::${name}` || n.qualifiedName.endsWith(`::${inner}::${name}`)));
const through = rustThroughWrapper('Pin', name, own, inner, ref, context);
@@ -17722,6 +17764,160 @@ function matchRustPinnedCall(receiver: string, name: string, ref: UnresolvedRef,
return target ? { original: ref, targetNodeId: target.id, confidence: 0.9, resolvedBy: 'instance-method' } : null;
}
/**
* A Rust call on what `.as_mut()` or `.as_ref()` hands back — `self.delay
* .as_mut().reset(…)`, `this.timer.as_mut().as_pin_mut()`, `reader.as_mut()
* .poll_read(…)` — which the extractor records by its bare name: a call on
* the receiver's declared type as `as_mut` passes it on (rustLentCall). A
* field is read from its struct's declaration, through pin-project's `let
* me = self.project()` too, a local or a parameter from its own. By the name
* alone, tokio's `self.delay.as_mut().reset(now)` on a `Pin<Box<Sleep>>`
* field went to the calling `Interval::reset`, and dioxus's
* `self.child.as_mut().map(…)` on an `Option` to a `GenerationalRefMut::map`.
* Undefined, for the name strategies to decide as before, when the
* receiver's type isn't written where it can be read: a `pin!` local, a
* call's result.
*/
function matchRustAsMutChainCall(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null | undefined {
const line = (context.getFileLines?.(ref.filePath) ?? context.readFile(ref.filePath)?.split(/\r?\n/))?.[ref.line - 1];
if (!line || !/\.\s*as_(?:mut|ref)\s*\(\s*\)/.test(line)) return undefined;
const name = ref.referenceName;
// The call's expression starts at its column with the whole chain:
// `self.delay.as_mut().reset(` — `?` after `as_mut()` taking what an
// `Option` / `Result` holds back out (`self.client.as_mut()?.next()`).
const lent = new RegExp(`^(self|[a-z_]\\w*)(?:\\s*\\.\\s*([a-z_]\\w*))?\\s*\\.\\s*as_(?:mut|ref)\\s*\\(\\s*\\)\\s*(\\?)?\\s*\\.\\s*${name}\\s*(?:\\(|::<)`)
.exec(line.slice(Math.max(0, ref.column)));
if (!lent) return undefined;
const [, base, field, question] = lent;
const tried = question !== undefined;
const caller = context.getNodeById?.(ref.fromNodeId);
if (!caller) return undefined;
if (field === undefined) {
// `self.as_mut()` through `self: Pin<&mut Self>` is that same `Pin`.
if (base === 'self') return rustSelfWrapper(caller) === 'Pin' && !tried ? matchRustSelfCall(name, ref, context) : undefined;
const declared = rustDeclaredTypeText(base!, ref, context);
return declared === null ? undefined : rustLentCall(declared, name, ref, context, tried);
}
// A field of the type `self` is, or one pin-project hands out:
// `Pin<&mut T>` for a `#[pin]` field, `&mut T` for another.
const projected = base !== 'self';
if (projected && !rustProjectsSelf(base!, ref, context)) return undefined;
const sep = caller.qualifiedName.lastIndexOf('::');
const owner = sep > 0 ? caller.qualifiedName.slice(0, sep).split('::').pop() : undefined;
const decl = owner ? rustFieldDeclaration(owner, field, ref, context) : null;
if (!decl) return undefined;
return rustLentCall(projected && decl.pinned ? `Pin<&mut ${decl.type}>` : decl.type, name, ref, context, tried);
}
/** Std pointers whose `as_ref` / `as_mut` lend what they hold: `Box<T>`, `NonNull<T>` → `&mut T`. */
const RUST_LENDING_POINTERS: ReadonlySet<string> = new Set(['Box', 'NonNull', 'Rc', 'Arc', 'Cow']);
/** A Rust type as written, by its last name — `&'a mut std::pin::Pin<Box<T>>` → `Pin` with `Box<T>` — after `&`, `dyn` and `impl`. */
function rustTypeHead(type: string): { outer: string; args: string; text: string } | null {
const text = type.replace(/^(?:&\s*(?:'\w+\s+)?(?:mut\s+)?)+/, '').replace(/^(?:dyn|impl)\s+/, '').trim();
const m = /^(?:[A-Za-z_]\w*\s*::\s*)*([A-Za-z_]\w*)\s*(?:<([\s\S]*)>)?/.exec(text);
return m ? { outer: m[1]!, args: m[2] ?? '', text } : null;
}
/**
* What `name` called on `x.as_mut()` / `x.as_ref()` reaches for an `x` of
* the Rust type `type` as written: `as_mut` re-pins what a `Pin<P>` points
* at (rustPinnedCallOn), lends a `Box<T>`'s or a `NonNull<T>`'s `T`, and
* turns an `Option<T>` into an `Option` again, whose methods are std's —
* unless `?` takes the `T` back out (`tried`: `self.client.as_mut()?.next()`).
* A type parameter's lends what its bound says, another std type's a std
* type: nothing of the project's either. Undefined for a project type's own
* `as_mut`, whose return type isn't read here.
*/
function rustLentCall(type: string, name: string, ref: UnresolvedRef, context: ResolutionContext, tried: boolean): ResolvedRef | null | undefined {
const head = rustTypeHead(type);
if (!head) return undefined;
if (head.outer === 'Pin' && !isRustProjectNamed('Pin', RUST_DECLARED_TYPE_KINDS, context)) {
return tried ? undefined : rustPinnedCallOn(rustPinnedPointee(head.text, ref, context), name, ref, context);
}
if (isRustProjectNamed(head.outer, RUST_DECLARED_TYPE_KINDS, context)) return undefined;
if (RUST_LENDING_POINTERS.has(head.outer)) return tried ? undefined : rustCallOnType(rustTypeTextAt(head.args), name, ref, context);
if (head.outer === 'Option' || head.outer === 'Result') return tried ? rustCallOnType(rustTypeTextAt(head.args), name, ref, context) : null;
if (RUST_STD_TYPES.has(head.outer) || isRustTypeParam(head.outer, ref, context)) return null;
return undefined;
}
/**
* What `name` called on a value of the Rust type `type` as written reaches:
* through a `Pin` (rustPinnedCallOn), through a `Box` or another std pointer
* to what it holds unless the pointer has the method itself, a project
* type's (or trait's) own method. A type parameter, std's or a dependency's
* type: nothing of the project's. Undefined for a type no declaration names.
*/
function rustCallOnType(type: string, name: string, ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null | undefined {
const head = rustTypeHead(type);
if (!head) return undefined;
if (head.outer === 'Pin' && !isRustProjectNamed('Pin', RUST_DECLARED_TYPE_KINDS, context)) {
return rustPinnedCallOn(rustPinnedPointee(head.text, ref, context), name, ref, context);
}
const outer = head.outer === 'Self' ? rustSelfType(ref, context)?.type : head.outer;
if (!outer || isRustTypeParam(outer, ref, context)) return null;
if (RUST_LENDING_POINTERS.has(outer) && !isRustProjectNamed(outer, RUST_DECLARED_TYPE_KINDS, context)) {
return RUST_WRAPPER_OWN_METHODS.get(outer)?.has(name) ? null : rustCallOnType(rustTypeTextAt(head.args), name, ref, context);
}
if (isRustOutsideSelf({ type: outer, root: null, trait: null, isTrait: false }, ref, context)) return null;
if (!isRustProjectNamed(outer, RUST_DECLARED_TYPE_KINDS, context)) return undefined;
return resolveMethodOnType(outer, name, ref, context, 0.85, 'instance-method');
}
/** Whether a Rust local is bound by pin-project's `let me = self.project()` (or `self.as_mut().project()`) in the calling function. */
function rustProjectsSelf(name: string, ref: UnresolvedRef, context: ResolutionContext): boolean {
const lines = context.getFileLines?.(ref.filePath) ?? context.readFile(ref.filePath)?.split(/\r?\n/) ?? [];
const start = Math.max(0, enclosingScopeStartLine(ref, context) - 1);
const bound = new RegExp(`\\blet\\s+(?:mut\\s+)?${name}\\b\\s*=\\s*(.*)$`);
for (let i = Math.min(lines.length, ref.line) - 1; i >= start; i--) {
const m = bound.exec(lines[i] ?? '');
if (m) return /^self\s*(?:\.\s*as_mut\s*\(\s*\)\s*)?\.\s*project\s*\(\s*\)/.test(m[1]!);
}
return false;
}
/**
* How a field of the Rust type `owner` is declared: its type as written, and
* whether pin-project pins it (`#[pin]`). Read from the struct's own lines —
* the struct's node, or, for one a macro wraps (`pin_project! { struct
* Sleep { … } }`), which the index doesn't hold, its text in the calling
* file. Null when no declaration of the field is found.
*/
function rustFieldDeclaration(owner: string, field: string, ref: UnresolvedRef, context: ResolutionContext): { type: string; pinned: boolean } | null {
const fileLines = (file: string): string[] => context.getFileLines?.(file) ?? context.readFile(file)?.split(/\r?\n/) ?? [];
const spans = preferCallSiteFile(context.getNodesByName(owner).filter((n) =>
n.language === 'rust' && (n.kind === 'struct' || n.kind === 'union')), ref.filePath)
.map((s) => fileLines(s.filePath).slice(s.startLine - 1, s.endLine));
if (spans.length === 0) {
const lines = fileLines(ref.filePath);
const header = new RegExp(`\\bstruct\\s+${owner}\\b`);
const at = lines.findIndex((l) => header.test(l.replace(/\/\/.*$/, '')));
if (at >= 0) {
let depth = 0;
let end = at;
for (let opened = false; end < lines.length; end++) {
for (const ch of (lines[end] ?? '').replace(/\/\/.*$/, '')) {
if (ch === '{') { depth++; opened = true; } else if (ch === '}') depth--;
}
if (opened && depth <= 0) break;
}
spans.push(lines.slice(at, end + 1));
}
}
const declared = new RegExp(`(?:^|[\\s{(,])(?:pub(?:\\([^)]*\\))?\\s+)?${field}\\s*:(?!:)\\s*(.*)$`);
for (const span of spans) {
for (let i = 0; i < span.length; i++) {
const line = (span[i] ?? '').replace(/\/\/.*$/, '');
const m = declared.exec(line);
if (!m) continue;
const pinned = /#\[pin\]/.test(line.slice(0, m.index + 1)) || /^\s*#\[pin\]\s*$/.test(span[i - 1] ?? '');
return { type: rustTypeTextAt(m[1]!), pinned };
}
}
return null;
}
/**
* Resolve a Rust call through a field of the enclosing type —
* `self.inner.run()`, emitted by the extractor as `self.inner.run` (#1585).
@@ -19962,6 +20158,15 @@ function matchReferenceInner(
if (chained !== undefined) return chained;
}
// A Rust call on what `.as_mut()` / `.as_ref()` hands back, which the
// extractor records by its bare name — `self.delay.as_mut().reset(…)` — is
// a call on the type the receiver is declared with, or on nothing of the
// project's (matchRustAsMutChainCall).
if (ref.language === 'rust' && ref.referenceKind === 'calls' && /^[A-Za-z_]\w*$/.test(ref.referenceName)) {
const lent = nmTimedT('rustAsMut', ref, () => matchRustAsMutChainCall(ref, context));
if (lent !== undefined) return lent;
}
// Try strategies in order of confidence
let result: ResolvedRef | null;