Merge remote-tracking branch 'oowekyala/jdk14-pattern-matching' into jdk14

This commit is contained in:
Andreas Dangel
2020-02-27 15:03:24 +01:00
15 changed files with 212 additions and 25 deletions

View File

@ -51,6 +51,8 @@ public abstract class BaseLanguageModule implements Language {
}
if (isDefault) {
assert defaultVersion == null
: "Default version already set to " + defaultVersion + ", cannot set it to " + languageVersion;
defaultVersion = languageVersion;
}
}
@ -109,6 +111,7 @@ public abstract class BaseLanguageModule implements Language {
@Override
public LanguageVersion getDefaultVersion() {
assert defaultVersion != null : "Null default version for language " + this;
return defaultVersion;
}

View File

@ -205,7 +205,7 @@ options {
STATIC = false;
USER_CHAR_STREAM = true;
JDK_VERSION = "1.5";
MULTI = true;
VISITOR = true;
NODE_USES_PARSER = true;
@ -294,25 +294,25 @@ public class JavaParser {
throwParseException("Can't use hexadecimal floating point literals in pre-JDK 1.5 target");
}
}
private void checkForBadNumericalLiteralslUsage(Token token) {
if (jdkVersion < 7) {
if (token.image.contains("_")) {
throwParseException("Can't use underscores in numerical literals when running in JDK inferior to 1.7 mode!");
}
if (token.image.startsWith("0b") || token.image.startsWith("0B")) {
throwParseException("Can't use binary numerical literals when running in JDK inferior to 1.7 mode!");
throwParseException("Can't use binary numerical literals when running in JDK inferior to 1.7 mode!");
}
}
}
private void checkForBadDiamondUsage() {
if (jdkVersion < 7) {
throwParseException("Cannot use the diamond generic notation when running in JDK inferior to 1.7 mode!");
}
}
private void checkForBadTryWithResourcesUsage() {
if (jdkVersion < 7) {
throwParseException("Cannot use the try-with-resources notation when running in JDK inferior to 1.7 mode!");
@ -355,6 +355,13 @@ public class JavaParser {
throwParseException("Cannot use explicit receiver parameters when running in JDK inferior to 1.8 mode!");
}
}
private void checkforBadInstanceOfPattern() {
if (jdkVersion != 14 || !preview) {
throwParseException("Cannot use type test patterns in instanceof when running in JDK other than 14-preview");
}
}
private void checkForBadAnonymousDiamondUsage() {
if (jdkVersion < 9) {
ASTAllocationExpression node = (ASTAllocationExpression)jjtree.peekNode();
@ -397,7 +404,7 @@ public class JavaParser {
}
}
private void checkForMultipleCaseLabels() {
if ((jdkVersion != 12 && jdkVersion != 13) || !preview) {
if (!switchExprAllowed()) {
throwParseException("Multiple case labels in switch statements are only supported with Java 12 or 13 Preview");
}
}
@ -410,14 +417,19 @@ public class JavaParser {
* See also comment at #Expression().
*/
private boolean inSwitchLabel = false;
private boolean switchExprAllowed() {
return jdkVersion >= 14 || jdkVersion >= 12 && preview;
}
private void checkForSwitchRules() {
if ((jdkVersion != 12 && jdkVersion != 13) || !preview) {
throwParseException("Switch rules in switch statements are only supported with Java 12 or 13 Preview");
if (!switchExprAllowed()) {
throwParseException("Switch rules in switch statements are only supported with Java 12 or 13 Preview, or Java >= 14");
}
}
private void checkForSwitchExpression() {
if ((jdkVersion != 12 && jdkVersion != 13) || !preview) {
throwParseException("Switch expressions are only supported with Java 12 or 13 Preview");
if (!switchExprAllowed()) {
throwParseException("Switch expressions are only supported with Java 12 or 13 Preview, or Java >= 14");
}
}
@ -447,7 +459,7 @@ public class JavaParser {
if (jdkVersion <= 3) {
return false;
}
return getToken(1).image.equals("assert");
}
@ -1429,7 +1441,7 @@ void EqualityExpression() #EqualityExpression(>1):
void InstanceOfExpression() #InstanceOfExpression(>1):
{}
{
RelationalExpression() [ LOOKAHEAD(2) "instanceof" Type() ]
RelationalExpression() [ LOOKAHEAD(2) "instanceof" Type() [ {checkforBadInstanceOfPattern();} VariableDeclaratorId() #TypeTestPattern(2) ] ]
}
void RelationalExpression() #RelationalExpression(>1):
@ -1447,7 +1459,7 @@ void RelationalExpression() #RelationalExpression(>1):
void ShiftExpression() #ShiftExpression(>1):
{}
{
AdditiveExpression()
AdditiveExpression()
( LOOKAHEAD(2)
( "<<" { jjtThis.setImage("<<");}
| RSIGNEDSHIFT() { jjtThis.setImage(">>"); }
@ -1717,7 +1729,7 @@ void Block() :
{Token t;}
{
"{"
( BlockStatement() )* t = "}" { if (isPrecededByComment(t)) { jjtThis.setContainsComment(); } }
}
@ -1971,7 +1983,7 @@ void ResourceSpecification() :
"("
Resources()
(LOOKAHEAD(2) ";")?
")"
")"
}
void Resources() :

View File

@ -28,8 +28,10 @@ public class JavaLanguageModule extends BaseLanguageModule {
addVersion("11", new JavaLanguageHandler(11), false);
addVersion("12", new JavaLanguageHandler(12), false);
addVersion("12-preview", new JavaLanguageHandler(12, true), false);
addVersion("13", new JavaLanguageHandler(13), true);
addVersion("13", new JavaLanguageHandler(13), true); // 13 is the default
addVersion("13-preview", new JavaLanguageHandler(13, true), false);
addVersion("14", new JavaLanguageHandler(14), false);
addVersion("14-preview", new JavaLanguageHandler(14, true), false);
}
}

View File

@ -16,7 +16,7 @@ import net.sourceforge.pmd.annotation.InternalApi;
*
* <pre>
*
* InstanceOfExpression ::= {@linkplain ASTShiftExpression ShiftExpression} "instanceof" {@linkplain ASTType Type}
* InstanceOfExpression ::= {@linkplain ASTShiftExpression ShiftExpression} "instanceof" ({@linkplain ASTType Type} | {@link ASTPattern Pattern})
*
* </pre>
*/
@ -46,7 +46,9 @@ public class ASTInstanceOfExpression extends AbstractJavaTypeNode {
* Gets the type against which the expression is tested.
*/
public ASTType getTypeNode() {
return (ASTType) getChild(1);
JavaNode child = getChild(1);
return child instanceof ASTType ? (ASTType) child
: ((ASTTypeTestPattern) child).getTypeNode();
}
}

View File

@ -0,0 +1,29 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import net.sourceforge.pmd.annotation.Experimental;
/**
* A pattern (for pattern matching constructs like {@link ASTInstanceOfExpression InstanceOfExpression}).
* This is a JDK 14 preview feature and is subject to change.
*
* <p>This interface will be implemented by all forms of patterns. For
* now, only type test patterns are supported. Record deconstruction
* patterns are in the works for JDK 15 preview.
*
* <p>See https://openjdk.java.net/jeps/305, https://openjdk.java.net/jeps/8235186
*
* <pre>
*
* Pattern ::= {@link ASTTypeTestPattern TypeTestPattern}
*
* </pre>
*/
@Experimental
public interface ASTPattern extends JavaNode {
}

View File

@ -0,0 +1,50 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import net.sourceforge.pmd.annotation.Experimental;
/**
* A type test pattern (JDK 14 preview feature). This can be found on
* the right-hand side of an {@link ASTInstanceOfExpression InstanceOfExpression}.
*
* <pre class="grammar">
*
* TypeTestPattern ::= {@linkplain ASTType Type} {@link ASTVariableDeclaratorId VariableDeclaratorId}
*
* </pre>
*/
@Experimental
public final class ASTTypeTestPattern extends AbstractJavaNode implements ASTPattern {
ASTTypeTestPattern(int id) {
super(id);
}
ASTTypeTestPattern(JavaParser p, int id) {
super(p, id);
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
/**
* Gets the type against which the expression is tested.
*/
public ASTType getTypeNode() {
return (ASTType) getChild(0);
}
/** Returns the declared variable. */
public ASTVariableDeclaratorId getVarId() {
return (ASTVariableDeclaratorId) getChild(1);
}
}

View File

@ -6,6 +6,7 @@ package net.sourceforge.pmd.lang.java.ast;
import java.util.List;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
@ -239,6 +240,15 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim
return isLambdaParamWithNoType() || isLocalVariableTypeInferred() || isLambdaTypeInferred();
}
/**
* Returns true if this is a binding variable in a
* {@linkplain ASTPattern pattern}.
*/
@Experimental
public boolean isPatternBinding() {
return getParent() instanceof ASTPattern;
}
private boolean isLocalVariableTypeInferred() {
if (isResourceDeclaration()) {
@ -288,6 +298,8 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim
} else if (isTypeInferred()) {
// lambda expression with lax types. The type is inferred...
return null;
} else if (getParent() instanceof ASTTypeTestPattern) {
return ((ASTTypeTestPattern) getParent()).getTypeNode();
} else {
Node n = getParent().getParent();
if (n instanceof ASTLocalVariableDeclaration || n instanceof ASTFieldDeclaration) {

View File

@ -4,6 +4,8 @@
package net.sourceforge.pmd.lang.java.ast;
import net.sourceforge.pmd.annotation.Experimental;
/**
* External wrapper for a visitor decorator. This one drives the AST visit, delegating to the base controlless visitor
* given in the constructor. Add decorators using the {@link #decorateWith(JavaParserVisitorDecorator)}.
@ -898,4 +900,11 @@ public class JavaParserDecoratedVisitor implements JavaParserVisitor {
visitor.visit(node, data);
return visit((JavaNode) node, data);
}
@Override
@Experimental
public Object visit(ASTTypeTestPattern node, Object data) {
visitor.visit(node, data);
return visit((JavaNode) node, data);
}
}

View File

@ -625,4 +625,9 @@ public class JavaParserVisitorAdapter implements JavaParserVisitor {
public Object visit(ASTYieldStatement node, Object data) {
return visit((JavaNode) node, data);
}
@Override
public Object visit(ASTTypeTestPattern node, Object data) {
return visit((JavaNode) node, data);
}
}

View File

@ -758,4 +758,9 @@ public class JavaParserVisitorDecorator implements JavaParserControllessVisitor
public Object visit(ASTYieldStatement node, Object data) {
return visitor.visit(node, data);
}
@Override
public Object visit(ASTTypeTestPattern node, Object data) {
return visitor.visit(node, data);
}
}

View File

@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.java.rule;
import java.util.List;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.JavaLanguageModule;
@ -123,6 +124,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTTypeBound;
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTTypeParameter;
import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters;
import net.sourceforge.pmd.lang.java.ast.ASTTypeTestPattern;
import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpressionNotPlusMinus;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
@ -834,4 +836,10 @@ public abstract class AbstractJavaRule extends AbstractRule implements JavaParse
public Object visit(ASTYieldStatement node, Object data) {
return visit((JavaNode) node, data);
}
@Override
@Experimental
public Object visit(ASTTypeTestPattern node, Object data) {
return visit((JavaNode) node, data);
}
}

View File

@ -24,9 +24,14 @@ public abstract class AbstractJavaScope extends AbstractScope {
}
protected void checkForDuplicatedNameDeclaration(NameDeclaration declaration) {
if (declaration instanceof VariableNameDeclaration && getDeclarations().keySet().contains(declaration)) {
throw new RuntimeException(declaration + " is already in the symbol table");
}
// Don't throw an exception, type tests patterns are tricky to implement
// and given it's a preview feature, and the sym table will be replaced
// for 7.0, it's not useful to support them.
// See https://cr.openjdk.java.net/~briangoetz/amber/pattern-semantics.html#scoping-of-pattern-variables
// if (declaration instanceof VariableNameDeclaration && getDeclarations().keySet().contains(declaration)) {
// throw new RuntimeException(declaration + " is already in the symbol table");
// }
}
@Override

View File

@ -0,0 +1,34 @@
package net.sourceforge.pmd.lang.java.ast
import net.sourceforge.pmd.lang.ast.test.shouldBe
import net.sourceforge.pmd.lang.java.ast.JavaVersion.J14__PREVIEW
import java.io.IOException
class ASTPatternTest : ParserTestSpec({
parserTest("Test patterns only available on JDK 14 (preview)", javaVersions = !J14__PREVIEW) {
expectParseException("Cannot use type test patterns in instanceof when running in JDK other than 14-preview") {
parseAstExpression("obj instanceof Class c")
}
}
parserTest("Test simple patterns", javaVersion = J14__PREVIEW) {
importedTypes += IOException::class.java
"obj instanceof Class c" should matchExpr<ASTInstanceOfExpression> {
unspecifiedChild()
child<ASTTypeTestPattern> {
it::getTypeNode shouldBe child(ignoreChildren = true) {}
it::getVarId shouldBe child {
it::getVariableName shouldBe "c"
}
}
}
}
})

View File

@ -3,18 +3,28 @@ package net.sourceforge.pmd.lang.java.ast
import io.kotlintest.matchers.string.shouldContain
import io.kotlintest.shouldThrow
import net.sourceforge.pmd.lang.ast.Node
import net.sourceforge.pmd.lang.ast.test.*
import net.sourceforge.pmd.lang.ast.test.Assertions
import net.sourceforge.pmd.lang.ast.test.NodeSpec
import net.sourceforge.pmd.lang.ast.test.matchNode
import net.sourceforge.pmd.lang.ast.test.shouldMatchNode
import net.sourceforge.pmd.lang.java.JavaParsingHelper
/**
* Represents the different Java language versions.
*/
enum class JavaVersion : Comparable<JavaVersion> {
J1_3, J1_4, J1_5, J1_6, J1_7, J1_8, J9, J10, J11, J12, J12__PREVIEW, J13, J13__PREVIEW;
J1_3, J1_4, J1_5, J1_6, J1_7, J1_8, J9, J10, J11,
J12, J12__PREVIEW,
J13, J13__PREVIEW,
J14, J14__PREVIEW;
/** Name suitable for use with e.g. [JavaParsingHelper.parse] */
val pmdName: String = name.removePrefix("J").replaceFirst("__", "-").replace('_', '.').toLowerCase()
val parser: JavaParsingHelper = JavaParsingHelper.WITH_PROCESSING.withDefaultVersion(pmdName)
operator fun not(): List<JavaVersion> = values().toList() - this
/**
* Overloads the range operator, e.g. (`J9..J11`).
* If both operands are the same, a singleton list is returned.

View File

@ -49,7 +49,8 @@ abstract class BaseParsingHelper<Self : BaseParsingHelper<Self, T>, T : RootNode
*/
fun getVersion(version: String?): LanguageVersion {
val language = LanguageRegistry.getLanguage(langName)
return if (version == null) language.defaultVersion else language.getVersion(version)
return if (version == null) language.defaultVersion
else language.getVersion(version) ?: throw AssertionError("Unsupported version $version for language $language")
}
val defaultVersion: LanguageVersion