[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:
Andreas Dangel
2021-02-12 12:44:08 +01:00
parent 87cdd60e39
commit 8c755fabe2
10 changed files with 741 additions and 31 deletions

View File

@ -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):

View File

@ -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 {
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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());

View File

@ -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");
}
}

View File

@ -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
}
}
}

View File

@ -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]

View File

@ -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();
}
}