[
https://issues.apache.org/jira/browse/GROOVY-12408?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Paul King updated GROOVY-12408:
-------------------------------
Description:
GROOVY-12399 restored Java's rule that position, not arm shape, decides whether
a {{switch}} produces a value, but made a special case of a {{switch}} that is
the last statement of a body, keeping it an expression there. This removes that
special case: such a {{switch}} is a statement like any other, and Groovy's
implicit return supplies the value from the arm bodies through {{ReturnAdder}},
exactly as it does for a trailing {{if}} or colon-form {{switch}}.
The resulting rule is one sentence: *a {{switch}} whose value is not used is a
statement, wherever it appears*; ask for expression semantics with {{return
switch}} or by assigning the result.
Net -151 lines, 110 of them the parse-tree walk that existed only to recognise
the special case.
This is safe now because the two things a {{SwitchStatement}} lacked have since
been given to it: GROOVY-12405 (jump-table dispatch) and GROOVY-12406
(duplicate-label diagnostic). Without those, this change would have moved the
most idiomatic Groovy form to a linear chain of {{isCase}} calls and silently
dropped a diagnostic.
h2. Behaviour
Measured on released Groovy 5.1.2 and 6.0.0-RC-1, and on master with this
change. Java columns from {{javac}} / {{javap}} on JDK 25. "n/a" means Java has
no counterpart; "not expressible" means {{javac}} rejects the shape.
Master has passed through intermediate states as GROOVY-12399, GROOVY-12405 and
GROOVY-12406 landed, and this change undoes part of the first. No released
Groovy exhibits any intermediate state, since all four land together in 7.0.0.
h3. Statement position
Restored to 5.1.2 on every row.
||Case||Java 25||5.1.2||RC-1||with this change||
|Value discarded, unmatched|no-op|no-op|throws|no-op|
|No default, type-checked|compiles|compiles|compile error|compiles|
|Enum, partial labels, unmatched|no-op|no-op|throws|no-op|
|Enum, null selector|NPE|no-op|throws|no-op|
|Enum covering every constant, constant added later|no-op|no-op|ICCE|no-op|
|Nested in a non-last colon case without {{break}}|no value there|next case's
value|throws|next case's value|
h3. Implicit-return position
Java has no such position, so 5.1.2 is the only reference. Every row matches it
except the primitive one.
||Case||Java 25||5.1.2||RC-1||with this change||
|Reference return type, unmatched|n/a|null|throws|null|
|In a closure|n/a|null|throws|null|
|No default, type-checked|n/a|compiles|compile error|compiles|
|Enum, constant added later|n/a|null|ICCE|null|
|{{case label}}, declared-{{Object}} label, statically compiled|n/a|dynamic
{{isCase}}|static {{isCase}}|dynamic {{isCase}}|
|{{int}} return type, unmatched|rejects: missing
return|GroovyCastException|throws|0|
The {{int}} row is the one deliberate departure from 5.1.2, and it is a
correction: every other way a method falls off its end already yielded the
type's default, and the arrow {{switch}} was the sole shape in the language
that threw instead.
h3. Expression position
Unchanged from RC-1 throughout. These are the GROOVY-12255 decisions and
nothing here revisits them.
||Case||Java 25||5.1.2||RC-1||with this change||
|Unmatched at runtime|must be exhaustive|null|throws|throws|
|Non-exhaustive, type-checked|compile error|compiles|compile error|compile
error|
|Enum, null selector|NPE|null|throws|throws|
|Enum, constant added later|MatchException|null|ICCE|ICCE|
|{{case label}}, declared-{{Object}} label, statically compiled|n/a|dynamic
{{isCase}}|static {{isCase}}|static {{isCase}}|
h3. Nested arrow arms
The inner {{switch}} follows whatever its own {{switch}} is. Java cannot
express any of these, since a switch expression is not a statement there.
||The switch owning the arm is||Java 25||5.1.2||RC-1||with this change||
|A statement whose value is discarded|not expressible|no-op|throws|no-op|
|In implicit-return position|not expressible|null|throws|null|
|A {{return switch}}|not expressible|null|throws|throws|
h3. Diagnostics and dispatch
||Case||Java 25||5.1.2||RC-1||with this change||
|Duplicate label, statement|compile error|accepted|compile error|compile error|
|Duplicate label, expression|compile error|accepted|compile error|compile error|
|Dispatch: arrow statement, implicit return, {{return switch}}|jump
table|closure call|jump table|jump table|
|Dispatch: colon statement|jump table|{{isCase}} chain|{{isCase}} chain|jump
table|
h3. Still rejected everywhere
{{return}}, {{break}} and {{continue}} inside a statement-position arrow arm
remain a parse error in 5.1.2, in RC-1 and on master. Java allows all three.
This predates 6.0, is unchanged here, and wants its own ticket.
h2. Dynamic versus {{@TypeChecked}} and {{@CompileStatic}}
Every row above was also run in all three modes. All agree except these three,
so the position rules themselves are mode-independent.
||Case||dynamic||{{@TypeChecked}}||{{@CompileStatic}}||
|Duplicate label, statement or expression|first match wins|compile
error|compile error|
|Non-exhaustive expression, unmatched|throws at runtime|compile error|compile
error|
|{{case label}}, declared-{{Object}} label, expression position|dynamic
{{isCase}}|dynamic {{isCase}}|static {{isCase}}|
The first two are the same thing: a diagnostic the type checker can give where
dynamic Groovy only discovers the problem at runtime. The third is a code
generation choice rather than a checking one, which is why {{@TypeChecked}}
sides with dynamic. It is the one remaining inconsistency in the design, and in
statement position it no longer arises.
h2. What the change does
||Change||Where||
|A {{switch}} that is an expression statement is a {{SwitchStatement}}, with no
exception for the last statement of a body|{{AstBuilder.visitCommandExprAlt}},
{{isSwitchValueUsed}}|
|Deleted: the walk that recognised implicit-return position, about 110
lines|{{isImplicitReturnPosition}}, {{returnsValue}}, {{isLastStatement}},
{{isLenientPosition}}, {{isColonArm}}|
|Deleted: the node-metadata marker and its three readers, so an unmatched
switch expression always throws and exhaustiveness always
applies|{{SwitchExpressionUtils}}, {{SwitchExpressionWriter}},
{{StaticTypesSwitchExpressionWriter}}, {{StaticTypeCheckingVisitor}}|
|Kept: the recursion that makes a {{switch}} nested in an arrow arm follow its
own {{switch}}|{{isSwitchValueUsed}}, {{isSwitchUsedAsStatement}}|
|Documentation brought in line with the rule|{{COMPATIBILITY.md}},
{{core-semantics.adoc}}|
The kept recursion is the one part of the position logic that has to survive:
an arm of a {{switch}} that is itself a statement produces nothing, so a
{{switch}} nested there is a statement too, however deeply nested.
> Compile implicit-return arrow switches as switch statements
> -----------------------------------------------------------
>
> Key: GROOVY-12408
> URL: https://issues.apache.org/jira/browse/GROOVY-12408
> Project: Groovy
> Issue Type: Improvement
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
>
> GROOVY-12399 restored Java's rule that position, not arm shape, decides
> whether a {{switch}} produces a value, but made a special case of a
> {{switch}} that is the last statement of a body, keeping it an expression
> there. This removes that special case: such a {{switch}} is a statement like
> any other, and Groovy's implicit return supplies the value from the arm
> bodies through {{ReturnAdder}}, exactly as it does for a trailing {{if}} or
> colon-form {{switch}}.
> The resulting rule is one sentence: *a {{switch}} whose value is not used is
> a statement, wherever it appears*; ask for expression semantics with {{return
> switch}} or by assigning the result.
> Net -151 lines, 110 of them the parse-tree walk that existed only to
> recognise the special case.
> This is safe now because the two things a {{SwitchStatement}} lacked have
> since been given to it: GROOVY-12405 (jump-table dispatch) and GROOVY-12406
> (duplicate-label diagnostic). Without those, this change would have moved the
> most idiomatic Groovy form to a linear chain of {{isCase}} calls and silently
> dropped a diagnostic.
> h2. Behaviour
> Measured on released Groovy 5.1.2 and 6.0.0-RC-1, and on master with this
> change. Java columns from {{javac}} / {{javap}} on JDK 25. "n/a" means Java
> has no counterpart; "not expressible" means {{javac}} rejects the shape.
> Master has passed through intermediate states as GROOVY-12399, GROOVY-12405
> and GROOVY-12406 landed, and this change undoes part of the first. No
> released Groovy exhibits any intermediate state, since all four land together
> in 7.0.0.
> h3. Statement position
> Restored to 5.1.2 on every row.
> ||Case||Java 25||5.1.2||RC-1||with this change||
> |Value discarded, unmatched|no-op|no-op|throws|no-op|
> |No default, type-checked|compiles|compiles|compile error|compiles|
> |Enum, partial labels, unmatched|no-op|no-op|throws|no-op|
> |Enum, null selector|NPE|no-op|throws|no-op|
> |Enum covering every constant, constant added later|no-op|no-op|ICCE|no-op|
> |Nested in a non-last colon case without {{break}}|no value there|next case's
> value|throws|next case's value|
> h3. Implicit-return position
> Java has no such position, so 5.1.2 is the only reference. Every row matches
> it except the primitive one.
> ||Case||Java 25||5.1.2||RC-1||with this change||
> |Reference return type, unmatched|n/a|null|throws|null|
> |In a closure|n/a|null|throws|null|
> |No default, type-checked|n/a|compiles|compile error|compiles|
> |Enum, constant added later|n/a|null|ICCE|null|
> |{{case label}}, declared-{{Object}} label, statically compiled|n/a|dynamic
> {{isCase}}|static {{isCase}}|dynamic {{isCase}}|
> |{{int}} return type, unmatched|rejects: missing
> return|GroovyCastException|throws|0|
> The {{int}} row is the one deliberate departure from 5.1.2, and it is a
> correction: every other way a method falls off its end already yielded the
> type's default, and the arrow {{switch}} was the sole shape in the language
> that threw instead.
> h3. Expression position
> Unchanged from RC-1 throughout. These are the GROOVY-12255 decisions and
> nothing here revisits them.
> ||Case||Java 25||5.1.2||RC-1||with this change||
> |Unmatched at runtime|must be exhaustive|null|throws|throws|
> |Non-exhaustive, type-checked|compile error|compiles|compile error|compile
> error|
> |Enum, null selector|NPE|null|throws|throws|
> |Enum, constant added later|MatchException|null|ICCE|ICCE|
> |{{case label}}, declared-{{Object}} label, statically compiled|n/a|dynamic
> {{isCase}}|static {{isCase}}|static {{isCase}}|
> h3. Nested arrow arms
> The inner {{switch}} follows whatever its own {{switch}} is. Java cannot
> express any of these, since a switch expression is not a statement there.
> ||The switch owning the arm is||Java 25||5.1.2||RC-1||with this change||
> |A statement whose value is discarded|not expressible|no-op|throws|no-op|
> |In implicit-return position|not expressible|null|throws|null|
> |A {{return switch}}|not expressible|null|throws|throws|
> h3. Diagnostics and dispatch
> ||Case||Java 25||5.1.2||RC-1||with this change||
> |Duplicate label, statement|compile error|accepted|compile error|compile
> error|
> |Duplicate label, expression|compile error|accepted|compile error|compile
> error|
> |Dispatch: arrow statement, implicit return, {{return switch}}|jump
> table|closure call|jump table|jump table|
> |Dispatch: colon statement|jump table|{{isCase}} chain|{{isCase}} chain|jump
> table|
> h3. Still rejected everywhere
> {{return}}, {{break}} and {{continue}} inside a statement-position arrow arm
> remain a parse error in 5.1.2, in RC-1 and on master. Java allows all three.
> This predates 6.0, is unchanged here, and wants its own ticket.
> h2. Dynamic versus {{@TypeChecked}} and {{@CompileStatic}}
> Every row above was also run in all three modes. All agree except these
> three, so the position rules themselves are mode-independent.
> ||Case||dynamic||{{@TypeChecked}}||{{@CompileStatic}}||
> |Duplicate label, statement or expression|first match wins|compile
> error|compile error|
> |Non-exhaustive expression, unmatched|throws at runtime|compile error|compile
> error|
> |{{case label}}, declared-{{Object}} label, expression position|dynamic
> {{isCase}}|dynamic {{isCase}}|static {{isCase}}|
> The first two are the same thing: a diagnostic the type checker can give
> where dynamic Groovy only discovers the problem at runtime. The third is a
> code generation choice rather than a checking one, which is why
> {{@TypeChecked}} sides with dynamic. It is the one remaining inconsistency in
> the design, and in statement position it no longer arises.
> h2. What the change does
> ||Change||Where||
> |A {{switch}} that is an expression statement is a {{SwitchStatement}}, with
> no exception for the last statement of a
> body|{{AstBuilder.visitCommandExprAlt}}, {{isSwitchValueUsed}}|
> |Deleted: the walk that recognised implicit-return position, about 110
> lines|{{isImplicitReturnPosition}}, {{returnsValue}}, {{isLastStatement}},
> {{isLenientPosition}}, {{isColonArm}}|
> |Deleted: the node-metadata marker and its three readers, so an unmatched
> switch expression always throws and exhaustiveness always
> applies|{{SwitchExpressionUtils}}, {{SwitchExpressionWriter}},
> {{StaticTypesSwitchExpressionWriter}}, {{StaticTypeCheckingVisitor}}|
> |Kept: the recursion that makes a {{switch}} nested in an arrow arm follow
> its own {{switch}}|{{isSwitchValueUsed}}, {{isSwitchUsedAsStatement}}|
> |Documentation brought in line with the rule|{{COMPATIBILITY.md}},
> {{core-semantics.adoc}}|
> The kept recursion is the one part of the position logic that has to survive:
> an arm of a {{switch}} that is itself a statement produces nothing, so a
> {{switch}} nested there is a statement too, however deeply nested.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)