[ 
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)

Reply via email to