mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-24 11:24:49 +08:00
fix(rust): a call on a field of self reaches the method of the field's declared type (#2719)
A Rust `self.field.m()` call was resolved by the last segment of the field's type name alone. `inner: multer::Field` linked to axum's own `Field::name`, `std: std::time::Instant` to tokio's `Instant` (the calling method itself), `inner: std::sync::Mutex<…>` to tokio's loom mock or async `Mutex::lock`, and a field generic over the impl's `S: Store` to nothing. The field's type is now read the way field values already are: from the struct's declaration, through the wrappers, by the `use`s of the struct's file. A struct's own parameter is known by the bounds that the impl around the call gives the parameter it is instantiated with (`struct Worker<S>` under `impl<S: Store> Worker<S>`). The method is the one the typed receiver path picks. The plain-name reading stays only where the field's type can't be read. A call on a std or dependency value reaches a project trait's impl for its type only when the impl is for that type. tokio's `impl Kill for Child` in process/windows.rs is for its own `struct Child`, not `std::process::Child`. The impl's type is read as the impl writes it: gleam's `impl LowestVersion for pubgrub::Range<Version>` is for pubgrub's `Range`. A/B with fresh indexes against main on axum, serde, ripgrep, clap, dioxus, tokio and gleam, every changed edge triaged: 361 calls linked that were not (fields bounded by the impl's traits, dioxus's signal fields, trait objects), 104 removed (calls on std and dependency fields, and 5 on tokio values whose methods sit inside `cfg_*!` macros), 59 moved to the right type of a shared name, 2,844 kept at a higher confidence; 62 fewer self-loops and none added. gin, serilog, flask and express are byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a33040f786
commit
7324faf69f
@@ -244,6 +244,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- 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 Rust, a `for` loop's variable now has the type of an item of what the loop goes over, like `for item in &self.items` over a `Vec<Item>`, `for (key, value) in &map` over a `HashMap`, or `for sc in cmd.get_subcommands()` over the `impl Iterator` a method returns, and each name in a tuple pattern like `let (tx, rx) = mpsc::channel(8);` has the type of its part of the tuple, so calls on them link to that type's methods. Before, such calls were linked by name: tokio's `tx.send(…)` calls on channel senders went to an internal type's `send`. Re-index Rust projects after upgrading.
|
||||
- In Rust, a call on a field of `self`, like `self.store.save()` or `self.inner.lock()`, now links to the method of the type the struct declares for that field, also when the field's type is a parameter the `impl` bounds by a trait (`impl<S: Store> Worker<S>`), a trait object, or held in an `Arc` or a `Box`. A field whose type comes from the standard library or a dependency, like `jar: cookie::CookieJar` or `inner: std::sync::Mutex<…>`, no longer links to the project's own type of the same name, which often made a wrapper's method look like it called itself. 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.
|
||||
- In TypeScript and JavaScript, an import of a package your project declares itself with `declare module '…'` in a `.d.ts` file, like a `typings/mammoth/index.d.ts` written for an untyped package or VS Code's own `vscode.d.ts`, now links to that declaration, so `codegraph callers`, impact and `codegraph affected` show the code that uses your typings instead of nothing or a project symbol that only shares a name. Named, default and namespace imports, `export =` and renaming export lists are read the way TypeScript reads them, a function such a file declares without a body (`export function convertToHtml(…): Promise<Result>;`) is now indexed so calls to it have something to link to, and only an import of its package reaches it. A `declare module` that augments a package's own types, a wildcard like `declare module '*.svg'`, and copies of typings kept in test fixtures are left alone. Re-index TypeScript projects after upgrading.
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* A Rust call on a field of the impl's own type — `self.store.save()` — is a
|
||||
* call on the type the struct declares for the field, read as a written
|
||||
* type: the struct's own parameter by the bounds the impl gives it, a trait
|
||||
* object by its trait, a pointer's own methods its own (`Arc::clone`), a
|
||||
* type name by the `use` that brings it into the struct's file.
|
||||
*/
|
||||
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;
|
||||
|
||||
const config = [
|
||||
'pub struct Config;',
|
||||
'',
|
||||
'impl Config {',
|
||||
' pub fn load(&self) {}',
|
||||
'}',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
beforeAll(async () => {
|
||||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-rust-self-field-'));
|
||||
const files: Record<string, string> = {
|
||||
'Cargo.toml': '[package]\nname = "app"\nversion = "0.1.0"\n\n[dependencies]\nsemverlib = "1"\n',
|
||||
'src/main.rs': 'mod config_a;\nmod config_b;\nmod model;\nmod proc;\nmod range;\nmod use_range;\nmod ver;\nmod worker;\n\nfn main() {}\n',
|
||||
'src/range.rs': '/// The project\'s own `Range`, a namesake of the dependency\'s.\npub struct Range;\n',
|
||||
'src/ver.rs': [
|
||||
'pub trait Lowest {',
|
||||
' fn lowest(&self) -> u32;',
|
||||
'}',
|
||||
'',
|
||||
'impl Lowest for semverlib::Range {',
|
||||
' fn lowest(&self) -> u32 {',
|
||||
' 0',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
].join('\n'),
|
||||
'src/use_range.rs': [
|
||||
'use semverlib::Range;',
|
||||
'',
|
||||
'use crate::ver::Lowest;',
|
||||
'',
|
||||
'pub fn check(r: Range) -> u32 {',
|
||||
' r.lowest()',
|
||||
'}',
|
||||
'',
|
||||
].join('\n'),
|
||||
'src/proc.rs': [
|
||||
'pub trait Kill {',
|
||||
' fn kill(&mut self) -> bool;',
|
||||
'}',
|
||||
'',
|
||||
'/// The project\'s own `Child`, holding std\'s.',
|
||||
'pub struct Child {',
|
||||
' child: std::process::Child,',
|
||||
'}',
|
||||
'',
|
||||
'impl Kill for Child {',
|
||||
' fn kill(&mut self) -> bool {',
|
||||
' self.child.kill().is_ok()',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
].join('\n'),
|
||||
'src/config_a.rs': config,
|
||||
'src/config_b.rs': config,
|
||||
'src/model.rs': [
|
||||
'pub trait Store {',
|
||||
' fn save(&self);',
|
||||
'}',
|
||||
'',
|
||||
'pub trait Render {',
|
||||
' fn render(&self);',
|
||||
'}',
|
||||
'',
|
||||
'pub struct State;',
|
||||
'',
|
||||
'impl Clone for State {',
|
||||
' fn clone(&self) -> Self {',
|
||||
' State',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
'pub struct Inner;',
|
||||
'',
|
||||
'impl Inner {',
|
||||
' pub fn run(&self) {}',
|
||||
'}',
|
||||
'',
|
||||
'// Namesakes, which a guess by name alone may take.',
|
||||
'pub struct Decoy;',
|
||||
'',
|
||||
'impl Decoy {',
|
||||
' pub fn save(&self) {}',
|
||||
' pub fn run(&self) {}',
|
||||
'}',
|
||||
'',
|
||||
].join('\n'),
|
||||
'src/worker.rs': [
|
||||
'use std::sync::Arc;',
|
||||
'',
|
||||
'use crate::config_b::Config;',
|
||||
'use crate::model::{Inner, Render, State, Store};',
|
||||
'',
|
||||
'pub struct Worker<S: Store> {',
|
||||
' store: S,',
|
||||
' shared: Arc<State>,',
|
||||
' cfg: Config,',
|
||||
' view: Box<dyn Render>,',
|
||||
'}',
|
||||
'',
|
||||
'impl<S: Store> Worker<S> {',
|
||||
' pub fn tick(&self) {',
|
||||
' self.store.save();',
|
||||
' let _ = self.shared.clone();',
|
||||
' self.cfg.load();',
|
||||
' self.view.render();',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
].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 call on a field of `self`', () => {
|
||||
it('is the method of the type the struct declares for the field', () => {
|
||||
// `S: Store` → `Store::save`; `Arc<State>`'s `clone` is the `Arc`'s;
|
||||
// `Config` is the one `config_b` declares, which the file uses;
|
||||
// `Box<dyn Render>` → `Render::render`.
|
||||
expect(callsFrom('src/worker.rs', 'tick')).toEqual(['src/config_b.rs@4', 'src/model.rs@2', 'src/model.rs@6']);
|
||||
});
|
||||
|
||||
it('is no project impl for a project type that only shares the std type\'s name', () => {
|
||||
// `std::process::Child::kill` is std's; the project's `impl Kill for
|
||||
// Child` is for its own `Child`, so the call is no self-loop. (The
|
||||
// trait's `kill` declaration links to that impl on its own.)
|
||||
const impl = cg.getNodesInFile('src/proc.rs').find((n) => n.name === 'kill' && n.startLine === 11)!;
|
||||
expect(cg.getOutgoingEdges(impl.id).filter((e) => e.kind === 'calls')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('a Rust call on a dependency\'s type', () => {
|
||||
it('is a project trait\'s impl for that type, read by the path the impl writes', () => {
|
||||
// `impl Lowest for semverlib::Range`, though the project has a `Range` of its own.
|
||||
expect(callsFrom('src/use_range.rs', 'check')).toEqual(['src/ver.rs@6']);
|
||||
});
|
||||
});
|
||||
@@ -8952,6 +8952,8 @@ interface RustImplHeader {
|
||||
self: string;
|
||||
/** The type names its arguments write (`Arc<Handle>` → `Handle`, `Pin<P>` → `P`). */
|
||||
selfArgs: string[];
|
||||
/** The implementing type as written, path and arguments: `pubgrub::Range<Version>`. */
|
||||
selfText: string;
|
||||
/** The impl's own type parameters (`impl<'a, P: DerefMut>` → `P`). */
|
||||
params: string[];
|
||||
}
|
||||
@@ -9002,6 +9004,7 @@ function parseRustImplHeader(text: string): RustImplHeader | null {
|
||||
traitArgs: args(traitPart),
|
||||
self: head(selfPart),
|
||||
selfArgs: args(selfPart.trim().replace(/^(?:&\s*(?:'\w+\s+)?(?:mut\s+)?|dyn\s+)+/, '')),
|
||||
selfText: selfPart.replace(/\{[\s\S]*$/, '').replace(/\bwhere\b[\s\S]*$/, '').trim(),
|
||||
params: params.map((p) => /^\s*(?:const\s+)?([A-Za-z_]\w*)/.exec(p)?.[1]).filter((p): p is string => p !== undefined),
|
||||
};
|
||||
}
|
||||
@@ -9409,6 +9412,12 @@ interface RustWrittenType {
|
||||
* value that is one of them is unread, whatever its bound.
|
||||
*/
|
||||
opaque?: string[];
|
||||
/**
|
||||
* What a declaration's own parameters are known by where the type is
|
||||
* reached: through `self` in `impl<S: Store> Worker<S>`, the struct's `S`
|
||||
* is the impl's, a `Store`. The traits are read where the impl is.
|
||||
*/
|
||||
bounds?: ReadonlyMap<string, { traits: string[]; file: string; at: number }>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -9787,6 +9796,12 @@ function rustReadType(written: RustWrittenType, ref: UnresolvedRef, context: Res
|
||||
}
|
||||
if (segments.length === 0) {
|
||||
if (written.opaque?.includes(name)) return { kind: 'unread' };
|
||||
const known = written.bounds?.get(name);
|
||||
if (known) {
|
||||
const traits = known.traits.filter((b) => !/^['?]/.test(b.trim()) && !RUST_MARKER_TRAITS.has(rustPathHead(b)?.name ?? ''));
|
||||
if (traits.length === 0) return { kind: 'unread' };
|
||||
return { kind: 'traits', traits, wrappers, byRef, written: { text: written.text, file: known.file, at: known.at } };
|
||||
}
|
||||
const bounds = rustGenericBounds(name, written, context);
|
||||
if (bounds !== undefined) {
|
||||
const traits = bounds.filter((b) => !RUST_MARKER_TRAITS.has(rustPathHead(b)?.name ?? ''));
|
||||
@@ -10181,7 +10196,56 @@ function rustFieldValue(
|
||||
const fields = rustDeclaredFields(decl, context);
|
||||
const text = /^\d+$/.test(field) ? fields?.tuple?.[Number(field)] : fields?.named?.get(field);
|
||||
if (!fields || !text) return null;
|
||||
return { text, file: decl.filePath, at: rustNodeOffset(decl, context), self: decl.name, generics: fields.generics };
|
||||
const value: RustWrittenType = { text, file: decl.filePath, at: rustNodeOffset(decl, context), self: decl.name, generics: fields.generics };
|
||||
if (recv === 'self') {
|
||||
const bounds = rustSelfParamBounds(fields.generics, scopes, at, ref.filePath);
|
||||
if (bounds) value.bounds = bounds;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* What the struct's own type parameters are known by inside the impl around
|
||||
* `at`: the bounds of the impl's parameter each one is instantiated with,
|
||||
* by position — `struct Worker<S>` under `impl<S: Store> Worker<S>` gives
|
||||
* `S` → `Store`. Undefined where the impl's type arguments don't line up
|
||||
* with the struct's parameters, or none is a bounded parameter of the impl.
|
||||
*/
|
||||
function rustSelfParamBounds(structGenerics: string, scopes: RustScopes, at: number, file: string): Map<string, { traits: string[]; file: string; at: number }> | undefined {
|
||||
if (structGenerics.trim() === '') return undefined;
|
||||
const block = rustImplAt(scopes, at);
|
||||
if (!block || block.kind !== 'impl') return undefined;
|
||||
// `impl<S: Store> [Trait for] Worker<S>`: the type after the impl's generics and a top-level ` for `.
|
||||
let header = scopes.code.slice(block.at, block.from).replace(/^impl\b/, '').trim();
|
||||
if (header.startsWith('<')) {
|
||||
const end = rustAngleEnd(header, 0);
|
||||
if (end < 0) return undefined;
|
||||
header = header.slice(end + 1).trim();
|
||||
}
|
||||
let depth = 0;
|
||||
for (let i = 0; i < header.length; i++) {
|
||||
const c = header[i];
|
||||
if (c === '<') depth++;
|
||||
else if (c === '>' && header[i - 1] !== '-') depth--;
|
||||
else if (depth === 0 && /\s/.test(c!) && /^for\s/.test(header.slice(i + 1))) {
|
||||
header = header.slice(i + 4).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
const self = rustPathHead(header.replace(/\bwhere\b[\s\S]*$/, '').trim());
|
||||
if (!self) return undefined;
|
||||
const args = splitRustTopLevel(self.args, ',').map((a) => a.trim()).filter((a) => a !== '');
|
||||
const params = splitRustTopLevel(structGenerics, ',').map((p) => /^\s*(?:const\s+)?('?[A-Za-z_]\w*)/.exec(p)?.[1] ?? '');
|
||||
if (params.length !== args.length) return undefined;
|
||||
const own = new Set(splitRustTopLevel(block.generics, ',').map((p) => /^\s*(?:const\s+)?([A-Za-z_]\w*)/.exec(p)?.[1]));
|
||||
const bounds = new Map<string, { traits: string[]; file: string; at: number }>();
|
||||
params.forEach((p, i) => {
|
||||
const arg = args[i]!;
|
||||
if (p === '' || p.startsWith("'") || !own.has(arg)) return;
|
||||
const traits = rustBoundsOf(arg, [{ generics: block.generics, where: block.where }]);
|
||||
if (traits && traits.length > 0) bounds.set(p, { traits, file, at });
|
||||
});
|
||||
return bounds.size > 0 ? bounds : undefined;
|
||||
}
|
||||
|
||||
/** The `index`th field of a Rust tuple type as written — `(K, V)` → `V` for 1 — through `&`; null for any other type. */
|
||||
@@ -10715,10 +10779,21 @@ function rustOutsideTypeCall(name: string, args: string, byRef: boolean, method:
|
||||
// its name (`fn local_addr(&self) { Self::local_addr(self) }` in axum's
|
||||
// `impl Listener for TcpListener`) shows the type has that method itself,
|
||||
// which a call reaches first.
|
||||
// An impl for a project type of the name is that type's, not the outside
|
||||
// one's: tokio's `impl Kill for Child` in process/windows.rs is for its own
|
||||
// `struct Child`, never `std::process::Child`. Its type is read as the impl
|
||||
// writes it: hexpm's `impl LowestVersion for pubgrub::Range<Version>` is
|
||||
// pubgrub's `Range`'s.
|
||||
const forOutside = (m: Node): boolean => {
|
||||
const written = rustTraitImplHeader(m, context)?.selfText.replace(/^(?:&\s*(?:'\w+\s+)?(?:mut\s+)?)+/, '') ?? '';
|
||||
const head = rustPathHead(written);
|
||||
const lookup = rustTypeLookup(head?.name ?? name, head?.segments ?? [], m.filePath, context, rustNodeOffset(m, context));
|
||||
return lookup.outside || !lookup.nodes.some((n) => RUST_OWNER_KINDS.has(n.kind));
|
||||
};
|
||||
const impls = rustSpecialized(rustMethodsNamed(name, method, context).filter((m) => {
|
||||
const impl = rustTraitImplHeader(m, context);
|
||||
return impl !== null && impl.self === name && isRustProjectNamed(impl.trait, RUST_TRAIT_KIND, context) &&
|
||||
isRustTraitInScope(impl.trait, ref, m, context) && !rustDelegatesToOwn(m, name, context);
|
||||
isRustTraitInScope(impl.trait, ref, m, context) && !rustDelegatesToOwn(m, name, context) && forOutside(m);
|
||||
}), args, context);
|
||||
if (impls.length === 1) return { original: ref, targetNodeId: impls[0]!.id, confidence: 0.85, resolvedBy: 'instance-method' };
|
||||
if (impls.length > 1) {
|
||||
@@ -10888,6 +10963,32 @@ function matchRustTypedReceiverCall(receiver: string, method: string, ref: Unres
|
||||
/** The refs matchRustTypedReceiverCall resolved: through a typed receiver, a trait's own method is in reach. */
|
||||
const RUST_TYPED_REFS = new WeakMap<ResolutionContext, WeakSet<UnresolvedRef>>();
|
||||
|
||||
/**
|
||||
* A Rust call on a field of the enclosing impl's type — `self.inner.run()`,
|
||||
* `self.0.len()` — as a call on the type the struct declares for the field:
|
||||
* through `Arc`, `Box`, `Pin` and the lock guards, by a bounded generic's
|
||||
* traits or a trait object's, with the struct's `use`s naming the type
|
||||
* (rustFieldValue, rustTypedMethodCall). Undefined where the field's type
|
||||
* can't be told: an enum's `self`, a trait's default method, a struct the
|
||||
* index doesn't hold.
|
||||
*/
|
||||
function matchRustTypedSelfFieldCall(field: string, method: string, ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null | undefined {
|
||||
if (!/^(?:[A-Za-z_]\w*|\d+)$/.test(field)) return undefined;
|
||||
const scopes = rustScopesOf(ref.filePath, context);
|
||||
if (!scopes) return undefined;
|
||||
const at = (scopes.lineStarts[ref.line - 1] ?? 0) + Math.max(0, ref.column);
|
||||
const written = rustFieldValue('self', field, at, scopes, ref, context, 0);
|
||||
if (!written) return undefined;
|
||||
const result = rustTypedMethodCall(rustReadType(written, ref, context), method, ref, context);
|
||||
if (result === 'unread') return undefined;
|
||||
if (result) {
|
||||
let typed = RUST_TYPED_REFS.get(context);
|
||||
if (!typed) RUST_TYPED_REFS.set(context, (typed = new WeakSet()));
|
||||
typed.add(ref);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function rustTypedReceiverCall(receiver: string, method: string, ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null | { unread: number } | undefined {
|
||||
const scopes = rustScopesOf(ref.filePath, context);
|
||||
if (!scopes) return undefined;
|
||||
@@ -18525,7 +18626,13 @@ export function matchMethodCall(
|
||||
// contain — whenever the field's type was external or merely shared a
|
||||
// method name with something nearby.
|
||||
if (ref.language === 'rust' && dotMatch && objectOrClass!.startsWith('self.')) {
|
||||
return matchRustSelfFieldCall(objectOrClass!.slice('self.'.length), methodName!, ref, context);
|
||||
// The field's type as the impl's struct declares it, read as any written
|
||||
// type is (matchRustTypedSelfFieldCall); the plain-name reading below
|
||||
// only where that can't be told.
|
||||
const field = objectOrClass!.slice('self.'.length);
|
||||
const typed = nmTimedT('mc-rustselffield', ref, () => matchRustTypedSelfFieldCall(field, methodName!, ref, context));
|
||||
if (typed !== undefined) return typed;
|
||||
return matchRustSelfFieldCall(field, methodName!, ref, context);
|
||||
}
|
||||
|
||||
// TS/JS call through a field of the enclosing class — `this.mailer.send()`,
|
||||
|
||||
Reference in New Issue
Block a user