fix(kotlin): record infix calls as calls (#2174)

`Users.id eq id1`, `single { … } bind I::class`: an infix_expression was
never extracted, so a project's infix DSL had no callers (Exposed's
`eq` > 1,000). Extract it as a call of the middle simple_identifier —
`lhs.fn` when the left operand is a plain name, else bare — in the TS
walker and the kernel (kernel-parity 0 diffs on okio/okhttp/Exposed).

A standard infix name on an expression receiver is gated like a chain
link (`alias(x) apply false` in a build script); a project's bitwise
extension on a number type (okio's `Byte.and`) is never chosen, typed or
guessed, since the stdlib's Int/Long members take `x and 0xff`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-09-30 15:52:23 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 0b74e94ee7
commit c6491ff62d
5 changed files with 167 additions and 1 deletions
+26
View File
@@ -862,6 +862,8 @@ impl<'t> Walker<'t> {
self.extract_import(node);
} else if kind == "call_expression" {
self.extract_call(node);
} else if kind == "infix_expression" {
self.extract_infix_call(node);
}
// companion_object, anonymous_initializer, secondary_constructor,
// getter/setter siblings, file_annotation, object_literal, if/when at
@@ -890,6 +892,8 @@ impl<'t> Walker<'t> {
if kind == "call_expression" {
self.extract_call(node);
} else if kind == "infix_expression" {
self.extract_infix_call(node);
}
// (INSTANTIATION_KINDS has no kotlin members; extractBareCall absent.)
@@ -1149,6 +1153,28 @@ impl<'t> Walker<'t> {
/// extractCall — the kotlin paths: navigation member branch (+ the #750
/// re-encode) and the raw-text else (paren-then-lambda / glued-invoke
/// garbage preserved).
/// extractKotlinInfixCall — `Users.id eq id1` / `a to b`: a call of the
/// middle simple_identifier, `lhs.fn` when the left operand is a plain
/// name, else the bare name; literal left operands emit nothing.
fn extract_infix_call(&mut self, node: Node<'t>) {
if self.stack.is_empty() || node.named_child_count() != 3 {
return;
}
let (Some(lhs), Some(func)) = (node.named_child(0), node.named_child(1)) else { return };
if func.kind() != "simple_identifier" || is_literal_receiver(lhs.kind()) {
return;
}
let caller = self.top_row();
let name = self.text(func);
let receiver = if lhs.kind() == "simple_identifier" { self.text(lhs) } else { "" };
let callee = if !receiver.is_empty() && receiver != "this" && receiver != "super" {
format!("{receiver}.{name}")
} else {
name.to_string()
};
self.push_ref_at(caller, &callee, edge_kind_index("calls").unwrap(), node);
}
fn extract_call(&mut self, node: Node<'t>) {
if self.stack.is_empty() {
return;