You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
java.py carries a full framework-semantics layer — Spring stereotypes to graph labels, HTTP mapping annotations, DI annotations, JPA @Entity/@Table to DbTable nodes (#887, #843).
kotlin.py is one of the largest language extractors in the repo (2194 lines), with genuinely sophisticated type inference — smart-cast tracking, scope-function receivers, collection element-type inference, typealias resolution. But it extracts no annotation at all:
$ grep -ic "annotation" src/codegraphcontext/tools/languages/kotlin.py
1 # and that one hit is the substring inside "_strip_type_modifiers"
So Java gets Spring awareness, and Kotlin — whose largest ecosystem is Android — gets none. Every @Composable, @HiltViewModel, @Inject, @Preview, @Entity, @Dao and @Test is invisible to the graph.
Why it's cheap
decorators STRING[] is already declared for Function and Class (database_embedded_kuzu.py:177-178), already in the property allow-list (:823-824), and three consumers already read it:
Consumer
Location
Dead-code analysis (exclude_decorated_with)
tools/code_finder.py:807-820
Inheritance resolution
indexing/resolution/inheritance.py:370-374
SCIP pipeline
indexing/scip_pipeline.py:136
Ten extractors populate the field; eleven do not — including java.py, which already parses annotations for labels and http_method but never writes them to decorators. Kotlin is not unique in lacking it. The reason to start there is that it is one of the biggest extractors in the repo and its dominant ecosystem is annotation-driven throughout.
The write path is generic (row = dict(item) → _sanitize_props), so populating decorators for Kotlin needs no schema change and no writer change.
Concrete impact today
cgc analyze dead-code is effectively unusable on an Android codebase. Almost nothing in an Android app is called by another Kotlin function — lifecycle methods are invoked by the framework, components are declared in the manifest, dependencies come from Hilt, composables are called by the Compose runtime, @Preview only by tooling, Room @Dao implementations are generated at build time, and @Test methods by the runner. Without annotations there is no way to express those exclusions, so the tool reports thousands of false positives.
Proposed staging
Sized so the no-schema-change slices can land first:
PR
Content
Schema change?
1a
Populate decorators for Kotlin functions and classes
Hilt @Binds / @Provides edges (relates to #846, #842)
Maybe (rel pairs)
4
is_composable + PREVIEWS edges
Yes
5
Gradle module path identity (:feature:symptoms — gradle.py:47 collapses it to the leaf name, so two impl modules collide on the Kuzu PRIMARY KEY)
No
6
Remove the shadowed add_file_to_graph (see below)
No
7
Android resources + manifest graph
Yes, large
PR 1a gates 1b, 2, 3 and 4 — they all need annotations to exist first.
I have 1a ready as a small, self-contained starting point — ~31 lines in kotlin.py, no schema change, and it immediately feeds the three existing consumers. I'll link the PR below.
Which of these slices would you like, and in what order? Happy to stop at 1a if you'd rather discuss the direction first.
Three unrelated pre-existing bugs found while investigating
Reporting rather than fixing — say the word if you'd like separate issues.
Kotlin object declarations with a single-line body are never indexed.object A { } is captured and object A {\n fun x() = 1\n} is captured, but object A { fun x() = 1 } yields zero captures — the grammar misparses it into an infix_expression with an object_literal and a lambda_literal, so KOTLIN_QUERIES["classes"]'s (object_declaration (type_identifier) @name) pattern (kotlin.py:17) never matches. The sibling class_declaration pattern handles bodies fine.
The writer stores every empty list as [""].writer.py:405 — b[k] = [str(x) for x in v] if v else [""] (and :409) — so a function with decorators: [] persists as [""], not []. Affects every language and every list-valued property, args included. It does not break find_dead_code ("" CONTAINS 'Preview' is false, so un-annotated functions are correctly retained), but [""] isn't what any caller means by "none".
Environment note: on macOS, three tests in tests/unit/parsers/test_kotlin_parser.py::TestKotlinFunctionCallResolution fail on a clean checkout because tempfile returns /var/... while the parser resolves /private/var/.... Unrelated to the above; mentioning it so it isn't mistaken for a regression in the PR.
The asymmetry
java.pycarries a full framework-semantics layer — Spring stereotypes to graph labels, HTTP mapping annotations, DI annotations, JPA@Entity/@TabletoDbTablenodes (#887, #843).kotlin.pyis one of the largest language extractors in the repo (2194 lines), with genuinely sophisticated type inference — smart-cast tracking, scope-function receivers, collection element-type inference, typealias resolution. But it extracts no annotation at all:So Java gets Spring awareness, and Kotlin — whose largest ecosystem is Android — gets none. Every
@Composable,@HiltViewModel,@Inject,@Preview,@Entity,@Daoand@Testis invisible to the graph.Why it's cheap
decorators STRING[]is already declared forFunctionandClass(database_embedded_kuzu.py:177-178), already in the property allow-list (:823-824), and three consumers already read it:exclude_decorated_with)tools/code_finder.py:807-820indexing/resolution/inheritance.py:370-374indexing/scip_pipeline.py:136Ten extractors populate the field; eleven do not — including
java.py, which already parses annotations for labels andhttp_methodbut never writes them todecorators. Kotlin is not unique in lacking it. The reason to start there is that it is one of the biggest extractors in the repo and its dominant ecosystem is annotation-driven throughout.The write path is generic (
row = dict(item)→_sanitize_props), so populatingdecoratorsfor Kotlin needs no schema change and no writer change.Concrete impact today
cgc analyze dead-codeis effectively unusable on an Android codebase. Almost nothing in an Android app is called by another Kotlin function — lifecycle methods are invoked by the framework, components are declared in the manifest, dependencies come from Hilt, composables are called by the Compose runtime,@Previewonly by tooling, Room@Daoimplementations are generated at build time, and@Testmethods by the runner. Without annotations there is no way to express those exclusions, so the tool reports thousands of false positives.Proposed staging
Sized so the no-schema-change slices can land first:
decoratorsfor Kotlin functions and classesvisibility+modifiers;Interface/Objectdecorator columns@Binds/@Providesedges (relates to #846, #842)is_composable+PREVIEWSedges:feature:symptoms—gradle.py:47collapses it to the leaf name, so twoimplmodules collide on the Kuzu PRIMARY KEY)add_file_to_graph(see below)PR 1a gates 1b, 2, 3 and 4 — they all need annotations to exist first.
I have 1a ready as a small, self-contained starting point — ~31 lines in
kotlin.py, no schema change, and it immediately feeds the three existing consumers. I'll link the PR below.Which of these slices would you like, and in what order? Happy to stop at 1a if you'd rather discuss the direction first.
Three unrelated pre-existing bugs found while investigating
Reporting rather than fixing — say the word if you'd like separate issues.
Kotlin
objectdeclarations with a single-line body are never indexed.object A { }is captured andobject A {\n fun x() = 1\n}is captured, butobject A { fun x() = 1 }yields zero captures — the grammar misparses it into aninfix_expressionwith anobject_literaland alambda_literal, soKOTLIN_QUERIES["classes"]'s(object_declaration (type_identifier) @name)pattern (kotlin.py:17) never matches. The siblingclass_declarationpattern handles bodies fine.graph_builder.pydefinesadd_file_to_graphtwice — line 203 and line 768. The second wins, so lines 203-~750 are dead code, and itsitem_mappingslist has drifted from the live one inindexing/persistence/writer.py:290-313(the live one handlesobjects,mixins,extensions,modules,enum_members; the dead one handles none of them). Reading the dead copy gives a wrong picture of which node types reach the graph. Possibly belongs with bug(indexer): parser audit roundup — context attribution, wrong Module names, missing declarations and duplicate call records across 12 languages #1538.The writer stores every empty list as
[""].writer.py:405—b[k] = [str(x) for x in v] if v else [""](and:409) — so a function withdecorators: []persists as[""], not[]. Affects every language and every list-valued property,argsincluded. It does not breakfind_dead_code("" CONTAINS 'Preview'is false, so un-annotated functions are correctly retained), but[""]isn't what any caller means by "none".Environment note: on macOS, three tests in
tests/unit/parsers/test_kotlin_parser.py::TestKotlinFunctionCallResolutionfail on a clean checkout becausetempfilereturns/var/...while the parser resolves/private/var/.... Unrelated to the above; mentioning it so it isn't mistaken for a regression in the PR.