Skip to content

Commit

Permalink
Issue #12507: Support the latest pattern matching syntax in switch la…
Browse files Browse the repository at this point in the history
…bels
  • Loading branch information
nrmancuso authored and rnveach committed May 14, 2023
1 parent 10f907a commit f5bed1f
Show file tree
Hide file tree
Showing 25 changed files with 7,116 additions and 55 deletions.
1 change: 1 addition & 0 deletions config/jsoref-spellchecker/whitelist.words
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ dcm
Dconnection
Ddry
declarationorder
Deconstruction
defau
defaultcasepresent
defaultcomeslast
Expand Down
26 changes: 0 additions & 26 deletions config/projects-to-test/openjdk19-excluded.files
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,6 @@
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]unicode[\\/]FirstChar2.java$"/>
</module>

<!-- until https://github.com/checkstyle/checkstyle/issues/12507 -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]patterns[\\/]NestedDeconstructionPattern.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]patterns[\\/]NullSwitch.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]patterns[\\/]ProxyMethodLookup.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]patterns[\\/]SimpleDeconstructionPattern.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]patterns[\\/]NullsInDeconstructionPatterns.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]patterns[\\/]GenericRecordDeconstructionPattern.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]patterns[\\/]Guards.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]patterns[\\/]Switches.java$"/>
</module>

