Skip to content

Issue #10848: Support patterns in switch#11100

Merged
rnveach merged 3 commits into
checkstyle:masterfrom
nrmancuso:issue-10848
Jan 23, 2022
Merged

Issue #10848: Support patterns in switch#11100
rnveach merged 3 commits into
checkstyle:masterfrom
nrmancuso:issue-10848

Conversation

@nrmancuso

@nrmancuso nrmancuso commented Dec 23, 2021

Copy link
Copy Markdown
Contributor

Closes #10848.

Relevant grammar from https://docs.oracle.com/javase/specs/jls/se17/preview/specs/patterns-switch-jls.html:


SwitchBlock:
    { SwitchRule {SwitchRule} }
    { {SwitchBlockStatementGroup} {SwitchLabel :} }

SwitchRule:
    SwitchLabel -> Expression ;
    SwitchLabel -> Block
    SwitchLabel -> ThrowStatement

SwitchBlockStatementGroup:
    SwitchLabel : BlockStatements

SwitchLabel:
    CaseOrDefaultLabel {: CaseOrDefaultLabel }

CaseOrDefaultLabel:
    case CaseLabelElement {, CaseLabelElement }
    default

CaseLabelElement:
    CaseConstant
    Pattern
    null
    default


Relevant grammar for patterns:

Pattern:
  PrimaryPattern
  GuardedPattern

GuardedPattern:
  PrimaryPattern && ConditionalAndExpression

PrimaryPattern:
  TypePattern
  ( Pattern )


Also, minor update to instanceof:

From https://openjdk.java.net/jeps/406:

We also change the grammar for instanceof expressions to:

InstanceofExpression:
  RelationalExpression instanceof ReferenceType
  RelationalExpression instanceof PrimaryPattern


Just a test here, I noticed that PMD release in projects files is not up to date.

Diff Regression projects: https://gist.githubusercontent.com/nmancus1/ea9cd53c4dc4be8be3e776d059542f00/raw/6eb067547d67808e3bbab428a4e2dad7a031676d/pmd.properties

Diff Regression config: https://gist.githubusercontent.com/nmancus1/3db3e23a5063857160d7d92cbdfa1bfe/raw/a200e0fb1c4700ecd084f9e0b03bf7dbb9694832/config.xml


ANTLR regression report

https://nmancus1.github.io/issue-10848/issue-10848-antlr-diff-pmd-checkstyle/index.html

All diffs are exceptions vs files we can parse now.


OpenJDK16 Check Regression Reports

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_30_openjdk16/diff_checks-only-javadoc-error/index.html

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_30_openjdk16/diff_part1/index.html

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_30_openjdk16/diff_part2/index.html

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_30_openjdk16/diff_part3/index.html

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_30_openjdk16/diff_part4/index.html

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_30_openjdk16/diff_part5/index.html

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_30_openjdk16/diff_part6/index.html

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_30_openjdk16/diff_sevntu-check-regression_part_1/index.html

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_30_openjdk16/diff_sevntu-check-regression_part_2/index.html

All reports for openjdk16 are clean.


Check regression reports

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_checks-only-javadoc-error/index.html

Diff in line number for exception messages, and diff in ability to parse new files in this PR branch (my-checkstyle).

https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_part1/index.html

New violations in files that we could not parse before; all violations are legit.


https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_part2/index.html

New violations in files that we could not parse before; all violations are legit.


https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_part3/index.html

Need to update MissingSwitchDefault.

Examples: https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_part3/my-checkstyle/xref/home/nick/development/contribution/checkstyle-tester/repositories/my-checkstyle/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/grammar/antlr4/InputAntlr4AstRegressionPatternMatchingInSwitch.java.html#L118

Issue opened at #11220

All other new violations in files that we could not parse before.


https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_part4/index.html

New violations in files that we could not parse before; all violations are legit.


https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_part5/index.html

New violations in files that we could not parse before; all violations are legit.


https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_part6/index.html

New violations in files that we could not parse before; all violations are legit.


https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_sevntu-check-regression_part_1/index.html

New violations in files that we could not parse before; all violations are legit.


https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_sevntu-check-regression_part_2/index.html

Regression in LogicConditionNeedOptimization: https://nmancus1.github.io/issue-10848/issue-10848_check_diff_reports_2021_12_31/diff_sevntu-check-regression_part_2/my-checkstyle/xref/home/nick/development/contribution/checkstyle-tester/repositories/my-checkstyle/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/grammar/antlr4/InputAntlr4AstRegressionPatternsInFor.java.html#L6

