Emit per-construct diagnostics for unsupported switch forms#419
Draft
fabiomadge wants to merge 1 commit into
Draft
Emit per-construct diagnostics for unsupported switch forms#419fabiomadge wants to merge 1 commit into
fabiomadge wants to merge 1 commit into
Conversation
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 2b of PLAN.md §3 — refusal-time diagnostics for switch forms that can't be encoded as if/conditional chains.
Resolves
case nullis deferred to Step 9 alongside Option-type synthesis.Summary
Suspenders'
switchToIf/switchExpressionToConditionalreturnOptional.empty()for forms they can't encode. Today those refused trees fall through to LOWER, which mutates them into shapesJavaToLaurelCompilercan't translate, surfacing as aJavaViolationException→ 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:
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 }(switch expr only)notSupported "switch rule block"case 0 -> throw ...(switch expr only)notSupported "switch rule throw statement"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 validif (selector == 1+1) { ... }. Post-hocOptional.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
Reporterfield and avisitTopLeveloverride matching the pattern used inMethodOrLoopContractCompiler.Fixture changes
TranslationErrors.javastays@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.switchExprIntexercisescase 1, 2 -> 20;and verifies). When the rest of the fixture clears in Steps 8-9,TranslationErrorswill flip cleanly.Base branch
Targets #417 (
step-2a/suppress-switch-restoration). Will rebase ontomainonce #416 → #417 chain merges.Test plan
./gradlew :verifier:testpasses — 116 / 0 / 72 (was 115 / 0 / 72 on Step 2a, +SwitchRefusal).JavaViolationExceptionstack traces in test output.SwitchRefusal.javacovers all five refusal classes end-to-end.