diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bd5b8d4..e7df53f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `::Name`, or bound in a trait's arguments like `Iterator`, now links only to what it can name: through `` 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 `::Io` linked to one listener's `type Io`, its `Iterator` 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::()`, and a call on a type that implements one trait twice, like `Encoder` 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. diff --git a/__tests__/rust-method-value-types.test.ts b/__tests__/rust-method-value-types.test.ts new file mode 100644 index 00000000..481e6825 --- /dev/null +++ b/__tests__/rust-method-value-types.test.ts @@ -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 = { + '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(&self) -> Option<&T> {', + ' None', + ' }', + '}', + '', + ].join('\n'), + 'src/codec.rs': [ + 'pub trait Encoder {', + ' fn encode(&mut self, item: Item);', + '}', + '', + 'pub struct Codec;', + 'pub struct Frame;', + '', + 'impl Encoder for Codec {', + ' fn encode(&mut self, item: Frame) {}', + '}', + '', + 'impl Encoder for Codec {', + ' fn encode(&mut self, item: String) {}', + '}', + '', + ].join('\n'), + 'src/printer.rs': [ + 'pub trait WriteColor {}', + '', + 'pub struct Standard {', + ' wtr: W,', + '}', + '', + 'impl Standard {', + ' pub fn sink(&self) {}', + '}', + '', + 'pub struct StandardBuilder;', + '', + 'impl StandardBuilder {', + ' pub fn build(&self, wtr: W) -> Standard {', + ' 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 {', + ' 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 {', + ' 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::() {', + ' 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::`, 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(…) -> Standard`: 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']); + }); +}); diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index fb9c4e87..8b600910 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -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(…) -> Standard`. 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>(); + +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::()` on `fn get(&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` 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(/(? 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(); + 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(/(? 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` or a `Pin>>`, 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* !/^'/.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* /^\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 for T` and `impl From 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 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`. + 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);