Skip to content

Commit 5c71a42

Browse files
rlublecopybara-github
authored andcommitted
Handle the general case of switch statement and switch expression.
Switch expressions are rewritten as switch statements in a expression block, e.g: ``` a + switch (someEnum) { case VALUE1: case VALUE2: yield 2; case LASTVALUE: yield 3; } ``` is rewritten to: ``` a + ^{ switch (someEnum) { case VALUE1: case VALUE2: return 2; case LASTVALUE: return 3; default: __builtin_unreachable(); }}() ``` and switches with patterns or guards are rewritten into if nests that set a selector variable and the switch is converted to use that variable, e.g: ``` switch (someEnum) { case String s -> s.length(); case Integer i -> i; default -> 0; } ``` is translated as if it was written as: ``` int selector = 0; if (someEnum instanceof String s) { selector = 1; } else if (someEnum instanceof Integer i) { selector = 2; } switch (selector) { case 1 -> s.length(); case 2 -> i; default -> 0; } ``` Note that the pattern variables will be extracted out and will have the proper value in the places where they can be accessed. PiperOrigin-RevId: 828453235
1 parent 728387c commit 5c71a42

File tree

14 files changed

+812
-804
lines changed

14 files changed

+812
-804
lines changed

translator/src/main/java/com/google/devtools/j2objc/gen/StatementGenerator.java

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
100100
import com.google.devtools.j2objc.ast.VariableDeclarationStatement;
101101
import com.google.devtools.j2objc.ast.WhileStatement;
102+
import com.google.devtools.j2objc.ast.YieldStatement;
102103
import com.google.devtools.j2objc.util.ElementUtil;
103104
import com.google.devtools.j2objc.util.NameTable;
104105
import com.google.devtools.j2objc.util.TypeUtil;
@@ -785,20 +786,8 @@ public boolean visit(SwitchCase node) {
785786
@Override
786787
@SuppressWarnings("UngroupedOverloads")
787788
public boolean visit(SwitchExpression node) {
788-
Expression expr = node.getExpression();
789-
buffer.append("switch (");
790-
expr.accept(this);
791-
buffer.append(") ");
792-
buffer.append("{\n");
793-
buffer.indent();
794-
for (Statement stmt : node.getStatements()) {
795-
buffer.printIndent();
796-
stmt.accept(this);
797-
}
798-
buffer.unindent();
799-
buffer.printIndent();
800-
buffer.append("}");
801-
return false;
789+
// Switch expressions are rewritten to switch statements by SwitchConstructRewriter.
790+
throw new AssertionError("switch expression not converted");
802791
}
803792

804793
@Override
@@ -997,6 +986,12 @@ public boolean visit(WhileStatement node) {
997986
return false;
998987
}
999988

989+
@Override
990+
public boolean visit(YieldStatement node) {
991+
// Yield statements are rewritten to return statements in SwitchConstructRewriter.
992+
throw new AssertionError("yield statement not converted");
993+
}
994+
1000995
@Override
1001996
public boolean visit(Initializer node) {
1002997
// All Initializer nodes should have been converted during initialization

translator/src/main/java/com/google/devtools/j2objc/javac/TreeConverter.java

Lines changed: 90 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package com.google.devtools.j2objc.javac;
1616

17+
import static com.google.common.base.Preconditions.checkArgument;
18+
1719
import com.google.common.collect.Lists;
1820
import com.google.devtools.j2objc.Options;
1921
import com.google.devtools.j2objc.ast.AbstractTypeDeclaration;
@@ -94,6 +96,7 @@
9496
import com.google.devtools.j2objc.ast.SuperMethodInvocation;
9597
import com.google.devtools.j2objc.ast.SuperMethodReference;
9698
import com.google.devtools.j2objc.ast.SwitchCase;
99+
import com.google.devtools.j2objc.ast.SwitchConstruct;
97100
import com.google.devtools.j2objc.ast.SwitchExpression;
98101
import com.google.devtools.j2objc.ast.SwitchStatement;
99102
import com.google.devtools.j2objc.ast.SynchronizedStatement;
@@ -206,7 +209,6 @@
206209
import com.sun.tools.javac.util.Position;
207210
import java.io.IOException;
208211
import java.util.ArrayList;
209-
import java.util.Iterator;
210212
import java.util.List;
211213
import java.util.Objects;
212214
import javax.lang.model.element.AnnotationMirror;
@@ -318,8 +320,6 @@ private TreeNode convertInner(Tree javacNode, TreePath parent) {
318320
return convertBlock((BlockTree) javacNode, parent);
319321
case "BREAK":
320322
return convertBreakStatement((BreakTree) javacNode);
321-
case "CASE":
322-
return convertCase((CaseTree) javacNode, parent);
323323
case "CATCH":
324324
return convertCatch((CatchTree) javacNode, parent);
325325
case "CLASS":
@@ -675,19 +675,6 @@ private TreeNode convertBreakStatement(BreakTree node) {
675675
return newNode;
676676
}
677677

678-
private TreeNode convertCase(CaseTree node, TreePath parent) {
679-
// Case statements are converted in convertSwitch().
680-
// TODO(b/456297342): Implement handling for patterns in case statements.
681-
SwitchCase newNode = new SwitchCase();
682-
List<? extends ExpressionTree> expressions = node.getExpressions();
683-
for (ExpressionTree expressionTree : expressions) {
684-
TreePath path = getTreePath(parent, node);
685-
newNode.addExpression((Expression) convert(expressionTree, path));
686-
}
687-
newNode.setIsDefault(expressions.isEmpty());
688-
return newNode;
689-
}
690-
691678
private TreeNode convertCatch(CatchTree node, TreePath parent) {
692679
TreePath path = getTreePath(parent, node);
693680
return new CatchClause()
@@ -1371,12 +1358,6 @@ private TreeNode convertRecord(ClassTree node, TreePath parent) {
13711358

13721359
private TreeNode convertReturn(ReturnTree node, TreePath parent) {
13731360
Expression expr = (Expression) convert(node.getExpression(), getTreePath(parent, node));
1374-
if (expr != null && expr.getKind() == TreeNode.Kind.SWITCH_EXPRESSION) {
1375-
// This is returning just a switch expression and the construction transforms yields to
1376-
// returns in this case so it is safe to just return the switch.
1377-
// TODO(b/456285695): Move this to a pass.
1378-
return new ExpressionStatement(expr);
1379-
}
13801361
return new ReturnStatement(expr);
13811362
}
13821363

@@ -1386,139 +1367,59 @@ private TreeNode convertStringLiteral(LiteralTree node, TreePath parent) {
13861367

13871368
private TreeNode convertSwitch(SwitchTree node, TreePath parent) {
13881369
TreePath path = getTreePath(parent, node);
1389-
SwitchStatement newNode =
1370+
SwitchStatement switchStatement =
13901371
new SwitchStatement().setExpression(convertWithoutParens(node.getExpression(), path));
1391-
for (CaseTree switchCase : node.getCases()) {
1392-
TreePath switchCasePath = getTreePath(path, switchCase);
1393-
if (switchCase.getCaseKind() == CaseKind.RULE) {
1394-
SwitchCase switchCaseStmt = convertCaseRule(switchCase, switchCasePath);
1395-
newNode.addStatement(switchCaseStmt);
1396-
newNode.addStatement(new BreakStatement());
1397-
} else {
1398-
newNode.addStatement((SwitchCase) convert(switchCase, path));
1399-
for (StatementTree s : switchCase.getStatements()) {
1400-
newNode.addStatement((Statement) convert(s, switchCasePath));
1401-
}
1402-
}
1372+
for (CaseTree caseTree : node.getCases()) {
1373+
TreePath switchCasePath = getTreePath(parent, caseTree);
1374+
SwitchCase switchCase = convertSwitchCaseLabel(caseTree, switchCasePath);
1375+
switchStatement.addStatement(switchCase);
1376+
convertSwitchCaseBody(caseTree, switchCasePath, switchStatement, switchCase);
14031377
}
1404-
return newNode;
1378+
1379+
return switchStatement;
14051380
}
14061381

14071382
private SwitchExpression convertSwitchExpression(SwitchExpressionTree node, TreePath parent) {
14081383
TreePath path = getTreePath(parent, node);
1409-
SwitchExpression newNode =
1410-
new SwitchExpression().setExpression(convertWithoutParens(node.getExpression(), path));
1411-
newNode.setTypeMirror(getTypeMirror(path));
1412-
1413-
Tree parentTree = parent.getLeaf();
1414-
// TODO(b/456285695): Move the optimization of the two cases to a pass. And implement the
1415-
// general case, possibly using block expressions.
1416-
boolean exprReturned = parentTree.getKind() == Tree.Kind.RETURN;
1417-
boolean exprSaved = parentTree.getKind() == Tree.Kind.VARIABLE;
1418-
VariableElement yieldSymbol = null;
1419-
if (parentTree instanceof JCVariableDecl varDecl) {
1420-
yieldSymbol = varDecl.sym;
1421-
}
1422-
1423-
List<? extends CaseTree> cases = node.getCases();
1424-
for (CaseTree caseTree : cases) {
1425-
TreePath switchCasePath = getTreePath(path, caseTree);
1426-
1427-
SwitchCase switchCase = convertCaseRule(caseTree, switchCasePath);
1428-
1429-
// Convert any cases that have multiple expressions into a list of
1430-
// SwitchCases for all but the last expression, remove them from
1431-
// the SwitchExpressionCase and insert their SwitchCase equivalent
1432-
// ahead of the current case.
1433-
List<Expression> caseExprs = switchCase.getExpressions();
1434-
if (caseExprs.size() > 1) {
1435-
Iterator<Expression> caseExprIter = caseExprs.iterator();
1436-
Expression caseExpr = caseExprIter.next();
1437-
do {
1438-
caseExprIter.remove();
1439-
SwitchCase newCase = new SwitchCase();
1440-
newCase.addExpression(caseExpr.copy());
1441-
newNode.addStatement(newCase);
1442-
caseExpr = caseExprIter.next();
1443-
} while (caseExprIter.hasNext());
1444-
}
14451384

1446-
TreeNode body = switchCase.getBody();
1447-
if (body.getKind() == TreeNode.Kind.YIELD_STATEMENT) {
1448-
Expression yield = ((YieldStatement) body).getExpression().copy();
1449-
if (exprReturned) {
1450-
switchCase.setBody(new ReturnStatement(yield));
1451-
} else if (exprSaved) {
1452-
Assignment assignment = new Assignment(new SimpleName(yieldSymbol), yield);
1453-
switchCase.setBody(new ExpressionStatement(assignment));
1454-
}
1455-
} else if (body.getKind() == TreeNode.Kind.BLOCK) {
1456-
List<Statement> blockStmts = ((Block) body).getStatements();
1457-
if (!blockStmts.isEmpty()) {
1458-
int lastIndex = blockStmts.size() - 1;
1459-
Statement lastStmt = blockStmts.get(lastIndex);
1460-
if (lastStmt.getKind() == TreeNode.Kind.YIELD_STATEMENT) {
1461-
Expression yield = ((YieldStatement) lastStmt).getExpression().copy();
1462-
if (exprReturned) {
1463-
switchCase.setBody(new ReturnStatement(yield));
1464-
} else if (exprSaved) {
1465-
Assignment assignment = new Assignment(new SimpleName(yieldSymbol), yield);
1466-
switchCase.setBody(new ExpressionStatement(assignment));
1467-
}
1468-
}
1469-
}
1470-
}
1471-
newNode.addStatement(switchCase);
1472-
if (exprSaved) {
1473-
newNode.addStatement(new BreakStatement());
1474-
}
1475-
}
1385+
SwitchExpression switchExpression =
1386+
new SwitchExpression()
1387+
.setExpression(convertWithoutParens(node.getExpression(), path))
1388+
.setTypeMirror(getTypeMirror(path));
14761389

1477-
if (!newNode.hasDefaultCase()) {
1478-
addUnreachableDirective(newNode);
1390+
for (CaseTree caseTree : node.getCases()) {
1391+
TreePath switchCasePath = getTreePath(parent, caseTree);
1392+
SwitchCase switchCase = convertSwitchCaseLabel(caseTree, switchCasePath);
1393+
switchExpression.addStatement(switchCase);
1394+
convertSwitchCaseBody(caseTree, switchCasePath, switchExpression, switchCase);
14791395
}
14801396

1481-
return newNode;
1482-
}
1483-
1484-
// If a switch expression doesn't have a default case, add a function call to tell clang that all
1485-
// cases are handled. This can be asserted because switch expressions are checked to be
1486-
// exhaustive.
1487-
private void addUnreachableDirective(SwitchExpression switchExpression) {
1488-
// If the switch expression has no default case, add one
1489-
SwitchCase defaultCase = new SwitchCase().setIsDefault(true);
1490-
switchExpression.addStatement(defaultCase);
1491-
1492-
TypeMirror voidType = newUnit.getEnv().typeUtil().getVoid();
1493-
FunctionElement element = new FunctionElement("__builtin_unreachable", voidType, null);
1494-
FunctionInvocation unreachableInvocation = new FunctionInvocation(element, voidType);
1495-
1496-
if (switchExpression.getStatements().isEmpty()
1497-
|| ((SwitchCase) switchExpression.getStatements().get(0)).getBody() != null) {
1498-
// If the switch expression has rules, add it as a rule.
1499-
defaultCase.setBody(new ExpressionStatement(unreachableInvocation));
1500-
} else {
1501-
// Otherwise, add it as a statement.
1502-
switchExpression.addStatement(new ExpressionStatement(unreachableInvocation));
1397+
if (!switchExpression.hasDefaultCase()) {
1398+
// Switch expressions are exhaustive, so if there is no default case, add one to signal the
1399+
// compiler that the default case is unreachable.
1400+
addUnreachableDirective(switchExpression);
15031401
}
1402+
1403+
return switchExpression;
15041404
}
15051405

1506-
private SwitchCase convertCaseRule(CaseTree caseTree, TreePath parent) {
1406+
private SwitchCase convertSwitchCaseLabel(CaseTree caseTree, TreePath parent) {
1407+
TreePath switchCasePath = getTreePath(parent, caseTree);
15071408
SwitchCase switchCase = new SwitchCase();
15081409
List<? extends CaseLabelTree> caseExpressionsList = caseTree.getLabels();
15091410
for (CaseLabelTree caseLabelTree : caseExpressionsList) {
15101411
switch (caseLabelTree.getKind()) {
15111412
case CONSTANT_CASE_LABEL -> {
15121413
ConstantCaseLabelTree constantCaseLabelTree = (ConstantCaseLabelTree) caseLabelTree;
15131414
switchCase.addExpression(
1514-
(Expression) convert(constantCaseLabelTree.getConstantExpression(), parent));
1415+
(Expression) convert(constantCaseLabelTree.getConstantExpression(), switchCasePath));
15151416
}
15161417
case PATTERN_CASE_LABEL -> {
15171418
PatternCaseLabelTree patternCaseLabelTree = (PatternCaseLabelTree) caseLabelTree;
15181419
if (patternCaseLabelTree.getPattern() instanceof BindingPatternTree bindingPatternTree) {
15191420
VariableTree varTree = (VariableTree) bindingPatternTree.getVariable();
15201421
VariableElement var =
1521-
((VariableDeclaration) convertVariableDeclaration(varTree, parent))
1422+
((VariableDeclaration) convertVariableDeclaration(varTree, switchCasePath))
15221423
.getVariableElement();
15231424
Pattern.BindingPattern pattern = new Pattern.BindingPattern(var);
15241425
switchCase.setPattern(pattern);
@@ -1528,25 +1429,72 @@ private SwitchCase convertCaseRule(CaseTree caseTree, TreePath parent) {
15281429
default -> throw new AssertionError("unknown case label type: " + caseLabelTree.getKind());
15291430
}
15301431
}
1531-
Tree javacBody = caseTree.getBody();
1532-
if (javacBody != null) {
1533-
TreeNode body = convert(javacBody, parent);
1534-
if (body instanceof Expression) {
1535-
body = new YieldStatement((Expression) body);
1432+
switchCase.setGuard((Expression) convert(caseTree.getGuard(), switchCasePath));
1433+
1434+
return switchCase;
1435+
}
1436+
1437+
private void convertSwitchCaseBody(
1438+
CaseTree caseTree, TreePath parent, SwitchConstruct switchConstruct, SwitchCase switchCase) {
1439+
TreePath switchCasePath = getTreePath(parent, caseTree);
1440+
if (caseTree.getCaseKind() == CaseKind.RULE) {
1441+
if (caseTree.getBody() != null) {
1442+
boolean isImplicitYield =
1443+
switchConstruct instanceof SwitchExpression switchExpression
1444+
&& switchExpression.getTypeMirror().getKind() != TypeKind.VOID;
1445+
1446+
TreeNode body = convert(caseTree.getBody(), parent);
1447+
if (body instanceof Expression expression) {
1448+
if (isImplicitYield) {
1449+
// Switch expression that are not void have an implicit yield.
1450+
body = new YieldStatement(expression);
1451+
} else {
1452+
body = new ExpressionStatement(expression);
1453+
}
1454+
}
1455+
if (!isImplicitYield) {
1456+
// Add a break statement to the switch statement with rules to avoid fallback.
1457+
Block block = body instanceof Block b ? b : new Block().addStatement((Statement) body);
1458+
block.addStatement(new BreakStatement());
1459+
body = block;
1460+
}
1461+
switchCase.setBody((Statement) body);
1462+
1463+
} else {
1464+
Block body = new Block();
1465+
for (StatementTree s : caseTree.getStatements()) {
1466+
body.addStatement((Statement) convert(s, switchCasePath));
1467+
}
1468+
switchCase.setBody(body);
15361469
}
1537-
switchCase.setBody((Statement) body);
15381470
} else {
1539-
Block body = new Block();
1540-
List<? extends StatementTree> statementTrees = caseTree.getStatements();
1541-
for (StatementTree statementTree : statementTrees) {
1542-
body.addStatement((Statement) convert(statementTree, parent));
1471+
checkArgument(caseTree.getBody() == null);
1472+
for (StatementTree s : caseTree.getStatements()) {
1473+
switchConstruct.addStatement((Statement) convert(s, switchCasePath));
15431474
}
1544-
switchCase.setBody(body);
15451475
}
1476+
}
1477+
1478+
// If a switch expression doesn't have a default case, add a function call to tell clang that all
1479+
// cases are handled. This can be asserted because switch expressions are checked to be
1480+
// exhaustive.
1481+
private void addUnreachableDirective(SwitchExpression switchExpression) {
1482+
// If the switch expression has no default case, add one
1483+
SwitchCase defaultCase = new SwitchCase().setIsDefault(true);
1484+
switchExpression.addStatement(defaultCase);
15461485

1547-
switchCase.setGuard((Expression) convert(caseTree.getGuard(), parent));
1486+
TypeMirror voidType = newUnit.getEnv().typeUtil().getVoid();
1487+
FunctionElement element = new FunctionElement("__builtin_unreachable", voidType, null);
1488+
FunctionInvocation unreachableInvocation = new FunctionInvocation(element, voidType);
15481489

1549-
return switchCase;
1490+
if (switchExpression.getStatements().isEmpty()
1491+
|| ((SwitchCase) switchExpression.getStatements().get(0)).getBody() != null) {
1492+
// If the switch expression has rules, add it as a rule.
1493+
defaultCase.setBody(new ExpressionStatement(unreachableInvocation));
1494+
} else {
1495+
// Otherwise, add it as a statement.
1496+
switchExpression.addStatement(new ExpressionStatement(unreachableInvocation));
1497+
}
15501498
}
15511499

15521500
private TreeNode convertSynchronized(SynchronizedTree node, TreePath parent) {

translator/src/main/java/com/google/devtools/j2objc/pipeline/TranslationProcessor.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
import com.google.devtools.j2objc.translate.SerializationStripper;
6565
import com.google.devtools.j2objc.translate.StaticVarRewriter;
6666
import com.google.devtools.j2objc.translate.SuperMethodInvocationRewriter;
67-
import com.google.devtools.j2objc.translate.SwitchCaseRewriter;
67+
import com.google.devtools.j2objc.translate.SwitchConstructRewriter;
6868
import com.google.devtools.j2objc.translate.SwitchRewriter;
6969
import com.google.devtools.j2objc.translate.UnsequencedExpressionRewriter;
7070
import com.google.devtools.j2objc.translate.VarargsRewriter;
@@ -211,6 +211,10 @@ public static void applyMutations(
211211
new AbstractMethodRewriter(unit, deadCodeMap).run();
212212
ticker.tick("AbstractMethodRewriter");
213213

214+
// Before: InstanceOfPatternRewriter - to handle instanceof patterns from the switch rewrite.
215+
new SwitchConstructRewriter(unit).run();
216+
ticker.tick("SwitchConstructRewriter");
217+
214218
// Before: VariableRenamer - VariableRenamer renames variables in a scope-aware manner.
215219
new InstanceOfPatternRewriter(unit).run();
216220
ticker.tick("InstanceOfPatternRewriter");
@@ -342,11 +346,6 @@ public static void applyMutations(
342346
new CastResolver(unit).run();
343347
ticker.tick("CastResolver");
344348

345-
// After: SwitchRewriter to handle Java 21 patterns and guards in switches,
346-
// and CastResolver to avoid duplicate cast checks.
347-
new SwitchCaseRewriter(unit).run();
348-
ticker.tick("SwitchCaseRewriter");
349-
350349
// After: InnerClassExtractor, Functionizer - Expects all types to be
351350
// top-level and functionizing to have occurred.
352351
new PrivateDeclarationResolver(unit).run();

0 commit comments

Comments
 (0)