fix(rust): a call through a trait-bounded generic, like Key::get(…) with Key: MapKey, reaches the bound trait's method (#2724)

A Rust call through a generic type parameter is a call of the method its
bounds bring in: protobuf's `Key::get(…)` in `fn map_get<Key: MapKey>`,
with `trait MapKey: … + FfiMapKey`, is `FfiMapKey::get`. No strategy
read the bounds for such a path. These calls linked only when a
qualified name happened to end with the written text, which the scope
boundary of the previous fix rightly stopped, and a parameter bounded
only by an outside trait (`T::default()` under `T: Default`) could reach
a project type's unrelated `default` by name.

matchRustGenericPath, right after the `Self::x` branch of
matchMethodCall, reads the bounds the function and the impl or trait
around the call put on the parameter (in `<…>` or a `where` clause) and
resolves the method among those traits and the traits they extend,
through the helpers typed receivers already use. Outside-only bounds,
markers, and two traits declaring the method give no edge. The ref is
recorded as typed, so a trait method's missing `pub` doesn't hide it
from another module.

Measured on main ac9f96f8: protobuf +47, tokio +43, axum +14, clap +10,
serde +7, nothing removed; ripgrep and eight controls in other languages
are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-21 02:25:32 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent ac9f96f8a3
commit f6d8f19b29
3 changed files with 208 additions and 0 deletions
+34
View File
@@ -9243,6 +9243,35 @@ function matchRustSelfPath(name: string, ref: UnresolvedRef, context: Resolution
return { original: ref, targetNodeId: target.id, confidence: target.filePath === ref.filePath ? 0.9 : 0.8, resolvedBy: 'qualified-name' };
}
/**
* A Rust call through a generic type parameter of the function or `impl`
* around it — `Key::get(…)` in protobuf's `fn map_get<Key: MapKey>`, with
* `trait MapKey: FfiMapKey` — is to the method one of the traits its bounds
* name declares, or one of the traits they extend: `FfiMapKey::get`. Bounds
* only from outside the project (`T: Default`) name no project method, nor do
* two traits that both declare it: null. Undefined when `name` is no generic
* parameter there.
*/
function matchRustGenericPath(name: string, method: string, ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null | undefined {
const scopes = rustScopesOf(ref.filePath, context);
if (!scopes) return undefined;
const written: RustWrittenType = { text: name, file: ref.filePath, at: (scopes.lineStarts[ref.line - 1] ?? 0) + Math.max(0, ref.column) };
const bounds = rustGenericBounds(name, written, context);
if (bounds === undefined) return undefined;
const traits = bounds.filter((b) => !/^['?]/.test(b.trim()) && !RUST_MARKER_TRAITS.has(rustPathHead(b)?.name ?? ''));
if (traits.length === 0) return null;
const result = rustTraitsMethodCall(traits, written, method, ref, context);
if (result === 'unread') return null;
// (The bound names the trait, so its method is reached as a typed call is:
// its missing `pub` doesn't hide it from another module.)
if (result) {
let typed = RUST_TYPED_REFS.get(context);
if (!typed) RUST_TYPED_REFS.set(context, (typed = new WeakSet()));
typed.add(ref);
}
return result;
}
/**
* Whether `candidate` is declared directly in an inline `mod <name> { … }` of
* its own file — the only way a path through `name` reaches it, since an
@@ -18745,6 +18774,11 @@ export function matchMethodCall(
const own = matchRustSelfPath(methodName!, ref, context);
if (own !== undefined) return own;
}
// A Rust `T::x(…)` through a generic parameter: the method its bounds' traits declare (matchRustGenericPath).
if (ref.language === 'rust' && match === colonMatch && /^[A-Z]\w*$/.test(objectOrClass!) && objectOrClass !== 'Self') {
const bounded = matchRustGenericPath(objectOrClass!, methodName!, ref, context);
if (bounded !== undefined) return bounded;
}
// Strategy 1: Direct class name match (existing logic). When the receiver
// names a class that exists in several files (`Logger.log()` / `Logger::log()`