PR at sevntu-checkstyle/sevntu.checkstyle#881

@nrmancuso nrmancuso self-assigned this Dec 23, 2021
@nrmancuso nrmancuso marked this pull request as draft December 23, 2021 08:00

@nrmancuso nrmancuso left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought:

Comment on lines +471 to +479
| | | | |--LAND -> && [47:27]
| | | | | |--PATTERN_VARIABLE_DEF -> PATTERN_VARIABLE_DEF [47:17]
| | | | | | |--MODIFIERS -> MODIFIERS [47:17]
| | | | | | |--TYPE -> TYPE [47:17]
| | | | | | | `--IDENT -> Integer [47:17]
| | | | | | `--IDENT -> i [47:25]
| | | | | `--GT -> > [47:32]
| | | | | |--IDENT -> i [47:30]
| | | | | `--NUM_INT -> 40 [47:34]

@nrmancuso nrmancuso Dec 23, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corresponding code:

            case Integer i && i > 40:
                System.out.println("Integer");

Should we add a new token, PATTERN_DEF ?

Example:

|-PATTERN_DEF -> [47:27]        
|   |--LAND -> && [47:27]
|   |   |--PATTERN_VARIABLE_DEF -> PATTERN_VARIABLE_DEF [47:17]
|   |   |   |--MODIFIERS -> MODIFIERS [47:17]
|   |   |   |--TYPE -> TYPE [47:17]
|   |   |   |   `--IDENT -> Integer [47:17]
|   |   |   `--IDENT -> i [47:25]
|   |   `--GT -> > [47:32]
|   |       |--IDENT -> i [47:30]
|   |       `--NUM_INT -> 40 [47:34]

This would apply only to applications of the pattern production rule, and would have no effect on existing o instanceof String s definitions (causing no changes to existing AST structure). As pattern definitions become more prevalent (and complex) in Java grammar, it may prove helpful to have a dedicated PATTERN_DEF node.

Relevant grammar:

Pattern:
  PrimaryPattern
  GuardedPattern

GuardedPattern:
  PrimaryPattern && ConditionalAndExpression

PrimaryPattern:
  TypePattern
  ( Pattern )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@romani @pbludov @rnveach @strkkk please share your thoughts.

@pbludov pbludov Dec 24, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I got it right,

            case Integer i && i < 40:
                System.out.println("Small integer");
                break;

will be

|-PATTERN_DEF -> [47:27]        
|   |--LAND -> && [47:27]
|   |   |--PATTERN_VARIABLE_DEF -> PATTERN_VARIABLE_DEF [47:17]
|   |   |   |--MODIFIERS -> MODIFIERS [47:17]
|   |   |   |--TYPE -> TYPE [47:17]
|   |   |   |   `--IDENT -> Integer [47:17]
|   |   |   `--IDENT -> i [47:25]
|   |   `--LT -> > [47:32]
|   |       |--IDENT -> i [47:30]
|   |       `--NUM_INT -> 40 [47:34]

and

            case Integer i:
                System.out.println("Any integer");
                break;

will be also a PATTERN_DEF:

