[java] JEP 394: Pattern Matching for instanceof for Java16
The TypePattern now allows the final keyword and Annotations. Pattern variables are now only effectively final.
This commit is contained in:
@ -1,4 +1,7 @@
|
||||
/**
|
||||
* JEP 394: Pattern Matching for instanceof for Java16
|
||||
* Andreas Dangel 02/2021
|
||||
*====================================================================
|
||||
* Remove support for Java 13 preview language features.
|
||||
* Promote text blocks as a permanent language features with Java 15.
|
||||
* Support Pattern Matching for instanceof with Java 15 Preview.
|
||||
@ -378,8 +381,8 @@ public class JavaParser {
|
||||
}
|
||||
|
||||
private void checkforBadInstanceOfPattern() {
|
||||
if (jdkVersion != 14 && jdkVersion != 15 || !preview) {
|
||||
throwParseException("Pattern Matching for instanceof is only supported with Java 14 Preview and Java 15 Preview");
|
||||
if (!(jdkVersion == 14 && preview || jdkVersion == 15 && preview || jdkVersion == 16)) {
|
||||
throwParseException("Pattern Matching for instanceof is only supported with Java 14 Preview and Java 15 Preview and Java 16");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1682,10 +1685,26 @@ void EqualityExpression() #EqualityExpression(>1):
|
||||
InstanceOfExpression() ( LOOKAHEAD(2) ( "==" {jjtThis.setImage("==");} | "!=" {jjtThis.setImage("!=");} ) InstanceOfExpression() )*
|
||||
}
|
||||
|
||||
void TypePattern() #TypeTestPattern:
|
||||
{}
|
||||
{
|
||||
( "final" {jjtThis.setFinal(true);} | Annotation() )*
|
||||
Type()
|
||||
VariableDeclaratorId()
|
||||
}
|
||||
|
||||
void InstanceOfExpression() #InstanceOfExpression(>1):
|
||||
{}
|
||||
{
|
||||
RelationalExpression() [ LOOKAHEAD(2) "instanceof" Type() [ {checkforBadInstanceOfPattern();} VariableDeclaratorId() #TypeTestPattern(2) ] ]
|
||||
RelationalExpression()
|
||||
[ "instanceof"
|
||||
(
|
||||
LOOKAHEAD("final" | "@") {checkforBadInstanceOfPattern();} TypePattern()
|
||||
|
|
||||
Type()
|
||||
[ {checkforBadInstanceOfPattern();} VariableDeclaratorId() #TypeTestPattern(2) ]
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
void RelationalExpression() #RelationalExpression(>1):
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
@ -8,22 +8,21 @@ import net.sourceforge.pmd.annotation.Experimental;
|
||||
|
||||
/**
|
||||
* A pattern (for pattern matching constructs like {@link ASTInstanceOfExpression InstanceOfExpression}).
|
||||
* This is a JDK 14 and JDK 15 preview feature and is subject to change.
|
||||
* This is a JDK 16 feature.
|
||||
*
|
||||
* <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
|
||||
* patterns is planned for a future JDK version.
|
||||
*
|
||||
* <pre class="grammar">
|
||||
*
|
||||
* Pattern ::= {@link ASTTypeTestPattern TypeTestPattern}
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://openjdk.java.net/jeps/394">JEP 394: Pattern Matching for instanceof</a>
|
||||
*/
|
||||
@Experimental
|
||||
public interface ASTPattern extends JavaNode {
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,24 +1,29 @@
|
||||
/**
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.sourceforge.pmd.annotation.Experimental;
|
||||
|
||||
/**
|
||||
* A type test pattern (JDK 14 preview feature). This can be found on
|
||||
* A type pattern (JDK16). This can be found on
|
||||
* the right-hand side of an {@link ASTInstanceOfExpression InstanceOfExpression}.
|
||||
*
|
||||
* <pre class="grammar">
|
||||
*
|
||||
* TypeTestPattern ::= {@linkplain ASTType Type} {@link ASTVariableDeclaratorId VariableDeclaratorId}
|
||||
* TypeTestPattern ::= ( "final" | {@linkplain ASTAnnotation Annotation} )* {@linkplain ASTType Type} {@link ASTVariableDeclaratorId VariableDeclaratorId}
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
*
|
||||
* @see <a href="https://openjdk.java.net/jeps/394">JEP 394: Pattern Matching for instanceof</a>
|
||||
*/
|
||||
@Experimental
|
||||
public final class ASTTypeTestPattern extends AbstractJavaNode implements ASTPattern {
|
||||
public final class ASTTypeTestPattern extends AbstractJavaAnnotatableNode implements ASTPattern {
|
||||
|
||||
private boolean isFinal;
|
||||
|
||||
ASTTypeTestPattern(int id) {
|
||||
super(id);
|
||||
@ -34,17 +39,28 @@ public final class ASTTypeTestPattern extends AbstractJavaNode implements ASTPat
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ASTAnnotation> getDeclaredAnnotations() {
|
||||
return this.findChildrenOfType(ASTAnnotation.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type against which the expression is tested.
|
||||
*/
|
||||
public ASTType getTypeNode() {
|
||||
return (ASTType) getChild(0);
|
||||
return getFirstChildOfType(ASTType.class);
|
||||
}
|
||||
|
||||
/** Returns the declared variable. */
|
||||
public ASTVariableDeclaratorId getVarId() {
|
||||
return (ASTVariableDeclaratorId) getChild(1);
|
||||
return getFirstChildOfType(ASTVariableDeclaratorId.class);
|
||||
}
|
||||
|
||||
void setFinal(boolean isFinal) {
|
||||
this.isFinal = isFinal;
|
||||
}
|
||||
|
||||
boolean isFinal() {
|
||||
return isFinal;
|
||||
}
|
||||
}
|
||||
|
@ -199,9 +199,10 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim
|
||||
return true;
|
||||
} else if (isLambdaParamWithNoType()) {
|
||||
return false;
|
||||
} else if (isPatternBinding()) {
|
||||
// implicitly like final, assignment of a pattern binding is not allowed
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getParent() instanceof ASTTypeTestPattern) {
|
||||
return ((ASTTypeTestPattern) getParent()).isFinal();
|
||||
}
|
||||
|
||||
if (getParent() instanceof ASTRecordComponent) {
|
||||
|
@ -83,7 +83,7 @@ public class Java14PreviewTest {
|
||||
Assert.assertEquals(String.class, variable.getType());
|
||||
Assert.assertEquals("s", variable.getVariableName());
|
||||
Assert.assertTrue(variable.isPatternBinding());
|
||||
Assert.assertTrue(variable.isFinal());
|
||||
Assert.assertFalse(variable.isFinal());
|
||||
// Note: these variables are not part of the symbol table
|
||||
// See ScopeAndDeclarationFinder#visit(ASTVariableDeclaratorId, Object)
|
||||
Assert.assertNull(variable.getNameDeclaration());
|
||||
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.ParseException;
|
||||
import net.sourceforge.pmd.lang.ast.test.BaseParsingHelper;
|
||||
import net.sourceforge.pmd.lang.ast.test.BaseTreeDumpTest;
|
||||
import net.sourceforge.pmd.lang.ast.test.RelevantAttributePrinter;
|
||||
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
|
||||
|
||||
public class Java16TreeDumpTest extends BaseTreeDumpTest {
|
||||
private final JavaParsingHelper java16 =
|
||||
JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("16")
|
||||
.withResourceContext(Java15TreeDumpTest.class, "jdkversiontests/java16/");
|
||||
private final JavaParsingHelper java16p = java16.withDefaultVersion("16-preview");
|
||||
private final JavaParsingHelper java15 = java16.withDefaultVersion("15");
|
||||
|
||||
public Java16TreeDumpTest() {
|
||||
super(new RelevantAttributePrinter(), ".java");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseParsingHelper<?, ?> getParser() {
|
||||
return java16;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patternMatchingInstanceof() {
|
||||
doTest("PatternMatchingInstanceof");
|
||||
|
||||
// extended tests for type resolution etc.
|
||||
ASTCompilationUnit compilationUnit = java16.parseResource("PatternMatchingInstanceof.java");
|
||||
List<ASTInstanceOfExpression> instanceOfExpressions = compilationUnit.findDescendantsOfType(ASTInstanceOfExpression.class);
|
||||
for (ASTInstanceOfExpression expr : instanceOfExpressions) {
|
||||
ASTVariableDeclaratorId variable = expr.getChild(1).getFirstChildOfType(ASTVariableDeclaratorId.class);
|
||||
Assert.assertEquals(String.class, variable.getType());
|
||||
// Note: these variables are not part of the symbol table
|
||||
// See ScopeAndDeclarationFinder#visit(ASTVariableDeclaratorId, Object)
|
||||
Assert.assertNull(variable.getNameDeclaration());
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void patternMatchingInstanceofBeforeJava16ShouldFail() {
|
||||
java15.parseResource("PatternMatchingInstanceof.java");
|
||||
}
|
||||
|
||||
}
|
@ -4,32 +4,67 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.test.shouldBe
|
||||
import io.kotest.matchers.shouldBe
|
||||
import net.sourceforge.pmd.lang.ast.test.shouldBe as typeShouldBe
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaVersion
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaVersion.*
|
||||
import java.io.IOException
|
||||
|
||||
class ASTPatternTest : ParserTestSpec({
|
||||
|
||||
parserTest("Test patterns only available on JDK 14+15 (preview)", javaVersions = JavaVersion.values().asList().minus(J14__PREVIEW).minus(J15__PREVIEW)) {
|
||||
parserTest("Test patterns only available on JDK 14+15 (preview) and JDK16 and JDK16 (preview)",
|
||||
javaVersions = JavaVersion.values().asList().minus(J14__PREVIEW).minus(J15__PREVIEW).minus(J16).minus(J16__PREVIEW)) {
|
||||
|
||||
expectParseException("Pattern Matching for instanceof is only supported with Java 14 Preview and Java 15 Preview") {
|
||||
expectParseException("Pattern Matching for instanceof is only supported with Java 14 Preview and Java 15 Preview and Java 16") {
|
||||
parseAstExpression("obj instanceof Class c")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
parserTest("Test simple patterns", javaVersions = listOf(J14__PREVIEW, J15__PREVIEW)) {
|
||||
parserTest("Test simple patterns", javaVersions = listOf(J14__PREVIEW, J15__PREVIEW, J16)) {
|
||||
|
||||
importedTypes += IOException::class.java
|
||||
|
||||
"obj instanceof Class c" should matchExpr<ASTInstanceOfExpression> {
|
||||
unspecifiedChild()
|
||||
child<ASTTypeTestPattern> {
|
||||
it::getTypeNode shouldBe child(ignoreChildren = true) {}
|
||||
it.isAnnotationPresent("java.lang.Deprecated") shouldBe false
|
||||
it::getTypeNode typeShouldBe child(ignoreChildren = true) {}
|
||||
|
||||
it::getVarId shouldBe child {
|
||||
it::getVariableName shouldBe "c"
|
||||
it::getVarId typeShouldBe child {
|
||||
it.name shouldBe "c"
|
||||
it.isFinal shouldBe false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"obj instanceof final Class c" should matchExpr<ASTInstanceOfExpression> {
|
||||
unspecifiedChild()
|
||||
child<ASTTypeTestPattern> {
|
||||
it.isAnnotationPresent("java.lang.Deprecated") shouldBe false
|
||||
it::getTypeNode typeShouldBe child(ignoreChildren = true) {}
|
||||
|
||||
it::getVarId typeShouldBe child {
|
||||
it.name shouldBe "c"
|
||||
it.isFinal shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"obj instanceof @Deprecated Class c" should matchExpr<ASTInstanceOfExpression> {
|
||||
unspecifiedChild()
|
||||
child<ASTTypeTestPattern> {
|
||||
child<ASTAnnotation>(ignoreChildren = true) {
|
||||
it.annotationName shouldBe "Deprecated"
|
||||
}
|
||||
|
||||
it.isAnnotationPresent("java.lang.Deprecated") shouldBe true
|
||||
|
||||
it::getTypeNode typeShouldBe child(ignoreChildren = true) {}
|
||||
|
||||
it::getVarId typeShouldBe child {
|
||||
it.name shouldBe "c"
|
||||
it.isFinal shouldBe false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@
|
||||
| | | +- Type[@Array = false, @ArrayDepth = 0, @ArrayType = false, @TypeImage = "String"]
|
||||
| | | | +- ReferenceType[@Array = false, @ArrayDepth = 0]
|
||||
| | | | +- ClassOrInterfaceType[@AnonymousClass = false, @Array = false, @ArrayDepth = 0, @Image = "String", @ReferenceToClassSameCompilationUnit = false]
|
||||
| | | +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = true, @FormalParameter = false, @Image = "s", @LambdaParameter = false, @LocalVariable = false, @Name = "s", @PatternBinding = true, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "s"]
|
||||
| | | +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = false, @FormalParameter = false, @Image = "s", @LambdaParameter = false, @LocalVariable = false, @Name = "s", @PatternBinding = true, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "s"]
|
||||
| | +- Statement[]
|
||||
| | | +- Block[@containsComment = true]
|
||||
| | | +- BlockStatement[@Allocation = false]
|
||||
@ -113,7 +113,7 @@
|
||||
| | | +- Type[@Array = false, @ArrayDepth = 0, @ArrayType = false, @TypeImage = "String"]
|
||||
| | | | +- ReferenceType[@Array = false, @ArrayDepth = 0]
|
||||
| | | | +- ClassOrInterfaceType[@AnonymousClass = false, @Array = false, @ArrayDepth = 0, @Image = "String", @ReferenceToClassSameCompilationUnit = false]
|
||||
| | | +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = true, @FormalParameter = false, @Image = "s", @LambdaParameter = false, @LocalVariable = false, @Name = "s", @PatternBinding = true, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "s"]
|
||||
| | | +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = false, @FormalParameter = false, @Image = "s", @LambdaParameter = false, @LocalVariable = false, @Name = "s", @PatternBinding = true, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "s"]
|
||||
| | +- Statement[]
|
||||
| | | +- Block[@containsComment = true]
|
||||
| | | +- BlockStatement[@Allocation = false]
|
||||
@ -179,7 +179,7 @@
|
||||
| | | | +- Type[@Array = false, @ArrayDepth = 0, @ArrayType = false, @TypeImage = "String"]
|
||||
| | | | | +- ReferenceType[@Array = false, @ArrayDepth = 0]
|
||||
| | | | | +- ClassOrInterfaceType[@AnonymousClass = false, @Array = false, @ArrayDepth = 0, @Image = "String", @ReferenceToClassSameCompilationUnit = false]
|
||||
| | | | +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = true, @FormalParameter = false, @Image = "s", @LambdaParameter = false, @LocalVariable = false, @Name = "s", @PatternBinding = true, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "s"]
|
||||
| | | | +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = false, @FormalParameter = false, @Image = "s", @LambdaParameter = false, @LocalVariable = false, @Name = "s", @PatternBinding = true, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "s"]
|
||||
| | | +- RelationalExpression[@Image = ">"]
|
||||
| | | +- PrimaryExpression[]
|
||||
| | | | +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false]
|
||||
@ -228,7 +228,7 @@
|
||||
| | | +- Type[@Array = false, @ArrayDepth = 0, @ArrayType = false, @TypeImage = "String"]
|
||||
| | | | +- ReferenceType[@Array = false, @ArrayDepth = 0]
|
||||
| | | | +- ClassOrInterfaceType[@AnonymousClass = false, @Array = false, @ArrayDepth = 0, @Image = "String", @ReferenceToClassSameCompilationUnit = false]
|
||||
| | | +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = true, @FormalParameter = false, @Image = "s", @LambdaParameter = false, @LocalVariable = false, @Name = "s", @PatternBinding = true, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "s"]
|
||||
| | | +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = false, @FormalParameter = false, @Image = "s", @LambdaParameter = false, @LocalVariable = false, @Name = "s", @PatternBinding = true, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "s"]
|
||||
| | +- RelationalExpression[@Image = ">"]
|
||||
| | +- PrimaryExpression[]
|
||||
| | | +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false]
|
||||
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
*
|
||||
* @see <a href="https://openjdk.java.net/jeps/394">JEP 394: Pattern Matching for instanceof</a>
|
||||
*/
|
||||
public class PatternMatchingInstanceof {
|
||||
private String s = "other string";
|
||||
|
||||
public void test() {
|
||||
Object obj = "abc";
|
||||
//obj = 1;
|
||||
if (obj instanceof String s) {
|
||||
System.out.println("a) obj == s: " + (obj == s)); // true
|
||||
s = "other value"; // not a compile error - s is only effectively final
|
||||
System.out.println("changed s to " + s + ": obj == s: " + (obj == s));
|
||||
} else {
|
||||
System.out.println("b) obj == s: " + (obj == s)); // false
|
||||
}
|
||||
|
||||
if (!(obj instanceof String s)) {
|
||||
System.out.println("c) obj == s: " + (obj == s)); // false
|
||||
} else {
|
||||
System.out.println("d) obj == s: " + (obj == s)); // true
|
||||
}
|
||||
|
||||
if (obj instanceof String s && s.length() > 2) {
|
||||
System.out.println("e) obj == s: " + (obj == s)); // true
|
||||
}
|
||||
if (obj instanceof String s || s.length() > 5) {
|
||||
System.out.println("f) obj == s: " + (obj == s)); // false
|
||||
}
|
||||
|
||||
// With Java16 there can be final and annotations
|
||||
if (obj instanceof final String s) {
|
||||
System.out.println("g) obj == s: " + (obj == s)); // true
|
||||
//s = "another value"; // compile error: error: cannot assign a value to final variable s
|
||||
} else {
|
||||
System.out.println("h) obj == s: " + (obj == s)); // false
|
||||
}
|
||||
if (obj instanceof @Deprecated String s) {
|
||||
System.out.println("i) obj == s: " + (obj == s)); // true
|
||||
} else {
|
||||
System.out.println("j) obj == s: " + (obj == s)); // false
|
||||
}
|
||||
if (obj instanceof final @Deprecated String s) {
|
||||
System.out.println("k) obj == s: " + (obj == s)); // true
|
||||
//s = "another value"; // compile error: error: cannot assign a value to final variable s
|
||||
} else {
|
||||
System.out.println("l) obj == s: " + (obj == s)); // false
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new PatternMatchingInstanceof().test();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user