A Rust call through a chained associated function — `Foo::new().bar()`, `Foo::with(cfg).build()` — dropped the receiver to a bare method name, which then attached to a same-named method on an unrelated type (a wrong edge) or didn't resolve. Ports the #645/#608 mechanism for Rust's `::` receivers: - Part 1: capture Rust return types; `-> Self` yields the `self` marker (resolved to the impl's own type, like PHP), references/generics are unwrapped/reduced. - Part 2: encode an associated-function chain (`Foo::new().bar`), gated to a scoped_identifier receiver so instance chains (`x.foo().bar()`) keep bare-name. - Part 3: resolve via matchScopedCallChain (PHP's `::` resolver, generalized), validated by resolveMethodOnType. Wire Rust into the conformance second pass (matchScopedCallChain variant) so a chained method provided by a trait the type implements (`impl Trait for Type` → existing implements edges) resolves too. Validated: synthetic decoy + args + Self + trait-default-conformance + absent safety tests; full suite green (lone failure is the known-flaky #662 daemon test, passes in isolation). Real-repo A/B vs main: clap (329 .rs) a net precision win — **+937 added (96% correct builder methods), 622 wrong->right retargets** (`Command::new().arg()` was mis-resolving to `ArgGroup::arg`, now `Command::arg`), +162 net unique edges; the pure-drops are largely wrong bare-name edges the fix correctly stops emitting. tokio-rs/bytes 0/0 (no regression). Known limit: the single-hop mechanism re-encodes only the first hop of a chain (deeper hops keep bare-name) — clap's unusually deep builder chains are partly covered. EXTRACTION_VERSION 10 -> 11. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7c7f0dd56f
commit
5805f01957
+15
-5
@@ -16,7 +16,7 @@ import {
|
||||
FrameworkResolver,
|
||||
ImportMapping,
|
||||
} from './types';
|
||||
import { matchReference, matchDottedCallChain, sameLanguageFamily, crossesKnownFamily } from './name-matcher';
|
||||
import { matchReference, matchDottedCallChain, matchScopedCallChain, sameLanguageFamily, crossesKnownFamily } from './name-matcher';
|
||||
import { resolveViaImport, resolveJvmImport, extractImportMappings, extractReExports, loadCppIncludeDirs, isPhpIncludePathRef } from './import-resolver';
|
||||
import { detectFrameworks } from './frameworks';
|
||||
import { synthesizeCallbackEdges } from './callback-synthesizer';
|
||||
@@ -32,8 +32,13 @@ const SUPERTYPE_BEARING_KINDS = new Set<Node['kind']>([
|
||||
'class', 'struct', 'interface', 'trait', 'protocol', 'enum',
|
||||
]);
|
||||
|
||||
/** Languages whose chained calls use the dotted `inner().method` encoding. */
|
||||
const DOT_CHAIN_LANGUAGES = new Set(['java', 'kotlin', 'csharp', 'swift']);
|
||||
/**
|
||||
* Languages whose chained static-factory/fluent calls defer to the conformance
|
||||
* second pass. Dotted-receiver languages resolve via matchDottedCallChain; the
|
||||
* `::`-receiver ones (Rust) via matchScopedCallChain.
|
||||
*/
|
||||
const CHAIN_LANGUAGES = new Set(['java', 'kotlin', 'csharp', 'swift', 'rust']);
|
||||
const SCOPED_CHAIN_LANGUAGES = new Set(['rust']);
|
||||
|
||||
/** The extractor's chained-receiver encoding: `<inner>().<method>`. */
|
||||
const CHAIN_SHAPE = /^(.+)\(\)\.(\w+)$/;
|
||||
@@ -726,7 +731,7 @@ export class ReferenceResolver {
|
||||
// resolvable once implements/extends edges exist (the conformance pass).
|
||||
if (
|
||||
ref.referenceKind === 'calls' &&
|
||||
DOT_CHAIN_LANGUAGES.has(ref.language) &&
|
||||
CHAIN_LANGUAGES.has(ref.language) &&
|
||||
CHAIN_SHAPE.test(ref.referenceName)
|
||||
) {
|
||||
this.deferredChainRefs.push(ref);
|
||||
@@ -839,7 +844,12 @@ export class ReferenceResolver {
|
||||
this.clearCaches();
|
||||
const resolved: ResolvedRef[] = [];
|
||||
for (const ref of deferred) {
|
||||
const match = this.gateLanguage(matchDottedCallChain(ref, this.context), ref);
|
||||
// `::`-receiver languages (Rust) split on `::` (matchScopedCallChain);
|
||||
// dotted-receiver languages on `.` (matchDottedCallChain).
|
||||
const chainMatch = SCOPED_CHAIN_LANGUAGES.has(ref.language)
|
||||
? matchScopedCallChain(ref, this.context)
|
||||
: matchDottedCallChain(ref, this.context);
|
||||
const match = this.gateLanguage(chainMatch, ref);
|
||||
if (match) resolved.push(match);
|
||||
}
|
||||
if (resolved.length === 0) return 0;
|
||||
|
||||
Reference in New Issue
Block a user