mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 13:53:01 +08:00
* fix(resolution): stop expression-receiver calls from binding to unrelated methods A method called on an expression receiver reached the resolver as the bare method name, and exact-match bound it to any project method of that name: `(await list()).map(...)` in TypeScript onto a UI adapter class's `map` (0.9), and `v.iter().map(...)` in Rust onto the same TypeScript method across languages (0.5). TS/JS extraction (mirrored in the kernel): look through receiver wrappers that keep the same object — parentheses, `!`, `as`/`satisfies`, `<T>x`, `await` (including the grammar's `(a && b)!` parse of `a && b!`) — so `x!.m()` is `x.m` and `(await f()).m()` is `f().m`; any other receiver with no static type (`(a ?? b).m()`, `f().list.m()`, `arr[0].m()`) emits nothing instead of the bare name. `this`/`super` (and chains rooted at them), `window.*` chains and `new C().m()` keep their existing bare form. Resolution: a bare-named call may cross a language family only through the C ABI (a C/C++ symbol, or a free function a C/Objective-C/Swift caller reaches, e.g. a cgo `//export`). A class member of another family, or a function/class/variable outside that pair, is rejected as the candidate a strategy commits to (exact-match single/best and fuzzy), never filtered out of the pool, so no lone survivor is manufactured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(resolution): reject unrelated cross-language matches (#1986) Name-based calls, constructors, and inheritance could select unrelated foreign-language symbols. Apply a shared interoperability policy to selected results without manufacturing replacement matches, including JSX/Vue synthesis. Preserve framework exports and ABI-backed calls, building on PR #1944's TS/Rust expression-receiver fixes. Validate with both repro scripts, 529 targeted tests, kernel/wasm parity, and clean TypeScript checks. Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1179,7 +1179,10 @@ impl<'t> Walker<'t> {
|
||||
.child_by_field_name("object")
|
||||
.or_else(|| func.child_by_field_name("operand"))
|
||||
.or_else(|| func.child_by_field_name("argument"))
|
||||
.or_else(|| func.named_child(0));
|
||||
.or_else(|| func.named_child(0))
|
||||
// Look through `(x)`, `x!`, `(x as T)`, `(await f())`
|
||||
// (mirrors peelTsJsReceiver).
|
||||
.map(peel_receiver);
|
||||
// Literal receivers call builtins, never project symbols (#1230).
|
||||
if let Some(r) = receiver {
|
||||
if is_literal_receiver(r.kind()) {
|
||||
@@ -1214,6 +1217,12 @@ impl<'t> Walker<'t> {
|
||||
// TreeSitterExtractor.extractCall.
|
||||
let Some(inner) = self.plain_inner_callee(r) else { return };
|
||||
callee_name = format!("{inner}().{method_name}");
|
||||
} else if receiver.is_some_and(|r| !keeps_bare_receiver(r, self.src)) {
|
||||
// An expression receiver with no static type
|
||||
// (`(a ?? b).map()`, `f().list.map()`): emit nothing
|
||||
// rather than the bare method name. Mirrors
|
||||
// TreeSitterExtractor.extractCall.
|
||||
return;
|
||||
} else {
|
||||
callee_name = method_name.to_string();
|
||||
}
|
||||
@@ -1508,3 +1517,59 @@ fn collapse_ws(s: &str) -> String {
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// TS/JS wrappers that keep a member call's receiver the same object
|
||||
/// (mirrors TS_JS_TRANSPARENT_RECEIVER_TYPES in tree-sitter.ts).
|
||||
fn is_transparent_receiver(kind: &str) -> bool {
|
||||
matches!(
|
||||
kind,
|
||||
"parenthesized_expression"
|
||||
| "non_null_expression"
|
||||
| "as_expression"
|
||||
| "satisfies_expression"
|
||||
| "type_assertion"
|
||||
| "await_expression"
|
||||
)
|
||||
}
|
||||
|
||||
/// Strip transparent wrappers off a receiver (mirrors peelTsJsReceiver),
|
||||
/// including the grammar's `(a && b)!` parse of `a && b!`.
|
||||
fn peel_receiver(node: Node<'_>) -> Node<'_> {
|
||||
let mut cur = node;
|
||||
while is_transparent_receiver(cur.kind()) {
|
||||
let mut inner = if cur.kind() == "type_assertion" {
|
||||
cur.named_child(cur.named_child_count().saturating_sub(1))
|
||||
} else {
|
||||
cur.named_child(0)
|
||||
};
|
||||
if cur.kind() == "non_null_expression" {
|
||||
while let Some(b) = inner.filter(|n| n.kind() == "binary_expression") {
|
||||
inner = b.child_by_field_name("right");
|
||||
}
|
||||
}
|
||||
match inner {
|
||||
Some(i) => cur = i,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
cur
|
||||
}
|
||||
|
||||
/// `this` / `super`, a member chain rooted at either or at `window`, or
|
||||
/// `new C()` still collapse to the bare method name (mirrors
|
||||
/// keepsBareTsJsReceiver).
|
||||
fn keeps_bare_receiver(node: Node<'_>, src: &str) -> bool {
|
||||
let mut cur = node;
|
||||
while matches!(cur.kind(), "member_expression" | "subscript_expression") {
|
||||
match cur.child_by_field_name("object") {
|
||||
Some(object) => cur = peel_receiver(object),
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
match cur.kind() {
|
||||
"this" | "super" => true,
|
||||
"identifier" => &src[cur.byte_range()] == "window",
|
||||
"new_expression" => cur.id() == node.id(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user