diff --git a/crates/ruff_python_parser/resources/inline/err/named_expr_slice.py b/crates/ruff_python_parser/resources/inline/err/named_expr_slice.py new file mode 100644 index 0000000000..c0afba07f6 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/named_expr_slice.py @@ -0,0 +1,4 @@ +# even after 3.9, an unparenthesized named expression is not allowed in a slice +lst[x:=1:-1] +lst[1:x:=1] +lst[1:3:x:=1] diff --git a/crates/ruff_python_parser/resources/inline/err/named_expr_slice_parse_error.py b/crates/ruff_python_parser/resources/inline/err/named_expr_slice_parse_error.py new file mode 100644 index 0000000000..275c6a18d1 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/named_expr_slice_parse_error.py @@ -0,0 +1,3 @@ +# parse_options: {"target-version": "3.8"} +# before 3.9, only emit the parse error, not the unsupported syntax error +lst[x:=1:-1] diff --git a/crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_index_py38.py b/crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_index_py38.py new file mode 100644 index 0000000000..6ea69e2ba2 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_index_py38.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.8"} +lst[x:=1] diff --git a/crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_set_comp_py38.py b/crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_set_comp_py38.py new file mode 100644 index 0000000000..b18e0db1c0 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_set_comp_py38.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.8"} +{last := x for x in range(3)} diff --git a/crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_set_literal_py38.py b/crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_set_literal_py38.py new file mode 100644 index 0000000000..373ed6210f --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_set_literal_py38.py @@ -0,0 +1,4 @@ +# parse_options: {"target-version": "3.8"} +{x := 1, 2, 3} +{1, x := 2, 3} +{1, 2, x := 3} diff --git a/crates/ruff_python_parser/resources/inline/ok/parenthesized_named_expr_index_py38.py b/crates/ruff_python_parser/resources/inline/ok/parenthesized_named_expr_index_py38.py new file mode 100644 index 0000000000..2d4b3d8fcd --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/parenthesized_named_expr_index_py38.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.8"} +lst[(x:=1)] diff --git a/crates/ruff_python_parser/resources/inline/ok/parenthesized_named_expr_py38.py b/crates/ruff_python_parser/resources/inline/ok/parenthesized_named_expr_py38.py new file mode 100644 index 0000000000..ff189e9941 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/parenthesized_named_expr_py38.py @@ -0,0 +1,3 @@ +# parse_options: {"target-version": "3.8"} +{(x := 1), 2, 3} +{(last := x) for x in range(3)} diff --git a/crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_expr_index_py39.py b/crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_expr_index_py39.py new file mode 100644 index 0000000000..813f1eb15c --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_expr_index_py39.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.9"} +lst[x:=1] diff --git a/crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_expr_py39.py b/crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_expr_py39.py new file mode 100644 index 0000000000..1b1bf189dc --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_expr_py39.py @@ -0,0 +1,3 @@ +# parse_options: {"target-version": "3.9"} +{x := 1, 2, 3} +{last := x for x in range(3)} diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index 59a91c5da9..defc9f6280 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -452,11 +452,55 @@ pub enum StarTupleKind { Yield, } +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum UnparenthesizedNamedExprKind { + SequenceIndex, + SetLiteral, + SetComprehension, +} + #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum UnsupportedSyntaxErrorKind { Match, Walrus, ExceptStar, + /// Represents the use of an unparenthesized named expression (`:=`) in a set literal, set + /// comprehension, or sequence index before Python 3.10. + /// + /// ## Examples + /// + /// These are allowed on Python 3.10: + /// + /// ```python + /// {x := 1, 2, 3} # set literal + /// {last := x for x in range(3)} # set comprehension + /// lst[x := 1] # sequence index + /// ``` + /// + /// But on Python 3.9 the named expression needs to be parenthesized: + /// + /// ```python + /// {(x := 1), 2, 3} # set literal + /// {(last := x) for x in range(3)} # set comprehension + /// lst[(x := 1)] # sequence index + /// ``` + /// + /// However, unparenthesized named expressions are never allowed in slices: + /// + /// ```python + /// lst[x:=1:-1] # syntax error + /// lst[1:x:=1] # syntax error + /// lst[1:3:x:=1] # syntax error + /// + /// lst[(x:=1):-1] # ok + /// lst[1:(x:=1)] # ok + /// lst[1:3:(x:=1)] # ok + /// ``` + /// + /// ## References + /// + /// - [Python 3.10 Other Language Changes](https://docs.python.org/3/whatsnew/3.10.html#other-language-changes) + UnparenthesizedNamedExpr(UnparenthesizedNamedExprKind), /// Represents the use of a parenthesized keyword argument name after Python 3.8. /// @@ -706,6 +750,15 @@ impl Display for UnsupportedSyntaxError { UnsupportedSyntaxErrorKind::Match => "Cannot use `match` statement", UnsupportedSyntaxErrorKind::Walrus => "Cannot use named assignment expression (`:=`)", UnsupportedSyntaxErrorKind::ExceptStar => "Cannot use `except*`", + UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr( + UnparenthesizedNamedExprKind::SequenceIndex, + ) => "Cannot use unparenthesized assignment expression in a sequence index", + UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr( + UnparenthesizedNamedExprKind::SetLiteral, + ) => "Cannot use unparenthesized assignment expression as an element in a set literal", + UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr( + UnparenthesizedNamedExprKind::SetComprehension, + ) => "Cannot use unparenthesized assignment expression as an element in a set comprehension", UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => { "Cannot use parenthesized keyword argument name" } @@ -765,6 +818,9 @@ impl UnsupportedSyntaxErrorKind { UnsupportedSyntaxErrorKind::Match => Change::Added(PythonVersion::PY310), UnsupportedSyntaxErrorKind::Walrus => Change::Added(PythonVersion::PY38), UnsupportedSyntaxErrorKind::ExceptStar => Change::Added(PythonVersion::PY311), + UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(_) => { + Change::Added(PythonVersion::PY39) + } UnsupportedSyntaxErrorKind::StarTuple(_) => Change::Added(PythonVersion::PY38), UnsupportedSyntaxErrorKind::RelaxedDecorator => Change::Added(PythonVersion::PY39), UnsupportedSyntaxErrorKind::PositionalOnlyParameter => { diff --git a/crates/ruff_python_parser/src/parser/expression.rs b/crates/ruff_python_parser/src/parser/expression.rs index f9f764e2ff..43ff8881af 100644 --- a/crates/ruff_python_parser/src/parser/expression.rs +++ b/crates/ruff_python_parser/src/parser/expression.rs @@ -11,7 +11,7 @@ use ruff_python_ast::{ }; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; -use crate::error::StarTupleKind; +use crate::error::{StarTupleKind, UnparenthesizedNamedExprKind}; use crate::parser::progress::ParserProgress; use crate::parser::{helpers, FunctionKind, Parser}; use crate::string::{parse_fstring_literal_element, parse_string_literal, StringType}; @@ -900,15 +900,48 @@ impl<'src> Parser<'src> { const STEP_END_SET: TokenSet = TokenSet::new([TokenKind::Comma, TokenKind::Rsqb]).union(NEWLINE_EOF_SET); + // test_err named_expr_slice + // # even after 3.9, an unparenthesized named expression is not allowed in a slice + // lst[x:=1:-1] + // lst[1:x:=1] + // lst[1:3:x:=1] + + // test_err named_expr_slice_parse_error + // # parse_options: {"target-version": "3.8"} + // # before 3.9, only emit the parse error, not the unsupported syntax error + // lst[x:=1:-1] + let start = self.node_start(); let lower = if self.at_expr() { let lower = self.parse_named_expression_or_higher(ExpressionContext::starred_conditional()); + + // This means we're in a subscript. if self.at_ts(NEWLINE_EOF_SET.union([TokenKind::Rsqb, TokenKind::Comma].into())) { + // test_ok parenthesized_named_expr_index_py38 + // # parse_options: {"target-version": "3.8"} + // lst[(x:=1)] + + // test_ok unparenthesized_named_expr_index_py39 + // # parse_options: {"target-version": "3.9"} + // lst[x:=1] + + // test_err unparenthesized_named_expr_index_py38 + // # parse_options: {"target-version": "3.8"} + // lst[x:=1] + if lower.is_unparenthesized_named_expr() { + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr( + UnparenthesizedNamedExprKind::SequenceIndex, + ), + lower.range(), + ); + } return lower.expr; } + // Now we know we're in a slice. if !lower.is_parenthesized { match lower.expr { Expr::Starred(_) => { @@ -1659,6 +1692,26 @@ impl<'src> Parser<'src> { ParseErrorType::IterableUnpackingInComprehension, &key_or_element, ); + } else if key_or_element.is_unparenthesized_named_expr() { + // test_ok parenthesized_named_expr_py38 + // # parse_options: {"target-version": "3.8"} + // {(x := 1), 2, 3} + // {(last := x) for x in range(3)} + + // test_ok unparenthesized_named_expr_py39 + // # parse_options: {"target-version": "3.9"} + // {x := 1, 2, 3} + // {last := x for x in range(3)} + + // test_err unparenthesized_named_expr_set_comp_py38 + // # parse_options: {"target-version": "3.8"} + // {last := x for x in range(3)} + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr( + UnparenthesizedNamedExprKind::SetComprehension, + ), + key_or_element.range(), + ); } Expr::SetComp(self.parse_set_comprehension_expression(key_or_element.expr, start)) @@ -1697,7 +1750,7 @@ impl<'src> Parser<'src> { )) } } - _ => Expr::Set(self.parse_set_expression(key_or_element.expr, start)), + _ => Expr::Set(self.parse_set_expression(key_or_element, start)), } } @@ -1847,19 +1900,42 @@ impl<'src> Parser<'src> { /// Parses a set expression. /// /// See: - fn parse_set_expression(&mut self, first_element: Expr, start: TextSize) -> ast::ExprSet { + fn parse_set_expression(&mut self, first_element: ParsedExpr, start: TextSize) -> ast::ExprSet { if !self.at_sequence_end() { self.expect(TokenKind::Comma); } - let mut elts = vec![first_element]; + // test_err unparenthesized_named_expr_set_literal_py38 + // # parse_options: {"target-version": "3.8"} + // {x := 1, 2, 3} + // {1, x := 2, 3} + // {1, 2, x := 3} + + if first_element.is_unparenthesized_named_expr() { + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr( + UnparenthesizedNamedExprKind::SetLiteral, + ), + first_element.range(), + ); + } + + let mut elts = vec![first_element.expr]; self.parse_comma_separated_list(RecoveryContextKind::SetElements, |parser| { - elts.push( - parser - .parse_named_expression_or_higher(ExpressionContext::starred_bitwise_or()) - .expr, - ); + let parsed_expr = + parser.parse_named_expression_or_higher(ExpressionContext::starred_bitwise_or()); + + if parsed_expr.is_unparenthesized_named_expr() { + parser.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr( + UnparenthesizedNamedExprKind::SetLiteral, + ), + parsed_expr.range(), + ); + } + + elts.push(parsed_expr.expr); }); self.expect(TokenKind::Rbrace); @@ -2410,6 +2486,11 @@ impl ParsedExpr { pub(super) const fn is_unparenthesized_starred_expr(&self) -> bool { !self.is_parenthesized && self.expr.is_starred_expr() } + + #[inline] + pub(super) const fn is_unparenthesized_named_expr(&self) -> bool { + !self.is_parenthesized && self.expr.is_named_expr() + } } impl From for ParsedExpr { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice.py.snap new file mode 100644 index 0000000000..815b74f88c --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice.py.snap @@ -0,0 +1,258 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/named_expr_slice.py +--- +## AST + +``` +Module( + ModModule { + range: 0..119, + body: [ + Expr( + StmtExpr { + range: 80..92, + value: Subscript( + ExprSubscript { + range: 80..92, + value: Name( + ExprName { + range: 80..83, + id: Name("lst"), + ctx: Load, + }, + ), + slice: Slice( + ExprSlice { + range: 84..91, + lower: Some( + Named( + ExprNamed { + range: 84..88, + target: Name( + ExprName { + range: 84..85, + id: Name("x"), + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 87..88, + value: Int( + 1, + ), + }, + ), + }, + ), + ), + upper: Some( + UnaryOp( + ExprUnaryOp { + range: 89..91, + op: USub, + operand: NumberLiteral( + ExprNumberLiteral { + range: 90..91, + value: Int( + 1, + ), + }, + ), + }, + ), + ), + step: None, + }, + ), + ctx: Load, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 93..100, + value: Subscript( + ExprSubscript { + range: 93..100, + value: Name( + ExprName { + range: 93..96, + id: Name("lst"), + ctx: Load, + }, + ), + slice: Slice( + ExprSlice { + range: 97..100, + lower: Some( + NumberLiteral( + ExprNumberLiteral { + range: 97..98, + value: Int( + 1, + ), + }, + ), + ), + upper: Some( + Name( + ExprName { + range: 99..100, + id: Name("x"), + ctx: Load, + }, + ), + ), + step: None, + }, + ), + ctx: Load, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 102..103, + value: NumberLiteral( + ExprNumberLiteral { + range: 102..103, + value: Int( + 1, + ), + }, + ), + }, + ), + Expr( + StmtExpr { + range: 105..114, + value: Subscript( + ExprSubscript { + range: 105..114, + value: Name( + ExprName { + range: 105..108, + id: Name("lst"), + ctx: Load, + }, + ), + slice: Slice( + ExprSlice { + range: 109..114, + lower: Some( + NumberLiteral( + ExprNumberLiteral { + range: 109..110, + value: Int( + 1, + ), + }, + ), + ), + upper: Some( + NumberLiteral( + ExprNumberLiteral { + range: 111..112, + value: Int( + 3, + ), + }, + ), + ), + step: Some( + Name( + ExprName { + range: 113..114, + id: Name("x"), + ctx: Load, + }, + ), + ), + }, + ), + ctx: Load, + }, + ), + }, + ), + Expr( + StmtExpr { + range: 116..117, + value: NumberLiteral( + ExprNumberLiteral { + range: 116..117, + value: Int( + 1, + ), + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # even after 3.9, an unparenthesized named expression is not allowed in a slice +2 | lst[x:=1:-1] + | ^^^^ Syntax Error: Unparenthesized named expression cannot be used here +3 | lst[1:x:=1] +4 | lst[1:3:x:=1] + | + + + | +1 | # even after 3.9, an unparenthesized named expression is not allowed in a slice +2 | lst[x:=1:-1] +3 | lst[1:x:=1] + | ^^ Syntax Error: Expected ']', found ':=' +4 | lst[1:3:x:=1] + | + + + | +1 | # even after 3.9, an unparenthesized named expression is not allowed in a slice +2 | lst[x:=1:-1] +3 | lst[1:x:=1] + | ^ Syntax Error: Expected a statement +4 | lst[1:3:x:=1] + | + + + | +1 | # even after 3.9, an unparenthesized named expression is not allowed in a slice +2 | lst[x:=1:-1] +3 | lst[1:x:=1] + | ^ Syntax Error: Expected a statement +4 | lst[1:3:x:=1] + | + + + | +2 | lst[x:=1:-1] +3 | lst[1:x:=1] +4 | lst[1:3:x:=1] + | ^^ Syntax Error: Expected ']', found ':=' + | + + + | +2 | lst[x:=1:-1] +3 | lst[1:x:=1] +4 | lst[1:3:x:=1] + | ^ Syntax Error: Expected a statement + | + + + | +2 | lst[x:=1:-1] +3 | lst[1:x:=1] +4 | lst[1:3:x:=1] + | ^ Syntax Error: Expected a statement + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice_parse_error.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice_parse_error.py.snap new file mode 100644 index 0000000000..d0166af2f4 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@named_expr_slice_parse_error.py.snap @@ -0,0 +1,85 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/named_expr_slice_parse_error.py +--- +## AST + +``` +Module( + ModModule { + range: 0..130, + body: [ + Expr( + StmtExpr { + range: 117..129, + value: Subscript( + ExprSubscript { + range: 117..129, + value: Name( + ExprName { + range: 117..120, + id: Name("lst"), + ctx: Load, + }, + ), + slice: Slice( + ExprSlice { + range: 121..128, + lower: Some( + Named( + ExprNamed { + range: 121..125, + target: Name( + ExprName { + range: 121..122, + id: Name("x"), + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 124..125, + value: Int( + 1, + ), + }, + ), + }, + ), + ), + upper: Some( + UnaryOp( + ExprUnaryOp { + range: 126..128, + op: USub, + operand: NumberLiteral( + ExprNumberLiteral { + range: 127..128, + value: Int( + 1, + ), + }, + ), + }, + ), + ), + step: None, + }, + ), + ctx: Load, + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # parse_options: {"target-version": "3.8"} +2 | # before 3.9, only emit the parse error, not the unsupported syntax error +3 | lst[x:=1:-1] + | ^^^^ Syntax Error: Unparenthesized named expression cannot be used here + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_index_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_index_py38.py.snap new file mode 100644 index 0000000000..55d81b949d --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_index_py38.py.snap @@ -0,0 +1,60 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_index_py38.py +--- +## AST + +``` +Module( + ModModule { + range: 0..53, + body: [ + Expr( + StmtExpr { + range: 43..52, + value: Subscript( + ExprSubscript { + range: 43..52, + value: Name( + ExprName { + range: 43..46, + id: Name("lst"), + ctx: Load, + }, + ), + slice: Named( + ExprNamed { + range: 47..51, + target: Name( + ExprName { + range: 47..48, + id: Name("x"), + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 50..51, + value: Int( + 1, + ), + }, + ), + }, + ), + ctx: Load, + }, + ), + }, + ), + ], + }, +) +``` +## Unsupported Syntax Errors + + | +1 | # parse_options: {"target-version": "3.8"} +2 | lst[x:=1] + | ^^^^ Syntax Error: Cannot use unparenthesized assignment expression in a sequence index on Python 3.8 (syntax was added in Python 3.9) + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_comp_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_comp_py38.py.snap new file mode 100644 index 0000000000..a608f55121 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_comp_py38.py.snap @@ -0,0 +1,91 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_set_comp_py38.py +--- +## AST + +``` +Module( + ModModule { + range: 0..73, + body: [ + Expr( + StmtExpr { + range: 43..72, + value: SetComp( + ExprSetComp { + range: 43..72, + elt: Named( + ExprNamed { + range: 44..53, + target: Name( + ExprName { + range: 44..48, + id: Name("last"), + ctx: Store, + }, + ), + value: Name( + ExprName { + range: 52..53, + id: Name("x"), + ctx: Load, + }, + ), + }, + ), + generators: [ + Comprehension { + range: 54..71, + target: Name( + ExprName { + range: 58..59, + id: Name("x"), + ctx: Store, + }, + ), + iter: Call( + ExprCall { + range: 63..71, + func: Name( + ExprName { + range: 63..68, + id: Name("range"), + ctx: Load, + }, + ), + arguments: Arguments { + range: 68..71, + args: [ + NumberLiteral( + ExprNumberLiteral { + range: 69..70, + value: Int( + 3, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ifs: [], + is_async: false, + }, + ], + }, + ), + }, + ), + ], + }, +) +``` +## Unsupported Syntax Errors + + | +1 | # parse_options: {"target-version": "3.8"} +2 | {last := x for x in range(3)} + | ^^^^^^^^^ Syntax Error: Cannot use unparenthesized assignment expression as an element in a set comprehension on Python 3.8 (syntax was added in Python 3.9) + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_literal_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_literal_py38.py.snap new file mode 100644 index 0000000000..d03f0ebe48 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@unparenthesized_named_expr_set_literal_py38.py.snap @@ -0,0 +1,185 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/unparenthesized_named_expr_set_literal_py38.py +--- +## AST + +``` +Module( + ModModule { + range: 0..88, + body: [ + Expr( + StmtExpr { + range: 43..57, + value: Set( + ExprSet { + range: 43..57, + elts: [ + Named( + ExprNamed { + range: 44..50, + target: Name( + ExprName { + range: 44..45, + id: Name("x"), + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 49..50, + value: Int( + 1, + ), + }, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 52..53, + value: Int( + 2, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 55..56, + value: Int( + 3, + ), + }, + ), + ], + }, + ), + }, + ), + Expr( + StmtExpr { + range: 58..72, + value: Set( + ExprSet { + range: 58..72, + elts: [ + NumberLiteral( + ExprNumberLiteral { + range: 59..60, + value: Int( + 1, + ), + }, + ), + Named( + ExprNamed { + range: 62..68, + target: Name( + ExprName { + range: 62..63, + id: Name("x"), + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 67..68, + value: Int( + 2, + ), + }, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 70..71, + value: Int( + 3, + ), + }, + ), + ], + }, + ), + }, + ), + Expr( + StmtExpr { + range: 73..87, + value: Set( + ExprSet { + range: 73..87, + elts: [ + NumberLiteral( + ExprNumberLiteral { + range: 74..75, + value: Int( + 1, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 77..78, + value: Int( + 2, + ), + }, + ), + Named( + ExprNamed { + range: 80..86, + target: Name( + ExprName { + range: 80..81, + id: Name("x"), + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 85..86, + value: Int( + 3, + ), + }, + ), + }, + ), + ], + }, + ), + }, + ), + ], + }, +) +``` +## Unsupported Syntax Errors + + | +1 | # parse_options: {"target-version": "3.8"} +2 | {x := 1, 2, 3} + | ^^^^^^ Syntax Error: Cannot use unparenthesized assignment expression as an element in a set literal on Python 3.8 (syntax was added in Python 3.9) +3 | {1, x := 2, 3} +4 | {1, 2, x := 3} + | + + + | +1 | # parse_options: {"target-version": "3.8"} +2 | {x := 1, 2, 3} +3 | {1, x := 2, 3} + | ^^^^^^ Syntax Error: Cannot use unparenthesized assignment expression as an element in a set literal on Python 3.8 (syntax was added in Python 3.9) +4 | {1, 2, x := 3} + | + + + | +2 | {x := 1, 2, 3} +3 | {1, x := 2, 3} +4 | {1, 2, x := 3} + | ^^^^^^ Syntax Error: Cannot use unparenthesized assignment expression as an element in a set literal on Python 3.8 (syntax was added in Python 3.9) + | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_index_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_index_py38.py.snap new file mode 100644 index 0000000000..c8e09fd54a --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_index_py38.py.snap @@ -0,0 +1,53 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/parenthesized_named_expr_index_py38.py +--- +## AST + +``` +Module( + ModModule { + range: 0..55, + body: [ + Expr( + StmtExpr { + range: 43..54, + value: Subscript( + ExprSubscript { + range: 43..54, + value: Name( + ExprName { + range: 43..46, + id: Name("lst"), + ctx: Load, + }, + ), + slice: Named( + ExprNamed { + range: 48..52, + target: Name( + ExprName { + range: 48..49, + id: Name("x"), + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 51..52, + value: Int( + 1, + ), + }, + ), + }, + ), + ctx: Load, + }, + ), + }, + ), + ], + }, +) +``` diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_py38.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_py38.py.snap new file mode 100644 index 0000000000..18a6f2406c --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@parenthesized_named_expr_py38.py.snap @@ -0,0 +1,132 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/parenthesized_named_expr_py38.py +--- +## AST + +``` +Module( + ModModule { + range: 0..92, + body: [ + Expr( + StmtExpr { + range: 43..59, + value: Set( + ExprSet { + range: 43..59, + elts: [ + Named( + ExprNamed { + range: 45..51, + target: Name( + ExprName { + range: 45..46, + id: Name("x"), + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 50..51, + value: Int( + 1, + ), + }, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 54..55, + value: Int( + 2, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 57..58, + value: Int( + 3, + ), + }, + ), + ], + }, + ), + }, + ), + Expr( + StmtExpr { + range: 60..91, + value: SetComp( + ExprSetComp { + range: 60..91, + elt: Named( + ExprNamed { + range: 62..71, + target: Name( + ExprName { + range: 62..66, + id: Name("last"), + ctx: Store, + }, + ), + value: Name( + ExprName { + range: 70..71, + id: Name("x"), + ctx: Load, + }, + ), + }, + ), + generators: [ + Comprehension { + range: 73..90, + target: Name( + ExprName { + range: 77..78, + id: Name("x"), + ctx: Store, + }, + ), + iter: Call( + ExprCall { + range: 82..90, + func: Name( + ExprName { + range: 82..87, + id: Name("range"), + ctx: Load, + }, + ), + arguments: Arguments { + range: 87..90, + args: [ + NumberLiteral( + ExprNumberLiteral { + range: 88..89, + value: Int( + 3, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ifs: [], + is_async: false, + }, + ], + }, + ), + }, + ), + ], + }, +) +``` diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_index_py39.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_index_py39.py.snap new file mode 100644 index 0000000000..e3329425de --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_index_py39.py.snap @@ -0,0 +1,53 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_expr_index_py39.py +--- +## AST + +``` +Module( + ModModule { + range: 0..53, + body: [ + Expr( + StmtExpr { + range: 43..52, + value: Subscript( + ExprSubscript { + range: 43..52, + value: Name( + ExprName { + range: 43..46, + id: Name("lst"), + ctx: Load, + }, + ), + slice: Named( + ExprNamed { + range: 47..51, + target: Name( + ExprName { + range: 47..48, + id: Name("x"), + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 50..51, + value: Int( + 1, + ), + }, + ), + }, + ), + ctx: Load, + }, + ), + }, + ), + ], + }, +) +``` diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_py39.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_py39.py.snap new file mode 100644 index 0000000000..33d284c9b5 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@unparenthesized_named_expr_py39.py.snap @@ -0,0 +1,132 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/unparenthesized_named_expr_py39.py +--- +## AST + +``` +Module( + ModModule { + range: 0..88, + body: [ + Expr( + StmtExpr { + range: 43..57, + value: Set( + ExprSet { + range: 43..57, + elts: [ + Named( + ExprNamed { + range: 44..50, + target: Name( + ExprName { + range: 44..45, + id: Name("x"), + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 49..50, + value: Int( + 1, + ), + }, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 52..53, + value: Int( + 2, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 55..56, + value: Int( + 3, + ), + }, + ), + ], + }, + ), + }, + ), + Expr( + StmtExpr { + range: 58..87, + value: SetComp( + ExprSetComp { + range: 58..87, + elt: Named( + ExprNamed { + range: 59..68, + target: Name( + ExprName { + range: 59..63, + id: Name("last"), + ctx: Store, + }, + ), + value: Name( + ExprName { + range: 67..68, + id: Name("x"), + ctx: Load, + }, + ), + }, + ), + generators: [ + Comprehension { + range: 69..86, + target: Name( + ExprName { + range: 73..74, + id: Name("x"), + ctx: Store, + }, + ), + iter: Call( + ExprCall { + range: 78..86, + func: Name( + ExprName { + range: 78..83, + id: Name("range"), + ctx: Load, + }, + ), + arguments: Arguments { + range: 83..86, + args: [ + NumberLiteral( + ExprNumberLiteral { + range: 84..85, + value: Int( + 3, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ifs: [], + is_async: false, + }, + ], + }, + ), + }, + ), + ], + }, +) +```