|-PATTERN_DEF -> [47:27]        
|   `--PATTERN_VARIABLE_DEF -> PATTERN_VARIABLE_DEF [47:17]
|       |--MODIFIERS -> MODIFIERS [47:17]
|       |--TYPE -> TYPE [47:17]
|       |   `--IDENT -> Integer [47:17]
|       `--IDENT -> i [47:25]

?

@nrmancuso nrmancuso Dec 24, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be also a PATTERN_DEF:
?

We can do this.

Or, we can have a simple PATTERN_VARIABLE_DEF with no PATTERN_DEF parent node for switch, just like we do for instanceof. I am not sure what will prove useful from a static analysis viewpoint, because I can't really think of how any current checks would benefit from more easily accessing patterns in the AST (by adding a new token). I can work up some more examples if we want to consider adding a new token.

@pbludov can you think of any existing checks (or new check ideas) that would benefit from having a PATTERN_DEF node?

@pbludov pbludov Dec 24, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having PATTERN_DEF over LAND makes sense. This will allow some checks to distinguish between && for patterns and conditions.

Adding PATTERN_DEF as the parent of PATTERN_VARIABLE_DEF will be a breaking change for some third-party checks.

I like simple PATTERN_VARIABLE_DEF for switch.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thought: the if has no PATTERN_DEF:

if (o instanceof Integer i && i < 40) { // LAND, LITERAL_INSTANCEOF, PATTERN_VARIABLE_DEF, LT
 ...
}

switch (o) {
  case Integer i && i < 40: // LAND, PATTERN_VARIABLE_DEF, LT ?
    break;
}

Should there be an extra node for case?

@nrmancuso nrmancuso Dec 27, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we think that it is not CASE specific syntax for pattern

It is not. This syntax can be used anywhere enhanced instance of with pattern matching can be used now, as long as expression is parenthesized.

Click for example
public class Test {
    void method1(Object o) { // should have violation
        if (o instanceof String s) {
            // ^ pattern variable introduced (simple type pattern)
        }
        
        if (o instanceof String s && s.length() > 6) {
            // ^ pattern variable introduced (simple type pattern),
            // then evaluated in boolean expression
            // 's.length() > 6' is usual boolean expression, not part of pattern
        }
        
        if (o instanceof (String s && s.length() > 4)) {
            // ^ pattern variable introduced in guarded pattern; this refines the pattern itself
            // 's.length() > 4` is part of the pattern
        }
        
        int x = o instanceof (String s && s.length() > 6) ? 4 : 5;
        int y = o instanceof String s && s.length() > 4 ? 2 : 3;
        int z = o instanceof String s ? 1 : 2;
        
        while (o instanceof (String s && s.length() > 6)) {
            
        }
        while (o instanceof String s && s.length() > 4) {
            
        }
        while (o instanceof String s) {
            
        }
        
        for (int i = 0; o instanceof Integer myInt && myInt > 5;) {
            
        }
        for (int i = 0; o instanceof (Integer myInt && myInt > 5);) {
            
        }
        for (int i = 0; o instanceof Integer myInt; ) {
            
        }
    }
}

to keep it specific...

To me, specificity is more important for the difference between a simple pattern variable introduction, like:

        if (o instanceof String s) {
            // ^ pattern variable introduced (simple type pattern)
        }

And

        if (o instanceof (String s && s.length() > 4)) {
            // ^ pattern variable introduced in guarded pattern; this refines the pattern itself
            // 's.length() > 4` is part of the pattern
        }

In simple pattern variable introduction, whatever surrounding context we need can already be gathered by a check (and probably already is). This does not require (or benefit from) a new parent PATTERN_DEF (and would cause breaking changes to AST.

In guarded and parenthesized patterns, we should be able to know exactly all that this pattern encompasses easily. Why? Because pattern definitions are going to get much more complicated soon due to pattern matching for records and arrays, in JEP405:

static 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);
    }
}

static void printSumOfFirstTwoXCoords(Object o) {
    if (o instanceof Point[] { Point(var x1, var y1), Point(var x2, var y2), ... }) {
        System.out.println(x1 + x2);
    }
}

In this case, we can easily have entire AST for Rectangle(ColoredPoint(Point(var x, var y), var c), var lr) in one go, and have a parent to look for when analyzing scopes of pattern variables, etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thought: the if has no PATTERN_DEF

Without parenthesized pattern, it would not. It's not really necessary, since && i < 40 is not part of the pattern. If a parenthesized pattern was used (o instanceof (Integer i && i < 40)), then it would now be a PATTERN_DEF by my proposal, because && i < 40 is now part of the pattern.

@nrmancuso nrmancuso Dec 27, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this: I will make a few small/simple input files and printed AST files, with examples from above for evaluation and add to this PR using proposed implementation. This will make it easier to visualize exact AST for certain syntax. Thumbs up on this for agreement.

TLDR of proposed implementation:

Integer i -> no PATTERN_DEF parent, in any case.

All other patterns have PATTERN_DEF parent.

No breaking changes to AST.

@nrmancuso nrmancuso Dec 30, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, implementation and tests updated. I am marking this ready for review. If we are good with AST structure, I will begin generating reports.

System.out.println("The rest (including null)");
}

// example of parenthesized pattern in instanceof

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From https://openjdk.java.net/jeps/406:

We also change the grammar for instanceof expressions to:

InstanceofExpression:
  RelationalExpression instanceof ReferenceType
  RelationalExpression instanceof PrimaryPattern

This means that we can have a parenthesized pattern here now:

