Improve switch grammar

This commit is contained in:
Clément Fournier
2019-12-14 18:02:49 +01:00
parent d4da257dc6
commit 77664d9d7f
28 changed files with 616 additions and 712 deletions

View File

@ -2782,49 +2782,36 @@ void SwitchStatement():
"switch" "(" Expression() ")" SwitchBlock()
}
void SwitchBlock() #void :
void SwitchBlock() #void:
{}
{
"{"
(
SwitchLabel()
(
"->" SwitchLabeledRulePart() (SwitchLabeledRule())*
|
":" (LOOKAHEAD(2) SwitchLabel() ":")*
// the lookahead is to prevent choosing BlockStatement when the token is "default",
// which could happen as local class declarations accept the "default" modifier.
(LOOKAHEAD({shouldStartStatementInSwitch()}) BlockStatement())*
(SwitchLabeledStatementGroup())*
)
)?
LOOKAHEAD(SwitchLabel() ":") (SwitchFallthroughBranch())+
| (SwitchArrowBranch())*
)
"}"
}
void SwitchLabeledRule() #void :
void SwitchArrowLahead() #void:
{}
{
SwitchLabel() "->" SwitchLabeledRulePart()
SwitchLabel() "->"
}
void SwitchLabeledRulePart() #void:
void SwitchArrowBranch():
{}
{
( Expression() ";" ) #SwitchLabeledExpression(2)
|
( Block() ) #SwitchLabeledBlock(2)
|
( ThrowStatement() ) #SwitchLabeledThrowStatement(2)
SwitchLabel() "->" (Expression() ";" | Block() | ThrowStatement())
}
// For PMD 7, make this a real node to group the labels + statements
void SwitchLabeledStatementGroup() #void:
void SwitchFallthroughBranch():
{}
{
(LOOKAHEAD(2) SwitchLabel() ":")+
// the lookahead is to prevent choosing BlockStatement when the token is "default",
// which could happen as local class declarations accept the "default" modifier.
(LOOKAHEAD({shouldStartStatementInSwitch()}) BlockStatement() )*
SwitchLabel() ":"
// the lookahead is to prevent choosing BlockStatement when the token is "default",
// which could happen as local class declarations accept the "default" modifier.
(LOOKAHEAD({shouldStartStatementInSwitch()}) BlockStatement() )*
}
void SwitchLabel() :

View File

