mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-14 07:59:40 +00:00
fix(extract): drop Kotlin generic type parameters from references
A `class Box<T>` / `fun <R> map(...)` binds `T`/`R` as local names, not
corpus symbols, but `_kotlin_collect_type_refs` appended the bare identifiers
used in a signature or property type as type references and ran them through
the sourceless-stub path. Every generic class/function in the corpus then
collapsed its `T` onto one bare `t` node — a god node that pollutes god-node
rankings, since single-letter type parameters are not in the analyzer's
builtin-noise filter. C#, Java, TypeScript, and Go already exclude theirs.
Mirrors the C#/Java pattern: a new `_kotlin_type_parameters_in_scope` helper
walks up to every enclosing declaration's `type_parameters` list (the use site
is `type_arguments`, a distinct node, so an instantiation argument is never
collected), and `_kotlin_collect_type_refs` auto-computes and threads that skip
set. Genuine user types and the generic container head still reference.
(cherry picked from commit a9304a245c)
This commit is contained in:
1 parent
9aa9d03aa5
commit
6d64681bd9
2 files changed
+91
-9
No files matched your search
@@ -947,10 +947,45 @@ def _kotlin_annotation_class_literal_refs(
|
||||
return names
|
||||
|
||||
|
||||
def _kotlin_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]]) -> None:
|
||||
"""Walk a Kotlin type expression; append (name, role) tuples."""
|
||||
def _kotlin_type_parameters_in_scope(node, source: bytes) -> frozenset[str]:
|
||||
"""Return Kotlin type-parameter names visible from ``node``.
|
||||
|
||||
A `class Box<T>` or `fun <R> map(...)` binds `T`/`R` as local names, not
|
||||
corpus symbols. Walking up to every enclosing declaration's `type_parameters`
|
||||
list (the use site is `type_arguments`, a different node, so this never
|
||||
collects an instantiation argument) collects them for the scope skip set.
|
||||
"""
|
||||
names: set[str] = set()
|
||||
scope = node
|
||||
while scope is not None:
|
||||
for child in scope.children:
|
||||
if child.type != "type_parameters":
|
||||
continue
|
||||
for param in child.children:
|
||||
if param.type != "type_parameter":
|
||||
continue
|
||||
for sub in param.children:
|
||||
if sub.type in ("identifier", "type_identifier"):
|
||||
name = _read_text(sub, source)
|
||||
if name:
|
||||
names.add(name)
|
||||
break
|
||||
scope = scope.parent
|
||||
return frozenset(names)
|
||||
|
||||
|
||||
def _kotlin_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[str, str]],
|
||||
skip: frozenset[str] | None = None) -> None:
|
||||
"""Walk a Kotlin type expression; append (name, role) tuples.
|
||||
|
||||
Generic type parameters in scope (``skip``) are local bindings, not
|
||||
references; emitting one mints a sourceless stub (the bare `t` god node a
|
||||
`<T>` class or function would otherwise share corpus-wide).
|
||||
"""
|
||||
if node is None:
|
||||
return
|
||||
if skip is None:
|
||||
skip = _kotlin_type_parameters_in_scope(node, source)
|
||||
t = node.type
|
||||
if t in ("integral_literal", "boolean_literal"):
|
||||
return
|
||||
@@ -958,14 +993,14 @@ def _kotlin_collect_type_refs(node, source: bytes, generic: bool, out: list[tupl
|
||||
for c in node.children:
|
||||
if c.type in ("identifier", "type_identifier"):
|
||||
text = _read_text(c, source)
|
||||
if text and text not in _KOTLIN_BUILTIN_TYPES and text not in _JAVA_BUILTIN_TYPES:
|
||||
if text and text not in _KOTLIN_BUILTIN_TYPES and text not in _JAVA_BUILTIN_TYPES and text not in skip:
|
||||
out.append((text, "generic_arg" if generic else "type"))
|
||||
break
|
||||
if c.type == "simple_user_type":
|
||||
for sub in c.children:
|
||||
if sub.type in ("identifier", "type_identifier"):
|
||||
text = _read_text(sub, source)
|
||||
if text and text not in _KOTLIN_BUILTIN_TYPES and text not in _JAVA_BUILTIN_TYPES:
|
||||
if text and text not in _KOTLIN_BUILTIN_TYPES and text not in _JAVA_BUILTIN_TYPES and text not in skip:
|
||||
out.append((text, "generic_arg" if generic else "type"))
|
||||
break
|
||||
break
|
||||
@@ -975,24 +1010,24 @@ def _kotlin_collect_type_refs(node, source: bytes, generic: bool, out: list[tupl
|
||||
if arg.type == "type_projection":
|
||||
for sub in arg.children:
|
||||
if sub.is_named:
|
||||
_kotlin_collect_type_refs(sub, source, True, out)
|
||||
_kotlin_collect_type_refs(sub, source, True, out, skip)
|
||||
elif arg.is_named:
|
||||
_kotlin_collect_type_refs(arg, source, True, out)
|
||||
_kotlin_collect_type_refs(arg, source, True, out, skip)
|
||||
return
|
||||
if t in ("identifier", "type_identifier"):
|
||||
text = _read_text(node, source)
|
||||
if text and text not in _KOTLIN_BUILTIN_TYPES and text not in _JAVA_BUILTIN_TYPES:
|
||||
if text and text not in _KOTLIN_BUILTIN_TYPES and text not in _JAVA_BUILTIN_TYPES and text not in skip:
|
||||
out.append((text, "generic_arg" if generic else "type"))
|
||||
return
|
||||
if t in ("nullable_type", "parenthesized_type", "type_reference"):
|
||||
for c in node.children:
|
||||
if c.is_named:
|
||||
_kotlin_collect_type_refs(c, source, generic, out)
|
||||
_kotlin_collect_type_refs(c, source, generic, out, skip)
|
||||
return
|
||||
if node.is_named:
|
||||
for c in node.children:
|
||||
if c.is_named:
|
||||
_kotlin_collect_type_refs(c, source, generic, out)
|
||||
_kotlin_collect_type_refs(c, source, generic, out, skip)
|
||||
|
||||
def _kotlin_property_type_node(property_node):
|
||||
"""Find the user_type node within a Kotlin property_declaration."""
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Kotlin generic type parameters must not mint sourceless god nodes.
|
||||
|
||||
A `class Box<T>` / `fun <R> map(...)` binds `T`/`R` as local names, not corpus
|
||||
symbols. The Kotlin type-ref pass appended the bare `T`/`R` identifiers used in
|
||||
a signature or property type as type references and ran them through the
|
||||
sourceless-stub path, so every generic class/function in the corpus collapsed
|
||||
its `T` onto one bare `t` node — a god node (single-letter type params are not
|
||||
in the analyzer's builtin-noise filter). C#, Java, TypeScript, and Go already
|
||||
exclude their type parameters; Kotlin now matches.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from graphify.extract import extract
|
||||
|
||||
|
||||
def _sourceless(r):
|
||||
return sorted({(n["id"], n.get("label")) for n in r["nodes"] if not n.get("source_file")})
|
||||
|
||||
|
||||
def test_generic_class_and_method_type_params_mint_no_stub(tmp_path):
|
||||
(tmp_path / "K.kt").write_text(
|
||||
"class Box<T>(val v: T) {\n"
|
||||
" fun get(): T? = v\n"
|
||||
" fun <R> map(f: (T) -> R): R? = null\n"
|
||||
"}\n"
|
||||
)
|
||||
r = extract([tmp_path / "K.kt"], cache_root=tmp_path, parallel=False)
|
||||
assert _sourceless(r) == []
|
||||
assert not any(e.get("target") in ("t", "r") for e in r["edges"])
|
||||
|
||||
|
||||
def test_user_and_container_types_still_referenced(tmp_path):
|
||||
(tmp_path / "K.kt").write_text(
|
||||
"class User(val name: String)\n"
|
||||
"class Box<T>(val v: T)\n"
|
||||
"fun wrap(u: User): Box<User> = Box(u)\n"
|
||||
)
|
||||
r = extract([tmp_path / "K.kt"], cache_root=tmp_path, parallel=False)
|
||||
targets = {
|
||||
rn["label"]
|
||||
for e in r["edges"] if e.get("relation") == "references"
|
||||
for rn in r["nodes"] if rn["id"] == e["target"]
|
||||
}
|
||||
assert "User" in targets
|
||||
assert "Box" in targets
|
||||
assert "T" not in targets
|
||||
assert _sourceless(r) == []
|
||||
Reference in new issue
Block a user