mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-25 08:11:54 +00:00
fix(kotlin): extract typealias declarations so references don't mint sourceless stubs
A Kotlin `typealias` is a named type, but `type_alias` was in neither `class_types` nor `function_types`, so the declaration was dropped from the graph. A property or parameter typed as the alias (`var onClick: ClickHandler?`, `fun handler(h: ClickHandler)`) still resolved the name `ClickHandler` through `ensure_named_node`, which minted a *sourceless* stub and pointed the `references` edge at it — even though the alias is declared in the same file. That violates the provenance invariant: a real source-backed declaration must not lose to a sourceless god node (and a bare-id stub becomes a cross-file hub shared by every same-named reference in the corpus). Scala and TypeScript already model type aliases as nodes, so Kotlin was the inconsistent outlier. `type_alias` has an `identifier` name (covered by `name_fallback_child_types`) and no body, so adding it to the Kotlin `class_types` emits a clean empty type node (no phantom members) that the in-file references now bind to.
This commit is contained in:
1 parent
7a32f3324e
commit
d2ac22c621
2 files changed
+59
-1
No files matched your search
+8
-1
@@ -1535,8 +1535,15 @@ _CSHARP_CONFIG = LanguageConfig(
|
||||
)
|
||||
|
||||
_KOTLIN_CONFIG = LanguageConfig(
|
||||
# `type_alias` is `typealias ClickHandler = (Int) -> Unit`. Without it the
|
||||
# declaration was dropped, yet an in-file field/parameter typed as the alias
|
||||
# still resolved its name through `ensure_named_node` and minted a
|
||||
# *sourceless* stub — a real in-file type losing to a sourceless god node
|
||||
# (provenance). Scala and TypeScript already model type aliases as nodes; the
|
||||
# alias has an `identifier` name (covered by `name_fallback_child_types`) and
|
||||
# no body, so it emits a clean empty type node those references bind to.
|
||||
ts_module="tree_sitter_kotlin",
|
||||
class_types=frozenset({"class_declaration", "object_declaration"}),
|
||||
class_types=frozenset({"class_declaration", "object_declaration", "type_alias"}),
|
||||
function_types=frozenset({"function_declaration"}),
|
||||
# Grammar 1.1.0 (PyPI tree_sitter_kotlin) names the import node `import`;
|
||||
# older forks use `import_header`. Accept both (#2526).
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""A Kotlin `typealias` is a named type and must be extracted as a sourced node.
|
||||
|
||||
`typealias ClickHandler = (Int) -> Unit` parses as `type_alias`, which was in
|
||||
neither `class_types` nor `function_types`, so the declaration was dropped. But
|
||||
a property or parameter typed as the alias (`var onClick: ClickHandler?`,
|
||||
`fun handler(h: ClickHandler)`) still ran the name `ClickHandler` through
|
||||
`ensure_named_node`, which minted a *sourceless* stub and pointed the
|
||||
`references` edge at it — even though the alias is declared in the same file.
|
||||
That is a provenance violation (a real source-backed declaration losing to a
|
||||
sourceless god node). Scala and TypeScript already model type aliases as nodes;
|
||||
`type_alias` has an `identifier` name and no body, so adding it to the Kotlin
|
||||
`class_types` emits a clean empty type node those references bind to.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from graphify.extract import extract
|
||||
|
||||
|
||||
def _extract(tmp_path, body: str):
|
||||
(tmp_path / "m.kt").write_text(body)
|
||||
return extract([tmp_path / "m.kt"], cache_root=tmp_path, parallel=False)
|
||||
|
||||
|
||||
def _node(r, label):
|
||||
return next((n for n in r["nodes"] if n.get("label") == label), None)
|
||||
|
||||
|
||||
def test_typealias_is_a_sourced_node_not_a_sourceless_stub(tmp_path):
|
||||
r = _extract(
|
||||
tmp_path,
|
||||
"typealias ClickHandler = (Int) -> Unit\n"
|
||||
"class Button {\n"
|
||||
" var onClick: ClickHandler? = null\n"
|
||||
" fun handler(h: ClickHandler) { onClick = h }\n"
|
||||
"}\n",
|
||||
)
|
||||
alias = _node(r, "ClickHandler")
|
||||
assert alias is not None, "typealias declaration was dropped"
|
||||
assert alias.get("source_file"), (
|
||||
f"typealias ClickHandler is a sourceless god node: {alias}"
|
||||
)
|
||||
assert not any(
|
||||
(not n.get("source_file")) and n.get("label") == "ClickHandler"
|
||||
for n in r["nodes"]
|
||||
)
|
||||
refs_to_alias = {
|
||||
e["source"]
|
||||
for e in r["edges"]
|
||||
if e["relation"] == "references" and e["target"] == alias["id"]
|
||||
}
|
||||
assert refs_to_alias, "references to the typealias did not bind to it"
|
||||
Reference in new issue
Block a user