mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-11-14 16:52:09 +08:00
fix(kotlin): a call through a property or a receiver chain resolves on its declared type
Kotlin indexes a class property without a signature, and a primary-constructor property (class A(private val repo: Repo)) is no node at all, so a call through either (probe.close() on a lateinit var probe: HttpProbe) found no receiver type and fell to the name-only strategies: the interface's method, or any same-named method elsewhere. A call through a chain (engine.pump.drain()) was emitted as its bare method name, and a local bound to a qualified call (val lease = HardwareLock.tryBegin()) was untyped. tree-sitter-kotlin also predates fun interface: such a declaration parsed as a broken function and, with a doc comment after it, its error recovery swallowed the next declaration too. The declared type is now read from the declaration itself: a property's own lines, the class header for a constructor property, a callee's declared return type for a local bound to a call, an enum entry's enum, each segment of a receiver chain in turn (the chain is kept in the reference name, by the wasm extractor and the native kernel alike), a superclass's property, and an outer function's local inside an anonymous object. A receiver whose type the project does not declare (Regex, Properties, a JDK class) gets no edge instead of a name-only guess, unless the project extends that type. The extractor's preParse blanks the fun of a fun interface, offset-preserving, so the file parses cleanly in both paths. Validation: - kotlin-property-receiver.test.ts: 17 tests, 12 red on main, all green here; kernel-kotlin-parity.test.ts pins the fun interface fixture. - Kernel parity (kotlin), 0 diffs: okhttp 588/619 files byte-identical (main's kernel 578, the rest deferred to wasm on parse errors), mihon 891/940 (887), nowinandroid 340/350 (339): the fun interface files no longer defer. - A/B against main with #1942 (positions only: the extractor now keeps a chain in the reference name): okhttp +299/-257, 2,554 retargeted, 2,158 of them test calls moving from the deprecated okhttp3.mockwebserver.MockWebServer (a 0.65 name guess) to the mockwebserver3.MockWebServer each file imports; mihon +82/-118, 337 retargeted (Preference get/changes/set/register onto the declared Preference); nowinandroid -9, 23 retargeted (properties onto their concrete test doubles); SystemUI's Kotlin +1/-104, 7 retargeted; jextract, click and gin identical. The removed are name guesses on library types (okio sinks, collections' filter and clear, OkHttp bodies, MutableStateFlow.update, Kotlin's apply). 7 right edges are lost where a chain passes through a library wrapper (Lazy.value, StateFlow.value, Pair.first, a ViewBinding's view). - Cost (mihon, median of 3 interleaved indexes): CPU 25.8 s on main with #1942 vs 26.7 s, wall 8.3 vs 8.4 s. Original PR #1933 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Colby Mchenry
co-authored by
Claude Opus 5.5
parent
77ee3484be
commit
01e0ac3aa7
@@ -87,6 +87,28 @@ fn strip_js_ws(s: &str) -> String {
|
||||
s.chars().filter(|c| !is_js_space(*c)).collect()
|
||||
}
|
||||
|
||||
/// A receiver chain the resolver can type (TS `KOTLIN_RECEIVER_CHAIN`):
|
||||
/// whitespace stripped, `?.` read as `.`, then `this` or an identifier
|
||||
/// followed by one to three `.identifier` segments.
|
||||
fn kotlin_receiver_chain(text: &str) -> Option<String> {
|
||||
let chain = strip_js_ws(text).replace("?.", ".");
|
||||
let segments: Vec<&str> = chain.split('.').collect();
|
||||
if segments.len() < 2 || segments.len() > 4 {
|
||||
return None;
|
||||
}
|
||||
let ident = |s: &str| {
|
||||
let b = s.as_bytes();
|
||||
!b.is_empty()
|
||||
&& (b[0].is_ascii_alphabetic() || b[0] == b'_')
|
||||
&& b.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'_')
|
||||
};
|
||||
if segments.iter().all(|s| ident(s)) {
|
||||
Some(chain)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A property's CODE children: the named child right after the `=` token, a
|
||||
/// `property_delegate` (`by lazy { … }`), and an accessor the grammar nested
|
||||
/// under the declaration (`val x: Int get() = compute()` — written on ONE line;
|
||||
@@ -1264,9 +1286,17 @@ impl<'t> Walker<'t> {
|
||||
} else {
|
||||
method_name.to_string()
|
||||
};
|
||||
} else if let Some(chain) = receiver
|
||||
.filter(|r| r.kind() == "navigation_expression")
|
||||
.and_then(|r| kotlin_receiver_chain(self.text(r)))
|
||||
{
|
||||
// Receiver chain `a.b` / `this.a` / `a?.b`: kept for the
|
||||
// resolver to type through the properties' declared types.
|
||||
callee_name = format!("{chain}.{method_name}");
|
||||
} else {
|
||||
// this_expression / super_expression / 2-hop nav /
|
||||
// postfix `!!` / parenthesized → bare method name.
|
||||
// this_expression / super_expression / a longer or
|
||||
// non-identifier chain / postfix `!!` / parenthesized →
|
||||
// bare method name.
|
||||
callee_name = method_name.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user