[java] Support Unnamed Patterns and Variables for Java 21 Preview
JEP 443
This commit is contained in:
@ -1,4 +1,6 @@
|
||||
/**
|
||||
* Support "JEP 443: Unnamed Patterns and Variables" for Java 21 Preview.
|
||||
* New AST nodes: ASTUnnamedPattern
|
||||
* Support "JEP 430: String Templates" for Java 21 Preview.
|
||||
* New AST nodes: ASTTemplateExpression, ASTTemplate, ASTTemplateFragment
|
||||
* Promote "JEP 441: Pattern Matching for switch" as permanent language feature for Java 21.
|
||||
@ -1884,13 +1886,26 @@ void RecordPattern():
|
||||
void RecordStructurePattern() #void:
|
||||
{}
|
||||
{
|
||||
"(" [ PatternList() ] ")"
|
||||
"(" [ ComponentPatternList() ] ")"
|
||||
}
|
||||
|
||||
void PatternList() :
|
||||
void ComponentPatternList() #PatternList :
|
||||
{}
|
||||
{
|
||||
Pattern() ( "," Pattern() )*
|
||||
ComponentPattern() ( "," ComponentPattern() )*
|
||||
}
|
||||
|
||||
void ComponentPattern() #void:
|
||||
{}
|
||||
{
|
||||
LOOKAHEAD({isKeyword("_")}) UnnamedPattern()
|
||||
| Pattern()
|
||||
}
|
||||
|
||||
void UnnamedPattern():
|
||||
{}
|
||||
{
|
||||
softKeyword("_")
|
||||
}
|
||||
|
||||
void InstanceOfExpression() #void:
|
||||
@ -2637,11 +2652,23 @@ void CaseLabelElement(ASTSwitchLabel label) #void:
|
||||
{
|
||||
"null" #NullLiteral [ "," "default" {label.setDefault();} ]
|
||||
|
|
||||
LOOKAHEAD(Pattern()) Pattern() [ LOOKAHEAD({isKeyword("when")}) Guard() ]
|
||||
LOOKAHEAD(Pattern()) CasePattern() [ LOOKAHEAD({isKeyword("when")}) Guard() ]
|
||||
|
|
||||
CaseConstant()
|
||||
}
|
||||
|
||||
void CaseConstant() #void:
|
||||
{}
|
||||
{
|
||||
ConditionalExpression()
|
||||
}
|
||||
|
||||
void CasePattern() #void:
|
||||
{}
|
||||
{
|
||||
Pattern()
|
||||
}
|
||||
|
||||
void Guard() :
|
||||
{}
|
||||
{
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* An unnamed pattern, a Java 21 Preview language feature.
|
||||
*
|
||||
* <pre class="grammar">
|
||||
*
|
||||
* UnnamedPattern ::= "_"
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://openjdk.org/jeps/443">JEP 443: Unnamed patterns and variables (Preview)</a> (Java 21)
|
||||
*/
|
||||
@Experimental
|
||||
public final class ASTUnnamedPattern extends AbstractJavaNode implements ASTPattern {
|
||||
|
||||
ASTUnnamedPattern(int id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <P, R> R acceptVisitor(JavaVisitor<? super P, ? extends R> visitor, P data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getParenthesisDepth() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -45,6 +45,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTType;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTTypeArguments;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTTypePattern;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTUnnamedPattern;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.JModifier;
|
||||
@ -167,10 +168,16 @@ public class LanguageLevelChecker<T> {
|
||||
|
||||
/**
|
||||
* String Templates.
|
||||
* @see <a href="https://openjdk.org/jeps/430">JEP 430: String Templates (Preview)</a>
|
||||
* @see <a href="https://openjdk.org/jeps/430">JEP 430: String Templates (Preview)</a> (Java 21)
|
||||
*/
|
||||
STRING_TEMPLATES(21, 21, false),
|
||||
|
||||
/**
|
||||
* Unnamed patterns and variables.
|
||||
* @see <a href="https://openjdk.org/jeps/443">JEP 443: Unnamed patterns and variables (Preview)</a> (Java 21)
|
||||
*/
|
||||
UNNAMED_PATTERNS_AND_VARIABLES(21, 21, false),
|
||||
|
||||
; // SUPPRESS CHECKSTYLE enum trailing semi is awesome
|
||||
|
||||
|
||||
@ -639,6 +646,12 @@ public class LanguageLevelChecker<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(ASTUnnamedPattern node, T data) {
|
||||
check(node, PreviewFeature.UNNAMED_PATTERNS_AND_VARIABLES, data);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitTypeDecl(ASTAnyTypeDeclaration node, T data) {
|
||||
if (node.getModifiers().hasAnyExplicitly(JModifier.SEALED, JModifier.NON_SEALED)) {
|
||||
@ -668,7 +681,11 @@ public class LanguageLevelChecker<T> {
|
||||
} else if ("assert".equals(simpleName)) {
|
||||
check(node, Keywords.ASSERT_AS_AN_IDENTIFIER, acc);
|
||||
} else if ("_".equals(simpleName)) {
|
||||
check(node, Keywords.UNDERSCORE_AS_AN_IDENTIFIER, acc);
|
||||
if (LanguageLevelChecker.this.preview) {
|
||||
check(node, PreviewFeature.UNNAMED_PATTERNS_AND_VARIABLES, acc);
|
||||
} else {
|
||||
check(node, Keywords.UNDERSCORE_AS_AN_IDENTIFIER, acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,7 +202,9 @@ public final class JavaRuleUtil {
|
||||
public static boolean isExplicitUnusedVarName(String name) {
|
||||
return name.startsWith("ignored")
|
||||
|| name.startsWith("unused")
|
||||
|| "_".equals(name); // before java 9 it's ok
|
||||
// before java 9 it's ok, after that, "_" is a reserved keyword
|
||||
// with Java 21 Preview (JEP 443), "_" means explicitly unused
|
||||
|| "_".equals(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@ -51,4 +53,18 @@ class Java21PreviewTreeDumpTest extends BaseTreeDumpTest {
|
||||
JTypeMirror typeMirror = templateExpression.getTypeMirror();
|
||||
assertEquals("java.lang.String", ((JClassSymbol) typeMirror.getSymbol()).getCanonicalName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void unnamedPatternsAndVariables() {
|
||||
doTest("Jep443_UnnamedPatternsAndVariables");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unnamedPatternsAndVariablesBeforeJava21Preview() {
|
||||
ParseException thrown = assertThrows(ParseException.class, () -> java21.parseResource("Jep443_UnnamedPatternsAndVariables.java"));
|
||||
assertThat(thrown.getMessage(), containsString("Since Java 9, '_' is reserved and cannot be used as an identifier"));
|
||||
|
||||
thrown = assertThrows(ParseException.class, () -> java21.parseResource("Jep443_UnnamedPatternsAndVariables2.java"));
|
||||
assertThat(thrown.getMessage(), containsString("Unnamed patterns and variables is a preview feature of JDK 21, you should select your language version accordingly"));
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,9 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -38,7 +39,7 @@ class Java21TreeDumpTest extends BaseTreeDumpTest {
|
||||
@Test
|
||||
void patternMatchingForSwitchBeforeJava21() {
|
||||
ParseException thrown = assertThrows(ParseException.class, () -> java20.parseResource("Jep441_PatternMatchingForSwitch.java"));
|
||||
assertTrue(thrown.getMessage().contains("Patterns in switch statements is a preview feature of JDK 20, you should select your language version accordingly"));
|
||||
assertThat(thrown.getMessage(), containsString("Patterns in switch statements is a preview feature of JDK 20, you should select your language version accordingly"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -49,8 +50,7 @@ class Java21TreeDumpTest extends BaseTreeDumpTest {
|
||||
@Test
|
||||
void dealingWithNullBeforeJava21() {
|
||||
ParseException thrown = assertThrows(ParseException.class, () -> java20.parseResource("DealingWithNull.java"));
|
||||
assertTrue(thrown.getMessage().contains("Null in switch cases is a preview feature of JDK 20, you should select your language version accordingly"),
|
||||
"Unexpected message: " + thrown.getMessage());
|
||||
assertThat(thrown.getMessage(), containsString("Null in switch cases is a preview feature of JDK 20, you should select your language version accordingly"));
|
||||
}
|
||||
|
||||
|
||||
@ -72,8 +72,7 @@ class Java21TreeDumpTest extends BaseTreeDumpTest {
|
||||
@Test
|
||||
void guardedPatternsBeforeJava21() {
|
||||
ParseException thrown = assertThrows(ParseException.class, () -> java20.parseResource("GuardedPatterns.java"));
|
||||
assertTrue(thrown.getMessage().contains("Patterns in switch statements is a preview feature of JDK 20, you should select your language version accordingly"),
|
||||
"Unexpected message: " + thrown.getMessage());
|
||||
assertThat(thrown.getMessage(), containsString("Patterns in switch statements is a preview feature of JDK 20, you should select your language version accordingly"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -84,8 +83,7 @@ class Java21TreeDumpTest extends BaseTreeDumpTest {
|
||||
@Test
|
||||
void patternsInSwitchLabelsBeforeJava21() {
|
||||
ParseException thrown = assertThrows(ParseException.class, () -> java20.parseResource("PatternsInSwitchLabels.java"));
|
||||
assertTrue(thrown.getMessage().contains("Patterns in switch statements is a preview feature of JDK 20, you should select your language version accordingly"),
|
||||
"Unexpected message: " + thrown.getMessage());
|
||||
assertThat(thrown.getMessage(), containsString("Patterns in switch statements is a preview feature of JDK 20, you should select your language version accordingly"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -106,8 +104,7 @@ class Java21TreeDumpTest extends BaseTreeDumpTest {
|
||||
@Test
|
||||
void recordPatternsJepBeforeJava21() {
|
||||
ParseException thrown = assertThrows(ParseException.class, () -> java20.parseResource("Jep440_RecordPatterns.java"));
|
||||
assertTrue(thrown.getMessage().contains("Record patterns is a preview feature of JDK 20, you should select your language version accordingly"),
|
||||
"Unexpected message: " + thrown.getMessage());
|
||||
assertThat(thrown.getMessage(), containsString("Record patterns is a preview feature of JDK 20, you should select your language version accordingly"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -118,8 +115,7 @@ class Java21TreeDumpTest extends BaseTreeDumpTest {
|
||||
@Test
|
||||
void recordPatternsBeforeJava21() {
|
||||
ParseException thrown = assertThrows(ParseException.class, () -> java20.parseResource("RecordPatterns.java"));
|
||||
assertTrue(thrown.getMessage().contains("Record patterns is a preview feature of JDK 20, you should select your language version accordingly"),
|
||||
"Unexpected message: " + thrown.getMessage());
|
||||
assertThat(thrown.getMessage(), containsString("Record patterns is a preview feature of JDK 20, you should select your language version accordingly"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @see <a href="https://openjdk.org/jeps/443">JEP 443: Unnamed Patterns and Variables (Preview)</a>
|
||||
*/
|
||||
class Jep443_UnamedPatternsAndVariables {
|
||||
record Point(int x, int y) { }
|
||||
enum Color { RED, GREEN, BLUE }
|
||||
record ColoredPoint(Point p, Color c) { }
|
||||
|
||||
void unnamedPatterns1() {
|
||||
ColoredPoint r = new ColoredPoint(new Point(3,4), Color.GREEN);
|
||||
|
||||
if (r instanceof ColoredPoint(Point p, Color _)) {
|
||||
System.out.println(p.x() + " " + p.y());
|
||||
}
|
||||
|
||||
if (r instanceof ColoredPoint(Point(int x, int y), _)) {
|
||||
System.out.println(x + " " + y);
|
||||
}
|
||||
}
|
||||
|
||||
sealed abstract class Ball permits RedBall, BlueBall, GreenBall { }
|
||||
final class RedBall extends Ball { }
|
||||
final class BlueBall extends Ball { }
|
||||
final class GreenBall extends Ball { }
|
||||
|
||||
record Box<T extends Ball>(T content) { }
|
||||
|
||||
void unnamedPatterns2() {
|
||||
Box<? extends Ball> b = new Box<>(new RedBall());
|
||||
switch (b) {
|
||||
case Box(RedBall _) -> processBox(b);
|
||||
case Box(BlueBall _) -> processBox(b);
|
||||
case Box(GreenBall _) -> stopProcessing();
|
||||
}
|
||||
|
||||
switch (b) {
|
||||
case Box(RedBall _), Box(BlueBall _) -> processBox(b);
|
||||
case Box(GreenBall _) -> stopProcessing();
|
||||
case Box(_) -> pickAnotherBox();
|
||||
}
|
||||
|
||||
int x = 42;
|
||||
switch (b) {
|
||||
// multiple patterns guarded by one guard
|
||||
case Box(RedBall _), Box(BlueBall _) when x == 42 -> processBox(b);
|
||||
case Box(_) -> pickAnotherBox();
|
||||
}
|
||||
}
|
||||
|
||||
private void processBox(Box<? extends Ball> b) {}
|
||||
private void stopProcessing() {}
|
||||
private void pickAnotherBox() {}
|
||||
|
||||
class Order {}
|
||||
private static final int LIMIT = 10;
|
||||
private int sideEffect() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void unnamedVariables(List<Order> orders) {
|
||||
int total = 0;
|
||||
for (Order _ : orders) {
|
||||
if (total < LIMIT) {
|
||||
total++;
|
||||
}
|
||||
}
|
||||
System.out.println("total: " + total);
|
||||
|
||||
for (int i = 0, _ = sideEffect(); i < 10; i++) {
|
||||
System.out.println(i);
|
||||
}
|
||||
|
||||
Queue<Integer> q = new ArrayDeque<>(); // x1, y1, z1, x2, y2, z2 ..
|
||||
while (q.size() >= 3) {
|
||||
int x = q.remove();
|
||||
int y = q.remove();
|
||||
int _ = q.remove(); // z is unused
|
||||
Point p = new Point(x, y);
|
||||
}
|
||||
while (q.size() >= 3) {
|
||||
var x = q.remove();
|
||||
var _ = q.remove();
|
||||
var _ = q.remove();
|
||||
Point p = new Point(x, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static class ScopedContext implements AutoCloseable {
|
||||
@Override
|
||||
public void close() { }
|
||||
public static ScopedContext acquire() {
|
||||
return new ScopedContext();
|
||||
}
|
||||
}
|
||||
|
||||
void unusedVariables2() {
|
||||
try (var _ = ScopedContext.acquire()) {
|
||||
//... acquiredContext not used ...
|
||||
}
|
||||
|
||||
String s = "123";
|
||||
try {
|
||||
int i = Integer.parseInt(s);
|
||||
System.out.println(i);
|
||||
} catch (NumberFormatException _) {
|
||||
System.out.println("Bad number: " + s);
|
||||
} catch (Exception _) {
|
||||
System.out.println("error...");
|
||||
}
|
||||
|
||||
List.of("a", "b").stream().collect(Collectors.toMap(String::toUpperCase, _ -> "NO_DATA"));
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @see <a href="https://openjdk.org/jeps/443">JEP 443: Unnamed Patterns and Variables (Preview)</a>
|
||||
*/
|
||||
class Jep443_UnamedPatternsAndVariables2 {
|
||||
record Point(int x, int y) { }
|
||||
enum Color { RED, GREEN, BLUE }
|
||||
record ColoredPoint(Point p, Color c) { }
|
||||
|
||||
void unnamedPatterns1() {
|
||||
ColoredPoint r = new ColoredPoint(new Point(3,4), Color.GREEN);
|
||||
|
||||
if (r instanceof ColoredPoint(Point(int x, int y), _)) {
|
||||
System.out.println(x + " " + y);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user