Skip to content

fix(java): attach enum body members to the enum, not the file - #3674

Closed
rajatnagda45 wants to merge 1 commit into
Graphify-Labs:v8from
rajatnagda45:fix/java-enum-body-members
Closed

rajatnagda45 wants to merge 1 commit into
Graphify-Labs:v8from
rajatnagda45:fix/java-enum-body-members

Conversation

@rajatnagda45

Copy link
Copy Markdown
Contributor

Problem

A Java enum wraps its fields, constructors and methods in an enum_body_declarations node, nested under enum_body after the constant list:

enum_declaration
└─ enum_body
   ├─ enum_constant  EARTH
   ├─ enum_constant  MARS
   └─ enum_body_declarations      ← the members live one level down
      ├─ field_declaration        mass
      ├─ constructor_declaration  Planet(...)
      └─ method_declaration       surfaceGravity()

The generic walker recurses into a class body keeping the enclosing type as the parent scope — but an unrecognised wrapper node resets that scope to None, because an unknown wrapper usually is a scope boundary. enum_body_declarations tripped exactly that reset, so for:

public enum Planet {
    EARTH(5.976e+24), MARS(6.421e+23);

    private final double mass;
    Planet(double mass) { this.mass = mass; }

    public double surfaceGravity() { return 6.67300E-11 * mass; }
}

Planet became a bare list of constants, surfaceGravity() was emitted as a file-level function, and the constructor was dropped entirely. Enums that carry real behaviour (the classic Java enum-with-methods pattern) lost their whole implementation from the graph, and calls made inside those bodies were mis-attributed.

Fix

enum_body_declarations is not a scope of its own — its members belong to the enum. Recurse through it transparently, preserving the enum as the parent scope. This mirrors the two transparent-wrapper cases already in the walker (companion_object for Kotlin, and ERROR parse-recovery nodes), which exist for the same reason.

Constructors and methods now hang off the enum via method edges, and their bodies are walked as call-graph scopes. The node name is Java-grammar-specific, so nothing else changes behaviour.

Test

New tests/test_java_enum_members.py:

  • enum method + constructor attach to the enum and don't leak onto the file (fails before the fix — the constructor was absent and the method was file-level);
  • a call from one enum method to another is captured;
  • the constant case_of edges are untouched.

The full test suite (5615 tests) passes.

A Java enum wraps its fields, constructors and methods in an
enum_body_declarations node, nested under enum_body after the constant
list. The generic walker recurses into a class body keeping the enclosing
type as the parent scope, but an unknown wrapper node resets that scope to
None (an unknown wrapper usually IS a scope boundary). That reset orphaned
every enum method, field and constructor onto the file:

    public enum Planet {
        EARTH(5.976e+24), MARS(6.421e+23);
        private final double mass;
        Planet(double mass) { this.mass = mass; }
        public double surfaceGravity() { return 6.673e-11 * mass; }
    }

Planet became a bare list of constants; surfaceGravity was emitted as a
file-level function and the constructor was dropped entirely.

enum_body_declarations is not a scope of its own - its members belong to
the enum - so recurse through it transparently, preserving the enum as the
parent scope (mirrors the companion_object and ERROR handling already in
the walker). Constructors and methods now hang off the enum via method
edges and their bodies are walked as call-graph scopes.

Adds regression coverage: members attach to the enum and don't leak onto
the file, an inter-method call is captured, and the constant case_of edges
are untouched.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Fixes Java enum members being orphaned onto the file: the generic walker now recurses transparently through enum_body_declarations, preserving parent_class_nid so fields, constructors, and methods attach to the enum and calls between enum method bodies land in the call graph. Constant case_of edges are unaffected.

No blocking issues surfaced.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 686 functions depend on the 237 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _extract_generic() — 18 callers, 29 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: extract_julia() — 17 callers, 7 callees
  • new: extract_cpp() — 29 callers, 3 callees
  • new: extract_vue() — 10 callers, 7 callees
  • new: walk() — 1 callers, 62 callees
  • …and 9 more — each is listed as a finding

Verification — 686 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: 626 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

23 of 287 test file(s) selected (8%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_build.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_extract.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_java_enum_members.py — impact, changed-test
  • tests/test_js_exported_scalar_bindings.py — impact
  • tests/test_languages.py — impact
  • tests/test_multilang.py — impact
  • tests/test_python_underscore_resolution.py — impact
  • tests/test_rationale.py — impact
  • tests/test_ruby_resolution.py — impact
  • tests/test_scala_self_type.py — impact
  • tests/test_swift_computed_properties.py — impact
  • tests/test_trailing_newline_not_a_syntax_error.py — impact
  • tests/test_ts_new_expression_calls.py — impact
  • tests/test_typescript_module_extensions.py — impact
  • tests/test_vue_extraction.py — impact

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 `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 17 more finding(s) on lines outside this diff (see the check run).

@rajatnagda45

Copy link
Copy Markdown
Contributor Author

This one surprised me — enum methods and constructors were landing on the file node instead of the enum. It's the enum_body_declarations wrapper resetting the parent scope to None, same category as the companion_object/ERROR cases already handled in the walker. Ran the full suite, stays green.

@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.65 (on PyPI). Cherry-picked with authorship preserved. Thanks @rajatnagda45!

@safishamsi safishamsi closed this Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants