Skip to content

Commit ea882ad

Browse files
SONARPY-917 Match / case statement: support wildcard and group patterns (#991)
1 parent 54e74cf commit ea882ad

File tree

13 files changed

+286
-21
lines changed

13 files changed

+286
-21
lines changed

python-frontend/src/main/java/org/sonar/plugins/python/api/tree/BaseTreeVisitor.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,16 @@ public void visitSequencePattern(SequencePattern sequencePattern) {
518518

519519
@Override
520520
public void visitStarPattern(StarPattern starPattern) {
521-
scan(starPattern.capturePattern());
521+
scan(starPattern.pattern());
522+
}
523+
524+
@Override
525+
public void visitWildcardPattern(WildcardPattern wildcardPattern) {
526+
// noop
527+
}
528+
529+
@Override
530+
public void visitGroupPattern(GroupPattern groupPattern) {
531+
scan(groupPattern.pattern());
522532
}
523533
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2021 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.plugins.python.api.tree;
21+
22+
/**
23+
* Group Pattern
24+
* <pre> case ({@link #pattern()}): ...</pre>
25+
*
26+
* See https://docs.python.org/3/reference/compound_stmts.html#group-patterns
27+
*/
28+
public interface GroupPattern extends Pattern {
29+
30+
Token leftPar();
31+
32+
Pattern pattern();
33+
34+
Token rightPar();
35+
36+
}

python-frontend/src/main/java/org/sonar/plugins/python/api/tree/StarPattern.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@
3232
*/
3333
public interface StarPattern extends Pattern {
3434

35-
CapturePattern capturePattern();
35+
/**
36+
* Return value can only be either {@link CapturePattern} or {@link WildcardPattern}
37+
*/
38+
Pattern pattern();
3639
}

python-frontend/src/main/java/org/sonar/plugins/python/api/tree/Tree.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ enum Kind {
100100

101101
GLOBAL_STMT(GlobalStatement.class),
102102

103+
GROUP_PATTERN(GroupPattern.class),
104+
103105
GUARD(Guard.class),
104106

105107
IF_STMT(IfStatement.class),
@@ -169,6 +171,8 @@ enum Kind {
169171

170172
WHILE_STMT(WhileStatement.class),
171173

174+
WILDCARD_PATTERN(WildcardPattern.class),
175+
172176
WITH_ITEM(WithItem.class),
173177

174178
WITH_STMT(WithStatement.class),

python-frontend/src/main/java/org/sonar/plugins/python/api/tree/TreeVisitor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,8 @@ public interface TreeVisitor {
190190
void visitSequencePattern(SequencePattern sequencePattern);
191191

192192
void visitStarPattern(StarPattern starPattern);
193+
194+
void visitWildcardPattern(WildcardPattern wildcardPattern);
195+
196+
void visitGroupPattern(GroupPattern groupPattern);
193197
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2021 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.plugins.python.api.tree;
21+
22+
/**
23+
* Group Pattern
24+
* <pre>case _: ...</pre>
25+
*
26+
* See https://docs.python.org/3/reference/compound_stmts.html#wildcard-patterns
27+
*/
28+
public interface WildcardPattern extends Pattern {
29+
30+
Token wildcard();
31+
32+
}

python-frontend/src/main/java/org/sonar/python/api/PythonGrammar.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ public enum PythonGrammar implements GrammarRuleKey {
166166
MAYBE_STAR_PATTERN,
167167
MAYBE_SEQUENCE_PATTERN,
168168
OPEN_SEQUENCE_PATTERN,
169+
WILDCARD_PATTERN,
170+
GROUP_PATTERN,
169171

170172
SIGNED_NUMBER,
171173
COMPLEX_NUMBER,
@@ -467,7 +469,7 @@ public static void compoundStatements(LexerfulGrammarBuilder b) {
467469

468470
b.rule(PATTERNS).is(b.firstOf(OPEN_SEQUENCE_PATTERN, PATTERN));
469471
b.rule(PATTERN).is(b.firstOf(AS_PATTERN, OR_PATTERN));
470-
b.rule(CLOSED_PATTERN).is(b.firstOf(LITERAL_PATTERN, CAPTURE_PATTERN, SEQUENCE_PATTERN));
472+
b.rule(CLOSED_PATTERN).is(b.firstOf(LITERAL_PATTERN, GROUP_PATTERN, WILDCARD_PATTERN, CAPTURE_PATTERN, SEQUENCE_PATTERN));
471473
b.rule(AS_PATTERN).is(OR_PATTERN, "as", CAPTURE_PATTERN);
472474
b.rule(OR_PATTERN).is(CLOSED_PATTERN, b.zeroOrMore("|", CLOSED_PATTERN));
473475
b.rule(CAPTURE_PATTERN).is(NAME);
@@ -479,8 +481,7 @@ public static void compoundStatements(LexerfulGrammarBuilder b) {
479481
b.rule(OPEN_SEQUENCE_PATTERN).is(MAYBE_STAR_PATTERN, ",", b.optional(MAYBE_SEQUENCE_PATTERN));
480482
b.rule(MAYBE_SEQUENCE_PATTERN).is(MAYBE_STAR_PATTERN, b.zeroOrMore(",", MAYBE_STAR_PATTERN), b.optional(","));
481483
b.rule(MAYBE_STAR_PATTERN).is(b.firstOf(STAR_PATTERN, PATTERN));
482-
// TODO: ADD wildcard pattern for "*_"
483-
b.rule(STAR_PATTERN).is("*", CAPTURE_PATTERN);
484+
b.rule(STAR_PATTERN).is("*", b.firstOf(WILDCARD_PATTERN, CAPTURE_PATTERN));
484485

485486
b.rule(LITERAL_PATTERN).is(b.firstOf(
486487
COMPLEX_NUMBER,
@@ -501,6 +502,9 @@ public static void compoundStatements(LexerfulGrammarBuilder b) {
501502
b.sequence("-", NUMBER)
502503
));
503504

505+
b.rule(WILDCARD_PATTERN).is("_");
506+
b.rule(GROUP_PATTERN).is("(", PATTERN, ")");
507+
504508
b.rule(FUNCDEF).is(b.optional(DECORATORS), b.optional("async"), "def", FUNCNAME, "(", b.optional(TYPEDARGSLIST), ")", b.optional(FUN_RETURN_ANNOTATION), ":", SUITE);
505509
b.rule(FUNCNAME).is(NAME);
506510
b.rule(FUN_RETURN_ANNOTATION).is("-", ">", TEST);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2021 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.tree;
21+
22+
import java.util.Arrays;
23+
import java.util.List;
24+
import org.sonar.plugins.python.api.tree.GroupPattern;
25+
import org.sonar.plugins.python.api.tree.Pattern;
26+
import org.sonar.plugins.python.api.tree.Token;
27+
import org.sonar.plugins.python.api.tree.Tree;
28+
import org.sonar.plugins.python.api.tree.TreeVisitor;
29+
30+
public class GroupPatternImpl extends PyTree implements GroupPattern {
31+
32+
private final Token leftPar;
33+
private final Pattern pattern;
34+
private final Token rightPar;
35+
36+
public GroupPatternImpl(Token leftPar, Pattern pattern, Token rightPar) {
37+
this.leftPar = leftPar;
38+
this.pattern = pattern;
39+
this.rightPar = rightPar;
40+
}
41+
42+
@Override
43+
public Token leftPar() {
44+
return leftPar;
45+
}
46+
47+
@Override
48+
public Pattern pattern() {
49+
return pattern;
50+
}
51+
52+
@Override
53+
public Token rightPar() {
54+
return rightPar;
55+
}
56+
57+
@Override
58+
public void accept(TreeVisitor visitor) {
59+
visitor.visitGroupPattern(this);
60+
}
61+
62+
@Override
63+
public Kind getKind() {
64+
return Kind.GROUP_PATTERN;
65+
}
66+
67+
@Override
68+
List<Tree> computeChildren() {
69+
return Arrays.asList(leftPar, pattern, rightPar);
70+
}
71+
}

python-frontend/src/main/java/org/sonar/python/tree/PythonTreeMaker.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.sonar.plugins.python.api.tree.FormattedExpression;
6565
import org.sonar.plugins.python.api.tree.FunctionDef;
6666
import org.sonar.plugins.python.api.tree.GlobalStatement;
67+
import org.sonar.plugins.python.api.tree.GroupPattern;
6768
import org.sonar.plugins.python.api.tree.Guard;
6869
import org.sonar.plugins.python.api.tree.IfStatement;
6970
import org.sonar.plugins.python.api.tree.ImportFrom;
@@ -859,10 +860,21 @@ public static Pattern closedPattern(AstNode closedPattern) {
859860
return new CapturePatternImpl(name(astNode.getFirstChild()));
860861
} else if (astNode.is(PythonGrammar.SEQUENCE_PATTERN)) {
861862
return sequencePattern(astNode);
863+
} else if (astNode.is(PythonGrammar.GROUP_PATTERN)) {
864+
return groupPattern(astNode);
865+
} else if (astNode.is(PythonGrammar.WILDCARD_PATTERN)) {
866+
return wildcardPattern(astNode);
862867
}
863868
throw new IllegalStateException(String.format("Pattern %s not recognized.", astNode.getName()));
864869
}
865870

871+
private static GroupPattern groupPattern(AstNode groupPattern) {
872+
Token leftPar = toPyToken(groupPattern.getFirstChild(PythonPunctuator.LPARENTHESIS).getToken());
873+
Pattern pattern = pattern(groupPattern.getFirstChild(PythonGrammar.PATTERN).getFirstChild());
874+
Token rightPar = toPyToken(groupPattern.getFirstChild(PythonPunctuator.RPARENTHESIS).getToken());
875+
return new GroupPatternImpl(leftPar, pattern, rightPar);
876+
}
877+
866878
private static SequencePattern sequencePattern(AstNode sequencePattern) {
867879
AstNode leftDelimiter = sequencePattern.getFirstChild(PythonPunctuator.LPARENTHESIS, PythonPunctuator.LBRACKET);
868880
AstNode rightDelimiter = sequencePattern.getFirstChild(PythonPunctuator.RPARENTHESIS, PythonPunctuator.RBRACKET);
@@ -911,10 +923,21 @@ private static Pattern maybeStarPattern(AstNode maybeStarPattern) {
911923

912924
private static StarPattern starPattern(AstNode starPattern) {
913925
Token starToken = toPyToken(starPattern.getFirstChild(PythonPunctuator.MUL).getToken());
926+
Pattern pattern;
914927
AstNode capturePattern = starPattern.getFirstChild(PythonGrammar.CAPTURE_PATTERN);
915-
return new StarPatternImpl(starToken, new CapturePatternImpl(name(capturePattern.getFirstChild())));
928+
if (capturePattern != null) {
929+
pattern = new CapturePatternImpl(name(capturePattern.getFirstChild()));
930+
} else {
931+
pattern = wildcardPattern(starPattern.getFirstChild(PythonGrammar.WILDCARD_PATTERN));
932+
}
933+
return new StarPatternImpl(starToken, pattern);
916934
}
917935

936+
private static WildcardPatternImpl wildcardPattern(AstNode wildcardPattern) {
937+
return new WildcardPatternImpl(toPyToken(wildcardPattern.getFirstChild().getToken()));
938+
}
939+
940+
918941
private static LiteralPattern literalPattern(AstNode literalPattern) {
919942
LiteralPattern.LiteralKind literalKind;
920943
if (literalPattern.hasDirectChildren(PythonGrammar.COMPLEX_NUMBER, PythonGrammar.SIGNED_NUMBER)) {

python-frontend/src/main/java/org/sonar/python/tree/StarPatternImpl.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import java.util.Arrays;
2323
import java.util.List;
24-
import org.sonar.plugins.python.api.tree.CapturePattern;
24+
import org.sonar.plugins.python.api.tree.Pattern;
2525
import org.sonar.plugins.python.api.tree.StarPattern;
2626
import org.sonar.plugins.python.api.tree.Token;
2727
import org.sonar.plugins.python.api.tree.Tree;
@@ -30,16 +30,16 @@
3030
public class StarPatternImpl extends PyTree implements StarPattern {
3131

3232
private final Token starToken;
33-
private final CapturePattern capturePattern;
33+
private final Pattern pattern;
3434

35-
public StarPatternImpl(Token starToken, CapturePattern capturePattern) {
35+
public StarPatternImpl(Token starToken, Pattern pattern) {
3636
this.starToken = starToken;
37-
this.capturePattern = capturePattern;
37+
this.pattern = pattern;
3838
}
3939

4040
@Override
41-
public CapturePattern capturePattern() {
42-
return capturePattern;
41+
public Pattern pattern() {
42+
return pattern;
4343
}
4444

4545
@Override
@@ -54,6 +54,6 @@ public Kind getKind() {
5454

5555
@Override
5656
List<Tree> computeChildren() {
57-
return Arrays.asList(starToken, capturePattern);
57+
return Arrays.asList(starToken, pattern);
5858
}
5959
}

0 commit comments

Comments
 (0)