Skip to content

Commit

Permalink
Disallow star pattern outside sequence pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruvmanila committed Mar 20, 2024
1 parent 3727e03 commit 801539c
Show file tree
Hide file tree
Showing 5 changed files with 632 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Star pattern is only allowed inside a sequence pattern
match subject:
case *_:
pass
case *_ as x:
pass
case *foo:
pass
case *foo | 1:
pass
case 1 | *foo:
pass
case Foo(*_):
pass
case Foo(x=*_):
pass
case {*_}:
pass
case {*_: 1}:
pass
case {None: *_}:
pass
case 1 + *_:
pass
5 changes: 5 additions & 0 deletions crates/ruff_python_parser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ pub enum ParseErrorType {
SimpleStmtAndCompoundStmtInSameLine,
/// An invalid `match` case pattern was found.
InvalidMatchPatternLiteral { pattern: TokenKind },
/// A star pattern was found outside a sequence pattern.
StarPatternUsageError,
/// The parser expected a specific token that was not found.
ExpectedToken {
expected: TokenKind,
Expand Down Expand Up @@ -183,6 +185,9 @@ impl std::fmt::Display for ParseErrorType {
ParseErrorType::InvalidMatchPatternLiteral { pattern } => {
write!(f, "invalid pattern `{pattern:?}`")
}
ParseErrorType::StarPatternUsageError => {
write!(f, "Star pattern cannot be used here")
}
ParseErrorType::UnexpectedIndentation => write!(f, "unexpected indentation"),
ParseErrorType::InvalidAssignmentTarget => write!(f, "invalid assignment target"),
ParseErrorType::InvalidNamedAssignmentTarget => {
Expand Down
68 changes: 53 additions & 15 deletions crates/ruff_python_parser/src/parser/pattern.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use ruff_python_ast::{self as ast, Expr, ExprContext, Number, Operator, Pattern, Singleton};
use ruff_text_size::{Ranged, TextRange, TextSize};
use ruff_text_size::{Ranged, TextSize};

use crate::parser::progress::ParserProgress;
use crate::parser::{recovery, Parser, RecoveryContextKind, SequenceMatchPatternParentheses};
Expand Down Expand Up @@ -60,31 +60,45 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-patterns>
pub(super) fn parse_match_patterns(&mut self) -> Pattern {
let start = self.node_start();
let pattern = self.parse_match_pattern();

// We don't yet know if it's a sequence pattern or a single pattern, so
// we need to allow star pattern here.
let pattern = self.parse_match_pattern(AllowStarPattern::Yes);

if self.at(TokenKind::Comma) {
Pattern::MatchSequence(self.parse_sequence_match_pattern(pattern, start, None))
} else {
// TODO(dhruvmanila): starred patterns can't be used outside of a sequence pattern
// We know it's not a sequenc pattern now, so check for star pattern usage.
if pattern.is_match_star() {
self.add_error(ParseErrorType::StarPatternUsageError, &pattern);
}
pattern
}
}

/// Parses an `or_pattern` or an `as_pattern`.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-pattern>
fn parse_match_pattern(&mut self) -> Pattern {
fn parse_match_pattern(&mut self, allow_star_pattern: AllowStarPattern) -> Pattern {
let start = self.node_start();
let mut lhs = self.parse_match_pattern_lhs();

// We don't yet know if it's an or pattern or an as pattern, so use whatever
// was passed in.
let mut lhs = self.parse_match_pattern_lhs(allow_star_pattern);

// Or pattern
if self.at(TokenKind::Vbar) {
// We know it's an `or` pattern now, so check for star pattern usage.
if lhs.is_match_star() {
self.add_error(ParseErrorType::StarPatternUsageError, &lhs);
}

let mut patterns = vec![lhs];
let mut progress = ParserProgress::default();

while self.eat(TokenKind::Vbar) {
progress.assert_progressing(self);
let pattern = self.parse_match_pattern_lhs();
let pattern = self.parse_match_pattern_lhs(AllowStarPattern::No);
patterns.push(pattern);
}

Expand All @@ -96,6 +110,11 @@ impl<'src> Parser<'src> {

// As pattern
if self.eat(TokenKind::As) {
// We know it's an `as` pattern now, so check for star pattern usage.
if lhs.is_match_star() {
self.add_error(ParseErrorType::StarPatternUsageError, &lhs);
}

let ident = self.parse_identifier();
lhs = Pattern::MatchAs(ast::PatternMatchAs {
range: self.node_range(start),
Expand All @@ -110,11 +129,17 @@ impl<'src> Parser<'src> {
/// Parses a pattern.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-closed_pattern>
fn parse_match_pattern_lhs(&mut self) -> Pattern {
fn parse_match_pattern_lhs(&mut self, allow_star_pattern: AllowStarPattern) -> Pattern {
let start = self.node_start();
let lhs = match self.current_token_kind() {
TokenKind::Lbrace => Pattern::MatchMapping(self.parse_match_pattern_mapping()),
TokenKind::Star => Pattern::MatchStar(self.parse_match_pattern_star()),
TokenKind::Star => {
let star_pattern = self.parse_match_pattern_star();
if allow_star_pattern.is_no() {
self.add_error(ParseErrorType::StarPatternUsageError, &star_pattern);
}
Pattern::MatchStar(star_pattern)
}
TokenKind::Lpar | TokenKind::Lsqb => self.parse_delimited_match_pattern(),
_ => self.parse_match_pattern_literal(),
};
Expand Down Expand Up @@ -152,7 +177,7 @@ impl<'src> Parser<'src> {
} else {
let key_start = parser.node_start();

let key = match parser.parse_match_pattern_lhs() {
let key = match parser.parse_match_pattern_lhs(AllowStarPattern::No) {
Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => *value,
Pattern::MatchSingleton(ast::PatternMatchSingleton { value, range }) => {
match value {
Expand All @@ -178,7 +203,7 @@ impl<'src> Parser<'src> {

parser.expect(TokenKind::Colon);

patterns.push(parser.parse_match_pattern());
patterns.push(parser.parse_match_pattern(AllowStarPattern::No));

if rest.is_some() {
parser.add_error(
Expand Down Expand Up @@ -263,7 +288,7 @@ impl<'src> Parser<'src> {
});
}

let mut pattern = self.parse_match_pattern();
let mut pattern = self.parse_match_pattern(AllowStarPattern::Yes);

if parentheses.is_list() || self.at(TokenKind::Comma) {
pattern = Pattern::MatchSequence(self.parse_sequence_match_pattern(
Expand Down Expand Up @@ -304,7 +329,7 @@ impl<'src> Parser<'src> {

self.parse_comma_separated_list(
RecoveryContextKind::SequenceMatchPattern(parentheses),
|parser| patterns.push(parser.parse_match_pattern()),
|parser| patterns.push(parser.parse_match_pattern(AllowStarPattern::Yes)),
);

if let Some(parentheses) = parentheses {
Expand Down Expand Up @@ -436,6 +461,7 @@ impl<'src> Parser<'src> {
TokenKind::Int | TokenKind::Float | TokenKind::Complex
) =>
{
// TODO(dhruvmanila): Shouldn't this be `parse_unary_expression`?
let parsed_expr = self.parse_lhs_expression();

let range = self.node_range(start);
Expand Down Expand Up @@ -521,7 +547,7 @@ impl<'src> Parser<'src> {
Box::new(recovery::pattern_to_expr(lhs))
};

let rhs_pattern = self.parse_match_pattern_lhs();
let rhs_pattern = self.parse_match_pattern_lhs(AllowStarPattern::No);
let rhs_value = if let Pattern::MatchValue(rhs) = rhs_pattern {
if !matches!(
&*rhs.value,
Expand Down Expand Up @@ -629,13 +655,13 @@ impl<'src> Parser<'src> {
RecoveryContextKind::MatchPatternClassArguments,
|parser| {
let pattern_start = parser.node_start();
let pattern = parser.parse_match_pattern();
let pattern = parser.parse_match_pattern(AllowStarPattern::No);

if parser.eat(TokenKind::Equal) {
has_seen_pattern = false;
has_seen_keyword_pattern = true;

let value_pattern = parser.parse_match_pattern();
let value_pattern = parser.parse_match_pattern(AllowStarPattern::No);

// Key can only be an identifier
if let Pattern::MatchAs(ast::PatternMatchAs {
Expand Down Expand Up @@ -694,3 +720,15 @@ impl<'src> Parser<'src> {
}
}
}

#[derive(Debug, Clone, Copy)]
enum AllowStarPattern {
Yes,
No,
}

impl AllowStarPattern {
const fn is_no(self) -> bool {
matches!(self, AllowStarPattern::No)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ Module(
1 | # Starred expression is not allowed as a mapping pattern key
2 | match subject:
3 | case {*key}:
| ^^^^ Syntax Error: invalid mapping pattern key
| ^^^^ Syntax Error: Star pattern cannot be used here
4 | pass
5 | case {*key: 1}:
|
Expand All @@ -359,7 +359,7 @@ Module(
3 | case {*key}:
4 | pass
5 | case {*key: 1}:
| ^^^^ Syntax Error: invalid mapping pattern key
| ^^^^ Syntax Error: Star pattern cannot be used here
6 | pass
7 | case {*key 1}:
|
Expand All @@ -369,7 +369,7 @@ Module(
5 | case {*key: 1}:
6 | pass
7 | case {*key 1}:
| ^^^^ Syntax Error: invalid mapping pattern key
| ^^^^ Syntax Error: Star pattern cannot be used here
8 | pass
9 | case {*key, None: 1}:
|
Expand All @@ -389,7 +389,7 @@ Module(
7 | case {*key 1}:
8 | pass
9 | case {*key, None: 1}:
| ^^^^ Syntax Error: invalid mapping pattern key
| ^^^^ Syntax Error: Star pattern cannot be used here
10 | pass
|

Expand Down
Loading

0 comments on commit 801539c

Please sign in to comment.