diff --git a/graphify/extractors/engine.py b/graphify/extractors/engine.py index 90c95cc49..723bef7ca 100644 --- a/graphify/extractors/engine.py +++ b/graphify/extractors/engine.py @@ -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"): diff --git a/tests/test_languages.py b/tests/test_languages.py index f610fbc40..f36ea7603 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -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 ─────────────────────────────────────────────────────────────────────