<!-- non-compilable -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]patterns[\\/]Domination.java$"/>
Expand Down
40 changes: 37 additions & 3 deletions src/main/java/com/puppycrawl/tools/checkstyle/JavaAstVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -1952,14 +1952,20 @@ public DetailAstImpl visitArguments(JavaLanguageParser.ArgumentsContext ctx) {

@Override
public DetailAstImpl visitPattern(JavaLanguageParser.PatternContext ctx) {
final ParserRuleContext primaryPattern = ctx.primaryPattern();
final JavaLanguageParser.InnerPatternContext innerPattern = ctx.innerPattern();
final ParserRuleContext primaryPattern = innerPattern.primaryPattern();
final ParserRuleContext recordPattern = innerPattern.recordPattern();
final boolean isSimpleTypePattern = primaryPattern != null
&& primaryPattern.getChild(0) instanceof JavaLanguageParser.TypePatternContext;

final DetailAstImpl pattern;
if (isSimpleTypePattern) {

if (recordPattern != null) {
pattern = visit(recordPattern);
}
else if (isSimpleTypePattern) {
// For simple type pattern like 'Integer i`, we do not add `PATTERN_DEF` parent
pattern = visit(ctx.primaryPattern());
pattern = visit(primaryPattern);
}
else {
pattern = createImaginary(TokenTypes.PATTERN_DEF);
Expand All @@ -1968,6 +1974,11 @@ public DetailAstImpl visitPattern(JavaLanguageParser.PatternContext ctx) {
return pattern;
}

@Override
public DetailAstImpl visitInnerPattern(JavaLanguageParser.InnerPatternContext ctx) {
return flattenedTree(ctx);
}

@Override
public DetailAstImpl visitGuardedPattern(JavaLanguageParser.GuardedPatternContext ctx) {
final DetailAstImpl guardAstNode = flattenedTree(ctx.guard());
Expand All @@ -1985,6 +1996,11 @@ public DetailAstImpl visitParenPattern(JavaLanguageParser.ParenPatternContext ct
return lparen;
}

@Override
public DetailAstImpl visitRecordPatternDef(JavaLanguageParser.RecordPatternDefContext ctx) {
return flattenedTree(ctx);
}

@Override
public DetailAstImpl visitTypePattern(
JavaLanguageParser.TypePatternContext ctx) {
Expand All @@ -1996,6 +2012,24 @@ public DetailAstImpl visitTypePattern(
return patternVariableDef;
}

@Override
public DetailAstImpl visitRecordPattern(JavaLanguageParser.RecordPatternContext ctx) {
final DetailAstImpl recordPattern = createImaginary(TokenTypes.RECORD_PATTERN_DEF);
recordPattern.addChild(createModifiers(ctx.mods));
processChildren(recordPattern,
ctx.children.subList(ctx.mods.size(), ctx.children.size()));
return recordPattern;
}

@Override
public DetailAstImpl visitRecordComponentPatternList(
JavaLanguageParser.RecordComponentPatternListContext ctx) {
final DetailAstImpl recordComponents =
createImaginary(TokenTypes.RECORD_PATTERN_COMPONENTS);
processChildren(recordComponents, ctx.children);
return recordComponents;
}

@Override
public DetailAstImpl visitPermittedSubclassesAndInterfaces(
JavaLanguageParser.PermittedSubclassesAndInterfacesContext ctx) {
Expand Down
188 changes: 188 additions & 0 deletions src/main/java/com/puppycrawl/tools/checkstyle/api/TokenTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -6272,6 +6272,194 @@ public final class TokenTypes {
public static final int LITERAL_WHEN =
JavaLanguageLexer.LITERAL_WHEN;

/**
* A {@code record} pattern definition. A record pattern consists of a type,
* a (possibly empty) record component pattern list which is used to match against
* the corresponding record components, and an optional identifier. Appears as part of
* an {@code instanceof} expression or a {@code case} label in a switch.
*
* <p>For example:</p>
* <pre>
* record R(Object o){}
* if (o instanceof R(String s) myRecord) {}
* switch (o) {
* case R(String s) myRecord -&gt; {}
* }
* </pre>
* <p>parses as:</p>
* <pre>
* |--RECORD_DEF -&gt; RECORD_DEF
* | |--MODIFIERS -&gt; MODIFIERS
* | |--LITERAL_RECORD -&gt; record
* | |--IDENT -&gt; R
* | |--LPAREN -&gt; (
* | |--RECORD_COMPONENTS -&gt; RECORD_COMPONENTS
* | | `--RECORD_COMPONENT_DEF -&gt; RECORD_COMPONENT_DEF
* | | |--ANNOTATIONS -&gt; ANNOTATIONS
* | | |--TYPE -&gt; TYPE
* | | | `--IDENT -&gt; Object
* | | `--IDENT -&gt; o
* | |--RPAREN -&gt; )
* | `--OBJBLOCK -&gt; OBJBLOCK
* | |--LCURLY -&gt; {
* | `--RCURLY -&gt; }
* |--LITERAL_IF -&gt; if
* | |--LPAREN -&gt; (
* | |--EXPR -&gt; EXPR
* | | `--LITERAL_INSTANCEOF -&gt; instanceof
* | | |--IDENT -&gt; o
* | | `--RECORD_PATTERN_DEF -&gt; RECORD_PATTERN_DEF
* | | |--MODIFIERS -&gt; MODIFIERS
* | | |--TYPE -&gt; TYPE
* | | | `--IDENT -&gt; R
* | | |--LPAREN -&gt; (
* | | |--RECORD_PATTERN_COMPONENTS -&gt; RECORD_PATTERN_COMPONENTS
* | | | `--PATTERN_VARIABLE_DEF -&gt; PATTERN_VARIABLE_DEF
* | | | |--MODIFIERS -&gt; MODIFIERS
* | | | |--TYPE -&gt; TYPE
* | | | | `--IDENT -&gt; String
* | | | `--IDENT -&gt; s
* | | |--RPAREN -&gt; )
* | | `--IDENT -&gt; myRecord
* | |--RPAREN -&gt; )
* | `--SLIST -&gt; {
* | `--RCURLY -&gt; }
* |--LITERAL_SWITCH -&gt; switch
* | |--LPAREN -&gt; (
* | |--EXPR -&gt; EXPR
* | | `--IDENT -&gt; o
* | |--RPAREN -&gt; )
* | |--LCURLY -&gt; {
* | |--SWITCH_RULE -&gt; SWITCH_RULE
* | | |--LITERAL_CASE -&gt; case
* | | | `--RECORD_PATTERN_DEF -&gt; RECORD_PATTERN_DEF
* | | | |--MODIFIERS -&gt; MODIFIERS
* | | | |--TYPE -&gt; TYPE
* | | | | `--IDENT -&gt; R
* | | | |--LPAREN -&gt; (
* | | | |--RECORD_PATTERN_COMPONENTS -&gt; RECORD_PATTERN_COMPONENTS
* | | | | `--PATTERN_VARIABLE_DEF -&gt; PATTERN_VARIABLE_DEF
* | | | | |--MODIFIERS -&gt; MODIFIERS
* | | | | |--TYPE -&gt; TYPE
* | | | | | `--IDENT -&gt; String
* | | | | `--IDENT -&gt; s
* | | | |--RPAREN -&gt; )
* | | | `--IDENT -&gt; myRecord
* | | |--LAMBDA -&gt; -&gt;
* | | `--SLIST -&gt; {
* | | `--RCURLY -&gt; }
* | `--RCURLY -&gt; }
* `--RCURLY -&gt; }
* </pre>
*
* @see <a href="https://openjdk.org/jeps/405">JEP 405: Record Patterns</a>
* @see #LITERAL_WHEN
* @see #PATTERN_VARIABLE_DEF
* @see #LITERAL_INSTANCEOF
* @see #SWITCH_RULE
*
* @since 10.12.0
*/
public static final int RECORD_PATTERN_DEF =
JavaLanguageLexer.RECORD_PATTERN_DEF;

/**
* A (possibly empty) record component pattern list which is used to match against
* the corresponding record components. Appears as part of a record pattern definition.
*
* <p>For example:</p>
* <pre>
* record R(Object o){}
* if (o instanceof R(String myComponent)) {}
* switch (o) {
* case R(String myComponent) when "component".equalsIgnoreCase(myComponent) -&gt; {}
* }
* </pre>
* <p>parses as:</p>
* <pre>
* |--RECORD_DEF -&gt; RECORD_DEF
* | |--MODIFIERS -&gt; MODIFIERS
* | |--LITERAL_RECORD -&gt; record
* | |--IDENT -&gt; R
* | |--LPAREN -&gt; (
* | |--RECORD_COMPONENTS -&gt; RECORD_COMPONENTS
* | | `--RECORD_COMPONENT_DEF -&gt; RECORD_COMPONENT_DEF
* | | |--ANNOTATIONS -&gt; ANNOTATIONS
* | | |--TYPE -&gt; TYPE
* | | | `--IDENT -&gt; Object
* | | `--IDENT -&gt; o
* | |--RPAREN -&gt; )
* | `--OBJBLOCK -&gt; OBJBLOCK
* | |--LCURLY -&gt; {
* | `--RCURLY -&gt; }
* |--LITERAL_IF -&gt; if
* | |--LPAREN -&gt; (
* | |--EXPR -&gt; EXPR
* | | `--LITERAL_INSTANCEOF -&gt; instanceof
* | | |--IDENT -&gt; o
* | | `--RECORD_PATTERN_DEF -&gt; RECORD_PATTERN_DEF
* | | |--MODIFIERS -&gt; MODIFIERS
* | | |--TYPE -&gt; TYPE
* | | | `--IDENT -&gt; R
* | | |--LPAREN -&gt; (
* | | |--RECORD_PATTERN_COMPONENTS -&gt; RECORD_PATTERN_COMPONENTS
* | | | `--PATTERN_VARIABLE_DEF -&gt; PATTERN_VARIABLE_DEF
* | | | |--MODIFIERS -&gt; MODIFIERS
* | | | |--TYPE -&gt; TYPE
* | | | | `--IDENT -&gt; String
* | | | `--IDENT -&gt; myComponent
* | | `--RPAREN -&gt; )
* | |--RPAREN -&gt; )
* | `--SLIST -&gt; {
* | `--RCURLY -&gt; }
* |--LITERAL_SWITCH -&gt; switch
* | |--LPAREN -&gt; (
* | |--EXPR -&gt; EXPR
* | | `--IDENT -&gt; o
* | |--RPAREN -&gt; )
* | |--LCURLY -&gt; {
* | |--SWITCH_RULE -&gt; SWITCH_RULE
* | | |--LITERAL_CASE -&gt; case
* | | | `--PATTERN_DEF -&gt; PATTERN_DEF
* | | | `--LITERAL_WHEN -&gt; when
* | | | |--RECORD_PATTERN_DEF -&gt; RECORD_PATTERN_DEF
* | | | | |--MODIFIERS -&gt; MODIFIERS
* | | | | |--TYPE -&gt; TYPE
* | | | | | `--IDENT -&gt; R
* | | | | |--LPAREN -&gt; (
* | | | | |--RECORD_PATTERN_COMPONENTS -&gt; RECORD_PATTERN_COMPONENTS
* | | | | | `--PATTERN_VARIABLE_DEF -&gt; PATTERN_VARIABLE_DEF
* | | | | | |--MODIFIERS -&gt; MODIFIERS
* | | | | | |--TYPE -&gt; TYPE
* | | | | | | `--IDENT -&gt; String
* | | | | | `--IDENT -&gt; myComponent
* | | | | `--RPAREN -&gt; )
* | | | `--METHOD_CALL -&gt; (
* | | | |--DOT -&gt; .
* | | | | |--STRING_LITERAL -&gt; "component"
* | | | | `--IDENT -&gt; equalsIgnoreCase
* | | | |--ELIST -&gt; ELIST
* | | | | `--EXPR -&gt; EXPR
* | | | | `--IDENT -&gt; myComponent
* | | | `--RPAREN -&gt; )
* | | |--LAMBDA -&gt; -&gt;
* | | `--SLIST -&gt; {
* | | `--RCURLY -&gt; }
* | `--RCURLY -&gt; }
* `--RCURLY -&gt; }
* </pre>
*
* @see <a href="https://openjdk.org/jeps/405">JEP 405: Record Patterns</a>
* @see #LITERAL_WHEN
* @see #PATTERN_VARIABLE_DEF
* @see #LITERAL_INSTANCEOF
* @see #SWITCH_RULE
*
* @since 10.12.0
*/
public static final int RECORD_PATTERN_COMPONENTS =
JavaLanguageLexer.RECORD_PATTERN_COMPONENTS;

/** Prevent instantiation. */
private TokenTypes() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ tokens {
LITERAL_YIELD, SWITCH_RULE,
LITERAL_NON_SEALED, LITERAL_SEALED, LITERAL_PERMITS,
PERMITS_CLAUSE, PATTERN_DEF, LITERAL_WHEN
PERMITS_CLAUSE, PATTERN_DEF, LITERAL_WHEN,
RECORD_PATTERN_DEF, RECORD_PATTERN_COMPONENTS
}

@header {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -853,8 +853,18 @@ arguments
: LPAREN expressionList? RPAREN
;

/**
* We do for patterns as we do for expressions; namely we have one parent
* 'PATTERN_DEF' node, then have all nested pattern definitions inside of
* the parent node.
*/
pattern
: innerPattern
;

innerPattern
: guardedPattern
| recordPattern
| primaryPattern
;

Expand All @@ -874,18 +884,22 @@ guard: ( LAND | LITERAL_WHEN );

primaryPattern
: typePattern #patternVariableDef
| LPAREN
// Set of production rules below should mirror `pattern` production rule
// above. We do not reuse `pattern` production rule here to avoid a bunch
// of nested `PATTERN_DEF` nodes, as we also do for expressions.
(guardedPattern | primaryPattern)
RPAREN #parenPattern
| LPAREN innerPattern RPAREN #parenPattern
| recordPattern #recordPatternDef
;

typePattern
: mods+=modifier* type=typeType[true] id
;

recordPattern
: mods+=modifier* type=typeType[true] LPAREN recordComponentPatternList? RPAREN id?
;

recordComponentPatternList
: innerPattern (COMMA innerPattern)*
;

permittedSubclassesAndInterfaces
: LITERAL_PERMITS classOrInterfaceType[false] (COMMA classOrInterfaceType[false])*
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public void testOrderOfProperties() {

@Test
public void testAcceptableTokensMakeSense() {
final int expectedTokenTypesTotalNumber = 186;
final int expectedTokenTypesTotalNumber = 188;
assertWithMessage("Total number of TokenTypes has changed, acceptable tokens in"
+ " IllegalTokenTextCheck need to be reconsidered.")
.that(TokenUtil.getTokenTypesTotalNumber())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,12 @@ public void testTokenNumbering() {
assertWithMessage(message)
.that(JavaLanguageLexer.LITERAL_WHEN)
.isEqualTo(214);
assertWithMessage(message)
.that(JavaLanguageLexer.RECORD_PATTERN_DEF)
.isEqualTo(215);
assertWithMessage(message)
.that(JavaLanguageLexer.RECORD_PATTERN_COMPONENTS)
.isEqualTo(216);

final int tokenCount = (int) Arrays.stream(JavaLanguageLexer.class.getDeclaredFields())
.filter(GeneratedJavaTokenTypesTest::isPublicStaticFinalInt)
Expand All @@ -719,7 +725,7 @@ public void testTokenNumbering() {
+ " 'GeneratedJavaTokenTypesTest' and verified"
+ " that their old numbering didn't change")
.that(tokenCount)
.isEqualTo(226);
.isEqualTo(228);
}

/**
Expand Down

0 comments on commit f5bed1f

Please sign in to comment.