fix(kotlin): read a function's signature positionally (#1495) (#1807)

Land upstream PR #1687 by danusha2345 (fix commit 6e9bbb26), using the
PR tip implementation with only a Rust doc-comment placement cleanup.

Read parameter lists and return types positionally in the wasm extractor
and native kernel in lockstep, preserving verbatim signature text.

Verified on Linux x64 with Node 22.19.0: reproduced three undefined
signatures in both backends before the fix, then confirmed all three
expected signatures and exact wasm/kernel parity after rebuilding
TypeScript and the linux-x64 kernel. All 31 focused tests pass: 15 Kotlin
extraction, 6 Kotlin parity, and 10 kernel scaffold checks, with
CODEGRAPH_KERNEL_EXPECT=1 for the native suites.

Add the upstream #1495 changelog bullet while preserving all other
Unreleased entries. Keep EXTRACTION_VERSION unchanged for this bug fix.

Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
Colby Mchenry
2026-09-08 19:06:39 -05:00
committed by GitHub
co-authored by Colby McHenry
parent 799f8b2a45
commit 748311feff
3 changed files with 57 additions and 6 deletions
+35 -2
View File
@@ -549,6 +549,39 @@ impl<'t> Walker<'t> {
.any(|c| c.kind() == "modifiers" && self.text(c).contains("suspend"))
}
/// `(params): ReturnType` — the positional read TreeSitterExtractor's
/// kotlin getSignature does (#1495): the `function_value_parameters` child,
/// then the type node that follows it before the body. Verbatim source text,
/// so it round-trips through parity byte-for-byte.
fn signature_of(&self, node: Node) -> Option<String> {
let mut params: Option<Node> = None;
let mut return_type: Option<Node> = None;
for i in 0..node.named_child_count() {
let Some(child) = node.named_child(i) else { continue };
if child.kind() == "function_value_parameters" {
params = Some(child);
continue;
}
if params.is_none() {
continue;
}
if matches!(child.kind(), "function_body" | "type_constraints") {
break;
}
if matches!(child.kind(), "user_type" | "nullable_type" | "function_type") {
return_type = Some(child);
break;
}
}
let params = params?;
let mut sig = self.text(params).to_string();
if let Some(rt) = return_type {
sig.push_str(": ");
sig.push_str(self.text(rt));
}
Some(sig)
}
/// extractKotlinReturnType — positional: the first user_type/nullable_type
/// AFTER function_value_parameters; function_body/type_constraints first →
/// None; Unit/Nothing → None; `: T` generic params leak (preserve).
@@ -918,7 +951,7 @@ impl<'t> Walker<'t> {
}
let extra = Extra {
docstring: preceding_docstring(node, self.src),
signature: None, // dead hook (zero fields)
signature: self.signature_of(node),
visibility: Some(self.visibility_of(node)),
is_async: Some(self.is_async(node)),
is_static: Some(false), // kotlin isStatic is always false
@@ -943,7 +976,7 @@ impl<'t> Walker<'t> {
let qualified_override = receiver.as_ref().map(|r| format!("{r}::{name}"));
let extra = Extra {
docstring: preceding_docstring(node, self.src),
signature: None,
signature: self.signature_of(node),
visibility: Some(self.visibility_of(node)),
is_async: Some(self.is_async(node)),
is_static: Some(false),