PrimaryPattern:
  TypePattern
  ( Pattern )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not breacking changes. parenthesized pattern not supported in jdk less jdk17. So this updates comes with "switch" of jdk17. I tested it.

Comment thread .drone.yml Outdated
@Vyom-Yadav

This comment has been minimized.

@nrmancuso nrmancuso force-pushed the issue-10848 branch 2 times, most recently from 2e59dec to 9b493c5 Compare December 29, 2021 17:29
Comment thread config/checkstyle_resources_checks.xml Outdated
value="[\\/]src[\\/](it|test)[\\/]resources(-noncompilable?)[\\/]"/>
<property name="fileNamePattern"
value="^(package-info.java)|(Input\w+\.java)|(.*\.properties)$"/>
value="^(package-info.java)|(Input\w+\.java)|(Input\w+\.txt)|(.*\.properties)$"/>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing pattern did not allow for expected AST output files in non-compilable. I think it is better to keep input files and expected output together.

@nrmancuso nrmancuso force-pushed the issue-10848 branch 2 times, most recently from 8c034a9 to 4ead50d Compare December 29, 2021 17:33
@nrmancuso nrmancuso marked this pull request as ready for review December 29, 2021 17:37
@nrmancuso nrmancuso force-pushed the issue-10848 branch 2 times, most recently from 8cf1d52 to 8831197 Compare December 29, 2021 17:49
@nrmancuso nrmancuso marked this pull request as draft December 29, 2021 22:13
@nrmancuso nrmancuso force-pushed the issue-10848 branch 2 times, most recently from 005fde1 to 7b017a2 Compare December 30, 2021 15:23
@nrmancuso nrmancuso marked this pull request as ready for review December 30, 2021 15:23
@nrmancuso nrmancuso assigned pbludov and unassigned nrmancuso Dec 30, 2021
@nrmancuso nrmancuso requested a review from pbludov December 30, 2021 15:28

@pbludov pbludov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nrmancuso nrmancuso force-pushed the issue-10848 branch 2 times, most recently from 5f0cb64 to cca37aa Compare December 30, 2021 17:01
@nrmancuso

Copy link
Copy Markdown
Contributor Author

Github, generate report

@github-actions

Copy link
Copy Markdown
Contributor

@nrmancuso

Copy link
Copy Markdown
Contributor Author

@romani all related PR's/ issues opened.

@romani

romani commented Jan 22, 2022

Copy link
Copy Markdown
Member

CaseOrDefaultLabel:
case CaseLabelElement {, CaseLabelElement }
default
CaseLabelElement:
CaseConstant
Pattern
null
default

Am I read this correctly that it can be:
case 1
case null
case default ????

in Inputs I see code like:

            case null, default:
                throw new UnsupportedOperationException("not supported!");

please add (if compilable):

            case default:
                throw new UnsupportedOperationException("not supported!");
$ cat Test.java 
public class Test {
      void m1(Object o) {
        switch(o) {
            case String s && s.length() > 4: // guarded pattern, `PATTERN_DEF`
                break;
            case (String s && s.length() > 6): // parenthesized pattern, `PATTERN_DEF`
                break;
            case String s: // type pattern, no `PATTERN_DEF`
                break;
            case default:
                throw new UnsupportedOperationException("not supported!");
        }
    }
  }
$ javac --enable-preview --release 17 Test.java 
Note: Test.java uses preview features of Java SE 17.
Note: Recompile with -Xlint:preview for details.
$ 

@romani romani left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

items:

Comment thread src/main/java/com/puppycrawl/tools/checkstyle/api/TokenTypes.java
@nrmancuso

nrmancuso commented Jan 22, 2022

Copy link
Copy Markdown
Contributor Author

Am I read this correctly that it can be:
case 1
case null
case default ????

By JLS and our grammar, yes. By compiler, no. null in this case refers to reference type "nulltype" (not primitive); you cannot use a constant (like 1) and a reference type in the same switch statement/ expression.

please add ...

Done.

@romani romani left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok to merge as item above to extend doc example is done

@romani

romani commented Jan 22, 2022

Copy link
Copy Markdown
Member

Github, generate web site

@github-actions

Copy link
Copy Markdown
Contributor

@nrmancuso

Copy link
Copy Markdown
Contributor Author

Github, generate site

@github-actions

Copy link
Copy Markdown
Contributor

Comment thread config/checkstyle_resources_checks.xml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support patterns in switch (preview-feature in Java 17) as described by JEP 406

6 participants