This commit is contained in:
Clément Fournier
2020-01-07 22:24:32 +01:00
parent ee1e9bf286
commit 17305f3781
14 changed files with 161 additions and 173 deletions

View File

@ -1802,6 +1802,7 @@ void FormalParameter() :
VariableDeclaratorId()
}
void ConstructorDeclaration(int modifiers) :
{jjtThis.setModifiers(modifiers);
JavaccToken t;}
@ -1927,15 +1928,10 @@ void AnnotatedClassOrInterfaceType() #void:
* See https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-UnannType
*/
void Type() #void:
{}
{
JavaccToken t;
}
{
// lookahead to catch arrays of primitive types.
// we can't lookahead for just PrimitiveType() "["
// because the "[" may be preceded by annotations
LOOKAHEAD(PrimitiveType() ArrayTypeDim() | <IDENTIFIER>) ReferenceType()
| PrimitiveType()
PrimitiveType() [ LOOKAHEAD(2) Dims() #ArrayType(2) ]
| ClassOrInterfaceType() [ LOOKAHEAD(2) Dims() #ArrayType(2) ]
}
void Dims() #ArrayDimensions:
@ -2522,7 +2518,6 @@ void LambdaParameterList():
void LambdaParameter():
{
boolean isVarType = false;
ASTLambdaParameter me = jjtThis;
boolean isFinal = false;
}
{
@ -2985,22 +2980,12 @@ void CatchClause() :
}
void CatchParameter() :
{boolean isFinal = false;}
void CatchParameter():
{boolean isFinal = false; boolean multi=false;}
{
isFinal=LocalVarModifierList() UnionType() VariableDeclaratorId()
{jjtThis.setFinal(isFinal);}
}
// Special type for catch formal parameters
// Eg `IOException | ParseException`
void UnionType() #UnionType(isUnion):
{boolean isUnion=false;}
{
// Annotations of the first class type belong to the
// catch parameter (the variable) because of syntactic ambiguity
// This is similar to how a local var type works.
ClassOrInterfaceType() ( "|" {isUnion=true;} AnnotatedClassOrInterfaceType() )*
isFinal=LocalVarModifierList() {jjtThis.setFinal(isFinal);}
( AnnotatedClassOrInterfaceType() ( "|" AnnotatedClassOrInterfaceType() {multi=true;} )* ) #UnionType(multi)
VariableDeclaratorId()
}
void FinallyClause() :

View File

@ -4,17 +4,12 @@
package net.sourceforge.pmd.lang.java.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A "catch" clause of a {@linkplain ASTTryStatement try statement}.
*
* <pre class="grammar">
*
* CatchClause ::= "catch" "(" {@link ASTFormalParameter FormalParameter} ")" {@link ASTBlock Block}
* CatchClause ::= "catch" "(" {@link ASTCatchParameter CatchParameter} ")" {@link ASTBlock Block}
*
* </pre>
*/
@ -39,73 +34,15 @@ public final class ASTCatchClause extends AbstractJavaNode {
}
/**
* Returns true if this node is a multi-catch statement,
* that is, it catches several unrelated exception types
* at the same time. Such a block can be declared like the
* following for example:
*
* <p>{@code catch (IllegalStateException | IllegalArgumentException e) {}}
*
* @return True if this node is a multi-catch statement
*/
public boolean isMulticatchStatement() {
return getFormal().isMultiCatch();
}
/**
* Returns the {@linkplain ASTCatchParameter CatchParameter} node.
*/
public ASTCatchParameter getFormal() {
return (ASTCatchParameter) jjtGetChild(0);
}
/**
* Returns the ID of the declared variable.
*/
public ASTVariableDeclaratorId getVariableId() {
return getFormal().getVariableId();
}
/**
* Returns the Block node of this catch branch.
*/
public ASTBlock getBlock() {
return (ASTBlock) getLastChild();
}
/**
* Returns the list of type nodes denoting the exception types
* caught by this catch block. The returned list has at least
* one element.
*/
public List<ASTType> getCaughtExceptionTypeNodes() {
ASTType typeNode = getFormal().getTypeNode();
return typeNode instanceof ASTUnionType
? typeNode.findChildrenOfType(ASTType.class)
: Collections.singletonList(typeNode);
/** Returns the catch parameter. */
public ASTCatchParameter getParameter() {
return (ASTCatchParameter) getFirstChild();
}
/**
* Returns the list of exception types caught by this catch block.
* Any of these can be null, if they couldn't be resolved. This can
* happen if the auxclasspath is not correctly set.
*/
@SuppressWarnings("unchecked")
public List<Class<? extends Exception>> getCaughtExceptionTypes() {
List<Class<? extends Exception>> result = new ArrayList<>();
for (ASTType type : getCaughtExceptionTypeNodes()) {
result.add((Class<? extends Exception>) type.getType());
}
return result;
}
/**
* Returns exception name caught by this catch block.
*/
public String getExceptionName() {
return getVariableId().getVariableName();
/** Returns the body of this catch branch. */
public ASTBlock getBody() {
return getFirstChildOfType(ASTBlock.class);
}
}

View File

@ -1,13 +1,15 @@
/*
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Formal parameter of a {@linkplain ASTCatchStatement catch statement}.
* The type node may be a {@link ASTUnionType union type}, which represents
* multi-catch clauses.
* Formal parameter of a {@linkplain ASTCatchClause catch clause}
* to represent the declared exception variable.
*
* <pre class="grammar">
*
@ -15,7 +17,7 @@ package net.sourceforge.pmd.lang.java.ast;
*
* </pre>
*/
public class ASTCatchParameter extends AbstractJavaTypeNode implements Annotatable {
public class ASTCatchParameter extends AbstractJavaNode implements InternalInterfaces.VariableIdOwner {
private boolean isFinal;
@ -28,14 +30,6 @@ public class ASTCatchParameter extends AbstractJavaTypeNode implements Annotatab
}
public boolean isFinal() {
return isFinal;
}
void setFinal(boolean f) {
isFinal = f;
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
@ -47,28 +41,43 @@ public class ASTCatchParameter extends AbstractJavaTypeNode implements Annotatab
visitor.visit(this, data);
}
/** Returns the name of the variable. */
public String getName() {
return getVariableId().getVariableName();
public boolean isFinal() {
return isFinal;
}
/** Returns the declarator ID of this catch parameter. */
public ASTVariableDeclaratorId getVariableId() {
void setFinal(boolean aFinal) {
isFinal = aFinal;
}
/**
* Returns true if this is a multi-catch parameter,
* that is, it catches several unrelated exception types
* at the same time. For example:
*
* <pre>catch (IllegalStateException | IllegalArgumentException e) {}</pre>
*/
public boolean isMulticatch() {
return getTypeNode() instanceof ASTUnionType;
}
@Override
@NonNull
public ASTVariableDeclaratorId getVarId() {
return (ASTVariableDeclaratorId) getLastChild();
}
/**
* Returns the type node of this formal parameter. This may be
* a {@linkplain ASTUnionType union type}.
*/
public ASTType getTypeNode() {
return getFirstChildOfType(ASTType.class);
/** Returns the name of this parameter. */
public String getName() {
return getVarId().getVariableName();
}
/**
* Returns true if this is a multi-catch node.
* Returns the type node of this catch parameter. May be a
* {@link ASTUnionType UnionType}.
*/
public boolean isMultiCatch() {
return getTypeNode() instanceof ASTUnionType;
public ASTType getTypeNode() {
return children(ASTType.class).first();
}
}

View File

@ -18,7 +18,7 @@ import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefin
* FormalParameter ::= ( "final" | {@link ASTAnnotation Annotation} )* {@link ASTType Type} [ "..." ] {@link ASTVariableDeclaratorId VariableDeclaratorId}
* </pre>
*/
public class ASTFormalParameter extends AbstractJavaAccessTypeNode implements Dimensionable, Annotatable {
public final class ASTFormalParameter extends AbstractJavaAccessTypeNode implements Dimensionable, Annotatable {
private boolean isVarargs;

View File

@ -24,7 +24,10 @@ import java.util.Iterator;
*
* </pre>
*/
public final class ASTIntersectionType extends AbstractJavaTypeNode implements ASTReferenceType, JSingleChildNode<ASTType>, Iterable<ASTType> {
public final class ASTIntersectionType extends AbstractJavaTypeNode
implements ASTReferenceType,
InternalInterfaces.AtLeastOneChildOfType<ASTType>,
Iterable<ASTType> {
ASTIntersectionType(int id) {
super(id);

View File

@ -16,7 +16,9 @@ import java.util.List;
*
* </pre>
*/
public final class ASTResourceList extends AbstractJavaNode implements Iterable<ASTResource>, JSingleChildNode<ASTResource> {
public final class ASTResourceList extends AbstractJavaNode
implements Iterable<ASTResource>,
InternalInterfaces.AtLeastOneChildOfType<ASTResource> {
private boolean trailingSemi;

View File

@ -69,7 +69,7 @@ public final class ASTTryStatement extends AbstractStatement {
* Returns the body of this try statement.
*/
public ASTBlock getBody() {
return (ASTBlock) jjtGetChild(1);
return children(ASTBlock.class).first();
}
/**

View File

@ -4,9 +4,6 @@
package net.sourceforge.pmd.lang.java.ast;
import java.util.Collections;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.annotation.Experimental;

View File

@ -4,14 +4,19 @@
package net.sourceforge.pmd.lang.java.ast;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import net.sourceforge.pmd.lang.java.ast.InternalInterfaces.AtLeastOneChildOfType;
/**
* Represents the type node of a multi-catch statement. This node is used
* to make the grammar of {@link ASTCatchStatement CatchStatement} more
* to make the grammar of {@link ASTCatchParameter CatchParameter} more
* straightforward. Note though, that the Java type system does not feature
* union types per se. The type of this node is defined as the least upper-bound
* union types at all. The type of this node is defined as the least upper-bound
* of all its components.
*
* <pre class="grammar">
@ -20,7 +25,10 @@ import java.util.Iterator;
*
* </pre>
*/
public final class ASTUnionType extends AbstractJavaTypeNode implements ASTReferenceType, JSingleChildNode<ASTClassOrInterfaceType>, Iterable<ASTClassOrInterfaceType> {
public final class ASTUnionType extends AbstractJavaTypeNode
implements ASTReferenceType,
AtLeastOneChildOfType<ASTClassOrInterfaceType>,
Iterable<ASTClassOrInterfaceType> {
ASTUnionType(int id) {
super(id);
@ -34,8 +42,7 @@ public final class ASTUnionType extends AbstractJavaTypeNode implements ASTRefer
@Override
public String getTypeImage() {
// TODO
return iterator().next().getTypeImage();
return children(ASTClassOrInterfaceType.class).toStream().map(ASTClassOrInterfaceType::getTypeImage).collect(Collectors.joining(" | "));
}
@Override
@ -49,9 +56,17 @@ public final class ASTUnionType extends AbstractJavaTypeNode implements ASTRefer
visitor.visit(this, data);
}
/**
* Returns the list of component types.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public List<ASTClassOrInterfaceType> getComponents() {
return (List<ASTClassOrInterfaceType>) (List) Arrays.asList(children);
}
@Override
public Iterator<ASTClassOrInterfaceType> iterator() {
return new NodeChildrenIterator<>(this, ASTClassOrInterfaceType.class);
return children(ASTClassOrInterfaceType.class).iterator();
}
@Override

View File

@ -92,4 +92,38 @@ final class InternalInterfaces {
}
}
/**
* Tags a node that has at least one child, then some methods never
* return null.
*/
interface AtLeastOneChildOfType<T extends JavaNode> extends JavaNode {
@Override
T jjtGetChild(int index);
/** Returns the first child of this node, never null. */
@Override
@NonNull
default T getFirstChild() {
assert jjtGetNumChildren() > 0;
return jjtGetChild(0);
}
/** Returns the last child of this node, never null. */
@Override
@NonNull
default T getLastChild() {
assert jjtGetNumChildren() > 0;
return jjtGetChild(jjtGetNumChildren() - 1);
}
}
interface VariableIdOwner extends JavaNode {
/** Returns the id of the declared variable. */
ASTVariableDeclaratorId getVarId();
}
}

View File

@ -254,7 +254,7 @@ public class LanguageLevelChecker<T> {
@Override
public void visit(ASTCatchClause node, T data) {
if (node.isMulticatchStatement()) {
if (node.getParameter().isMulticatch()) {
check(node, RegularLanguageFeature.COMPOSITE_CATCH_CLAUSES, data);
}
visitChildren(node, data);

View File

@ -16,7 +16,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
import net.sourceforge.pmd.lang.java.ast.ASTType;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
@ -30,7 +29,7 @@ public class IdenticalCatchBranchesRule extends AbstractJavaRule {
private boolean areEquivalent(ASTCatchClause st1, ASTCatchClause st2) {
return hasSameSubTree(st1.getBlock(), st2.getBlock(), st1.getExceptionName(), st2.getExceptionName());
return hasSameSubTree(st1.getBody(), st2.getBody(), st1.getParameter().getName(), st2.getParameter().getName());
}
@ -71,17 +70,7 @@ public class IdenticalCatchBranchesRule extends AbstractJavaRule {
// Gets the representation of the set of catch statements as a single multicatch
private String getCaughtExceptionsAsString(ASTCatchClause stmt) {
StringBuilder sb = new StringBuilder();
final String delim = " | ";
for (ASTType type : stmt.getCaughtExceptionTypeNodes()) {
sb.append(type.getTypeImage()).append(delim);
}
// remove the last delimiter
sb.replace(sb.length() - 3, sb.length(), "");
return sb.toString();
return stmt.getParameter().getTypeNode().getTypeImage();
}

View File

@ -22,22 +22,15 @@ class ASTCatchClauseTest : ParserTestSpec({
importedTypes += IOException::class.java
"try { } catch (IOException ioe) { }" should matchStmt<ASTTryStatement> {
block()
it::getBody shouldBe block { }
catchClause("ioe") {
it::isMulticatchStatement shouldBe false
it::getExceptionName shouldBe "ioe"
var types: List<ASTClassOrInterfaceType>? = null
catchFormal("ioe") {
types = listOf(classType("IOException"))
it::isMulticatch shouldBe false
it::getTypeNode shouldBe classType("IOException")
variableId("ioe")
}
block()
it::getCaughtExceptionTypeNodes shouldBe types!!
it::getBody shouldBe block { }
}
}
@ -48,26 +41,49 @@ class ASTCatchClauseTest : ParserTestSpec({
importedTypes += IOException::class.java
"try { } catch (IOException | AssertionError e) { }" should matchStmt<ASTTryStatement> {
block()
it::getBody shouldBe block { }
catchClause("e") {
it::isMulticatchStatement shouldBe true
it::getExceptionName shouldBe "e"
var types: List<ASTClassOrInterfaceType>? = null
catchFormal("e") {
unionType {
val t1 = classType("IOException")
val t2 = classType("AssertionError")
it::isMulticatch shouldBe true
types = listOf(t1, t2)
it::getTypeNode shouldBe unionType {
classType("IOException")
classType("AssertionError")
}
variableId("e")
}
block()
it::getCaughtExceptionTypeNodes shouldBe types!!
it::getBody shouldBe block { }
}
}
}
parserTest("Test annotated multicatch", javaVersions = J1_8..Latest) {
importedTypes += IOException::class.java
"try { } catch (@B IOException | @A AssertionError e) { }" should matchStmt<ASTTryStatement> {
it::getBody shouldBe block { }
catchClause("e") {
catchFormal("e") {
it::isMulticatch shouldBe true
annotation("B") // not a type annotation
unionType {
classType("IOException")
classType("AssertionError") {
annotation("A")
}
}
variableId("e")
}
block {}
}
}

View File

@ -56,9 +56,9 @@ fun TreeNodeWrapper<Node, *>.annotation(name: String, spec: NodeSpec<ASTAnnotati
spec()
}
fun TreeNodeWrapper<Node, *>.catchClause(name:String, spec: NodeSpec<ASTCatchClause> = EmptyAssertions) =
fun TreeNodeWrapper<Node, *>.catchClause(name: String, spec: NodeSpec<ASTCatchClause> = EmptyAssertions) =
child<ASTCatchClause> {
it::getExceptionName shouldBe name
it.parameter::getName shouldBe name
spec()
}
@ -280,6 +280,7 @@ fun TreeNodeWrapper<Node, *>.classType(simpleName: String, contents: NodeSpec<AS
it::getSimpleName shouldBe simpleName
contents()
}
fun TreeNodeWrapper<Node, *>.unionType(contents: NodeSpec<ASTUnionType> = EmptyAssertions) =
child<ASTUnionType>(ignoreChildren = contents == EmptyAssertions) {
contents()