[java] Add support for RecordPatterns
See JEP 405
This commit is contained in:
@ -2,6 +2,7 @@
|
||||
* Support "JEP 427: Pattern Matching for switch (Third Preview)" for Java 19 Preview
|
||||
* Note: GuardedPattern is deprecated and only valid for 17-preview and 18-preview
|
||||
* New AST node: Guard - used within switch case labels for refining a pattern
|
||||
* Support "JEP 405: Record Patterns (Preview)" for Java 19 Preview
|
||||
* Andreas Dangel 07/2022
|
||||
*====================================================================
|
||||
* Support "JEP 420: Pattern Matching for switch (Second Preview)" for Java 18 Preview
|
||||
@ -578,6 +579,12 @@ public class JavaParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForRecordPatterns() {
|
||||
if (!((jdkVersion == 19) && preview)) {
|
||||
throwParseException("Record Patterns are only supported with JDK 19 Preview.");
|
||||
}
|
||||
}
|
||||
|
||||
// This is a semantic LOOKAHEAD to determine if we're dealing with an assert
|
||||
// Note that this can't be replaced with a syntactic lookahead
|
||||
// since "assert" isn't a string literal token
|
||||
@ -1778,8 +1785,9 @@ void EqualityExpression() #EqualityExpression(>1):
|
||||
void Pattern() #void:
|
||||
{}
|
||||
{
|
||||
TypePattern() [ GuardedPatternCondition() #GuardedPattern(2) {checkForGuardedPatterns();} ]
|
||||
| ParenthesizedPattern()
|
||||
LOOKAHEAD(ReferenceType() "(") RecordPattern()
|
||||
| LOOKAHEAD("(") ParenthesizedPattern()
|
||||
| TypePattern() [ GuardedPatternCondition() #GuardedPattern(2) {checkForGuardedPatterns();} ]
|
||||
}
|
||||
|
||||
void GuardedPatternCondition() #void:
|
||||
@ -1802,6 +1810,24 @@ void TypePattern():
|
||||
VariableDeclaratorId()
|
||||
}
|
||||
|
||||
void RecordPattern():
|
||||
{ checkForRecordPatterns(); }
|
||||
{
|
||||
ReferenceType() RecordStructurePattern() [ VariableDeclaratorId() ]
|
||||
}
|
||||
|
||||
void RecordStructurePattern():
|
||||
{}
|
||||
{
|
||||
"(" [ RecordComponentPatternList() ] ")"
|
||||
}
|
||||
|
||||
void RecordComponentPatternList() #void:
|
||||
{}
|
||||
{
|
||||
Pattern() ( "," Pattern() )*
|
||||
}
|
||||
|
||||
void InstanceOfExpression() #InstanceOfExpression(>1):
|
||||
{}
|
||||
{
|
||||
@ -1811,6 +1837,8 @@ void InstanceOfExpression() #InstanceOfExpression(>1):
|
||||
LOOKAHEAD("final" | "@") {checkforBadInstanceOfPattern();} Pattern()
|
||||
|
|
||||
LOOKAHEAD("(") Pattern() {checkForParenthesizedInstanceOfPattern();}
|
||||
|
|
||||
LOOKAHEAD(ReferenceType() "(") RecordPattern()
|
||||
|
|
||||
Type()
|
||||
[ {checkforBadInstanceOfPattern();} VariableDeclaratorId() #TypePattern(2) ]
|
||||
|
@ -10,17 +10,16 @@ import net.sourceforge.pmd.annotation.Experimental;
|
||||
* A pattern (for pattern matching constructs like {@link ASTInstanceOfExpression InstanceOfExpression}
|
||||
* or within a {@link ASTSwitchLabel}). 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 is planned for a future JDK version.
|
||||
* <p>This interface will be implemented by all forms of patterns.
|
||||
*
|
||||
* <pre class="grammar">
|
||||
*
|
||||
* Pattern ::= {@link ASTTypePattern TypePattern}
|
||||
* Pattern ::= {@linkplain ASTTypePattern TypePattern} | {@linkplain ASTRecordPattern RecordPattern}
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://openjdk.java.net/jeps/394">JEP 394: Pattern Matching for instanceof</a>
|
||||
* @see <a href="https://openjdk.org/jeps/394">JEP 394: Pattern Matching for instanceof</a>
|
||||
* @see <a href="https://openjdk.org/jeps/405">JEP 405: Record Patterns (Preview)</a>
|
||||
*/
|
||||
public interface ASTPattern extends JavaNode {
|
||||
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 record pattern (JDK19). This can be found on
|
||||
* the right-hand side of an {@link ASTInstanceOfExpression InstanceOfExpression}.
|
||||
*
|
||||
* <pre class="grammar">
|
||||
*
|
||||
* RecordPattern ::= {@linkplain ASTReferenceType ReferenceType} {@linkplain ASTRecordStructurePattern RecordStructurePattern} [ {@linkplain ASTVariableDeclaratorId} VariableDeclaratorId ]
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://openjdk.org/jeps/405">JEP 405: Record Patterns (Preview)</a>
|
||||
*/
|
||||
@Experimental
|
||||
public final class ASTRecordPattern extends AbstractJavaNode implements ASTPattern {
|
||||
|
||||
private int parenDepth;
|
||||
|
||||
ASTRecordPattern(int id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
ASTRecordPattern(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 ASTReferenceType getTypeNode() {
|
||||
return getFirstChildOfType(ASTReferenceType.class);
|
||||
}
|
||||
|
||||
/** Returns the declared variable. */
|
||||
public ASTVariableDeclaratorId getVarId() {
|
||||
return getFirstChildOfType(ASTVariableDeclaratorId.class);
|
||||
}
|
||||
|
||||
void bumpParenDepth() {
|
||||
parenDepth++;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Experimental
|
||||
public int getParenthesisDepth() {
|
||||
return parenDepth;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Contains a potentially empty list of nested Patterns for {@linkplain ASTRecordPattern RecordPattern} (JDK 19).
|
||||
*
|
||||
* <pre class="grammar">
|
||||
*
|
||||
* RecordStructurePattern ::= "(" {@linkplain ASTPattern Pattern} ( "," {@linkplain ASTPattern pattern} ) ")"
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://openjdk.org/jeps/405">JEP 405: Record Patterns (Preview)</a>
|
||||
*/
|
||||
@Experimental
|
||||
public final class ASTRecordStructurePattern extends AbstractJavaNode {
|
||||
ASTRecordStructurePattern(int id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
ASTRecordStructurePattern(JavaParser p, int id) {
|
||||
super(p, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
@ -957,4 +957,18 @@ public class JavaParserDecoratedVisitor implements JavaParserVisitor {
|
||||
visitor.visit(node, data);
|
||||
return visit((JavaNode) node, data);
|
||||
}
|
||||
|
||||
@Experimental
|
||||
@Override
|
||||
public Object visit(ASTRecordPattern node, Object data) {
|
||||
visitor.visit(node, data);
|
||||
return visit((JavaNode) node, data);
|
||||
}
|
||||
|
||||
@Experimental
|
||||
@Override
|
||||
public Object visit(ASTRecordStructurePattern node, Object data) {
|
||||
visitor.visit(node, data);
|
||||
return visit((JavaNode) node, data);
|
||||
}
|
||||
}
|
||||
|
@ -675,4 +675,16 @@ public class JavaParserVisitorAdapter implements JavaParserVisitor {
|
||||
public Object visit(ASTGuard node, Object data) {
|
||||
return visit((JavaNode) node, data);
|
||||
}
|
||||
|
||||
@Experimental
|
||||
@Override
|
||||
public Object visit(ASTRecordPattern node, Object data) {
|
||||
return visit((JavaNode) node, data);
|
||||
}
|
||||
|
||||
@Experimental
|
||||
@Override
|
||||
public Object visit(ASTRecordStructurePattern node, Object data) {
|
||||
return visit((JavaNode) node, data);
|
||||
}
|
||||
}
|
||||
|
@ -807,4 +807,16 @@ public class JavaParserVisitorDecorator implements JavaParserControllessVisitor
|
||||
public Object visit(ASTGuard node, Object data) {
|
||||
return visitor.visit(node, data);
|
||||
}
|
||||
|
||||
@Experimental
|
||||
@Override
|
||||
public Object visit(ASTRecordPattern node, Object data) {
|
||||
return visitor.visit(node, data);
|
||||
}
|
||||
|
||||
@Experimental
|
||||
@Override
|
||||
public Object visit(ASTRecordStructurePattern node, Object data) {
|
||||
return visitor.visit(node, data);
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,8 @@ import net.sourceforge.pmd.lang.java.ast.ASTRecordBody;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTRecordComponent;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTRecordComponentList;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTRecordDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTRecordPattern;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTRecordStructurePattern;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTRelationalExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTResource;
|
||||
@ -889,5 +891,17 @@ public abstract class AbstractJavaRule extends AbstractRule implements JavaParse
|
||||
return visit((JavaNode) node, data);
|
||||
}
|
||||
|
||||
@Experimental
|
||||
@Override
|
||||
public Object visit(ASTRecordPattern node, Object data) {
|
||||
return visit((JavaNode) node, data);
|
||||
}
|
||||
|
||||
@Experimental
|
||||
@Override
|
||||
public Object visit(ASTRecordStructurePattern node, Object data) {
|
||||
return visit((JavaNode) node, data);
|
||||
}
|
||||
|
||||
// CPD-ON
|
||||
}
|
||||
|
@ -4,7 +4,9 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import org.junit.Assert;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
|
||||
@ -31,13 +33,13 @@ public class Java19PreviewTreeDumpTest extends BaseTreeDumpTest {
|
||||
|
||||
@Test
|
||||
public void dealingWithNullBeforeJava19Preview() {
|
||||
ParseException thrown = Assert.assertThrows(ParseException.class, new ThrowingRunnable() {
|
||||
ParseException thrown = assertThrows(ParseException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
java19.parseResource("DealingWithNull.java");
|
||||
}
|
||||
});
|
||||
Assert.assertTrue("Unexpected message: " + thrown.getMessage(),
|
||||
assertTrue("Unexpected message: " + thrown.getMessage(),
|
||||
thrown.getMessage().contains("Null case labels in switch are only supported with JDK 17 Preview or JDK 18 Preview or JDK 19 Preview."));
|
||||
}
|
||||
|
||||
@ -58,13 +60,13 @@ public class Java19PreviewTreeDumpTest extends BaseTreeDumpTest {
|
||||
|
||||
@Test
|
||||
public void guardedAndParenthesizedPatternsBeforeJava19Preview() {
|
||||
ParseException thrown = Assert.assertThrows(ParseException.class, new ThrowingRunnable() {
|
||||
ParseException thrown = assertThrows(ParseException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
java19.parseResource("GuardedAndParenthesizedPatterns.java");
|
||||
}
|
||||
});
|
||||
Assert.assertTrue("Unexpected message: " + thrown.getMessage(),
|
||||
assertTrue("Unexpected message: " + thrown.getMessage(),
|
||||
thrown.getMessage().contains("Pattern Matching in Switch is only supported with JDK 17 Preview or JDK 18 Preview or JDK 19 Preview."));
|
||||
}
|
||||
|
||||
@ -75,13 +77,13 @@ public class Java19PreviewTreeDumpTest extends BaseTreeDumpTest {
|
||||
|
||||
@Test
|
||||
public void patternsInSwitchLabelsBeforeJava19Preview() {
|
||||
ParseException thrown = Assert.assertThrows(ParseException.class, new ThrowingRunnable() {
|
||||
ParseException thrown = assertThrows(ParseException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
java19.parseResource("PatternsInSwitchLabels.java");
|
||||
}
|
||||
});
|
||||
Assert.assertTrue("Unexpected message: " + thrown.getMessage(),
|
||||
assertTrue("Unexpected message: " + thrown.getMessage(),
|
||||
thrown.getMessage().contains("Pattern Matching in Switch is only supported with JDK 17 Preview or JDK 18 Preview or JDK 19 Preview."));
|
||||
}
|
||||
|
||||
@ -99,4 +101,21 @@ public class Java19PreviewTreeDumpTest extends BaseTreeDumpTest {
|
||||
public void scopeOfPatternVariableDeclarations() {
|
||||
doTest("ScopeOfPatternVariableDeclarations");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recordPatterns() {
|
||||
doTest("RecordPatterns");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recordPatternsBeforeJava19Preview() {
|
||||
ParseException thrown = assertThrows(ParseException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
java19.parseResource("RecordPatterns.java");
|
||||
}
|
||||
});
|
||||
assertTrue("Unexpected message: " + thrown.getMessage(),
|
||||
thrown.getMessage().contains("Record Patterns are only supported with JDK 19 Preview."));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see <a href="https://openjdk.org/jeps/405">JEP 405: Record Patterns (Preview)</a>
|
||||
*/
|
||||
public class RecordPatterns {
|
||||
|
||||
record Point(int x, int y) {}
|
||||
enum Color { RED, GREEN, BLUE }
|
||||
record ColoredPoint(Point p, Color c) {}
|
||||
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
|
||||
|
||||
void printSum1(Object o) {
|
||||
if (o instanceof Point p) {
|
||||
int x = p.x();
|
||||
int y = p.y();
|
||||
System.out.println(x+y);
|
||||
}
|
||||
}
|
||||
|
||||
// record pattern
|
||||
void printSum2(Object o) {
|
||||
if (o instanceof Point(int x, int y)) {
|
||||
System.out.println(x+y);
|
||||
}
|
||||
}
|
||||
|
||||
void printUpperLeftColoredPoint(Rectangle r) {
|
||||
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
|
||||
System.out.println(ul.c());
|
||||
}
|
||||
}
|
||||
|
||||
// nested record pattern
|
||||
void printColorOfUpperLeftPoint(Rectangle r) {
|
||||
if (r instanceof Rectangle(ColoredPoint(Point p, Color c),
|
||||
ColoredPoint lr)) {
|
||||
System.out.println(c);
|
||||
}
|
||||
}
|
||||
|
||||
// fully nested record pattern, also using "var"
|
||||
void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
|
||||
if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c),
|
||||
var lr)) {
|
||||
System.out.println("Upper-left corner: " + x);
|
||||
}
|
||||
}
|
||||
|
||||
// record patterns with generic types
|
||||
record Box<T>(T t) {}
|
||||
void test1(Box<Object> bo) {
|
||||
if (bo instanceof Box<Object>(String s)) {
|
||||
System.out.println("String " + s);
|
||||
}
|
||||
}
|
||||
void test2(Box<String> bo) {
|
||||
if (bo instanceof Box<String>(var s)) {
|
||||
System.out.println("String " + s);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user