fix(rust): a local holding a method call's value has the type the method returns (#2716)

A Rust local bound to a method call (`let server = b.port(80).build()`,
`let s = cfg.load()?`, `let conn = c.connect().await`, `if let Some(c) =
arg.get::<Completer>()`) had no type the typed receiver path could read,
so calls on it fell to the name strategies. Those strategies did not read
`?` on a returned `Result`, a `self` method's value or a turbofish, and
picked a same-named method of another type.

The value is now the return type of the method the call reaches on its
receiver's type, read from that method's signature. A receiver is a local,
`self`, or another such call. `.await` hands out an `async fn`'s return
type (read off the source: the extractor's async flag is never set for
Rust) or an `impl Future`'s `Output`, and an `async fn` not awaited is a
future, left unread. A method's own type parameter is the call's turbofish
argument. A parameter the call doesn't write stays opaque: `Standard<W>`
is a `Standard`, but a value that is `W` itself is unread rather than its
bound. A trait's provided method returning `Self` gives the receiver's
own type.

A type with two impls of one trait, `Encoder<Bytes>` and `Encoder<&[u8]>`
or `Service<IncomingStream>` and `Service<Request<B>>`, was ambiguous for
a typed call. The impl whose trait argument is the call's first
argument's type now wins.

A/B with fresh indexes against main on axum, serde, ripgrep, clap, dioxus,
tokio and gleam, every changed edge triaged: 536 calls linked that were
not, 69 removed (calls on std or dependency values a method returns, and
tokio values whose read and write methods come from macro-declared
extension traits), 50 moved to the declared type's method, 565 kept at a
higher confidence. gin, serilog, flask and express are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-20 19:41:15 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 23b5ca1b43
commit f76021db22
3 changed files with 504 additions and 9 deletions
+1
View File
@@ -237,6 +237,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In Rust, a trait's associated type written as `<T as Trait>::Name`, or bound in a trait's arguments like `Iterator<Item = &str>`, now links only to what it can name: through `<Self as Trait>` the `impl`'s own, through a type that type's `impl` of the trait, and nothing through a generic parameter or from a binding. Before, axum's `<L as Listener>::Io` linked to one listener's `type Io`, its `Iterator<Item = &str>` to an unrelated `enum Item`, and dioxus's `Storage = …` bindings to a `Storage` trait. Re-index Rust projects after upgrading.
- In Rust, a path through a module declared inline in the same file, like `inner::Cell` for a `mod inner { … }` above it, now finds the item however the code is indented, and no longer one in a module nested inside it.
- In Rust, a method call on a local variable or a parameter, like `opts.build()`, `server.run()` or `serializer.serialize_str(…)`, now links to the method of the type the variable is declared with: its parameter type or `let` annotation, the value it is built from (`Server::new()`, `Opts { … }`, `Arc::new(…)`), or the field a match arm takes from a variant (`Commands::Build(opts) => opts.build()`). A call through `&`, `Box`, `Rc` or `Arc` reaches the value inside, a call on an `impl Trait`, a `dyn Trait` or a bounded generic reaches the trait's method, and among types that share a name, the one the file imports wins. A call on a standard-library or dependency value, like `cx.waker()` on a `Context` or `path.join(…)` on a `PathBuf`, no longer links to an unrelated project method of the same name. Before, such calls were linked by the method's name alone, so dioxus's `opts.build()` lost its link once another type had a `build`. Re-index Rust projects after upgrading. Thanks @Egrn for the report and @danusha2345. (#2614)
- In Rust, a variable that holds what a method call returns, like `let server = builder.port(80).build();`, `let settings = config.load()?;` or `let conn = client.connect().await;`, now has the type that method is declared to return, so calls on the variable link to that type's methods. A generic method's type comes from the call's turbofish, like `arg.get::<ValueHint>()`, and a call on a type that implements one trait twice, like `Encoder<Bytes>` and `Encoder<&[u8]>`, links to the implementation that takes the argument given. Before, such calls were linked by name and could reach another type's method of the same name. 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.
- In C++, a call on a parameter, a local lambda or functor, a lambda's parameter or a range-`for` variable no longer links to a function or method elsewhere that only shares its name, like protocolbuffers/protobuf's code generators calling `format(…)` on their `Formatter& format` parameter, which linked to the conformance suite's `TestResult::format`, or facebook/rocksdb's tests calling a local `auto key = [](int i) {…};`. C++ finds the local first, so that is what the call calls.
+338
View File
@@ -0,0 +1,338 @@
/**
* A Rust local bound to a method call's value — `let server = b.port(80)
* .build()`, `let s = cfg.load()?`, `let conn = c.connect().await` — has the
* type the method it reaches returns, read from that method's signature, so
* a call on the local reaches that type's method. `.await` hands out an
* `async fn`'s return type, or an `impl Future`'s `Output`; an `async fn`'s
* value not awaited is a future, whose type the signature doesn't write.
*/
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-method-value-'));
const files: Record<string, string> = {
'Cargo.toml': '[package]\nname = "app"\nversion = "0.1.0"\n',
'src/main.rs': [
'mod app;',
'mod codec;',
'mod ext;',
'mod net;',
'mod other;',
'mod printer;',
'mod widget;',
'',
'fn main() {}',
'',
].join('\n'),
'src/ext.rs': [
'pub trait Extension {}',
'',
'pub struct Completer;',
'',
'impl Extension for Completer {}',
'',
'impl Completer {',
' pub fn complete_at(&self) {}',
'}',
'',
'pub struct Arg;',
'',
'impl Arg {',
' pub fn get<T: Extension>(&self) -> Option<&T> {',
' None',
' }',
'}',
'',
].join('\n'),
'src/codec.rs': [
'pub trait Encoder<Item> {',
' fn encode(&mut self, item: Item);',
'}',
'',
'pub struct Codec;',
'pub struct Frame;',
'',
'impl Encoder<Frame> for Codec {',
' fn encode(&mut self, item: Frame) {}',
'}',
'',
'impl Encoder<String> for Codec {',
' fn encode(&mut self, item: String) {}',
'}',
'',
].join('\n'),
'src/printer.rs': [
'pub trait WriteColor {}',
'',
'pub struct Standard<W> {',
' wtr: W,',
'}',
'',
'impl<W: WriteColor> Standard<W> {',
' pub fn sink(&self) {}',
'}',
'',
'pub struct StandardBuilder;',
'',
'impl StandardBuilder {',
' pub fn build<W: WriteColor>(&self, wtr: W) -> Standard<W> {',
' Standard { wtr }',
' }',
'}',
'',
'pub struct NoColor;',
'',
'impl WriteColor for NoColor {}',
'',
].join('\n'),
'src/widget.rs': [
'pub trait Configure {',
' fn configured(self) -> Self',
' where',
' Self: Sized,',
' {',
' self',
' }',
'}',
'',
'pub struct Widget;',
'',
'impl Configure for Widget {}',
'',
'impl Widget {',
' pub fn draw(&self) {}',
'}',
'',
].join('\n'),
'src/net.rs': [
'pub struct Builder {',
' port: u16,',
'}',
'',
'impl Builder {',
' pub fn new() -> Self {',
' Builder { port: 0 }',
' }',
' pub fn port(self, port: u16) -> Self {',
' Builder { port }',
' }',
' pub fn build(self) -> Server {',
' Server',
' }',
'}',
'',
'pub struct Server;',
'',
'impl Server {',
' pub fn run(&self) {}',
' fn state(&self) -> State {',
' State',
' }',
' pub fn tick(&self) {',
' let st = self.state();',
' st.advance();',
' }',
'}',
'',
'pub struct State;',
'',
'impl State {',
' pub fn advance(&self) {}',
'}',
'',
'pub struct Config;',
'',
'impl Config {',
' pub fn load(&self) -> Result<Settings, String> {',
' Ok(Settings)',
' }',
'}',
'',
'pub struct Settings;',
'',
'impl Settings {',
' pub fn apply(&self) {}',
'}',
'',
'pub struct Client;',
'',
'impl Client {',
' pub async fn connect(&self) -> Conn {',
' Conn',
' }',
' pub fn fetch(&self) -> impl std::future::Future<Output = Conn> {',
' async { Conn }',
' }',
'}',
'',
'pub struct Conn;',
'',
'impl Conn {',
' pub fn send(&self) {}',
'}',
'',
].join('\n'),
'src/other.rs': [
'pub struct Other;',
'',
'impl Other {',
' pub fn run(&self) {}',
' pub fn advance(&self) {}',
' pub fn apply(&self) {}',
' pub fn send(&self) {}',
' pub fn build(&self) -> Other {',
' Other',
' }',
' pub async fn connect(&self) -> Other {',
' Other',
' }',
' pub fn fetch(&self) -> Other {',
' Other',
' }',
'}',
'',
].join('\n'),
'src/app.rs': [
'use crate::net::{Builder, Client, Config};',
'',
'pub fn start() {',
' let b = Builder::new();',
' let server = b.port(80).build();',
' server.run();',
'}',
'',
'pub fn setup(cfg: Config) -> Result<(), String> {',
' let s = cfg.load()?;',
' s.apply();',
' Ok(())',
'}',
'',
'pub async fn talk(c: Client) {',
' let conn = c.connect().await;',
' conn.send();',
'}',
'',
'pub async fn fetched(c: Client) {',
' let conn = c.fetch().await;',
' conn.send();',
'}',
'',
'// Same-file namesakes, which a guess by name alone prefers.',
'pub struct Decoy;',
'',
'impl Decoy {',
' pub fn run(&self) {}',
' pub fn apply(&self) {}',
' pub fn send(&self) {}',
' pub fn complete_at(&self) {}',
' pub fn draw(&self) {}',
' pub fn sink(&self) {}',
'}',
'',
'pub fn print(b: crate::printer::StandardBuilder) {',
' let printer = b.build(crate::printer::NoColor);',
' printer.sink();',
'}',
'',
'pub fn complete(arg: crate::ext::Arg) {',
' if let Some(completer) = arg.get::<crate::ext::Completer>() {',
' completer.complete_at();',
' }',
'}',
'',
'pub fn encode_frame(mut codec: crate::codec::Codec) {',
' use crate::codec::Encoder;',
' codec.encode(crate::codec::Frame);',
' codec.encode(String::new());',
'}',
'',
'pub fn show(w: crate::widget::Widget) {',
' use crate::widget::Configure;',
' let w2 = w.configured();',
' w2.draw();',
'}',
'',
].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` edges out of `fn`, declared in `file`, reach, as `file@line`, sorted. */
const callsFrom = (file: string, fn: string): string[] => {
const out: string[] = [];
for (const node of cg.getNodesInFile(file).filter((n) => n.name === fn)) {
for (const edge of cg.getOutgoingEdges(node.id)) {
if (edge.kind !== 'calls') continue;
const target = cg.getNode(edge.target)!;
out.push(`${target.filePath}@${target.startLine}`);
}
}
return [...new Set(out)].sort();
};
describe('a Rust local bound to a method call\'s value', () => {
it('has the type a builder chain\'s last method returns', () => {
// `server.run()` is `Server::run`, whatever the chain's `.build()`, a bare
// call by its name, links to.
const calls = callsFrom('src/app.rs', 'start');
expect(calls).toContain('src/net.rs@20');
expect(calls).not.toContain('src/app.rs@29');
expect(calls).not.toContain('src/other.rs@4');
});
it('has the type `?` takes out of a returned `Result`', () => {
expect(callsFrom('src/app.rs', 'setup')).toEqual(['src/net.rs@39', 'src/net.rs@47']);
});
it('has the type an awaited `async fn` returns', () => {
expect(callsFrom('src/app.rs', 'talk')).toEqual(['src/net.rs@53', 'src/net.rs@64']);
});
it('has the `Output` of an awaited `impl Future`', () => {
expect(callsFrom('src/app.rs', 'fetched')).toEqual(['src/net.rs@56', 'src/net.rs@64']);
});
it('has the type a `self` method returns', () => {
expect(callsFrom('src/net.rs', 'tick')).toEqual(['src/net.rs@21', 'src/net.rs@33']);
});
it('has the type the call\'s turbofish gives the method\'s own type parameter', () => {
// `completer.complete_at()` is `Completer`'s. (The extractor names the
// turbofish call itself `arg.get::<crate::ext::Completer>`, which no
// strategy reads yet.)
const calls = callsFrom('src/app.rs', 'complete');
expect(calls).toContain('src/ext.rs@8');
expect(calls).not.toContain('src/app.rs@32');
});
it('has the type the return type\'s head names, whatever generic it carries', () => {
// `build<W: WriteColor>(…) -> Standard<W>`: a `Standard`, whatever `W` is.
expect(callsFrom('src/app.rs', 'print')).toEqual(['src/printer.rs@14', 'src/printer.rs@8']);
});
it('has the receiver\'s own type where a trait\'s provided method returns `Self`', () => {
expect(callsFrom('src/app.rs', 'show')).toEqual(['src/widget.rs@15', 'src/widget.rs@2']);
});
});
describe('a Rust call on a type with two impls of one trait', () => {
it('is the impl whose trait argument is the call\'s argument\'s type', () => {
expect(callsFrom('src/app.rs', 'encode_frame')).toEqual(['src/codec.rs@13', 'src/codec.rs@9']);
});
});
+165 -9
View File
@@ -9393,6 +9393,12 @@ interface RustWrittenType {
self?: string;
generics?: string;
wrappers?: string[];
/**
* Generic parameters the type names that a call's value fixes, which
* nothing here says: `W` in `build<W: WriteColor>(…) -> Standard<W>`. A
* value that is one of them is unread, whatever its bound.
*/
opaque?: string[];
}
/**
@@ -9770,6 +9776,7 @@ function rustReadType(written: RustWrittenType, ref: UnresolvedRef, context: Res
return { kind: 'named', name: self.type, path: self.root ? [self.root] : [], args: '', wrappers, byRef, written };
}
if (segments.length === 0) {
if (written.opaque?.includes(name)) return { kind: 'unread' };
const bounds = rustGenericBounds(name, written, context);
if (bounds !== undefined) {
const traits = bounds.filter((b) => !RUST_MARKER_TRAITS.has(rustPathHead(b)?.name ?? ''));
@@ -9825,9 +9832,13 @@ function rustValueType(value: string, at: number, scopes: RustScopes, ref: Unres
if (depth > 3) return null;
let v = value.trim();
let unwraps = 0;
let awaits = 0;
for (;;) {
const before = v;
v = v.replace(/\s*\.\s*await$/, '');
if (/\.\s*await$/.test(v)) {
v = v.replace(/\s*\.\s*await$/, '');
awaits++;
}
if (v.endsWith('?')) {
v = v.slice(0, -1).trimEnd();
unwraps++;
@@ -9877,13 +9888,14 @@ function rustValueType(value: string, at: number, scopes: RustScopes, ref: Unres
}
return rustFunctionReturn(segments.slice(0, -1), last, unwraps, at, ref, context);
}
// `x.clone()` is a value of `x`'s type.
if (call && call.args.trim() === '') {
const cloned = /^([a-z_]\w*)\s*\.\s*clone$/.exec(call.callee);
if (call) {
// `x.clone()` is a value of `x`'s type.
const cloned = call.args.trim() === '' ? /^([a-z_]\w*)\s*\.\s*clone$/.exec(call.callee) : null;
if (cloned) return unwrapped(rustLocalType(cloned[1]!, at, scopes, ref, context, depth));
return null;
// `x.m(…)`, `self.m(…)`, `x.m(…)?.n(…)`: what the method it reaches returns.
const method = /^([\s\S]+?)\s*\.\s*([a-z_]\w*)\s*(?:::\s*<([^()]*)>)?$/.exec(call.callee);
return method ? rustMethodReturn(method[1]!, method[2]!, method[3], unwraps, awaits, at, scopes, ref, context, depth) : null;
}
if (call) return null;
const literal = /^((?:[A-Za-z_]\w*\s*::\s*)*[A-Z]\w*)\s*(?:::\s*<[^{}]*>\s*)?\{/.exec(v);
if (literal) {
if (!rustGroupIsWhole(v.slice(v.indexOf('{')))) return null;
@@ -9978,6 +9990,137 @@ function rustFunctionReturn(modules: string[], fn: string, unwraps: number, at:
return unwraps > 0 ? rustUnwrapped(made, unwraps) : made;
}
/**
* What a method call's value is — `let b = builder.port(80)`, `let s =
* self.state()?`, `let n = conn.next_frame().await?` — read from the
* signature of the method the call reaches on its receiver's type
* (rustTypedMethodCall), where `Self` is that method's impl type. A receiver
* is a local, `self`, or another call's value. Null where any step can't be
* told: a receiver or a method from outside the project, a return type
* naming the impl's or the method's own generics, an `async fn` not awaited.
*/
function rustMethodReturn(
receiver: string, method: string, turbofish: string | undefined, unwraps: number, awaits: number, at: number,
scopes: RustScopes, ref: UnresolvedRef, context: ResolutionContext, depth: number,
): RustWrittenType | null {
let memo = RUST_METHOD_RETURNS.get(context);
if (!memo) RUST_METHOD_RETURNS.set(context, (memo = new Map()));
const key = `${ref.filePath}\0${at}\0${receiver}\0${method}\0${turbofish ?? ''}\0${unwraps}\0${awaits}\0${depth}`;
const hit = memo.get(key);
if (hit !== undefined) return hit;
const made = readRustMethodReturn(receiver, method, turbofish, unwraps, awaits, at, scopes, ref, context, depth);
if (memo.size >= 4096) memo.delete(memo.keys().next().value!);
memo.set(key, made);
return made;
}
const RUST_METHOD_RETURNS = new WeakMap<ResolutionContext, Map<string, RustWrittenType | null>>();
function readRustMethodReturn(
receiver: string, method: string, turbofish: string | undefined, unwraps: number, awaits: number, at: number,
scopes: RustScopes, ref: UnresolvedRef, context: ResolutionContext, depth: number,
): RustWrittenType | null {
const recv = receiver.trim();
let written: RustWrittenType | null;
if (recv === 'self') written = { text: 'Self', file: ref.filePath, at };
else if (/^[a-z_]\w*$/.test(recv)) written = rustLocalType(recv, at, scopes, ref, context, depth);
else if (/[)?]$/.test(recv)) written = rustValueType(recv, at, scopes, ref, context, depth + 1);
else return null;
if (!written) return null;
const read = rustReadType(written, ref, context);
const result = rustTypedMethodCall(read, method, ref, context);
if (!result || result === 'unread') return null;
const target = context.getNodeById?.(result.targetNodeId);
const text = target ? rustReturnText(target.signature) : null;
if (!target || !text) return null;
let made: RustWrittenType | null = { text, file: target.filePath, at: rustSignatureOffset(target, context) };
made = rustReturnInstance(made, target, turbofish, read, at, ref, context);
// An `async fn`'s value is a future of what it returns; `.await` hands that out.
const isAsync = rustIsAsyncFn(target, context);
if (awaits > 1 || (awaits === 0 && isAsync)) return null;
if (awaits === 1 && !isAsync) made = rustFutureOutput(made);
return made && unwraps > 0 ? rustUnwrapped(made, unwraps) : made;
}
/**
* A method's return type as the call makes it, where it names a generic or
* `Self`: the method's own type parameters are what the call's turbofish
* writes (clap's `arg.get::<ArgValueCompleter>()` on `fn get<T: ArgExt>(&self)
* -> Option<&T>`), read where the call is, when the rest of the type is std's;
* a trait's provided method's `Self` is the receiver's own type. A parameter
* the call doesn't write, or one of the impl's, stays opaque: the type is
* still the one its head names (`Standard<W>` is a `Standard`), but a value
* that is the parameter itself is unread, the bound alone not telling.
*/
function rustReturnInstance(
made: RustWrittenType, target: Node, turbofish: string | undefined, read: RustTypeRead, at: number,
ref: UnresolvedRef, context: ResolutionContext,
): RustWrittenType {
const names = [...made.text.matchAll(/(?<!['\w])([A-Za-z_]\w*)/g)].map((m) => m[1]!)
.filter((n) => !/^(?:mut|dyn|impl|const|for)$/.test(n));
const generics = names.filter((n) => n !== 'Self' && rustGenericBounds(n, made, context) !== undefined);
if (generics.length === 0) {
// `fn with_x(self) -> Self` in a trait: the value is the receiver's type, not the trait.
const bare = made.text.replace(/^(?:&\s*(?:'\w+\s+)?(?:mut\s+)?)+/, '').trim();
if (bare === 'Self' && read.kind === 'named' && rustTraitOf(target, context) !== null) {
const typeText = [...read.path, read.name].join('::') + (read.args ? `<${read.args}>` : '');
return { text: made.text.replace(/Self\s*$/, typeText), file: read.written.file, at: read.written.at };
}
return made;
}
const opaque: RustWrittenType = { ...made, opaque: [...new Set(generics)] };
if (turbofish === undefined) return opaque;
const scopes = rustScopesOf(made.file, context);
const own = scopes ? rustFnAt(scopes, made.at) : undefined;
const params = own ? splitRustTopLevel(own.generics, ',').map((p) => /^\s*(?:const\s+)?([A-Za-z_]\w*)/.exec(p)?.[1]).filter((p): p is string => p !== undefined) : [];
const args = splitRustTopLevel(turbofish, ',').map((a) => a.trim()).filter((a) => !a.startsWith("'"));
const bound = new Map<string, string>();
for (const g of new Set(generics)) {
const i = params.indexOf(g);
if (i < 0 || i >= args.length || args[i] === '_') return opaque;
bound.set(g, args[i]!);
}
// The type is then read where the call writes the turbofish: anything else it names must mean the same there.
if (names.some((n) => !bound.has(n) && (n === 'Self' || !(RUST_STD_TYPES.has(n) || RUST_PRELUDE.has(n) || RUST_PRIMITIVE_TYPES.has(n))))) return opaque;
const text = made.text.replace(/(?<!['\w])([A-Za-z_]\w*)/g, (n) => bound.get(n) ?? n);
return { text, file: ref.filePath, at };
}
/** Whether a Rust function is declared `async`, read off its source: the extractor's flag never says. */
function rustIsAsyncFn(fn: Node, context: ResolutionContext): boolean {
const scopes = rustScopesOf(fn.filePath, context);
if (!scopes) return false;
const start = rustNodeOffset(fn, context);
const head = scopes.code.slice(start, start + 300);
const name = head.search(new RegExp(`\\bfn\\s+(?:r#)?${fn.name.replace(/^r#/, '')}\\b`));
return name >= 0 && /\basync\b/.test(head.slice(0, name));
}
/**
* What `.await` hands out of a value of a Rust future type: the `Output` of
* `impl Future<Output = T>` or a `Pin<Box<dyn Future<Output = T>>>`, the `T`
* of futures' `BoxFuture<'_, T>`. Null for any other type.
*/
function rustFutureOutput(written: RustWrittenType): RustWrittenType | null {
let t = written.text.trim();
for (const wrapper of [/^(?:(?:std|core)\s*::\s*pin\s*::\s*)?Pin\s*</, /^(?:(?:std|alloc)\s*::\s*boxed\s*::\s*)?Box\s*</]) {
const open = wrapper.exec(t);
if (open && rustAngleEnd(t, open[0].length - 1) === t.length - 1) t = t.slice(open[0].length, -1).trim();
}
const boxed = /^(?:[A-Za-z_]\w*\s*::\s*)*(?:BoxFuture|LocalBoxFuture)\s*</.exec(t);
if (boxed) {
if (rustAngleEnd(t, boxed[0].length - 1) !== t.length - 1) return null;
const args = splitRustTopLevel(t.slice(boxed[0].length, -1), ',').filter((a) => !/^'/.test(a.trim()));
return args.length === 1 ? { ...written, text: args[0]!.trim() } : null;
}
const future = /^(?:impl|dyn)\s+(?:(?:std|core)\s*::\s*future\s*::\s*)?(?:Future|IntoFuture)\s*</.exec(t);
if (!future) return null;
const end = rustAngleEnd(t, future[0].length - 1);
if (end < 0 || !/^\s*(?:\+[\s\S]*)?$/.test(t.slice(end + 1))) return null;
const output = splitRustTopLevel(t.slice(future[0].length, end), ',').map((a) => /^\s*Output\s*=\s*([\s\S]+)$/.exec(a)?.[1]).find((o) => o !== undefined);
return output ? { ...written, text: output.trim() } : null;
}
/**
* The type a name a pattern binds is: in `Some(x)` / `Ok(x)` / `Err(e)` what
* the matched value's `Option` or `Result` holds; in a variant's or a
@@ -10500,9 +10643,21 @@ function rustTypedMethodCall(read: RustTypeRead, method: string, ref: Unresolved
if (traits.length > 1) traits = traits.filter((t) => isRustTraitCallable(t, ref, viaTraits.get(t)![0]!, context));
// Two traits in scope with the method: Rust asks which.
if (traits.length !== 1) return null;
const methods = rustSpecialized(viaTraits.get(traits[0]!)!, read.args, context);
// `impl From<A> for T` and `impl From<B> for T`: the argument decides.
if (new Set(methods.map((m) => rustTraitImplHeader(m, context)?.traitArgs.join(','))).size > 1) return null;
let methods = rustSpecialized(viaTraits.get(traits[0]!)!, read.args, context);
// `impl Encoder<Bytes> for C` and `impl Encoder<&[u8]> for C`: the call's
// argument decides, where its type is one impl's trait argument —
// tokio's `codec.encode(Bytes::from(…), &mut buf)`, axum's
// `router.call(request)` on a `request: Request<String>`.
const traitArgs = (m: Node): string => rustTraitImplHeader(m, context)?.traitArgs.join(',') ?? '';
if (new Set(methods.map(traitArgs)).size > 1) {
const fit = methods.filter((m) => {
const args = rustTraitImplHeader(m, context)?.traitArgs ?? [];
const head = args.find((a) => /^[A-Z]/.test(a)) ?? args[0];
return head !== undefined && rustFirstArgIs(head, ref, context);
});
if (fit.length === 0 || new Set(fit.map(traitArgs)).size > 1) return null;
methods = fit;
}
return rustPickMethod(methods, typeFiles, ref, 0.9);
}
// An impl of an alias of the type, for the instantiation it names: gleam's
@@ -16550,6 +16705,7 @@ export function clearNameMatcherMemos(context: ResolutionContext): void {
RUST_SCOPES.delete(context);
RUST_USE_PATHS.delete(context);
RUST_WALKED_ITEMS.delete(context);
RUST_METHOD_RETURNS.delete(context);
RUST_TRAIT_OF.delete(context);
RUST_TRAIT_IMPLS.delete(context);
RUST_TYPED_REFS.delete(context);