diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index 8644268f9e..04f822a66f 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -416,6 +416,12 @@ public class JavaParser { } } + private void checkForBreakExpression() { + if (jdkVersion < 12) { + throwParseException("Expressions in break statements are only supported with Java 12"); + } + } + private void checkForYieldStatement() { if (jdkVersion < 13) { throwParseException("Yield statements are only supported with Java 13"); @@ -2655,7 +2661,7 @@ void ForUpdate() : void BreakStatement() : {Token t;} { - "break" [ t= {jjtThis.setImage(t.image);} ] ";" + "break" [ LOOKAHEAD( ";") t= {jjtThis.setImage(t.image);} | Expression() {checkForBreakExpression();} ] ";" } void ContinueStatement() : diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java index 0c6d22e772..04cd88176a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java @@ -24,4 +24,13 @@ public class ASTBreakStatement extends AbstractJavaNode { public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + @Override + public String getImage() { + String result = super.getImage(); + if (result == null && hasDescendantOfType(ASTName.class)) { + result = getFirstDescendantOfType(ASTName.class).getImage(); + } + return result; + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java index 4247539afb..46f18958ae 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java @@ -34,6 +34,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTArrayDimsAndInits; import net.sourceforge.pmd.lang.java.ast.ASTBlock; import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; import net.sourceforge.pmd.lang.java.ast.ASTCastExpression; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; @@ -1186,7 +1187,7 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { super.visit(node, data); JavaTypeDefinition type = null; - // first try to determine the type based on the first expression/yield of a switch rule + // first try to determine the type based on the first expression/break/yield of a switch rule List rules = node.findChildrenOfType(ASTSwitchLabeledRule.class); for (ASTSwitchLabeledRule rule : rules) { Node body = rule.jjtGetChild(1); // second child is either Expression, Block, ThrowStatement @@ -1194,6 +1195,14 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { type = ((ASTExpression) body).getTypeDefinition(); break; } else if (body instanceof ASTBlock) { + List breaks = body.findDescendantsOfType(ASTBreakStatement.class); + if (!breaks.isEmpty()) { + ASTExpression expression = breaks.get(0).getFirstChildOfType(ASTExpression.class); + if (expression != null) { + type = expression.getTypeDefinition(); + break; + } + } List yields = body.findDescendantsOfType(ASTYieldStatement.class); if (!yields.isEmpty()) { ASTExpression expression = yields.get(0).getFirstChildOfType(ASTExpression.class); @@ -1205,10 +1214,18 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { } } if (type == null) { - // now check the labels and their expressions of yield statements + // now check the labels and their expressions of break/yield statements for (int i = 0; i < node.jjtGetNumChildren(); i++) { Node child = node.jjtGetChild(i); if (child instanceof ASTBlockStatement) { + List breaks = child.findDescendantsOfType(ASTBreakStatement.class); + if (!breaks.isEmpty()) { + ASTExpression expression = breaks.get(0).getFirstChildOfType(ASTExpression.class); + if (expression != null) { + type = expression.getTypeDefinition(); + break; + } + } List yields = child.findDescendantsOfType(ASTYieldStatement.class); if (!yields.isEmpty()) { ASTExpression expression = yields.get(0).getFirstChildOfType(ASTExpression.class); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java12Test.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java12Test.java index 5c493a2a06..543767ca0b 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java12Test.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java12Test.java @@ -77,8 +77,7 @@ public class Java12Test { @Test public void testSwitchExpressions() { - // note: this uses java13 as we need to use the yield statement - ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("13", + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("12", loadSource("SwitchExpressions.java")); Assert.assertNotNull(compilationUnit); @@ -92,4 +91,26 @@ public class Java12Test { Assert.assertEquals(Integer.TYPE, localVarDecl.getType()); Assert.assertEquals(Integer.TYPE, switchExpression.getType()); } + + @Test + public void testSwitchExpressionsBreak() { + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("12", + loadSource("SwitchExpressionsBreak.java")); + Assert.assertNotNull(compilationUnit); + + ASTSwitchExpression switchExpression = compilationUnit.getFirstDescendantOfType(ASTSwitchExpression.class); + Assert.assertEquals(11, switchExpression.jjtGetNumChildren()); + Assert.assertTrue(switchExpression.jjtGetChild(0) instanceof ASTExpression); + Assert.assertEquals(5, switchExpression.findChildrenOfType(ASTSwitchLabel.class).size()); + + ASTBreakStatement breakStatement = switchExpression.getFirstDescendantOfType(ASTBreakStatement.class); + Assert.assertEquals("SwitchExpressionsBreak.SIX", breakStatement.getImage()); + Assert.assertTrue(breakStatement.jjtGetChild(0) instanceof ASTExpression); + + ASTLocalVariableDeclaration localVar = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class).get(1); + ASTVariableDeclarator localVarDecl = localVar.getFirstChildOfType(ASTVariableDeclarator.class); + Assert.assertEquals(Integer.TYPE, localVarDecl.getType()); + Assert.assertEquals(Integer.TYPE, switchExpression.getType()); + } + } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java12/SwitchExpressions.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java12/SwitchExpressions.java index 3629f00f61..76a5ae96e2 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java12/SwitchExpressions.java +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java12/SwitchExpressions.java @@ -23,7 +23,7 @@ public class SwitchExpressions { default -> { int k = day * 2; int result = f(k); - yield result; + break result; } }; System.out.printf("NumLetters: %d%n", numLetters); diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java12/SwitchExpressionsBreak.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java12/SwitchExpressionsBreak.java new file mode 100644 index 0000000000..03cb6a40a1 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java12/SwitchExpressionsBreak.java @@ -0,0 +1,36 @@ +/** + * + * @see JEP 325: Switch Expressions (Preview) + */ +public class SwitchExpressionsBreak { + private static final int MONDAY = 1; + private static final int TUESDAY = 2; + private static final int WEDNESDAY = 3; + private static final int THURSDAY = 4; + private static final int FRIDAY = 5; + private static final int SATURDAY = 6; + private static final int SUNDAY = 7; + + private static final int SIX = 6; + + public static void main(String[] args) { + int day = FRIDAY; + + var numLetters = switch (day) { + case MONDAY, FRIDAY, SUNDAY: break SwitchExpressionsBreak.SIX; + case TUESDAY : break 7; + case THURSDAY, SATURDAY : break 8; + case WEDNESDAY : break 9; + default : { + int k = day * 2; + int result = f(k); + break result; + } + }; + System.out.printf("NumLetters: %d%n", numLetters); + } + + private static int f(int k) { + return k*3; + } +}