Skip to content

Emit per-construct diagnostics for unsupported switch forms#419

Draft
fabiomadge wants to merge 1 commit into
step-2a/suppress-switch-restorationfrom
step-2b/switch-refusal-diagnostics
Draft

Emit per-construct diagnostics for unsupported switch forms#419
fabiomadge wants to merge 1 commit into
step-2a/suppress-switch-restorationfrom
step-2b/switch-refusal-diagnostics

Conversation

@fabiomadge
Copy link
Copy Markdown
Collaborator

Step 2b of PLAN.md §3 — refusal-time diagnostics for switch forms that can't be encoded as if/conditional chains.

Resolves

Summary

Suspenders' switchToIf/switchExpressionToConditional return Optional.empty() for forms they can't encode. Today those refused trees fall through to LOWER, which mutates them into shapes JavaToLaurelCompiler can't translate, surfacing as a JavaViolationException → method-level skip via Step 1's catch. The error points at the method, not the offending construct.

This PR replaces the fall-through with refusal-time diagnostics emitted at each offending element's source position. The check happens at SUSPEND time, before LOWER runs:

Construct Diagnostic Anchor
case A: (statement-form) notSupported "switch labeled statement group" case node
case Pattern p when ... notSupported "case pattern" case node
case 1 + 1 -> ... notSupported "non-literal case constant" label expr
case 0 -> { block } (switch expr only) notSupported "switch rule block" body
case 0 -> throw ... (switch expr only) notSupported "switch rule throw statement" body

After diagnostics, the offending switch is replaced with a no-op placeholder (empty block for statement form, typed-zero literal for primitive-typed expression form) so LOWER doesn't mutate the refused tree.

Implementation notes

Pre-walk for refusal detection. The encoder silently accepts some refused forms — most notably case 1 + 1, where Attr constant-folds the binary expression and the encoder produces a valid if (selector == 1+1) { ... }. Post-hoc Optional.empty() detection misses these. A pre-walk (hasRefusedConstruct) inspects all labels and body shapes before encoding and is the source of truth for refusal.

Reference-typed switch expressions. Fall through to the existing skip path rather than substituting a typed-null placeholder. Building a typed null is fragile across javac versions, and this path is unreachable today (reference-typed switch expressions trip earlier on type translation).

Reporter wiring. Suspenders gains a Reporter field and a visitTopLevel override matching the pattern used in MethodOrLoopContractCompiler.

Fixture changes

TranslationErrors.java stays @JVerifyTest(skip = "...") — its non-switch markers (lines 18, 24, 125, 148, 155, 160, 183) gate the file flip on Steps 8-9. Two aspirational "switch labeled statement group" markers on multi-label arrow cases (lines 94 and 112) removed: those weren't real refusal targets, multi-label arrow form with a valid body is supported by Step 2a (Switches.switchExprInt exercises case 1, 2 -> 20; and verifies). When the rest of the fixture clears in Steps 8-9, TranslationErrors will flip cleanly.

Base branch

Targets #417 (step-2a/suppress-switch-restoration). Will rebase onto main once #416#417 chain merges.

Test plan

  • ./gradlew :verifier:test passes — 116 / 0 / 72 (was 115 / 0 / 72 on Step 2a, +SwitchRefusal).
  • No new uncaught JavaViolationException stack traces in test output.
  • No green → red transitions.
  • SwitchRefusal.java covers all five refusal classes end-to-end.

Suspenders' switchToIf/switchExpressionToConditional return
Optional.empty() for forms they can't encode (case-statement groups,
non-literal labels, pattern guards, block/throw bodies in switch
expressions). Today those refused trees fall through to LOWER, which
mutates them into shapes JavaToLaurelCompiler can't translate, surfacing
as a JavaViolationException → method-level skip via Step 1's catch. The
error message points at the method, not the offending construct.

Replace the fall-through with refusal-time diagnostics emitted at each
offending element's source position. Suspended at SUSPEND time, before
LOWER runs, so each label/body shape has its diagnostic anchored where
the user wrote it:
  - case A: (statement-form)        notSupported "switch labeled statement group"
  - case Pattern p when ...         notSupported "case pattern"
  - case 1 + 1 -> ...               notSupported "non-literal case constant"
  - case 0 -> { block }             notSupported "switch rule block" (switch expressions only)
  - case 0 -> throw ...             notSupported "switch rule throw statement" (switch expressions only)

The check is via a pre-walk (hasRefusedConstruct) before encoding —
necessary because the encoder silently accepts some refused forms (e.g.
case 1 + 1 has a constant-folded type but a JCBinary expression node).
The pre-walk is the source of truth for refusal.

After diagnostics are emitted, the offending switch is replaced with a
no-op placeholder (empty block for statement form, typed-zero literal
for primitive-typed expression form) so LOWER doesn't mutate the
refused tree. Reference-typed switch expressions fall through to the
existing skip path — building a typed null is fragile across javac
versions and that path is unreachable today (reference-typed switch
expressions trip earlier on type translation).

Step 2b of PLAN.md §3. Switch encoding (Step 2a) plus refusal
diagnostics (Step 2b) closes the switch-desugaring portion of #401.
case null is left for Step 9 alongside Option-type synthesis.

Tests:
  SwitchRefusal regression test covers all five refusal classes.
  TranslationErrors stays @JVerifyTest(skip = "..."): its non-switch
    markers (lines 18, 24, 125, 148, 155, 160, 183) gate the file flip
    on Steps 8-9. Two aspirational "switch labeled statement group"
    markers on multi-label arrow cases (lines 94 and 112) removed —
    those weren't real refusal targets, multi-label arrow form with a
    valid body is supported by Step 2a.
  Full suite: 116 / 0 / 72 (was 115 / 0 / 72; +SwitchRefusal).
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.

1 participant