Skip to content

Commit 1389fc5

Browse files
SONARPY-918 Match / case statement: as patterns
1 parent 6b6a244 commit 1389fc5

File tree

9 files changed

+154
-4
lines changed

9 files changed

+154
-4
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
/**
24+
* As pattern
25+
*
26+
* <pre>{@link #pattern()} as {@link #alias()}</pre>
27+
*
28+
* See https://docs.python.org/3/reference/compound_stmts.html#as-patterns
29+
*/
30+
public interface AsPattern extends Pattern {
31+
32+
Pattern pattern();
33+
34+
Name alias();
35+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,12 @@ public void visitLiteralPattern(LiteralPattern literalPattern) {
490490
// noop
491491
}
492492

493+
@Override
494+
public void visitAsPattern(AsPattern asPattern) {
495+
scan(asPattern.pattern());
496+
scan(asPattern.alias());
497+
}
498+
493499
@Override
494500
public void visitGuard(Guard guard) {
495501
scan(guard.condition());

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public interface Tree {
4242
enum Kind {
4343
ALIASED_NAME(AliasedName.class),
4444

45+
AS_PATTERN(AsPattern.class),
46+
4547
REGULAR_ARGUMENT(RegularArgument.class),
4648

4749
ARG_LIST(ArgList.class),

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,5 +179,7 @@ public interface TreeVisitor {
179179

180180
void visitLiteralPattern(LiteralPattern literalPattern);
181181

182+
void visitAsPattern(AsPattern asPattern);
183+
182184
void visitGuard(Guard guard);
183185
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,10 @@ public enum PythonGrammar implements GrammarRuleKey {
155155
GUARD,
156156

157157
PATTERN,
158+
AS_PATTERN,
159+
CLOSED_PATTERN,
158160
LITERAL_PATTERN,
161+
CAPTURE_PATTERN,
159162

160163
SIGNED_NUMBER,
161164
COMPLEX_NUMBER,
@@ -455,8 +458,10 @@ public static void compoundStatements(LexerfulGrammarBuilder b) {
455458
b.rule(CASE_BLOCK).is("case", PATTERN, b.optional(GUARD), ":", SUITE);
456459
b.rule(GUARD).is("if", NAMED_EXPR_TEST);
457460

458-
// TODO: this should be either OR_PATTERN or AS_PATTERN
459-
b.rule(PATTERN).is(LITERAL_PATTERN);
461+
b.rule(PATTERN).is(b.firstOf(AS_PATTERN, CLOSED_PATTERN));
462+
b.rule(CLOSED_PATTERN).is(LITERAL_PATTERN);
463+
b.rule(AS_PATTERN).is(CLOSED_PATTERN, "as", CAPTURE_PATTERN);
464+
b.rule(CAPTURE_PATTERN).is(NAME);
460465

461466
b.rule(LITERAL_PATTERN).is(b.firstOf(
462467
COMPLEX_NUMBER,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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.AsPattern;
25+
import org.sonar.plugins.python.api.tree.Name;
26+
import org.sonar.plugins.python.api.tree.Pattern;
27+
import org.sonar.plugins.python.api.tree.Token;
28+
import org.sonar.plugins.python.api.tree.Tree;
29+
import org.sonar.plugins.python.api.tree.TreeVisitor;
30+
31+
public class AsPatternImpl extends PyTree implements AsPattern {
32+
private final Pattern pattern;
33+
private final Token asKeyword;
34+
private final Name alias;
35+
36+
public AsPatternImpl(Pattern pattern, Token asKeyword, Name alias) {
37+
this.pattern = pattern;
38+
this.asKeyword = asKeyword;
39+
this.alias = alias;
40+
}
41+
42+
@Override
43+
public Pattern pattern() {
44+
return pattern;
45+
}
46+
47+
@Override
48+
public Name alias() {
49+
return alias;
50+
}
51+
52+
@Override
53+
public void accept(TreeVisitor visitor) {
54+
visitor.visitAsPattern(this);
55+
}
56+
57+
@Override
58+
public Kind getKind() {
59+
return Kind.AS_PATTERN;
60+
}
61+
62+
@Override
63+
List<Tree> computeChildren() {
64+
return Arrays.asList(pattern, asKeyword, alias);
65+
}
66+
}

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.sonar.plugins.python.api.tree.AnyParameter;
3535
import org.sonar.plugins.python.api.tree.ArgList;
3636
import org.sonar.plugins.python.api.tree.Argument;
37+
import org.sonar.plugins.python.api.tree.AsPattern;
3738
import org.sonar.plugins.python.api.tree.AssertStatement;
3839
import org.sonar.plugins.python.api.tree.AssignmentExpression;
3940
import org.sonar.plugins.python.api.tree.AssignmentStatement;
@@ -804,7 +805,7 @@ public MatchStatement matchStatement(AstNode matchStmt) {
804805

805806
public CaseBlock caseBlock(AstNode caseBlock) {
806807
Token caseKeyword = toPyToken(caseBlock.getTokens().get(0));
807-
Pattern pattern = pattern(caseBlock.getFirstChild(PythonGrammar.PATTERN));
808+
Pattern pattern = pattern(caseBlock.getFirstChild(PythonGrammar.PATTERN).getFirstChild());
808809
Guard guard = null;
809810
AstNode guardNode = caseBlock.getFirstChild(PythonGrammar.GUARD);
810811
if (guardNode != null) {
@@ -824,7 +825,22 @@ public Guard guard(AstNode guardNode) {
824825

825826
private static Pattern pattern(AstNode pattern) {
826827
// TODO: consider OR Patterns and other kind of patterns
827-
AstNode literalPattern = pattern.getFirstChild(PythonGrammar.LITERAL_PATTERN);
828+
if (pattern.is(PythonGrammar.CLOSED_PATTERN)) {
829+
return literalPattern(pattern.getFirstChild());
830+
} else if (pattern.is(PythonGrammar.AS_PATTERN)) {
831+
return asPattern(pattern);
832+
}
833+
throw new IllegalStateException(String.format("Pattern %s not recognized.", pattern.getName()));
834+
}
835+
836+
private static AsPattern asPattern(AstNode asPattern) {
837+
Pattern pattern = literalPattern(asPattern.getFirstChild(PythonGrammar.CLOSED_PATTERN).getFirstChild());
838+
Token asKeyword = toPyToken(asPattern.getFirstChild(PythonKeyword.AS).getToken());
839+
Name alias = name(asPattern.getFirstChild(PythonGrammar.CAPTURE_PATTERN).getFirstChild());
840+
return new AsPatternImpl(pattern, asKeyword, alias);
841+
}
842+
843+
private static LiteralPattern literalPattern(AstNode literalPattern) {
828844
LiteralPattern.LiteralKind literalKind;
829845
if (literalPattern.hasDirectChildren(PythonGrammar.COMPLEX_NUMBER, PythonGrammar.SIGNED_NUMBER)) {
830846
literalKind = LiteralPattern.LiteralKind.NUMBER;

python-frontend/src/test/java/org/sonar/python/parser/compound/statements/CasePatternsTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,11 @@ public void complex_numbers() {
6666
.matches("3 + 4j")
6767
.matches("42j");
6868
}
69+
70+
@Test
71+
public void as_patterns() {
72+
assertThat(p)
73+
.matches("'foo' as x")
74+
.notMatches("'foo' as x as y");
75+
}
6976
}

python-frontend/src/test/java/org/sonar/python/tree/PythonTreeMakerMatchStatementTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.sonar.python.tree;
2121

2222
import org.junit.Test;
23+
import org.sonar.plugins.python.api.tree.AsPattern;
2324
import org.sonar.plugins.python.api.tree.CaseBlock;
2425
import org.sonar.plugins.python.api.tree.Expression;
2526
import org.sonar.plugins.python.api.tree.Guard;
@@ -146,4 +147,14 @@ public void literal_patterns() {
146147
assertThat(literalPattern.literalKind()).isEqualTo(LiteralPattern.LiteralKind.BOOLEAN);
147148
assertThat(literalPattern.valueAsString()).isEqualTo("False");
148149
}
150+
151+
@Test
152+
public void as_pattern() {
153+
setRootRule(PythonGrammar.CASE_BLOCK);
154+
CaseBlock caseBlock = parse("case \"foo\" as x: ...", treeMaker::caseBlock);
155+
AsPattern asPattern = (AsPattern) caseBlock.pattern();
156+
assertThat(asPattern.pattern()).isInstanceOf(LiteralPattern.class);
157+
assertThat(asPattern.alias().name()).isEqualTo("x");
158+
assertThat(asPattern.children()).extracting(Tree::getKind).containsExactly(Tree.Kind.LITERAL_PATTERN, Tree.Kind.TOKEN, Tree.Kind.NAME);
159+
}
149160
}

0 commit comments

Comments
 (0)