Merge remote-tracking branch 'oowekyala/jdk14-pattern-matching' into jdk14
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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() :
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
@ -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.
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user