fix(rust): a call through a Pin, Rc or Box self reaches the method Rust picks, inherent first (#2701)

A Rust method called through a self declared Pin<&mut Self>, Box<Self>,
Rc<Self> or Arc<Self> was typed by its wrapper. tokio's
self.poll_flush(cx) in impl AsyncWrite for File went to its blanket impl
AsyncWrite for Pin<P>, whose poll_flush would need a Pin<&mut Pin<...>>,
and nearly every other such call linked to nothing: tokio's
self.poll_write_priv(..) and self.get_pin_mut(), dioxus's
self.in_scope(..) through self: &Rc<Self>.

Such a call is now looked up as Rust does: the type's own method taking
self through that wrapper first, then the wrapper's own (Pin::get_mut,
Rc's clone: std's, which the index doesn't hold, or a project impl on the
wrapper taking &self), then the type's other methods by deref. A value
declared Pin<&mut T> is looked up the same way on T, so
read.poll_read(..) on a generic R no longer reaches the Pin<P> impl.

A type's inherent method comes before a trait's of the same name, as in
Rust: on a path (tokio's Arc::from_raw(ptr) is std's, not its impl Link
for Arc<ScheduledIo>'s), on a typed value, and through self, where each
of several trait impls for one type calls its own block's method. In a
trait impl, fn wake(self) { self.wake() } hands the call on to the
type's own method, never back to itself, and (self.f)(x) calls what a
field holds, no method. A method called on a tuple, serde's
(self.ip(), self.port()).serialize(s), is no named type's.

A/B on tokio, axum, clap, serde, ripgrep, gleam and dioxus: 30 wrong
links removed, 13 moved to the right target, 74 right links added, none
lost. 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 16:32:16 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 8beab37c46
commit f2f5dee044
3 changed files with 838 additions and 22 deletions
+1
View File
@@ -213,6 +213,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In Swift, a call on a local created through `try`, `try!`, `try?` or `await`, or with a generic type's constructor, now links to that type's method: after `let storage = try! DiskStorage.Backend<String>(config: config)`, `storage.store(…)` links to `Backend`'s `store`. Before, such a local had no known type, so the call was left unlinked or matched by name to another type's method. Re-index Swift projects after upgrading.
- 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 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 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.
+514
View File
@@ -0,0 +1,514 @@
/**
* A Rust method call reaches what Rust's method lookup picks:
* - through a wrapper — `self: Pin<&mut Self>`, `self: Rc<Self>`, `read:
* Pin<&mut R>` — the wrapped type's own method taking `self` through it
* first (`fn poll_flush(self: Pin<&mut Self>, …)`), then the wrapper's own
* (`Pin::get_mut`, `Rc`'s `clone`, an impl on `Pin<P>` taking `&self`), then
* the wrapped type's other methods. tokio's `self.poll_flush(cx)` in `impl
* AsyncWrite for File` went to its blanket `impl AsyncWrite for Pin<P>`,
* whose `poll_flush` would need a `Pin<&mut Pin<…>>`;
* - a type's inherent method before a trait's of the same name: tokio's
* `Arc::from_raw(ptr)` is std's own, not its `impl Link for Arc<…>`'s, and
* a trait impl's `fn wake(self) { self.wake() }` hands the call on to the
* type's own `wake`, never back to itself;
* - `(self.into_route)(state)` calls what a field holds, no method;
* - a tuple's method is no named type's: serde's `(self.ip(),
* self.port()).serialize(s)` went to the `impl Serialize for Path` below it.
*/
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-wrapped-self-'));
const files: Record<string, string> = {
'Cargo.toml': '[package]\nname = "rt"\nversion = "0.1.0"\n',
'src/main.rs': 'mod copy;\nmod cursor;\nmod erased;\nmod file;\nmod grid;\nmod io;\nmod link;\nmod measure;\nmod net;\nmod runtime;\nmod ser;\nmod shape;\nmod sleep;\nmod tester;\nmod wake;\nmod waker;\n\nfn main() {}\n',
// A field holding a function, called through `self: Box<Self>`, beside a method of its name.
'src/erased.rs': [
'pub struct MakeErased {',
' into_route: fn(u8) -> u8,',
'}',
'',
'impl MakeErased {',
' fn into_route(self: Box<Self>, state: u8) -> u8 {',
' (self.into_route)(state)',
' }',
'',
' fn call(self: Box<Self>, state: u8) -> u8 {',
' self.into_route(state)',
' }',
'}',
'',
].join('\n'),
// A trait impl handing a call on to the type's own method of its name.
'src/waker.rs': [
'use std::task::Waker;',
'',
'trait WakerRef {',
' fn wake(self);',
'}',
'',
'impl WakerRef for Waker {',
' fn wake(self) {',
' self.wake();',
' }',
'}',
'',
'impl WakerRef for &Waker {',
' fn wake(self) {',
' self.wake_by_ref();',
' }',
'}',
'',
].join('\n'),
'src/measure.rs': [
'use crate::grid::Grid;',
'',
'pub trait Measure {',
' fn len(&self) -> usize;',
'}',
'',
'impl Measure for Grid {',
' fn len(&self) -> usize {',
' self.len()',
' }',
'}',
'',
].join('\n'),
'src/tester.rs': [
'use std::future::Future;',
'use std::pin::Pin;',
'use std::task::{Context, Poll};',
'',
'pub struct Tester {',
' done: bool,',
'}',
'',
'impl Future for Tester {',
' type Output = ();',
'',
' fn poll(mut self: Pin<&mut Self>, cx: &mut Context<\'_>) -> Poll<()> {',
' if self.done {',
' return Poll::Ready(());',
' }',
' self.done = true;',
' self.poll(cx)',
' }',
'}',
'',
].join('\n'),
'src/io.rs': [
'use std::pin::Pin;',
'use std::task::{Context, Poll};',
'',
'pub trait AsyncWrite {',
' fn poll_write(self: Pin<&mut Self>, cx: &mut Context<\'_>, buf: &[u8]) -> Poll<usize>;',
' fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<\'_>) -> Poll<()>;',
' fn poll_write_vectored(self: Pin<&mut Self>, cx: &mut Context<\'_>, buf: &[u8]) -> Poll<usize> {',
' self.poll_write(cx, buf)',
' }',
' fn is_write_vectored(&self) -> bool {',
' false',
' }',
'}',
'',
'pub trait AsyncRead {',
' fn poll_read(self: Pin<&mut Self>, cx: &mut Context<\'_>, buf: &mut [u8]) -> Poll<usize>;',
'}',
'',
'impl<P> AsyncWrite for Pin<P>',
'where',
' P: std::ops::DerefMut,',
' P::Target: AsyncWrite,',
'{',
' fn poll_write(self: Pin<&mut Self>, cx: &mut Context<\'_>, buf: &[u8]) -> Poll<usize> {',
' Poll::Ready(buf.len())',
' }',
'',
' fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<\'_>) -> Poll<()> {',
' Poll::Ready(())',
' }',
'',
' fn is_write_vectored(&self) -> bool {',
' true',
' }',
'}',
'',
'impl<P> AsyncRead for Pin<P>',
'where',
' P: std::ops::DerefMut,',
' P::Target: AsyncRead,',
'{',
' fn poll_read(self: Pin<&mut Self>, cx: &mut Context<\'_>, buf: &mut [u8]) -> Poll<usize> {',
' Poll::Ready(0)',
' }',
'}',
'',
].join('\n'),
'src/file.rs': [
'use std::pin::Pin;',
'use std::task::{Context, Poll};',
'',
'use crate::io::AsyncWrite;',
'',
'pub struct File {',
' pending: usize,',
'}',
'',
'impl File {',
' pub fn get_mut(&mut self) -> &mut usize {',
' &mut self.pending',
' }',
'',
' fn complete(&mut self) -> usize {',
' self.pending',
' }',
'}',
'',
'impl AsyncWrite for File {',
' fn poll_write(self: Pin<&mut Self>, cx: &mut Context<\'_>, buf: &[u8]) -> Poll<usize> {',
' Poll::Ready(buf.len())',
' }',
'',
' fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<\'_>) -> Poll<()> {',
' let _ = self.complete();',
' let me = self.get_mut();',
' Poll::Ready(())',
' }',
'',
' fn poll_write_vectored(self: Pin<&mut Self>, cx: &mut Context<\'_>, buf: &[u8]) -> Poll<usize> {',
' self.poll_flush(cx);',
' Poll::Ready(0)',
' }',
'}',
'',
].join('\n'),
// Two impls of one trait for one outside type: each `self.poll_flush` is its own block's.
'src/cursor.rs': [
'use std::io::Cursor;',
'use std::pin::Pin;',
'use std::task::{Context, Poll};',
'',
'use crate::io::AsyncWrite;',
'',
'impl AsyncWrite for Cursor<Vec<u8>> {',
' fn poll_write(self: Pin<&mut Self>, cx: &mut Context<\'_>, buf: &[u8]) -> Poll<usize> {',
' self.poll_flush(cx);',
' Poll::Ready(0)',
' }',
'',
' fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<\'_>) -> Poll<()> {',
' Poll::Ready(())',
' }',
'}',
'',
'impl AsyncWrite for Cursor<Box<[u8]>> {',
' fn poll_write(self: Pin<&mut Self>, cx: &mut Context<\'_>, buf: &[u8]) -> Poll<usize> {',
' self.poll_flush(cx);',
' Poll::Ready(0)',
' }',
'',
' fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<\'_>) -> Poll<()> {',
' Poll::Ready(())',
' }',
'}',
'',
].join('\n'),
// Through a parameter declared `Pin<&mut …>`.
'src/copy.rs': [
'use std::pin::Pin;',
'use std::task::{Context, Poll};',
'',
'use crate::file::File;',
'use crate::io::{AsyncRead, AsyncWrite};',
'',
'pub fn read_some<R: AsyncRead + ?Sized>(read: Pin<&mut R>, cx: &mut Context<\'_>, buf: &mut [u8]) -> Poll<usize> {',
' read.poll_read(cx, buf)',
'}',
'',
'pub fn vectored<T: AsyncWrite + ?Sized>(io: Pin<&mut T>) -> bool {',
' io.is_write_vectored()',
'}',
'',
'pub fn flush_file(file: Pin<&mut File>, cx: &mut Context<\'_>) -> Poll<()> {',
' file.poll_flush(cx)',
'}',
'',
].join('\n'),
'src/sleep.rs': [
'use std::pin::Pin;',
'',
'pub struct Sleep;',
'',
'impl Sleep {',
' pub fn reset(self: Pin<&mut Self>) {}',
'',
' pub fn deadline(&self) -> u64 {',
' 0',
' }',
'}',
'',
'pub fn rearm(sleep: Pin<&mut Sleep>) -> u64 {',
' let at = sleep.deadline();',
' sleep.reset();',
' at',
'}',
'',
].join('\n'),
'src/runtime.rs': [
'use std::rc::Rc;',
'',
'#[derive(Debug)]',
'pub struct Runtime;',
'',
'impl Clone for Runtime {',
' fn clone(&self) -> Self {',
' Runtime',
' }',
'}',
'',
'impl Runtime {',
' fn in_scope(&self) {}',
'',
' pub fn handle_event(self: Rc<Self>) {',
' let me = self.clone();',
' self.in_scope();',
' }',
'}',
'',
'pub struct Route;',
'',
'impl Route {',
' fn into_route(self: Box<Self>) -> Route {',
' *self',
' }',
'',
' fn call(self: Box<Self>) -> Route {',
' self.into_route()',
' }',
'}',
'',
].join('\n'),
'src/link.rs': [
'use std::ptr::NonNull;',
'',
'pub unsafe trait Link {',
' type Target;',
' unsafe fn from_raw(ptr: NonNull<Self::Target>) -> Self;',
'}',
'',
].join('\n'),
'src/net.rs': [
'use std::ptr::NonNull;',
'use std::sync::Arc;',
'',
'use crate::link::Link;',
'',
'pub struct ScheduledIo;',
'',
'unsafe impl Link for Arc<ScheduledIo> {',
' type Target = ScheduledIo;',
'',
' unsafe fn from_raw(ptr: NonNull<ScheduledIo>) -> Arc<ScheduledIo> {',
' unsafe { Arc::from_raw(ptr.as_ptr() as *const _) }',
' }',
'}',
'',
].join('\n'),
'src/wake.rs': [
'use std::ptr::NonNull;',
'use std::sync::Arc;',
'',
'use crate::link::Link;',
'use crate::net::ScheduledIo;',
'',
'pub unsafe fn wake(ptr: NonNull<ScheduledIo>) -> Arc<ScheduledIo> {',
' unsafe { Arc::from_raw(ptr.as_ptr()) }',
'}',
'',
].join('\n'),
// An inherent method and a trait's of one name: the inherent is the one called.
'src/grid.rs': [
'pub struct Grid {',
' cells: Vec<u8>,',
'}',
'',
'impl Grid {',
' pub fn len(&self) -> usize {',
' self.cells.len()',
' }',
'}',
'',
].join('\n'),
'src/shape.rs': [
'use crate::grid::Grid;',
'',
'pub trait Shape {',
' fn len(&self) -> usize;',
' fn area(&self) -> usize;',
'}',
'',
'impl Shape for Grid {',
' fn len(&self) -> usize {',
' 0',
' }',
'',
' fn area(&self) -> usize {',
' self.len()',
' }',
'}',
'',
'pub fn count(grid: &Grid) -> usize {',
' grid.len()',
'}',
'',
].join('\n'),
// A tuple's `serialize` is no named type's.
'src/ser.rs': [
'pub trait Serialize {',
' fn serialize(&self, out: &mut String);',
'}',
'',
'pub struct SocketAddr {',
' ip: u32,',
' port: u16,',
'}',
'',
'impl Serialize for SocketAddr {',
' fn serialize(&self, out: &mut String)',
' where',
' Self: Sized,',
' {',
' if out.is_empty() {',
' out.push(\'[\');',
' } else {',
' (self.ip, self.port).serialize(out)',
' }',
' }',
'}',
'',
'impl Serialize for Path {',
' fn serialize(&self, out: &mut String) {}',
'}',
'',
'pub struct Path;',
'',
].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 through `self: Pin<&mut Self>`', () => {
it('is the type\'s own method taking `self` that way, not a blanket impl on `Pin<P>`', () => {
expect(callsAt('src/file.rs', 32)).toEqual(['File::poll_flush@src/file.rs:25']);
});
it('in a trait\'s own body is the trait\'s method', () => {
expect(callsAt('src/io.rs', 8)).toEqual(['AsyncWrite::poll_write@src/io.rs:5']);
});
it('is the impl block\'s own method when one outside type has several impls', () => {
expect(callsAt('src/cursor.rs', 9)).toEqual(['Cursor::poll_flush@src/cursor.rs:13']);
expect(callsAt('src/cursor.rs', 20)).toEqual(['Cursor::poll_flush@src/cursor.rs:24']);
});
it('is `Pin`\'s own method before the type\'s namesake', () => {
expect(callsAt('src/file.rs', 27)).toEqual([]);
});
it('reaches the type\'s other methods through it', () => {
expect(callsAt('src/file.rs', 26)).toEqual(['File::complete@src/file.rs:15']);
});
});
describe('a Rust call on a value declared `Pin<&mut T>`', () => {
it('is never a blanket impl\'s method taking `self: Pin<&mut Self>`', () => {
expect(callsAt('src/copy.rs', 8)).toEqual([]);
});
it('keeps a blanket impl\'s `&self` method, which `Pin` answers first', () => {
expect(callsAt('src/copy.rs', 12)).toEqual(['Pin::is_write_vectored@src/io.rs:32']);
});
it('is the pinned type\'s own method', () => {
expect(callsAt('src/copy.rs', 16)).toEqual(['File::poll_flush@src/file.rs:25']);
expect(callsAt('src/sleep.rs', 14)).toEqual(['Sleep::deadline@src/sleep.rs:8']);
expect(callsAt('src/sleep.rs', 15)).toEqual(['Sleep::reset@src/sleep.rs:6']);
});
});
describe('a Rust call through `self: Rc<Self>` / `self: Box<Self>`', () => {
it('is the type\'s own method', () => {
expect(callsAt('src/runtime.rs', 17)).toEqual(['Runtime::in_scope@src/runtime.rs:13']);
expect(callsAt('src/runtime.rs', 29)).toEqual(['Route::into_route@src/runtime.rs:24']);
});
it('is the wrapper\'s own `clone`, not the type\'s', () => {
expect(callsAt('src/runtime.rs', 16)).toEqual([]);
});
it('is no method when it calls what a field holds, `(self.into_route)(state)`', () => {
expect(callsAt('src/erased.rs', 7)).toEqual([]);
expect(callsAt('src/erased.rs', 11)).toEqual(['MakeErased::into_route@src/erased.rs:6']);
});
});
describe('a Rust trait impl calling `self.<its own name>()`', () => {
it('hands the call on to the type\'s inherent method', () => {
expect(callsAt('src/measure.rs', 9)).toEqual(['Grid::len@src/grid.rs:6']);
});
it('links nothing for a type from outside the project, whose own method the index doesn\'t hold', () => {
expect(callsAt('src/waker.rs', 9)).toEqual([]);
});
it('calls itself again where the project\'s type has no such method', () => {
expect(callsAt('src/tester.rs', 17)).toEqual(['Tester::poll@src/tester.rs:12']);
});
});
describe('a Rust type\'s inherent method comes before a trait\'s of the same name', () => {
it('leaves std\'s inherent `Arc::from_raw` to std, with a project trait\'s in scope', () => {
expect(callsAt('src/net.rs', 12)).toEqual([]);
expect(callsAt('src/wake.rs', 8)).toEqual([]);
});
it('takes a project type\'s inherent method over its trait impl\'s', () => {
expect(callsAt('src/shape.rs', 14)).toEqual(['Grid::len@src/grid.rs:6']);
expect(callsAt('src/shape.rs', 19)).toEqual(['Grid::len@src/grid.rs:6']);
});
});
describe('a Rust method called on a tuple', () => {
it('is no named type\'s', () => {
expect(callsAt('src/ser.rs', 18)).toEqual([]);
});
});
+323 -22
View File
@@ -5419,7 +5419,28 @@ function rustGoCallShape(ref: UnresolvedRef, context: ResolutionContext): { shap
const before = line.slice(0, start);
if (/::\s*$/.test(before)) return { shape: 'path' };
if (!/\.\s*$/.test(before)) return { shape: 'bare' };
return { shape: 'chained', receiver: rustGoReceiverName(before.replace(/\?\s*\.\s*$/, '')) };
const receiver = before.replace(/\?\s*\.\s*$/, '');
return { shape: 'chained', receiver: isTupleReceiver(receiver) ? '()' : rustGoReceiverName(receiver) };
}
/**
* Whether a receiver's text ends in a tuple, `(self.ip(), self.port()).`: a
* parenthesized list with a comma of its own, no callee or macro before it.
*/
function isTupleReceiver(text: string): boolean {
const src = text.replace(/\s*\.\s*$/, '');
if (!src.endsWith(')')) return false;
let depth = 0;
let comma = false;
let i = src.length - 1;
for (; i >= 0; i--) {
const ch = src[i]!;
if (ch === ')' || ch === ']' || ch === '}') depth++;
else if (ch === '(' || ch === '[' || ch === '{') {
if (--depth === 0) break;
} else if (ch === ',' && depth === 1) comma = true;
}
return i >= 0 && comma && !/[\w$>\])!?]\s*$/.test(src.slice(0, i));
}
/**
@@ -5469,6 +5490,9 @@ function isRustGoCallTarget(n: Node, shape: ReturnType<typeof rustGoCallShape>):
if (!shape || shape.shape === 'path') return true;
const member = n.kind === 'method';
if (shape.shape === 'bare') return !member;
// A tuple's method is no named type's: serde's `(self.ip(),
// self.port()).serialize(s)` went to the `impl Serialize for Path` below it.
if (shape.receiver === '()') return false;
if (!member) return n.kind !== 'function';
// A name the standard library's own types all carry (`unwrap`, `clone`,
// `iter`, Go's `String` / `Get`) needs a receiver named after the owner; a
@@ -7915,6 +7939,84 @@ const RUST_STD_TYPES = new Set([
]);
const RUST_DECLARED_TYPE_KINDS = new Set(['struct', 'enum', 'union', 'type_alias', 'trait']);
const rustNameSets = (lists: Record<string, string>): ReadonlyMap<string, ReadonlySet<string>> =>
new Map(Object.entries(lists).map(([type, names]) => [type, new Set(names.split(' '))]));
const RUST_RC_INHERENT = 'new new_cyclic new_uninit new_uninit_slice new_zeroed pin try_new try_unwrap into_inner unwrap_or_clone ' +
'into_raw as_ptr from_raw increment_strong_count decrement_strong_count downgrade weak_count strong_count get_mut make_mut ptr_eq ' +
'downcast assume_init';
/** `Pin`'s methods; its other functions (`Pin::new`, `Pin::into_inner`) are reached by path alone. */
const RUST_PIN_METHODS = 'as_ref as_mut as_deref_mut set get_ref get_mut get_unchecked_mut into_ref map_unchecked map_unchecked_mut';
/**
* What std declares on its own types, which Rust picks before any trait's
* function of the same name, on a path (`Arc::from_raw(ptr)`) as on a value
* (`v.len()`): tokio's `Arc::from_raw(…)` is std's own, never its `impl Link
* for Arc<ScheduledIo>`'s. Only what the type itself declares: a slice's
* `iter`, which a `Vec` reaches by deref, comes after a trait impl on the
* `Vec`. A name missing here leaves the trait's function in reach.
*/
const RUST_STD_INHERENT = rustNameSets({
Arc: RUST_RC_INHERENT,
Rc: RUST_RC_INHERENT,
Weak: 'new upgrade strong_count weak_count as_ptr into_raw from_raw ptr_eq',
Box: 'new new_uninit new_uninit_slice new_zeroed pin try_new from_raw into_raw from_non_null into_non_null leak into_pin ' +
'into_boxed_slice into_inner as_ptr as_mut_ptr downcast assume_init write',
NonNull: 'new new_unchecked dangling as_ptr as_ref as_mut cast add sub offset byte_add byte_sub byte_offset offset_from read ' +
'read_unaligned read_volatile write write_bytes write_unaligned write_volatile replace swap copy_to copy_from ' +
'copy_to_nonoverlapping copy_from_nonoverlapping drop_in_place from_ref from_mut slice_from_raw_parts len addr with_addr ' +
'map_addr align_offset is_aligned',
Pin: `new new_unchecked into_inner into_inner_unchecked static_ref static_mut ${RUST_PIN_METHODS}`,
Cell: 'new set swap replace into_inner get update as_ptr get_mut from_mut take',
RefCell: 'new into_inner replace replace_with swap borrow try_borrow borrow_mut try_borrow_mut as_ptr get_mut take',
Mutex: 'new lock try_lock is_poisoned clear_poison into_inner get_mut',
RwLock: 'new read try_read write try_write is_poisoned clear_poison into_inner get_mut',
Vec: 'new with_capacity from_raw_parts capacity reserve reserve_exact try_reserve try_reserve_exact shrink_to_fit shrink_to ' +
'into_boxed_slice truncate as_slice as_mut_slice as_ptr as_mut_ptr set_len swap_remove insert remove retain retain_mut ' +
'dedup_by_key dedup_by dedup push pop pop_if append drain clear len is_empty split_off resize_with resize leak ' +
'spare_capacity_mut extend_from_slice extend_from_within splice extract_if into_flattened',
String: 'new with_capacity from_utf8 from_utf8_lossy from_utf8_unchecked from_utf16 from_utf16_lossy from_raw_parts into_bytes ' +
'as_str as_mut_str push_str extend_from_within capacity reserve reserve_exact try_reserve try_reserve_exact shrink_to_fit ' +
'shrink_to push as_bytes truncate pop remove retain insert insert_str as_mut_vec len is_empty split_off clear drain ' +
'replace_range into_boxed_str leak',
Option: 'is_some is_some_and is_none is_none_or as_ref as_mut as_pin_ref as_pin_mut as_slice as_mut_slice expect unwrap ' +
'unwrap_or unwrap_or_else unwrap_or_default unwrap_unchecked map inspect map_or map_or_else ok_or ok_or_else as_deref ' +
'as_deref_mut iter iter_mut and and_then filter or or_else xor insert get_or_insert get_or_insert_default ' +
'get_or_insert_with take take_if replace zip unzip copied cloned transpose flatten',
Result: 'is_ok is_ok_and is_err is_err_and ok err as_ref as_mut map map_or map_or_else map_err inspect inspect_err as_deref ' +
'as_deref_mut iter iter_mut expect unwrap unwrap_or_default expect_err unwrap_err into_ok into_err and and_then or or_else ' +
'unwrap_or unwrap_or_else unwrap_unchecked unwrap_err_unchecked copied cloned transpose flatten',
});
/** What the std traits a smart pointer implements answer through it: `Clone`, `AsRef`, `Deref`, `Debug`, `PartialEq`, … */
const RUST_POINTER_TRAIT_METHODS = 'clone clone_from as_ref borrow deref fmt eq ne partial_cmp lt le gt ge cmp max min clamp hash ' +
'into try_into to_string';
/**
* What a std wrapper a value is reached through answers itself, ahead of the
* type it wraps — Rust tries the wrapper before dereferencing it: `Pin`'s own
* methods, and the std traits it implements (`Rc`'s `clone`, `Deref`,
* `Debug`). tokio's `self.get_mut()` through `self: Pin<&mut Self>` is
* `Pin::get_mut`, not its own `BufReader::get_mut`. (`Pin<&mut T>` is no
* `Clone`: `self.clone()` through one is the type's.)
*/
const RUST_WRAPPER_OWN_METHODS = rustNameSets({
Pin: `${RUST_PIN_METHODS} deref deref_mut fmt eq ne partial_cmp lt le gt ge cmp max min clamp hash into try_into to_string`,
Box: `${RUST_POINTER_TRAIT_METHODS} as_mut borrow_mut deref_mut downcast`,
Rc: `${RUST_POINTER_TRAIT_METHODS} downcast`,
Arc: `${RUST_POINTER_TRAIT_METHODS} downcast`,
});
/**
* The std wrapper a Rust method's `self` is declared through — `self:
* Pin<&mut Self>` → `Pin`, `self: &Arc<Self>` → `Arc` — read off its
* signature; null for `self`, `&self`, `&mut self` or `self: &Self`.
*/
function rustSelfWrapper(method: Node): string | null {
return /^\(\s*(?:mut\s+)?self\s*:\s*(?:&\s*(?:'\w+\s+)?(?:mut\s+)?)?(?:(?:std|core|alloc)\s*::\s*\w+\s*::\s*)?(Pin|Box|Rc|Arc)\s*</
.exec(method.signature ?? '')?.[1] ?? null;
}
interface RustImplHeader {
/** The trait's name, the last segment of its path (`linked_list::Link` → `Link`). */
trait: string;
@@ -7922,6 +8024,10 @@ interface RustImplHeader {
traitArgs: string[];
/** The implementing type's name (`NonNull<Entry>` → `NonNull`). */
self: string;
/** The type names its arguments write (`Arc<Handle>` → `Handle`, `Pin<P>` → `P`). */
selfArgs: string[];
/** The impl's own type parameters (`impl<'a, P: DerefMut>` → `P`). */
params: string[];
}
const RUST_IMPL_HEADERS = new WeakMap<ResolutionContext, Map<string, RustImplHeader | null>>();
@@ -7930,13 +8036,20 @@ const RUST_IMPL_HEADERS = new WeakMap<ResolutionContext, Map<string, RustImplHea
function parseRustImplHeader(text: string): RustImplHeader | null {
let rest = text.replace(/^\s*(?:pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?impl\b/, '').replace(/\{[\s\S]*$/, '').replace(/\bwhere\b[\s\S]*$/, '').trim();
// The impl's own generics, `<'a, T: Into<U>>`.
const params: string[] = [];
if (rest.startsWith('<')) {
let depth = 0;
let i = 0;
let from = 1;
for (; i < rest.length; i++) {
if (rest[i] === '<') depth++;
else if (rest[i] === '>' && --depth === 0) break;
else if (rest[i] === ',' && depth === 1) {
params.push(rest.slice(from, i));
from = i + 1;
}
}
params.push(rest.slice(from, i));
rest = rest.slice(i + 1).trim();
}
// ` for ` outside the angle brackets parts the trait from the type.
@@ -7951,10 +8064,20 @@ function parseRustImplHeader(text: string): RustImplHeader | null {
const traitPart = rest.slice(0, split).trim().replace(/^!/, '');
const head = (part: string): string =>
/([A-Za-z_]\w*)\s*(?:<|$)/.exec(part.replace(/^(?:&\s*(?:'\w+\s+)?(?:mut\s+)?|dyn\s+)+/, '').trim())?.[1] ?? '';
const args = (part: string): string[] => {
const open = part.indexOf('<');
return open < 0 ? [] : [...part.slice(open + 1).matchAll(/(?<!['\w])([A-Za-z_]\w*)/g)]
.map((m) => m[1]!).filter((id) => id !== 'mut' && id !== 'dyn');
};
const open = traitPart.indexOf('<');
const traitArgs = open < 0 ? [] : [...traitPart.slice(open + 1).matchAll(/(?<!['\w])([A-Za-z_]\w*)/g)]
.map((m) => m[1]!).filter((id) => id !== 'mut' && id !== 'dyn');
return { trait: head(open < 0 ? traitPart : traitPart.slice(0, open)), traitArgs, self: head(rest.slice(split + 5)) };
const selfPart = rest.slice(split + 5);
return {
trait: head(open < 0 ? traitPart : traitPart.slice(0, open)),
traitArgs: args(traitPart),
self: head(selfPart),
selfArgs: args(selfPart.trim().replace(/^(?:&\s*(?:'\w+\s+)?(?:mut\s+)?|dyn\s+)+/, '')),
params: params.map((p) => /^\s*(?:const\s+)?([A-Za-z_]\w*)/.exec(p)?.[1]).filter((p): p is string => p !== undefined),
};
}
/** The `impl Trait<…> for Type<…>` a Rust method is declared in: null for an inherent impl, or none. */
@@ -8058,6 +8181,8 @@ export function isRustForeignImplCallable(target: Node, ref: UnresolvedRef, cont
if (!RUST_STD_TYPES.has(owner) || isRustProjectNamed(owner, RUST_DECLARED_TYPE_KINDS, context)) return true;
const impl = rustTraitImplHeader(target, context);
if (!impl || impl.self !== owner || impl.trait === '') return true;
// The type's own function of the name, std's, comes first (RUST_STD_INHERENT).
if (RUST_STD_INHERENT.get(owner)?.has(target.name)) return false;
if (isRustProjectNamed(impl.trait, RUST_TRAIT_KIND, context)) return isRustTraitInScope(impl.trait, ref, target, context);
const local = impl.traitArgs.find((t) => isRustProjectNamed(t, RUST_DECLARED_TYPE_KINDS, context));
return local === undefined || rustFirstArgIs(local, ref, context);
@@ -8073,8 +8198,13 @@ function declaresRustVariant(type: Node, variant: string, context: ResolutionCon
/** The line of the `impl` / `trait` header above `ref` in its file (0 for none). */
function rustEnclosingImplLine(ref: UnresolvedRef, context: ResolutionContext): number {
const lines = context.getFileLines?.(ref.filePath) ?? context.readFile(ref.filePath)?.split(/\r?\n/) ?? [];
for (let i = ref.line - 1; i >= 0; i--) {
return rustImplLineAt(ref.filePath, ref.line, context);
}
/** The line of the `impl` / `trait` header at or above `line` of `filePath` (0 for none). */
function rustImplLineAt(filePath: string, line: number, context: ResolutionContext): number {
const lines = context.getFileLines?.(filePath) ?? context.readFile(filePath)?.split(/\r?\n/) ?? [];
for (let i = line - 1; i >= 0; i--) {
if (/^\s*(?:pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?(?:impl|trait)\b/.test(lines[i] ?? '')) return i + 1;
}
return 0;
@@ -8199,6 +8329,39 @@ function isRustGenericParam(name: string, ref: UnresolvedRef, context: Resolutio
return new RegExp(`<[^<>]*(?:<[^<>]*>[^<>]*)*\\b${name}\\b\\s*(?:[:,>=])`).test(text);
}
/**
* Whether `name` is a type parameter the `fn` or the `impl` around `ref`
* declares in its own generics (`fn read<R: AsyncRead>(…)`, `impl<T> …`) —
* not a type its arguments merely name, as `Pin<&mut File>` names `File`.
*/
function isRustTypeParam(name: string, ref: UnresolvedRef, context: ResolutionContext): boolean {
const lines = context.getFileLines?.(ref.filePath) ?? context.readFile(ref.filePath)?.split(/\r?\n/) ?? [];
const headerAt = (line: number): string => {
let text = '';
for (let j = line - 1; j >= 0 && j < Math.min(lines.length, line + 7) && !/[{;]/.test(text); j++) text += ` ${(lines[j] ?? '').replace(/\/\/.*$/, '')}`;
return text;
};
return [enclosingScopeStartLine(ref, context), rustEnclosingImplLine(ref, context)].some((line) => {
if (line === 0) return false;
const header = headerAt(line);
const open = /\b(?:fn\s+[A-Za-z_]\w*|impl)\s*</.exec(header);
if (!open) return false;
let depth = 0;
let from = open.index + open[0].length;
for (let i = from - 1; i < header.length; i++) {
const ch = header[i];
const param = (): boolean => new RegExp(`^\\s*(?:const\\s+)?${name}\\b`).test(header.slice(from, i));
if (ch === '<') depth++;
else if (ch === '>' && header[i - 1] !== '-' && --depth === 0) return param();
else if (ch === ',' && depth === 1) {
if (param()) return true;
from = i + 1;
}
}
return false;
});
}
/** What only exists inside a type, reachable through a receiver alone. */
const TYPE_MEMBER_KINDS: ReadonlySet<string> = new Set(['method', 'property', 'field', 'enum_member']);
@@ -9727,6 +9890,16 @@ export function resolveMethodOnType(
return null;
}
// Rust picks a type's inherent method before a trait's of the same name —
// where one type has the name: two types of it each keep their own.
if (ref.language === 'rust' && matches.length > 1) {
const inherent = matches.filter((m) => !isRustTraitImplMethod(m, context));
if (inherent.length > 0 && inherent.length < matches.length &&
context.getNodesByName(typeName).filter((n) => n.language === 'rust' && RUST_DECLARED_TYPE_KINDS.has(n.kind)).length <= 1) {
matches = inherent;
}
}
if (matches.length > 1 && preferredFqn && ES_IMPORT_LANGUAGES.has(ref.language)) {
const chosen = matches.find((m) => m.filePath === preferredFqn);
if (chosen) return { original: ref, targetNodeId: chosen.id, confidence, resolvedBy };
@@ -15192,6 +15365,18 @@ export function matchMethodCall(
if (typeMember !== undefined) return typeMember;
}
// Rust call on the enclosing type itself — `self.reset()`, emitted as
// `self.reset` (#1861). EXCLUSIVE: the owner is written on the `impl` line
// and carried in the calling method's qualified name, so it is not a guess.
// Letting this shape reach the bare-name strategies below is how
// `self.reset()` resolved to a same-named method on an unrelated type
// whenever that type's method happened to sit nearer the call site. Ahead
// of the local declarations below, which read `self: Pin<&mut Self>` as a
// `Pin` (matchRustSelfCall looks through it).
if (ref.language === 'rust' && dotMatch && objectOrClass === 'self') {
return matchRustSelfCall(methodName!, ref, context);
}
// Infer the receiver's type from its local declaration/initializer in the
// enclosing scope, then resolve the method on that type (#1108). C++ keeps its
// dedicated inferrer (header scan + `auto`); every other language uses the
@@ -15335,6 +15520,11 @@ export function matchMethodCall(
if (element === undefined) return resolveMethodOnType(inferredType, methodName!, ref, context, 0.9, 'instance-method', declared.file);
}
}
// A Rust value declared `Pin<…>` is looked up through the `Pin`, on the
// type it pins (matchRustPinnedCall) — unless `Pin` is the project's own.
if (ref.language === 'rust' && inferredType === 'Pin' && !isRustProjectNamed('Pin', RUST_DECLARED_TYPE_KINDS, context)) {
return matchRustPinnedCall(objectOrClass!, methodName!, ref, context);
}
if (inferredType) {
// A Go local bound from a type assertion calls a method of the type Go
// finds for it, or nothing the project declares: `flusher.Flush()` after
@@ -15489,17 +15679,6 @@ export function matchMethodCall(
return matchRustSelfFieldCall(objectOrClass!.slice('self.'.length), methodName!, ref, context);
}
// Rust call on the enclosing type itself — `self.reset()`, emitted as
// `self.reset` (#1861). Same discipline as the field branch above, and
// EXCLUSIVE for the same reason: the owner is written on the `impl` line and
// carried in the calling method's qualified name, so it is not a guess.
// Letting this shape reach the bare-name strategies below is how
// `self.reset()` resolved to a same-named method on an unrelated type
// whenever that type's method happened to sit nearer the call site.
if (ref.language === 'rust' && dotMatch && objectOrClass === 'self') {
return matchRustSelfCall(methodName!, ref, context);
}
// TS/JS call through a field of the enclosing class — `this.mailer.send()`,
// emitted as `this.mailer.send` (#1496). Same discipline as the Rust branch
// above, and EXCLUSIVE for the same reason: the field's declared type off
@@ -16740,9 +16919,10 @@ export function rustFieldTypeName(raw: string): string | null {
* The owner is the calling method's qualified-name prefix (`Target::run` →
* `Target`), which is where the `impl` block's type ends up. A free function
* has no `self`, so a caller whose qualified name carries no owner declines.
* Exactly one candidate must belong to that owner: a project with two `impl`
* blocks for the same type is normal, two same-named methods on it is not, and
* guessing between them is the failure this replaces.
* Among the owner's methods of the name, the one Rust picks
* (pickRustOwnMethod): a project with two `impl` blocks for the same type is
* normal, and guessing between same-named methods by proximity is the failure
* this replaces.
*/
function matchRustSelfCall(
methodName: string,
@@ -16774,16 +16954,137 @@ function matchRustSelfCall(
if (owners.filter((n) => n.filePath === caller.filePath).length !== 1) return null;
owned = owned.filter((n) => n.filePath === caller.filePath);
}
if (owned.length !== 1) return null;
// `(self.into_route)(state)` calls what a field holds, no method: axum's
// `MakeErasedHandler::into_route` went to itself.
const at = (context.getFileLines?.(ref.filePath) ?? context.readFile(ref.filePath)?.split(/\r?\n/))?.[ref.line - 1]?.slice(ref.column);
if (at?.startsWith('(') && new RegExp(`^\\(\\s*self\\s*\\.\\s*${methodName}\\s*\\)`).test(at)) return null;
// In a trait impl, `fn wake(self) { self.wake() }` hands the call on to the
// type's own `wake`: its inherent one, or for a type from outside the
// project one the index doesn't hold (tokio's `impl WakerRef for Waker`) —
// never back to the impl, unless the type is the project's and has none (a
// `poll` that polls itself again).
if (caller.name === methodName && isRustTraitImplMethod(caller, context)) {
const inherent = owned.filter((n) => !isRustTraitImplMethod(n, context));
if (inherent.length > 0) owned = inherent;
else {
const self = rustSelfType(ref, context);
if (!self || isRustOutsideSelf(self, ref, context)) return null;
}
}
// `self` declared through a wrapper — `self: Pin<&mut Self>`, `self:
// Rc<Self>` — is looked up through it (rustThroughWrapper).
const wrapper = rustSelfWrapper(caller);
if (wrapper && !isRustProjectNamed(wrapper, RUST_DECLARED_TYPE_KINDS, context)) {
const through = rustThroughWrapper(wrapper, methodName, owned, owner.split('::').pop()!, ref, context);
if (!Array.isArray(through)) return through && { original: ref, targetNodeId: through.id, confidence: 0.9, resolvedBy: 'qualified-name' };
owned = through;
}
const target = pickRustOwnMethod(owned, ref, context);
if (!target) return null;
return {
original: ref,
targetNodeId: owned[0]!.id,
targetNodeId: target.id,
confidence: 0.9,
resolvedBy: 'qualified-name',
};
}
/**
* Which of a Rust type's methods of one name a call on the type reaches, as
* Rust picks: its inherent one; else the one of the trait impl the call is
* written in (each of tokio's `impl AsyncWrite for io::Cursor<…>` blocks calls
* its own `poll_flush`), or the only one. Several inherent ones (`#[cfg]`
* twins) or trait impls of the name are past telling apart.
*/
function pickRustOwnMethod(owned: Node[], ref: UnresolvedRef, context: ResolutionContext): Node | null {
if (owned.length <= 1) return owned[0] ?? null;
const inherent = owned.filter((n) => !isRustTraitImplMethod(n, context));
if (inherent.length > 0) return inherent.length === 1 ? inherent[0]! : null;
const header = rustEnclosingImplLine(ref, context);
const block = owned.filter((n) => n.filePath === ref.filePath && rustImplLineAt(n.filePath, n.startLine, context) === header);
return block.length === 1 ? block[0]! : null;
}
/**
* Rust's method lookup on a value reached through a std wrapper — `self:
* Pin<&mut Self>`, `read: Pin<&mut R>`, `self: Rc<Self>` — where `own` are
* the wrapped type's methods of the name and `inner` that type (null for a
* type parameter or one not known). Rust tries the wrapper before
* dereferencing it:
* - the wrapped type's method taking `self` through that wrapper (`fn
* poll_flush(self: Pin<&mut Self>, …)`) is a method of the wrapper's type;
* - then the wrapper's own: std's (RUST_WRAPPER_OWN_METHODS), which the index
* doesn't hold, or a project impl on the wrapper, blanket or for `inner`,
* taking `self`, `&self` or `&mut self` (tokio's `impl AsyncWrite for
* Pin<P>`'s `is_write_vectored`). Its methods taking `self: Pin<&mut Self>`
* would need a `Pin<&mut Pin<…>>`: tokio's `self.poll_flush(cx)` in `impl
* AsyncWrite for File` went to that impl's;
* - then the wrapped type's other methods, by deref.
* The candidates to pick from, or the target itself (null for std's).
*/
function rustThroughWrapper(
wrapper: string, name: string, own: Node[], inner: string | null, ref: UnresolvedRef, context: ResolutionContext,
): Node[] | Node | null {
const through = own.filter((n) => rustSelfWrapper(n) === wrapper);
if (through.length > 0) return through;
if (RUST_WRAPPER_OWN_METHODS.get(wrapper)?.has(name)) return null;
const onWrapper = context.getNodesByName(name).filter((n) => n.language === 'rust' && n.kind === 'method' &&
(n.qualifiedName === `${wrapper}::${name}` || n.qualifiedName.endsWith(`::${wrapper}::${name}`)) &&
rustSelfWrapper(n) === null && rustImplFor(n, inner, context));
if (onWrapper.length > 0) return preferCallSiteFile(onWrapper, ref.filePath)[0]!;
return own;
}
/** Whether `method`'s impl on a wrapper applies to one of `inner`: a blanket impl (`impl<P> AsyncWrite for Pin<P>`), or one naming it (`impl Schedule for Arc<Handle>`). */
function rustImplFor(method: Node, inner: string | null, context: ResolutionContext): boolean {
const impl = rustTraitImplHeader(method, context);
return impl !== null && impl.selfArgs.some((a) => impl.params.includes(a) || a === inner);
}
/**
* 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)`).
*/
function rustPinnedType(receiver: 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`);
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;
}
return null;
}
/**
* 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>`.
*/
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;
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);
let target = through as Node | null;
if (Array.isArray(through)) {
const inherent = through.filter((n) => !isRustTraitImplMethod(n, context));
target = preferCallSiteFile(inherent.length > 0 ? inherent : through, ref.filePath)[0] ?? null;
}
return target ? { original: ref, targetNodeId: target.id, confidence: 0.9, resolvedBy: 'instance-method' } : null;
}
/**
* Resolve a Rust call through a field of the enclosing type —
* `self.inner.run()`, emitted by the extractor as `self.inner.run` (#1585).