fix(extract): filter Kotlin builtin/stdlib types from references graph

_kotlin_collect_type_refs had no equivalent of _JAVA_BUILTIN_TYPES /
_PYTHON_ANNOTATION_NOISE / _GO_PREDECLARED_TYPES, so every Boolean/String/
Int/List/... type annotation in a .kt file became a real graph node/edge.
Unrelated files sharing only these language-level types were getting
merged into the same community by cluster-only.

Add _KOTLIN_BUILTIN_TYPES (kotlin.* scalars, collections, throwables -
deliberately not Android/JVM framework types like Context/View/Bundle,
matching how _JAVA_BUILTIN_TYPES doesn't filter framework types either)
and check it plus _JAVA_BUILTIN_TYPES (Kotlin freely references
java.util.Date/UUID/etc) at the three call sites that previously appended
unconditionally.

Deliberately excludes "Result" from the filter list even though
kotlin.Result exists - it collides with the very common user-defined
sealed-class name (confirmed against this repo's own sample.kt fixture,
which defines its own Result<T>).

Verified on a ~400-file Android/Kotlin project: 3693->3239 nodes (-12%),
6481->5221 edges (-19%), with a previously merged 5-file grab-bag
community cleanly splitting out an unrelated screen-dimension utility.
This commit is contained in:
kebwlmbhee
2026-07-14 23:32:03 +01:00
committed by safishamsi
parent 8361a81f73
commit 5c0a04c991
2 changed files with 53 additions and 3 deletions
+32 -3
View File
@@ -607,6 +607,35 @@ def _php_method_return_type_node(method_node):
return c
return None
# Kotlin stdlib scalar/collection/core types that appear constantly as type
# annotations but carry no useful semantic meaning as graph nodes (mirrors
# _JAVA_BUILTIN_TYPES / _PYTHON_ANNOTATION_NOISE / _GO_PREDECLARED_TYPES).
# Kotlin compiles to the JVM and freely references java.* types too, so this
# is combined with _JAVA_BUILTIN_TYPES at the call site rather than duplicated.
_KOTLIN_BUILTIN_TYPES = frozenset({
# kotlin — scalars & core
"Any", "Unit", "Nothing", "Boolean", "Byte", "Short", "Int", "Long",
"Float", "Double", "Char", "String", "CharSequence", "Number",
"Comparable", "Enum", "Annotation", "Pair", "Triple", "Lazy",
"Function",
# kotlin — throwables
"Throwable", "Exception", "RuntimeException", "Error",
"IllegalArgumentException", "IllegalStateException", "NullPointerException",
"IndexOutOfBoundsException", "ClassCastException", "NumberFormatException",
"ArithmeticException", "UnsupportedOperationException",
"NoSuchElementException", "ConcurrentModificationException",
"StackOverflowError", "OutOfMemoryError", "AssertionError",
"InterruptedException",
# kotlin.collections
"Array", "List", "MutableList", "ArrayList", "Set", "MutableSet",
"HashSet", "LinkedHashSet", "Map", "MutableMap", "HashMap",
"LinkedHashMap", "Collection", "MutableCollection", "Iterable",
"MutableIterable", "Iterator", "MutableIterator", "ListIterator",
"MutableListIterator", "Sequence", "Comparator",
# kotlin.text
"Regex", "MatchResult", "StringBuilder",
})
def _kotlin_user_type_name(user_type_node, source: bytes) -> str | None:
"""Return the head identifier text from a Kotlin user_type node (without generics)."""
if user_type_node is None:
@@ -636,14 +665,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:
if text and text not in _KOTLIN_BUILTIN_TYPES and text not in _JAVA_BUILTIN_TYPES:
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:
if text and text not in _KOTLIN_BUILTIN_TYPES and text not in _JAVA_BUILTIN_TYPES:
out.append((text, "generic_arg" if generic else "type"))
break
break
@@ -659,7 +688,7 @@ def _kotlin_collect_type_refs(node, source: bytes, generic: bool, out: list[tupl
return
if t in ("identifier", "type_identifier"):
text = _read_text(node, source)
if text:
if text and text not in _KOTLIN_BUILTIN_TYPES and text not in _JAVA_BUILTIN_TYPES:
out.append((text, "generic_arg" if generic else "type"))
return
if t in ("nullable_type", "parenthesized_type", "type_reference"):
+21
View File
@@ -656,6 +656,27 @@ def test_kotlin_parameter_return_generic_and_field_contexts():
assert ("run", "DataProcessor") in _edge_labels(r, "references", "generic_arg")
assert ("DataProcessor", "Result") in _edge_labels(r, "references", "field")
def test_kotlin_builtin_types_not_emitted_as_references():
# kotlin.* scalar/collection/core types used as parameter, return, or field
# types carry no useful graph meaning: they never resolve to a project node,
# so emitting `references` edges to them is pure noise (mirrors the Java
# _JAVA_BUILTIN_TYPES / Python _PYTHON_ANNOTATION_NOISE handling).
r = extract_kotlin(FIXTURES / "sample.kt")
ref_targets = {target for (_, target) in _edge_labels(r, "references")}
for builtin in ("String", "Int"):
assert builtin not in ref_targets, (
f"builtin type {builtin!r} should not be a references target"
)
def test_kotlin_user_types_still_emit_references():
# Guard against over-filtering: a user-defined class sharing its name with a
# common domain-modeling identifier (Result) must still resolve to a real
# edge - the builtin filter is a fixed name list, so it must stay narrow
# enough not to swallow common user-chosen names like a sealed-class "Result".
r = extract_kotlin(FIXTURES / "sample.kt")
assert ("DataProcessor", "Result") in _edge_labels(r, "references", "field")
assert ("run", "DataProcessor") in _edge_labels(r, "references", "parameter_type")
# ── Scala ─────────────────────────────────────────────────────────────────────