fix(scala): extract Scala 3 enum definitions, their cases and methods - #3937
rajatnagda45 wants to merge 1 commit into
Conversation
`enum_definition` was missing from the Scala class_types, so a Scala 3 `enum` produced no nodes at all: the enum type, its cases, and any methods declared in its body all vanished from the graph, and references to the enum resolved to nothing. Add `enum_definition` to the Scala class_types and `enum_body` to the body fallbacks so the type gets a node and its body is walked, and add `_scala_extra_walk` to emit a node plus a `case_of` edge per case. Cases nest under an `enum_case_definitions` wrapper as `simple_enum_case` (`case Red`) or `full_enum_case` (`case Add(x: Int)`); both carry the case name as an `identifier` child. This is the Scala parity of Java's enum_constant (Graphify-Labs#1719), Kotlin's enum_entry (Graphify-Labs#1738), and Swift. Case constructor parameter types and per-case `extends` arguments are not linked; the case node and its case_of edge are the fix for the dropped members.
|
Thanks for the pull request, @rajatnagda45. A maintainer will review it soon. Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions. A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic. |
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds Scala 3 enum support: enum_definition is now a class-like container, and the body walk reaches enum_body, so enums and their methods show up in the graph instead of disappearing entirely. _scala_extra_walk emits a node for each simple_enum_case and full_enum_case, with a case_of edge back to the enum, matching Java, Kotlin and Swift. Case constructor parameter types and per-case extends aren't linked, and when two cases differ only by letter case, the first declaration keeps the shared id.
No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2800 functions depend on the 1093 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 719 callers, 48 callees - new:
_rebuild_code()— 147 callers, 56 callees - new:
_extract_generic()— 18 callers, 29 callees - new:
extract_js()— 87 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
main()— 98 callers, 3 callees - new:
dispatch_command()— 2 callers, 126 callees - new:
extract_objc()— 27 callers, 9 callees - …and 45 more — each is listed as a finding
Verification — 2800 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2620 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
131 of 308 test file(s) selected (43%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_astro_import_ids.py— impacttests/test_blade_extractor.py— impacttests/test_build.py— impacttests/test_builtin_global_type_refs.py— impacttests/test_case_sensitive_resolution.py— impacttests/test_cjs_module_extension.py— impacttests/test_cobol_extractor.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_cpp_objc_cross_file_calls.py— impacttests/test_cross_extension_reexport_self_cycle.py— impacttests/test_cross_language_call_resolution.py— impacttests/test_cross_repo_external_call_guards.py— impacttests/test_cross_repo_member_calls.py— impacttests/test_csharp_call_site_generic_args.py— impacttests/test_csharp_enum_members.py— impacttests/test_csharp_field_generic_args.py— impacttests/test_csharp_generic_callsites.py— impacttests/test_csharp_interface_dispatch.py— impacttests/test_csharp_member_calls.py— impacttests/test_csharp_member_nodes.py— impacttests/test_csharp_object_creation.py— impacttests/test_csharp_partial_classes.py— impacttests/test_csharp_tuple_type_refs.py— impacttests/test_csharp_type_resolution.py— impacttests/test_definition_file_portability.py— impacttests/test_detect.py— impacttests/test_dotnet.py— impacttests/test_duplicate_annotation_edges.py— impacttests/test_elixir_import_resolution.py— impacttests/test_erlang_extractor.py— impacttests/test_extract.py— impacttests/test_extract_cache_location.py— impacttests/test_extract_php_closures.py— impacttests/test_file_label_disambiguation.py— impacttests/test_file_node_id_spec.py— impacttests/test_forwarding_review_findings.py— impacttests/test_go_builtin_call_targets.py— impacttests/test_go_import_repoint.py— impacttests/test_go_interface_methods.py— impacttests/test_go_qualified_resolution.py— impacttests/test_import_extension_resolution.py— impacttests/test_import_self_loops.py— impacttests/test_imported_export_forwarding.py— impacttests/test_incremental.py— impacttests/test_indirect_call_arrow_single_param_shadow.py— impacttests/test_indirect_call_block_scoped_shadow.py— impacttests/test_indirect_call_catch_binding_shadow.py— impacttests/test_indirect_call_external_import_shadow.py— impacttests/test_indirect_call_for_of_binding_shadow.py— impact- … and 81 more
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `config` is annotated `LanguageConfig` — outside the synthesizable primitive/collection set
· 53 more finding(s) on lines outside this diff (see the check run).
|
Shipped in v0.9.73 (now on PyPI) via an authorship-preserving cherry-pick, so your commit keeps contributor-graph credit. Thanks @rajatnagda45! Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.73 |
Problem
A Scala 3
enumproduces no nodes at all.enum_definitionwas missing from the Scalaclass_types, so the enum type, its cases, and any methods declared in its body all vanish from the graph, and a reference to the enum resolves to nothing.Before this change the whole graph for that file is just the file node —
Color,Op, every case, andrunare all absent. Every other language with enums (Java'senum_constant#1719, Kotlin'senum_entry#1738, Swift, TypeScript) emits a node per member with acase_ofedge; Scala was the odd one out.Implementation
enum_definitionto_SCALA_CONFIG.class_typesso the enum gets a node and the class heritage/body machinery runs for it.enum_bodyto_SCALA_CONFIG.body_fallback_child_types— an enum wraps its members inenum_body, nottemplate_body, so the body walk needs it to reach the enum's methods and cases._scala_extra_walk, dispatched fortree_sitter_scala, to emit a node plus acase_ofedge per case. Cases nest under anenum_case_definitionswrapper assimple_enum_case(case Red) orfull_enum_case(case Add(x: Int)); both carry the case name as anidentifierchild. This mirrors the existing Java/Kotlin/Swift enum-member handlers.Verification
test_scala_enum_definition_cases_and_methods— fails on the pre-fix code (noColor/Op/case nodes), passes after.uv run pytest tests/ -q— 6100 passed, 15 skipped.uv run ruff check .— clean.uv run pyright— no new errors introduced (error count unchanged against the pre-existing baseline).Limitations
Case constructor parameter types (
case Add(x: Int)) and per-caseextendsarguments are not linked; the case node and itscase_ofedge are the fix for the dropped members.