Skip to content

Commit

Permalink
Issue checkstyle#6615: Add support for Java 14 switch and yield
Browse files Browse the repository at this point in the history
  • Loading branch information
nrmancuso committed Jul 14, 2020
1 parent a38a23c commit 5921ced
Show file tree
Hide file tree
Showing 10 changed files with 2,964 additions and 40 deletions.
2 changes: 2 additions & 0 deletions config/ant-phase-verify.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@

<!-- Exclude because printed AST has line length > 100 characters -->
<exclude name="**/InputJava14InstanceofWithPatternMatchingAST.txt"/>
<exclude name="**/InputJava14SwitchExpression.txt"/>


</fileset>
</path>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3595,6 +3595,13 @@ public final class TokenTypes {
public static final int RECORD_DEF =
GeneratedJavaTokenTypes.RECORD_DEF;

/**
* The {@code yield} keyword. This element appears
* as part of a yield statement.
**/
public static final int LITERAL_YIELD =
GeneratedJavaTokenTypes.LITERAL_yield;

/** Prevent instantiation. */
private TokenTypes() {
}
Expand Down
99 changes: 66 additions & 33 deletions src/main/resources/com/puppycrawl/tools/checkstyle/grammar/java.g
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ tokens {
FLOAT_LITERAL; DOUBLE_LITERAL; HEX_FLOAT_LITERAL; HEX_DOUBLE_LITERAL;
SIGNED_INTEGER; BINARY_EXPONENT;
PATTERN_VARIABLE_DEF; RECORD_DEF; LITERAL_record="record";
PATTERN_VARIABLE_DEF; RECORD_DEF; LITERAL_record="record"; LITERAL_yield="yield";
}
{
Expand Down Expand Up @@ -1118,6 +1118,12 @@ traditionalStatement
// record declaration, note that you cannot have modifiers in this case
| recordDefinition[#null]

// switch/case statement
| switchExpression

// Yield statement, must be in a switchBlock
| yieldStatement

// An expression statement. This could be a method call,
// assignment statement, or any other expression evaluated for
// side-effects.
Expand Down Expand Up @@ -1160,16 +1166,11 @@ traditionalStatement
// Return an expression
| "return"^ (expression)? SEMI

// switch/case statement
| "switch"^ LPAREN expression RPAREN LCURLY
( casesGroup )*
RCURLY

// exception try-catch block
| tryBlock

// throw an exception
| "throw"^ expression SEMI
| throwStatement

// synchronize a statement
| "synchronized"^ LPAREN expression RPAREN compoundStatement
Expand All @@ -1178,6 +1179,10 @@ traditionalStatement
| s:SEMI {#s.setType(EMPTY_STAT);}
;

yieldStatement!
: l:LITERAL_yield e:expression s:(SEMI)? {## = #(l,e,s);}
;

forStatement
: f:"for"^
LPAREN
Expand Down Expand Up @@ -1210,6 +1215,10 @@ elseStatement
: "else"^ statement
;

switchBlock
: LCURLY ( casesGroup )* RCURLY
;

casesGroup
: ( // CONFLICT: to which case group do the statements bind?
// ANTLR generates proper code: it groups the
Expand All @@ -1226,7 +1235,12 @@ casesGroup
;

aCase
: ("case"^ expression | "default"^) COLON
: ("case"^ exprConditionalExpression (COMMA exprConditionalExpression )* | "default"^)
( COLON | (LAMBDA switchRule)=>(LAMBDA switchRule))
;

exprConditionalExpression
: conditionalExpression {## = #(#[EXPR,"EXPR"],##);}
;

caseSList
Expand All @@ -1244,6 +1258,10 @@ caseSList
{#caseSList = #(#[SLIST,"SLIST"],#caseSList);}
;

switchRule
: ( expression SEMI | compoundStatement | throwStatement )
;

// The initializer for a for loop
forInit
// if it looks like a declaration, it is
Expand Down Expand Up @@ -1277,6 +1295,10 @@ tryBlock
( finallyHandler )?
;

throwStatement
: "throw"^ expression SEMI
;

resourceSpecification
: LPAREN resources (SEMI)? RPAREN
{#resourceSpecification =
Expand Down Expand Up @@ -1497,31 +1519,35 @@ unaryExpression
unaryExpressionNotPlusMinus
: BNOT^ unaryExpression
| LNOT^ unaryExpression
| (switchExpression)=>switchExpression
| (castExpression)=>castExpression
;

| ( // subrule allows option to shut off warnings
options {
// "(int" ambig with postfixExpr due to lack of sequence
// info in linear approximate LL(k). It's ok. Shut up.
generateAmbigWarnings=false;
}
: // If typecast is built in type, must be numeric operand
// Also, no reason to backtrack if type keyword like int, float...
(LPAREN builtInTypeSpec[true] RPAREN unaryExpression) =>
lpb:LPAREN^ {#lpb.setType(TYPECAST);} builtInTypeSpec[true] RPAREN
unaryExpression
// Have to backtrack to see if operator follows. If no operator
// follows, it's a typecast. No semantic checking needed to parse.
// if it _looks_ like a cast, it _is_ a cast; else it's a "(expr)"
| (LPAREN typeCastParameters RPAREN unaryExpressionNotPlusMinus)=>
lp:LPAREN^ {#lp.setType(TYPECAST);} typeCastParameters RPAREN
unaryExpressionNotPlusMinus
| (LPAREN typeCastParameters RPAREN lambdaExpression) =>
lpl:LPAREN^ {#lpl.setType(TYPECAST);} typeCastParameters RPAREN
lambdaExpression
| postfixExpression
castExpression
: ( // subrule allows option to shut off warnings
options {
// "(int" ambig with postfixExpr due to lack of sequence
// info in linear approximate LL(k). It's ok. Shut up.
generateAmbigWarnings=false;
}
: // If typecast is built in type, must be numeric operand
// Also, no reason to backtrack if type keyword like int, float...
(LPAREN builtInTypeSpec[true] RPAREN unaryExpression) =>
lpb:LPAREN^ {#lpb.setType(TYPECAST);} builtInTypeSpec[true] RPAREN
castExpression
// Have to backtrack to see if operator follows. If no operator
// follows, it's a typecast. No semantic checking needed to parse.
// if it _looks_ like a cast, it _is_ a cast; else it's a "(expr)"
| (LPAREN typeCastParameters RPAREN unaryExpressionNotPlusMinus)=>
lp:LPAREN^ {#lp.setType(TYPECAST);} typeCastParameters RPAREN
castExpression
| (LPAREN typeCastParameters RPAREN lambdaExpression) =>
lpl:LPAREN^ {#lpl.setType(TYPECAST);}
typeCastParameters RPAREN lambdaExpression
| postfixExpression
)
;
Expand Down Expand Up @@ -1587,6 +1613,11 @@ postfixExpression
)
;
switchExpression
:
"switch"^ LPAREN expression RPAREN switchBlock
;
// the basic element of an expression
primaryExpression
: (typeSpec[false] DOUBLE_COLON) => typeSpec[false]
Expand Down Expand Up @@ -1725,9 +1756,11 @@ lambdaBody
// This rule was created to remedy the "keyword as identifier" problem
// See: https://github.com/checkstyle/checkstyle/issues/8308
id: IDENT | recordKey ;
id: IDENT | recordKey | yieldKey;
recordKey: "record" {#recordKey.setType(IDENT);};
yieldKey: "yield" {#yieldKey.setType(IDENT);};
//----------------------------------------------------------------------------
// The Java scanner
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public void testOrderOfProperties() throws Exception {

@Test
public void testAcceptableTokensMakeSense() {
final int expectedTokenTypesTotalNumber = 172;
final int expectedTokenTypesTotalNumber = 173;
assertEquals(expectedTokenTypesTotalNumber, TokenUtil.getTokenTypesTotalNumber(),
"Total number of TokenTypes has changed, acceptable tokens in"
+ " IllegalTokenTextCheck need to be reconsidered.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ public void testJava14Records() throws Exception {
getNonCompilablePath("java14/InputJava14Records.java"));
}

@Test
public void testJava14SwitchExpression() throws Exception {
verifyAst(getPath("java14/InputJava14SwitchExpression.txt"),
getNonCompilablePath("java14/InputJava14SwitchExpression.java"));
}

@Test
public void testImpossibleExceptions() throws Exception {
AssertGeneratedJavaLexer.verifyFail("mSTD_ESC", 'a');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,10 @@ public void testTokenNumbering() {
assertEquals(198, GeneratedJavaTokenTypes.PATTERN_VARIABLE_DEF, message);
assertEquals(199, GeneratedJavaTokenTypes.RECORD_DEF, message);
assertEquals(200, GeneratedJavaTokenTypes.LITERAL_record, message);
assertEquals(201, GeneratedJavaTokenTypes.LITERAL_yield, message);

// Read JavaDoc before changing
assertEquals(199, GeneratedJavaTokenTypes.class.getDeclaredFields().length,
assertEquals(200, GeneratedJavaTokenTypes.class.getDeclaredFields().length,
"all tokens must be added to list in"
+ " 'GeneratedJavaTokenTypesTest' and verified"
+ " that their old numbering didn't change");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ public class AllChecksTest extends AbstractModuleTestSupport {
"NUM_INT", "ANNOTATION_DEF", "METHOD_REF", "TYPE_ARGUMENTS",
"DOUBLE_COLON", "IDENT", "MOD_ASSIGN", "LITERAL_FOR", "SUPER_CTOR_CALL",
"STRING_LITERAL", "ARRAY_DECLARATOR", "LITERAL_CASE",
"PATTERN_VARIABLE_DEF", "RECORD_DEF", "LITERAL_RECORD").collect(
"PATTERN_VARIABLE_DEF", "RECORD_DEF", "LITERAL_RECORD",
"LITERAL_YIELD").collect(
Collectors.toSet()));
// we have no need to block specific token text
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalTokenText",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,16 @@ public void testIsCommentType() {
public void testGetTokenTypesTotalNumber() {
final int tokenTypesTotalNumber = TokenUtil.getTokenTypesTotalNumber();

assertEquals(172, tokenTypesTotalNumber, "Invalid token total number");
assertEquals(173, tokenTypesTotalNumber, "Invalid token total number");
}

@Test
public void testGetAllTokenIds() {
final int[] allTokenIds = TokenUtil.getAllTokenIds();
final int sum = Arrays.stream(allTokenIds).sum();

assertEquals(172, allTokenIds.length, "Invalid token length");
assertEquals(16259, sum, "invalid sum");
assertEquals(173, allTokenIds.length, "Invalid token length");
assertEquals(16460, sum, "invalid sum");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ I<String> lambdaCapture2(int i) {
case 1 -> () -> "1" + j; //capture local variable
default -> {
String k = "D";
yield () -> k; //capture local from the switch expr.
yield() ->k; //capture local from the switch expr.
}
};
}
Expand Down
Loading

0 comments on commit 5921ced

Please sign in to comment.