@ -16,7 +16,7 @@ import java.util.Iterator;
*
* </pre>
*/
public final class ASTBlock extends AbstractStatement implements Iterable<ASTStatement> {
public final class ASTBlock extends AbstractStatement implements Iterable<ASTStatement>, ASTSwitchArrowRHS {
private boolean containsComment;

View File

@ -36,7 +36,11 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*
* </pre>
*/
public interface ASTExpression extends JavaNode, TypeNode, ASTMemberValue {
public interface ASTExpression
extends JavaNode,
TypeNode,
ASTMemberValue,
ASTSwitchArrowRHS {
/**
* Always returns true. This is to allow XPath queries

View File

@ -0,0 +1,49 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
/**
* A non-fallthrough switch rule, introduced with switch expressions.
* See {@link ASTSwitchLike}.
*
* <pre class="grammar">
*
* SwitchLabeledExpression ::= {@link ASTSwitchLabel SwitchLabel} "->" {@link ASTSwitchArrowRHS SwitchArrowRHS}
*
* </pre>
*/
public final class ASTSwitchArrowBranch extends AbstractJavaNode implements LeftRecursiveNode {
ASTSwitchArrowBranch(int id) {
super(id);
}
ASTSwitchArrowBranch(JavaParser p, int id) {
super(p, id);
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
@Override
public <T> void jjtAccept(SideEffectingVisitor<T> visitor, T data) {
visitor.visit(this, data);
}
/** Returns the label, which may be compound. */
public ASTSwitchLabel getLabel() {
return (ASTSwitchLabel) getFirstChild();
}
/** Returns the right hand side of the arrow. */
public ASTSwitchArrowRHS getRightHandSide() {
return (ASTSwitchArrowRHS) getLastChild();
}
}

View File

@ -0,0 +1,20 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
/**
* A node that can appear as the right-hand-side of a {@link ASTSwitchArrowBranch SwitchArrowRule}.
*
* <pre class="grammar">
*
* SwitchArrowRightHandSide ::= {@link ASTExpression Expression}
* | {@link ASTBlock Block}
* | {@link ASTThrowStatement ThrowStatement}
*
* </pre>
*/
public interface ASTSwitchArrowRHS extends JavaNode {
}

View File

@ -0,0 +1,26 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
/**
* A branch of a {@link ASTSwitchLike SwitchLike}.
*
* <pre class="grammar">
*
* SwitchBranch ::= {@link ASTSwitchArrowBranch SwitchArrowBranch}
* | {@link ASTSwitchFallthroughBranch FallthroughBranch}
*
* </pre>
*/
public interface ASTSwitchBranch extends JavaNode {
/**
* Returns the label, which may be compound.
*/
default ASTSwitchLabel getLabel() {
return (ASTSwitchLabel) getFirstChild();
}
}

View File

@ -6,32 +6,19 @@ package net.sourceforge.pmd.lang.java.ast;
/**
* A switch expression, as introduced in Java 12. This node only occurs
* in the contexts where an expression is expected. In particular, if
* in the contexts where an expression is expected. In particular,
* switch constructs occurring in statement position are parsed as a
* {@linkplain ASTSwitchStatement SwitchStatement}, and not a
* {@link ASTSwitchExpression SwitchExpression} within a
* {@link ASTStatementExpression StatementExpression}. That is
* because switch statements are not required to be exhaustive, contrary
* {@link ASTExpressionStatement ExpressionStatement}. That is because
* switch statements are not required to be exhaustive, contrary
* to switch expressions.
*
* <p>Their syntax is identical though.
*
* <p>TODO Do we group the statements of a SwitchNormalBlock ? Do we unify
* their interface ? SwitchStatement implements Iterator&lt;SwitchLabel&gt;
* which seems shitty tbh.
*
* <pre class="grammar">
*
* SwitchExpression ::= "switch" "(" {@link ASTExpression Expression} ")" SwitchBlock
*
* SwitchBlock ::= SwitchArrowBlock | SwitchNormalBlock
*
* SwitchArrowBlock ::= "{" ( {@link ASTSwitchLabeledRule SwitchLabeledRule} )* "}"
* SwitchNormalBlock ::= "{" ( {@linkplain ASTSwitchLabel SwitchLabel} {@linkplain ASTBlockStatement BlockStatement}* )* "}"
*
* </pre>
* <p>Their syntax is identical though, and described on {@link ASTSwitchLike}.
*/
public final class ASTSwitchExpression extends AbstractJavaExpr implements ASTExpression {
public final class ASTSwitchExpression extends AbstractJavaExpr
implements ASTExpression,
ASTSwitchLike {
ASTSwitchExpression(int id) {
super(id);
@ -52,13 +39,4 @@ public final class ASTSwitchExpression extends AbstractJavaExpr implements ASTEx
visitor.visit(this, data);
}
/**
* Gets the expression tested by this switch.
* This is the expression between the parentheses.
*/
public ASTExpression getTestedExpression() {
return (ASTExpression) jjtGetChild(0);
}
}

View File

@ -0,0 +1,65 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import java.util.List;
/**
* A fallthrough switch branch. This contains exactly one label, and zero
* or more statements. Fallthrough must be handled by looking at the siblings.
* For example, in the following, the branch for {@code case 1:} has no statements,
* while the branch for {@code case 2:} has two.
*
* <pre>{@code
*
* switch (foo) {
* case 1:
* case 2:
* do1Or2();
* break;
* default:
* doDefault();
* break;
* }
*
* }</pre>
*
*
* <pre class="grammar">
*
* SwitchFallthroughBranch ::= {@link ASTSwitchLabel SwitchLabel} ":" {@link ASTStatement Statement}*
*
* </pre>
*/
public final class ASTSwitchFallthroughBranch extends AbstractJavaNode
implements LeftRecursiveNode, ASTSwitchBranch {
ASTSwitchFallthroughBranch(int id) {
super(id);
}
ASTSwitchFallthroughBranch(JavaParser p, int id) {
super(p, id);
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
@Override
public <T> void jjtAccept(SideEffectingVisitor<T> visitor, T data) {
visitor.visit(this, data);
}
/**
* Returns the list of statements dominated by the labels. This list is possibly empty.
*/
public List<ASTStatement> getStatements() {
return findChildrenOfType(ASTStatement.class);
}
}

View File

@ -5,6 +5,7 @@
package net.sourceforge.pmd.lang.java.ast;
import java.util.Iterator;
import java.util.List;
/**
@ -37,10 +38,19 @@ public final class ASTSwitchLabel extends AbstractJavaNode implements Iterable<A
isDefault = true;
}
/** Returns true if this is the {@code default} label. */
public boolean isDefault() {
return isDefault;
}
/**
* Returns the expressions of this label, or an empty list if this
* is the default label.
*/
public List<ASTExpression> getExprList() {
return findChildrenOfType(ASTExpression.class);
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);

View File

@ -1,36 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
/**
* One of the {@linkplain ASTSwitchLabeledRule SwitchLabeledRule}s.
*
* <pre class="grammar">
*
* SwitchLabeledBlock ::= {@link ASTSwitchLabel SwitchLabel} "->" {@link ASTBlock Block}
*
* </pre>
*/
public final class ASTSwitchLabeledBlock extends AbstractJavaNode implements ASTSwitchLabeledRule {
ASTSwitchLabeledBlock(int id) {
super(id);
}
ASTSwitchLabeledBlock(JavaParser p, int id) {
super(p, id);
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
@Override
public <T> void jjtAccept(SideEffectingVisitor<T> visitor, T data) {
visitor.visit(this, data);
}
}

View File

@ -1,36 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
/**
* One of the {@linkplain ASTSwitchLabeledRule SwitchLabeledRule}s.
*
* <pre class="grammar">
*
* SwitchLabeledExpression ::= {@link ASTSwitchLabel SwitchLabel} "->" {@link ASTExpression Expression}
*
* </pre>
*/
public final class ASTSwitchLabeledExpression extends AbstractJavaNode implements ASTSwitchLabeledRule {
ASTSwitchLabeledExpression(int id) {
super(id);
}
ASTSwitchLabeledExpression(JavaParser p, int id) {
super(p, id);
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
@Override
public <T> void jjtAccept(SideEffectingVisitor<T> visitor, T data) {
visitor.visit(this, data);
}
}

View File

@ -1,29 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
/**
* A non-fallthrough switch case, written with an arrow ({@code ->})
* instead of a colon.
*
* <pre class="grammar">
*
* SwitchLabeledRule ::= {@link ASTSwitchLabeledExpression SwitchLabeledExpression}
* | {@link ASTSwitchLabeledBlock SwitchLabeledBlock}
* | {@link ASTSwitchLabeledThrowStatement SwitchLabeledThrowStatement}
*
* </pre>
*/
public interface ASTSwitchLabeledRule extends JavaNode, LeftRecursiveNode {
// needs to extend LeftRecursiveNode to fit the text range to the children
/**
* Returns the label of this expression.
*/
default ASTSwitchLabel getLabel() {
return (ASTSwitchLabel) jjtGetChild(0);
}
}

View File

@ -1,36 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
/**
* One of the {@linkplain ASTSwitchLabeledRule SwitchLabeledRule}s.
*
* <pre class="grammar">
*
* SwitchLabeledThrowStatement ::= {@link ASTSwitchLabel SwitchLabel} "->" {@link ASTThrowStatement ThrowStatement}
*
* </pre>
*/
public final class ASTSwitchLabeledThrowStatement extends AbstractJavaNode implements ASTSwitchLabeledRule {
ASTSwitchLabeledThrowStatement(int id) {
super(id);
}
ASTSwitchLabeledThrowStatement(JavaParser p, int id) {
super(p, id);
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
@Override
public <T> void jjtAccept(SideEffectingVisitor<T> visitor, T data) {
visitor.visit(this, data);
}
}

View File

@ -0,0 +1,100 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import java.util.Iterator;
import java.util.Set;
import org.apache.commons.lang3.EnumUtils;
import net.sourceforge.pmd.lang.ast.NodeStream;
/**
* Common supertype for {@linkplain ASTSwitchStatement switch statements}
* and {@linkplain ASTSwitchExpression switch expressions}. Their grammar
* is identical, and is described below. The difference is that switch
* expressions need to be exhaustive.
*
* <pre class="grammar">
*
* SwitchLike ::= {@link ASTSwitchExpression SwitchExpression}
* | {@link ASTSwitchStatement SwitchStatement}
*
* ::= "switch" "(" {@link ASTExpression Expression} ")" SwitchBlock
*
* SwitchBlock ::= SwitchArrowBlock | SwitchNormalBlock
*
* SwitchArrowBlock ::= "{" {@link ASTSwitchArrowBranch SwitchArrowBranch}* "}"
* SwitchNormalBlock ::= "{" {@linkplain ASTSwitchFallthroughBranch SwitchFallthroughBranch}* "}"
*
* </pre>
*/
public interface ASTSwitchLike extends JavaNode, Iterable<ASTSwitchBranch> {
/**
* Returns true if this switch has a {@code default} case.
*/
default boolean hasDefaultCase() {
ASTSwitchBranch last = getBranches().last();
return last != null && last.getLabel().isDefault();
}
/**
* Returns a stream of all branches of this switch.
*/
default NodeStream<ASTSwitchBranch> getBranches() {
return children(ASTSwitchBranch.class);
}
/**
* Gets the expression tested by this switch.
* This is the expression between the parentheses.
*/
default ASTExpression getTestedExpression() {
return (ASTExpression) jjtGetChild(0);
}
/**
* Returns true if this switch statement tests an expression
* having an enum type and all the constants of this type
* are covered by a switch case. Returns false if the type of
* the tested expression could not be resolved.
*/
default boolean isExhaustiveEnumSwitch() {
ASTExpression expression = getTestedExpression();
if (expression.getType() == null) {
return false;
}
if (Enum.class.isAssignableFrom(expression.getType())) {
@SuppressWarnings("unchecked")
Set<String> constantNames = EnumUtils.getEnumMap((Class<? extends Enum>) expression.getType()).keySet();
for (ASTSwitchBranch branch : this) {
// since this is an enum switch, the labels are necessarily
// the simple name of some enum constant.
constantNames.remove(branch.getLabel().getFirstDescendantOfType(ASTName.class).getImage());
}
return constantNames.isEmpty();
}
return false;
}
@Override
default Iterator<ASTSwitchBranch> iterator() {
return children(ASTSwitchBranch.class).iterator();
}
}

View File

@ -4,18 +4,12 @@
package net.sourceforge.pmd.lang.java.ast;
import java.util.Iterator;
import java.util.Set;
import org.apache.commons.lang3.EnumUtils;
/**
* Represents a {@code switch} statement. Its syntax is identical to
* {@link ASTSwitchExpression SwitchExpression}, though it does not need
* to be exhaustive. See the doc of that node for details.
* Represents a {@code switch} statement. See {@link ASTSwitchLike} for
* its grammar.
*/
public final class ASTSwitchStatement extends AbstractStatement implements Iterable<ASTSwitchLabel> {
public final class ASTSwitchStatement extends AbstractStatement implements ASTSwitchLike {
ASTSwitchStatement(int id) {
super(id);
}
@ -36,64 +30,4 @@ public final class ASTSwitchStatement extends AbstractStatement implements Itera
visitor.visit(this, data);
}
/**
* Returns true if this switch has a {@code default} case.
*/
public boolean hasDefaultCase() {
for (ASTSwitchLabel label : this) {
if (label.isDefault()) {
return true;
}
}
return false;
}
/**
* Gets the expression tested by this switch.
* This is the expression between the parentheses.
*/
public ASTExpression getTestedExpression() {
return (ASTExpression) jjtGetChild(0);
}
/**
* Returns true if this switch statement tests an expression
* having an enum type and all the constants of this type
* are covered by a switch case. Returns false if the type of
* the tested expression could not be resolved.
*/
public boolean isExhaustiveEnumSwitch() {
ASTExpression expression = getTestedExpression();
if (expression.getType() == null) {
return false;
}
if (Enum.class.isAssignableFrom(expression.getType())) {
@SuppressWarnings("unchecked")
Set<String> constantNames = EnumUtils.getEnumMap((Class<? extends Enum>) expression.getType()).keySet();
for (ASTSwitchLabel label : this) {
// since this is an enum switch, the labels are necessarily
// the simple name of some enum constant.
constantNames.remove(label.getFirstDescendantOfType(ASTName.class).getImage());
}
return constantNames.isEmpty();
}
return false;
}
@Override
public Iterator<ASTSwitchLabel> iterator() {
return children(ASTSwitchLabel.class).iterator();
}
}

View File

@ -13,7 +13,7 @@ package net.sourceforge.pmd.lang.java.ast;
*
* </pre>
*/
public final class ASTThrowStatement extends AbstractStatement {
public final class ASTThrowStatement extends AbstractStatement implements ASTSwitchArrowRHS {
ASTThrowStatement(int id) {
super(id);
@ -34,7 +34,9 @@ public final class ASTThrowStatement extends AbstractStatement {
visitor.visit(this, data);
}
/** Returns the expression for the thrown exception. */
/**
* Returns the expression for the thrown exception.
*/
public ASTExpression getExpr() {
return (ASTExpression) getFirstChild();
}

View File

@ -23,7 +23,6 @@ public class ASTYieldStatement extends AbstractStatement {
super(p, id);
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);

View File

@ -37,10 +37,6 @@ public class SideEffectingVisitorAdapter<T> implements SideEffectingVisitor<T> {
// TODO delegation
public void visit(ASTSwitchLabeledRule node, T data) {
visit((JavaNode) node, data);
}
public void visit(ASTAnyTypeDeclaration node, T data) {
visit((JavaNode) node, data);
}

View File

@ -31,9 +31,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTNumericLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTReceiverParameter;
import net.sourceforge.pmd.lang.java.ast.ASTResource;
import net.sourceforge.pmd.lang.java.ast.ASTStringLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchArrowBranch;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabeledRule;
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
import net.sourceforge.pmd.lang.java.ast.ASTType;
import net.sourceforge.pmd.lang.java.ast.ASTTypeArguments;
@ -275,7 +275,7 @@ public class LanguageLevelChecker<T> {
}
@Override
public void visit(ASTSwitchLabeledRule node, T data) {
public void visit(ASTSwitchArrowBranch node, T data) {
check(node, PreviewFeature.SWITCH_RULES, data);
visitChildren(node, data);
}

View File

@ -14,7 +14,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
import net.sourceforge.pmd.lang.java.ast.ASTForeachStatement;
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchBranch;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
@ -59,16 +59,16 @@ public class CycloVisitor extends JavaParserVisitorAdapter {
((MutableInt) data).add(CycloMetric.booleanExpressionComplexity(node.getTestedExpression()));
}
for (ASTSwitchLabel label : node) {
if (label.isDefault()) {
for (ASTSwitchBranch branch : node) {
if (branch.getLabel().isDefault()) {
// like for "else", default is not a decision point
continue;
}
if (considerBooleanPaths) {
((MutableInt) data).increment();
} else if (node.jjtGetNumChildren() > 1 + label.jjtGetChildIndex()
&& node.jjtGetChild(label.jjtGetChildIndex() + 1) instanceof ASTBlockStatement) {
} else if (node.jjtGetNumChildren() > 1 + branch.jjtGetChildIndex()
&& node.jjtGetChild(branch.jjtGetChildIndex() + 1) instanceof ASTBlockStatement) {
// an empty label is only counted if we count boolean paths
((MutableInt) data).increment();
}

View File

@ -34,9 +34,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
import net.sourceforge.pmd.lang.java.ast.ASTArguments;
import net.sourceforge.pmd.lang.java.ast.ASTArrayDimsAndInits;
import net.sourceforge.pmd.lang.java.ast.ASTArrayType;
import net.sourceforge.pmd.lang.java.ast.ASTBlock;
import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement;
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
import net.sourceforge.pmd.lang.java.ast.ASTClassLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
@ -71,7 +68,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
import net.sourceforge.pmd.lang.java.ast.ASTRelationalExpression;
import net.sourceforge.pmd.lang.java.ast.ASTShiftExpression;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabeledRule;
import net.sourceforge.pmd.lang.java.ast.ASTType;
import net.sourceforge.pmd.lang.java.ast.ASTTypeArgument;
import net.sourceforge.pmd.lang.java.ast.ASTTypeArguments;
@ -83,7 +79,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
import net.sourceforge.pmd.lang.java.ast.ASTWildcardBounds;
import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement;
import net.sourceforge.pmd.lang.java.ast.InternalApiBridge;
import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter;
import net.sourceforge.pmd.lang.java.ast.TypeNode;
@ -1135,60 +1130,59 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
@Override
public Object visit(ASTSwitchExpression node, Object data) {
super.visit(node, data);
JavaTypeDefinition type = null;
// first try to determine the type based on the first expression/break/yield of a switch rule
List<ASTSwitchLabeledRule> rules = node.findChildrenOfType(ASTSwitchLabeledRule.class);
for (ASTSwitchLabeledRule rule : rules) {
Node body = rule.jjtGetChild(1); // second child is either Expression, Block, ThrowStatement
if (body instanceof ASTExpression) {
type = ((ASTExpression) body).getTypeDefinition();
break;
} else if (body instanceof ASTBlock) {
List<ASTBreakStatement> breaks = body.findDescendantsOfType(ASTBreakStatement.class);
if (!breaks.isEmpty()) {
ASTExpression expression = breaks.get(0).getFirstChildOfType(ASTExpression.class);
if (expression != null) {
type = expression.getTypeDefinition();
break;
}
}
List<ASTYieldStatement> yields = body.findDescendantsOfType(ASTYieldStatement.class);
if (!yields.isEmpty()) {
ASTExpression expression = yields.get(0).getFirstChildOfType(ASTExpression.class);
if (expression != null) {
type = expression.getTypeDefinition();
break;
}
}
}
}
if (type == null) {
// 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<ASTBreakStatement> breaks = child.findDescendantsOfType(ASTBreakStatement.class);
if (!breaks.isEmpty()) {
ASTExpression expression = breaks.get(0).getFirstChildOfType(ASTExpression.class);
if (expression != null) {
type = expression.getTypeDefinition();
break;
}
}
List<ASTYieldStatement> yields = child.findDescendantsOfType(ASTYieldStatement.class);
if (!yields.isEmpty()) {
ASTExpression expression = yields.get(0).getFirstChildOfType(ASTExpression.class);
if (expression != null && expression.getTypeDefinition() != null) {
type = expression.getTypeDefinition();
break;
}
}
}
}
}
setTypeDefinition(node, type);
//
// JavaTypeDefinition type = null;
// // first try to determine the type based on the first expression/break/yield of a switch rule
// List<ASTSwitchLabeledRule> rules = node.findChildrenOfType(ASTSwitchLabeledRule.class);
// for (ASTSwitchLabeledRule rule : rules) {
// Node body = rule.jjtGetChild(1); // second child is either Expression, Block, ThrowStatement
// if (body instanceof ASTExpression) {
// type = ((ASTExpression) body).getTypeDefinition();
// break;
// } else if (body instanceof ASTBlock) {
// List<ASTBreakStatement> breaks = body.findDescendantsOfType(ASTBreakStatement.class);
// if (!breaks.isEmpty()) {
// ASTExpression expression = breaks.get(0).getFirstChildOfType(ASTExpression.class);
// if (expression != null) {
// type = expression.getTypeDefinition();
// break;
// }
// }
// List<ASTYieldStatement> yields = body.findDescendantsOfType(ASTYieldStatement.class);
// if (!yields.isEmpty()) {
// ASTExpression expression = yields.get(0).getFirstChildOfType(ASTExpression.class);
// if (expression != null) {
// type = expression.getTypeDefinition();
// break;
// }
// }
// }
// }
// if (type == null) {
// // 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<ASTBreakStatement> breaks = child.findDescendantsOfType(ASTBreakStatement.class);
// if (!breaks.isEmpty()) {
// ASTExpression expression = breaks.get(0).getFirstChildOfType(ASTExpression.class);
// if (expression != null) {
// type = expression.getTypeDefinition();
// break;
// }
// }
// List<ASTYieldStatement> yields = child.findDescendantsOfType(ASTYieldStatement.class);
// if (!yields.isEmpty()) {
// ASTExpression expression = yields.get(0).getFirstChildOfType(ASTExpression.class);
// if (expression != null && expression.getTypeDefinition() != null) {
// type = expression.getTypeDefinition();
// break;
// }
// }
// }
// }
// }
// setTypeDefinition(node, type);
return data;
}

View File

@ -8,7 +8,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@ -31,88 +30,15 @@ public class Java12Test {
ParserTstUtil.parseAndTypeResolveJava("11", loadSource("MultipleCaseLabels.java"));
}
@Test
public void testMultipleCaseLabels() {
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("12-preview",
loadSource("MultipleCaseLabels.java"));
Assert.assertNotNull(compilationUnit);
ASTSwitchStatement switchStatement = compilationUnit.getFirstDescendantOfType(ASTSwitchStatement.class);
Assert.assertTrue(switchStatement.jjtGetChild(0) instanceof ASTExpression);
Assert.assertTrue(switchStatement.jjtGetChild(1) instanceof ASTSwitchLabel);
ASTSwitchLabel switchLabel = switchStatement.getFirstChildOfType(ASTSwitchLabel.class);
Assert.assertEquals(3, switchLabel.findChildrenOfType(ASTExpression.class).size());
}
@Test(expected = ParseException.class)
public void testSwitchRulesJava11() {
ParserTstUtil.parseAndTypeResolveJava("11", loadSource("SwitchRules.java"));
}
@Test
public void testSwitchRules() {
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("12-preview",
loadSource("SwitchRules.java"));
Assert.assertNotNull(compilationUnit);
ASTSwitchStatement switchStatement = compilationUnit.getFirstDescendantOfType(ASTSwitchStatement.class);
Assert.assertTrue(switchStatement.jjtGetChild(0) instanceof ASTExpression);
Assert.assertTrue(switchStatement.jjtGetChild(1) instanceof ASTSwitchLabeledExpression);
ASTSwitchLabeledExpression switchLabeledExpression = (ASTSwitchLabeledExpression) switchStatement.jjtGetChild(1);
Assert.assertEquals(2, switchLabeledExpression.jjtGetNumChildren());
Assert.assertTrue(switchLabeledExpression.jjtGetChild(0) instanceof ASTSwitchLabel);
Assert.assertTrue(switchLabeledExpression.jjtGetChild(1) instanceof ASTExpression);
ASTSwitchLabeledBlock switchLabeledBlock = (ASTSwitchLabeledBlock) switchStatement.jjtGetChild(4);
Assert.assertEquals(2, switchLabeledBlock.jjtGetNumChildren());
Assert.assertTrue(switchLabeledBlock.jjtGetChild(0) instanceof ASTSwitchLabel);
Assert.assertTrue(switchLabeledBlock.jjtGetChild(1) instanceof ASTBlock);
ASTSwitchLabeledThrowStatement switchLabeledThrowStatement = (ASTSwitchLabeledThrowStatement) switchStatement.jjtGetChild(5);
Assert.assertEquals(2, switchLabeledThrowStatement.jjtGetNumChildren());
Assert.assertTrue(switchLabeledThrowStatement.jjtGetChild(0) instanceof ASTSwitchLabel);
Assert.assertTrue(switchLabeledThrowStatement.jjtGetChild(1) instanceof ASTThrowStatement);
}
@Test(expected = ParseException.class)
public void testSwitchExpressionsJava11() {
ParserTstUtil.parseAndTypeResolveJava("11", loadSource("SwitchExpressions.java"));
}
@Test
public void testSwitchExpressions() {
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("12-preview",
loadSource("SwitchExpressions.java"));
Assert.assertNotNull(compilationUnit);
ASTSwitchExpression switchExpression = compilationUnit.getFirstDescendantOfType(ASTSwitchExpression.class);
Assert.assertEquals(6, switchExpression.jjtGetNumChildren());
Assert.assertTrue(switchExpression.jjtGetChild(0) instanceof ASTExpression);
Assert.assertEquals(5, switchExpression.findChildrenOfType(ASTSwitchLabeledRule.class).size());
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());
}
@Test
public void testSwitchExpressionsBreak() {
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("12-preview",
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());
}
}

View File

@ -8,7 +8,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@ -27,44 +26,6 @@ public class Java13Test {
}
}
@Test
public void testSwitchExpressions() {
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("13-preview",
loadSource("SwitchExpressions.java"));
Assert.assertNotNull(compilationUnit);
ASTSwitchExpression switchExpression = compilationUnit.getFirstDescendantOfType(ASTSwitchExpression.class);
Assert.assertEquals(4, switchExpression.jjtGetNumChildren());
Assert.assertTrue(switchExpression.jjtGetChild(0) instanceof ASTExpression);
Assert.assertEquals(3, switchExpression.findChildrenOfType(ASTSwitchLabeledRule.class).size());
Assert.assertEquals(1, switchExpression.findChildrenOfType(ASTSwitchLabeledBlock.class).size());
Assert.assertEquals(1, switchExpression.findDescendantsOfType(ASTYieldStatement.class).size());
ASTYieldStatement yieldStatement = switchExpression.getFirstDescendantOfType(ASTYieldStatement.class);
Assert.assertEquals(Integer.TYPE, yieldStatement.getExpr().getType());
}
@Test
public void testSwitchExpressionsYield() {
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("13-preview",
loadSource("SwitchExpressionsYield.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());
ASTYieldStatement yieldStatement = switchExpression.getFirstDescendantOfType(ASTYieldStatement.class);
Assert.assertEquals("SwitchExpressionsBreak.SIX", yieldStatement.getImage());
Assert.assertTrue(yieldStatement.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());
}
@Test(expected = ParseException.class)
public void testSwitchExpressionsBeforeJava13() {

View File

@ -286,7 +286,7 @@ public class ParserCornersTest {
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("11", readAsString("SwitchWithFallthrough.java"));
Assert.assertNotNull(compilationUnit);
ASTSwitchStatement switchStatement = compilationUnit.getFirstDescendantOfType(ASTSwitchStatement.class);
Assert.assertEquals(2, switchStatement.findChildrenOfType(ASTSwitchLabel.class).size());
Assert.assertEquals(2, switchStatement.findChildrenOfType(ASTSwitchFallthroughBranch.class).size());
}
@Test
@ -294,7 +294,7 @@ public class ParserCornersTest {
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("11", readAsString("SwitchStatements.java"));
Assert.assertNotNull(compilationUnit);
ASTSwitchStatement switchStatement = compilationUnit.getFirstDescendantOfType(ASTSwitchStatement.class);
Assert.assertEquals(2, switchStatement.findChildrenOfType(ASTSwitchLabel.class).size());
Assert.assertEquals(2, switchStatement.findChildrenOfType(ASTSwitchBranch.class).size());
}

View File

@ -235,6 +235,19 @@ fun TreeNodeWrapper<Node, *>.breakStatement(label: String? = null, contents: Nod
contents()
}
fun TreeNodeWrapper<Node, *>.yieldStatement(contents: ValuedNodeSpec<ASTYieldStatement, ASTExpression?> = {null}) =
child<ASTYieldStatement> {
val e = contents()
if (e != null) it::getExpr shouldBe e
else unspecifiedChild()
}
fun TreeNodeWrapper<Node, *>.throwStatement(contents: NodeSpec<ASTThrowStatement> = EmptyAssertions) =
child<ASTThrowStatement>(ignoreChildren = contents == EmptyAssertions) {
contents()
}
fun TreeNodeWrapper<Node, *>.localVarDecl(contents: NodeSpec<ASTLocalVariableDeclaration> = EmptyAssertions) =
child<ASTLocalVariableDeclaration>(ignoreChildren = contents == EmptyAssertions) {
contents()
@ -393,6 +406,34 @@ fun TreeNodeWrapper<Node, *>.switchExpr(assertions: NodeSpec<ASTSwitchExpression
child(ignoreChildren = assertions == EmptyAssertions) {
assertions()
}
fun TreeNodeWrapper<Node, *>.switchStmt(assertions: NodeSpec<ASTSwitchStatement> = EmptyAssertions) =
child<ASTSwitchStatement>(ignoreChildren = assertions == EmptyAssertions) {
assertions()
}
fun TreeNodeWrapper<Node, *>.switchArrow(rhs: ValuedNodeSpec<ASTSwitchArrowBranch, ASTSwitchArrowRHS?> = { null }) =
child<ASTSwitchArrowBranch> {
val rhs = rhs()
if (rhs != null) it::getRightHandSide shouldBe rhs
else unspecifiedChildren(2) // label + rhs
}
fun TreeNodeWrapper<Node, *>.switchFallthrough(assertions: NodeSpec<ASTSwitchFallthroughBranch> = EmptyAssertions) =
child<ASTSwitchFallthroughBranch>(ignoreChildren = assertions == EmptyAssertions) {
assertions()
}
fun TreeNodeWrapper<Node, *>.switchLabel(assertions: NodeSpec<ASTSwitchLabel> = EmptyAssertions) =
child<ASTSwitchLabel>(ignoreChildren = assertions == EmptyAssertions) {
it::isDefault shouldBe false
assertions()
}
fun TreeNodeWrapper<Node, *>.switchDefaultLabel(assertions: NodeSpec<ASTSwitchLabel> = EmptyAssertions) =
child<ASTSwitchLabel>(ignoreChildren = assertions == EmptyAssertions) {
it::isDefault shouldBe true
it::getExprList shouldBe emptyList()
assertions()
}
fun TreeNodeWrapper<Node, *>.number(primitiveType: ASTPrimitiveType.PrimitiveType? = null, assertions: NodeSpec<ASTNumericLiteral> = EmptyAssertions) =
child<ASTNumericLiteral> {

View File

@ -1,41 +0,0 @@
/**
*
*
* @see <a href="http://openjdk.java.net/jeps/354">JEP 354: Switch Expressions (Preview)</a>
*/
public class SwitchExpressions {
private enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
public static void main(String[] args) {
Day day = Day.FRIDAY;
int j = switch (day) {
case MONDAY -> 0;
case TUESDAY -> 1;
default -> {
int k = day.toString().length();
int result = f(k);
yield result;
}
};
System.out.printf("j = %d%n", j);
String s = "Bar";
int result = switch (s) {
case "Foo":
yield 1;
case "Bar":
yield 2;
default:
System.out.println("Neither Foo nor Bar, hmmm...");
yield 0;
};
System.out.printf("result = %d%n", result);
}
private static int f(int k) {
return k+1;
}
}

View File

@ -1,36 +0,0 @@
/**
*
* @see <a href="https://openjdk.java.net/jeps/325">JEP 325: Switch Expressions (Preview)</a>
*/
public class SwitchExpressionsYield {
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: yield SwitchExpressionsBreak.SIX;
case TUESDAY : yield 7;
case THURSDAY, SATURDAY : yield 8;
case WEDNESDAY : yield 9;
default : {
int k = day * 2;
int result = f(k);
yield result;
}
};
System.out.printf("NumLetters: %d%n", numLetters);
}
private static int f(int k) {
return k*3;
}
}