From 6a1fa4778f1f5910a16e064a0fd5e0f9da4324a8 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 7 Nov 2023 07:16:06 -0500 Subject: [PATCH] Reject more syntactically invalid Python programs (#8524) ## Summary This commit adds some additional error checking to the parser such that assignments that are invalid syntax are rejected. This covers the obvious cases like `5 = 3` and some not so obvious cases like `x + y = 42`. This does add an additional recursive call to the parser for the cases handling assignments. I had initially been concerned about doing this, but `set_context` is already doing recursion during assignments, so I didn't feel as though this was changing any fundamental performance characteristics of the parser. (Also, in practice, I would expect any such recursion here to be quite shallow since the recursion is done on the target of an assignment. Such things are rarely nested much in practice.) Fixes #6895 ## Test Plan I've added unit tests covering every case that is detected as invalid on an `Expr`. --- .../src/rules/eradicate/detection.rs | 8 +- .../test/fixtures/ruff/expression/yield.py | 2 - .../format@expression__yield.py.snap | 4 - crates/ruff_python_parser/src/invalid.rs | 733 ++++++++++++++++++ crates/ruff_python_parser/src/lexer.rs | 3 + crates/ruff_python_parser/src/lib.rs | 1 + crates/ruff_python_parser/src/python.lalrpop | 27 +- crates/ruff_python_parser/src/python.rs | 220 +++--- ...r__invalid__tests__ok_assignment_expr.snap | 33 + ...__invalid__tests__ok_attribute_normal.snap | 40 + ...r__invalid__tests__ok_attribute_weird.snap | 41 + ...invalid__tests__ok_ipy_escape_command.snap | 20 + ...ython_parser__invalid__tests__ok_list.snap | 76 ++ ...ython_parser__invalid__tests__ok_name.snap | 30 + ...rser__invalid__tests__ok_slice_normal.snap | 70 ++ ...arser__invalid__tests__ok_slice_weird.snap | 71 ++ ...on_parser__invalid__tests__ok_starred.snap | 36 + ...__invalid__tests__ok_subscript_normal.snap | 44 ++ ...r__invalid__tests__ok_subscript_weird.snap | 45 ++ ...thon_parser__invalid__tests__ok_tuple.snap | 76 ++ 20 files changed, 1432 insertions(+), 148 deletions(-) create mode 100644 crates/ruff_python_parser/src/invalid.rs create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_assignment_expr.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_attribute_normal.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_attribute_weird.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_ipy_escape_command.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_list.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_name.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_slice_normal.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_slice_weird.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_starred.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_subscript_normal.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_subscript_weird.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_tuple.snap diff --git a/crates/ruff_linter/src/rules/eradicate/detection.rs b/crates/ruff_linter/src/rules/eradicate/detection.rs index 9d4eef33ef..f3f3474c8d 100644 --- a/crates/ruff_linter/src/rules/eradicate/detection.rs +++ b/crates/ruff_linter/src/rules/eradicate/detection.rs @@ -163,11 +163,15 @@ mod tests { "# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create(", &[] )); - assert!(comment_contains_code( + assert!(comment_contains_code("# )", &[])); + + // This used to return true, but our parser has gotten a bit better + // at rejecting invalid Python syntax. And indeed, this is not valid + // Python code. + assert!(!comment_contains_code( "# app_label=\"core\", model=\"user\"", &[] )); - assert!(comment_contains_code("# )", &[])); // TODO(charlie): This should be `true` under aggressive mode. assert!(!comment_contains_code("#def foo():", &[])); diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py index 8700b7ac06..e0f3c2e806 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/yield.py @@ -22,8 +22,6 @@ def foo(): pass - (yield a, b) = (1, 2) - # some comment for e in l : yield e # some comment diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap index e6f39afa29..463ed0d1ec 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__yield.py.snap @@ -28,8 +28,6 @@ def foo(): pass - (yield a, b) = (1, 2) - # some comment for e in l : yield e # some comment @@ -151,8 +149,6 @@ def foo(): # comment pass - (yield a, b) = (1, 2) - # some comment for e in l: yield e # some comment diff --git a/crates/ruff_python_parser/src/invalid.rs b/crates/ruff_python_parser/src/invalid.rs new file mode 100644 index 0000000000..8424567113 --- /dev/null +++ b/crates/ruff_python_parser/src/invalid.rs @@ -0,0 +1,733 @@ +/*! +Defines some helper routines for rejecting invalid Python programs. + +These routines are named in a way that supports qualified use. For example, +`invalid::assignment_targets`. +*/ + +use {ruff_python_ast::Expr, ruff_text_size::TextSize}; + +use crate::lexer::{LexicalError, LexicalErrorType}; + +/// Returns an error for invalid assignment targets. +/// +/// # Errors +/// +/// This returns an error when any of the given expressions are themselves +/// or contain an expression that is invalid on the left hand side of an +/// assignment. For example, all literal expressions are invalid assignment +/// targets. +pub(crate) fn assignment_targets(targets: &[Expr]) -> Result<(), LexicalError> { + for t in targets { + assignment_target(t)?; + } + Ok(()) +} + +/// Returns an error if the given target is invalid for the left hand side of +/// an assignment. +/// +/// # Errors +/// +/// This returns an error when the given expression is itself or contains an +/// expression that is invalid on the left hand side of an assignment. For +/// example, all literal expressions are invalid assignment targets. +pub(crate) fn assignment_target(target: &Expr) -> Result<(), LexicalError> { + // Allowing a glob import here because of its limited scope. + #[allow(clippy::enum_glob_use)] + use self::Expr::*; + + let err = |location: TextSize| -> LexicalError { + let error = LexicalErrorType::AssignmentError; + LexicalError { error, location } + }; + match *target { + BoolOp(ref e) => Err(err(e.range.start())), + NamedExpr(ref e) => Err(err(e.range.start())), + BinOp(ref e) => Err(err(e.range.start())), + UnaryOp(ref e) => Err(err(e.range.start())), + Lambda(ref e) => Err(err(e.range.start())), + IfExp(ref e) => Err(err(e.range.start())), + Dict(ref e) => Err(err(e.range.start())), + Set(ref e) => Err(err(e.range.start())), + ListComp(ref e) => Err(err(e.range.start())), + SetComp(ref e) => Err(err(e.range.start())), + DictComp(ref e) => Err(err(e.range.start())), + GeneratorExp(ref e) => Err(err(e.range.start())), + Await(ref e) => Err(err(e.range.start())), + Yield(ref e) => Err(err(e.range.start())), + YieldFrom(ref e) => Err(err(e.range.start())), + Compare(ref e) => Err(err(e.range.start())), + Call(ref e) => Err(err(e.range.start())), + FormattedValue(ref e) => Err(err(e.range.start())), + // FString is recursive, but all its forms are invalid as an + // assignment target, so we can reject it without exploring it. + FString(ref e) => Err(err(e.range.start())), + StringLiteral(ref e) => Err(err(e.range.start())), + BytesLiteral(ref e) => Err(err(e.range.start())), + NumberLiteral(ref e) => Err(err(e.range.start())), + BooleanLiteral(ref e) => Err(err(e.range.start())), + NoneLiteral(ref e) => Err(err(e.range.start())), + EllipsisLiteral(ref e) => Err(err(e.range.start())), + // This isn't in the Python grammar but is Jupyter notebook specific. + // It seems like this should be an error. It does also seem like the + // parser prevents this from ever appearing as an assignment target + // anyway. ---AG + IpyEscapeCommand(ref e) => Err(err(e.range.start())), + // The only nested expressions allowed as an assignment target + // are star exprs, lists and tuples. + Starred(ref e) => assignment_target(&e.value), + List(ref e) => assignment_targets(&e.elts), + Tuple(ref e) => assignment_targets(&e.elts), + // Subscript is recursive and can be invalid, but aren't syntax errors. + // For example, `5[1] = 42` is a type error. + Subscript(_) => Ok(()), + // Similar to Subscript, e.g., `5[1:2] = [42]` is a type error. + Slice(_) => Ok(()), + // Similar to Subscript, e.g., `"foo".y = 42` is an attribute error. + Attribute(_) => Ok(()), + // These are always valid as assignment targets. + Name(_) => Ok(()), + } +} + +#[cfg(test)] +mod tests { + use crate::parse_suite; + + // First we test, broadly, that various kinds of assignments are now + // rejected by the parser. e.g., `5 = 3`, `5 += 3`, `(5): int = 3`. + + // Regression test: https://github.com/astral-sh/ruff/issues/6895 + #[test] + fn err_literal_assignment() { + let ast = parse_suite(r"5 = 3", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + // This test previously passed before the assignment operator checking + // above, but we include it here for good measure. + #[test] + fn err_assignment_expr() { + let ast = parse_suite(r"(5 := 3)", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: UnrecognizedToken( + ColonEqual, + None, + ), + offset: 3, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_literal_augment_assignment() { + let ast = parse_suite(r"5 += 3", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_literal_annotation_assignment() { + let ast = parse_suite(r"(5): int = 3", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 1, + source_path: "", + }, +) +"#); + } + + // Now we exhaustively test all possible cases where assignment can fail. + + #[test] + fn err_bool_op() { + let ast = parse_suite(r"x or y = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_named_expr() { + let ast = parse_suite(r"(x := 5) = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 1, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_bin_op() { + let ast = parse_suite(r"x + y = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_unary_op() { + let ast = parse_suite(r"-x = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_lambda() { + let ast = parse_suite(r"(lambda _: 1) = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 1, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_if_exp() { + let ast = parse_suite(r"a if b else c = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_dict() { + let ast = parse_suite(r"{'a':5} = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_set() { + let ast = parse_suite(r"{a} = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_list_comp() { + let ast = parse_suite(r"[x for x in xs] = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_set_comp() { + let ast = parse_suite(r"{x for x in xs} = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_dict_comp() { + let ast = parse_suite(r"{x: x*2 for x in xs} = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_generator_exp() { + let ast = parse_suite(r"(x for x in xs) = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_await() { + let ast = parse_suite(r"await x = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_yield() { + let ast = parse_suite(r"(yield x) = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 1, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_yield_from() { + let ast = parse_suite(r"(yield from xs) = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 1, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_compare() { + let ast = parse_suite(r"a < b < c = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_call() { + let ast = parse_suite(r"foo() = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_formatted_value() { + // N.B. It looks like the parser can't generate a top-level + // FormattedValue, where as the official Python AST permits + // representing a single f-string containing just a variable as a + // FormattedValue directly. + // + // Bottom line is that because of this, this test is (at present) + // duplicative with the `fstring` test. That is, in theory these tests + // could fail independently, but in practice their failure or success + // is coupled. + // + // See: https://docs.python.org/3/library/ast.html#ast.FormattedValue + let ast = parse_suite(r#"f"{quux}" = 42"#, ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_fstring() { + let ast = parse_suite(r#"f"{foo} and {bar}" = 42"#, ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_string_literal() { + let ast = parse_suite(r#""foo" = 42"#, ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_bytes_literal() { + let ast = parse_suite(r#"b"foo" = 42"#, ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_number_literal() { + let ast = parse_suite(r"123 = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_boolean_literal() { + let ast = parse_suite(r"True = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_none_literal() { + let ast = parse_suite(r"None = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_ellipsis_literal() { + let ast = parse_suite(r"... = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 0, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_starred() { + let ast = parse_suite(r"*foo() = 42", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 1, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_list() { + let ast = parse_suite(r"[x, foo(), y] = [42, 42, 42]", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 4, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_list_nested() { + let ast = parse_suite(r"[[a, b], [[42]], d] = [[1, 2], [[3]], 4]", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 11, + source_path: "", + }, +) +"#); + } + + #[test] + fn err_tuple() { + let ast = parse_suite(r"(x, foo(), y) = (42, 42, 42)", ""); + insta::assert_debug_snapshot!(ast, @r#" +Err( + ParseError { + error: Lexical( + AssignmentError, + ), + offset: 4, + source_path: "", + }, +) +"#); + } + + // This last group of tests checks that assignments we expect to be parsed + // (including some interesting ones) continue to be parsed successfully. + + #[test] + fn ok_starred() { + let ast = parse_suite(r"*foo = 42", ""); + insta::assert_debug_snapshot!(ast); + } + + #[test] + fn ok_list() { + let ast = parse_suite(r"[x, y, z] = [1, 2, 3]", ""); + insta::assert_debug_snapshot!(ast); + } + + #[test] + fn ok_tuple() { + let ast = parse_suite(r"(x, y, z) = (1, 2, 3)", ""); + insta::assert_debug_snapshot!(ast); + } + + #[test] + fn ok_subscript_normal() { + let ast = parse_suite(r"x[0] = 42", ""); + insta::assert_debug_snapshot!(ast); + } + + // This is actually a type error, not a syntax error. So check that it + // doesn't fail parsing. + #[test] + fn ok_subscript_weird() { + let ast = parse_suite(r"5[0] = 42", ""); + insta::assert_debug_snapshot!(ast); + } + + #[test] + fn ok_slice_normal() { + let ast = parse_suite(r"x[1:2] = [42]", ""); + insta::assert_debug_snapshot!(ast); + } + + // This is actually a type error, not a syntax error. So check that it + // doesn't fail parsing. + #[test] + fn ok_slice_weird() { + let ast = parse_suite(r"5[1:2] = [42]", ""); + insta::assert_debug_snapshot!(ast); + } + + #[test] + fn ok_attribute_normal() { + let ast = parse_suite(r"foo.bar = 42", ""); + insta::assert_debug_snapshot!(ast); + } + + // This is actually an attribute error, not a syntax error. So check that + // it doesn't fail parsing. + #[test] + fn ok_attribute_weird() { + let ast = parse_suite(r#""foo".y = 42"#, ""); + insta::assert_debug_snapshot!(ast); + } + + #[test] + fn ok_name() { + let ast = parse_suite(r"foo = 42", ""); + insta::assert_debug_snapshot!(ast); + } + + // This is a sanity test for what looks like an ipython directive being + // assigned to. Although this doesn't actually parse as an assignment + // statement, but rather, a directive whose value is `foo = 42`. + #[test] + fn ok_ipy_escape_command() { + use crate::Mode; + + let src = r"!foo = 42"; + let tokens = crate::lexer::lex(src, Mode::Ipython); + let ast = crate::parse_tokens(tokens, src, Mode::Ipython, ""); + insta::assert_debug_snapshot!(ast); + } + + #[test] + fn ok_assignment_expr() { + let ast = parse_suite(r"(x := 5)", ""); + insta::assert_debug_snapshot!(ast); + } +} diff --git a/crates/ruff_python_parser/src/lexer.rs b/crates/ruff_python_parser/src/lexer.rs index b4f3436d5a..0ca7cbc80d 100644 --- a/crates/ruff_python_parser/src/lexer.rs +++ b/crates/ruff_python_parser/src/lexer.rs @@ -1344,6 +1344,8 @@ pub enum LexicalErrorType { LineContinuationError, /// An unexpected end of file was encountered. Eof, + /// Occurs when a syntactically invalid assignment was encountered. + AssignmentError, /// An unexpected error occurred. OtherError(String), } @@ -1389,6 +1391,7 @@ impl std::fmt::Display for LexicalErrorType { write!(f, "unexpected character after line continuation character") } LexicalErrorType::Eof => write!(f, "unexpected EOF while parsing"), + LexicalErrorType::AssignmentError => write!(f, "invalid assignment target"), LexicalErrorType::OtherError(msg) => write!(f, "{msg}"), } } diff --git a/crates/ruff_python_parser/src/lib.rs b/crates/ruff_python_parser/src/lib.rs index 28ff5229c1..53c482dfd8 100644 --- a/crates/ruff_python_parser/src/lib.rs +++ b/crates/ruff_python_parser/src/lib.rs @@ -123,6 +123,7 @@ use crate::lexer::LexResult; mod function; // Skip flattening lexer to distinguish from full ruff_python_parser mod context; +mod invalid; pub mod lexer; mod parser; mod soft_keywords; diff --git a/crates/ruff_python_parser/src/python.lalrpop b/crates/ruff_python_parser/src/python.lalrpop index dfd28bad4a..9c73f648bf 100644 --- a/crates/ruff_python_parser/src/python.lalrpop +++ b/crates/ruff_python_parser/src/python.lalrpop @@ -13,6 +13,7 @@ use crate::{ context::set_context, string::{StringType, concatenate_strings, parse_fstring_middle, parse_string_literal}, token::{self, StringKind}, + invalid, }; use lalrpop_util::ParseError; @@ -108,12 +109,12 @@ DelStatement: ast::Stmt = { }; ExpressionStatement: ast::Stmt = { - => { + =>? { // Just an expression, no assignment: if suffix.is_empty() { - ast::Stmt::Expr( + Ok(ast::Stmt::Expr( ast::StmtExpr { value: Box::new(expression.into()), range: (location..end_location).into() } - ) + )) } else { let mut targets = vec![set_context(expression.into(), ast::ExprContext::Store)]; let mut values = suffix; @@ -123,25 +124,27 @@ ExpressionStatement: ast::Stmt = { for target in values { targets.push(set_context(target.into(), ast::ExprContext::Store)); } - - ast::Stmt::Assign( + invalid::assignment_targets(&targets)?; + Ok(ast::Stmt::Assign( ast::StmtAssign { targets, value, range: (location..end_location).into() } - ) + )) } }, - => { - ast::Stmt::AugAssign( + =>? { + invalid::assignment_target(&target.expr)?; + Ok(ast::Stmt::AugAssign( ast::StmtAugAssign { target: Box::new(set_context(target.into(), ast::ExprContext::Store)), op, value: Box::new(rhs.into()), range: (location..end_location).into() }, - ) + )) }, - > ":" > => { + > ":" > =>? { let simple = target.expr.is_name_expr(); - ast::Stmt::AnnAssign( + invalid::assignment_target(&target.expr)?; + Ok(ast::Stmt::AnnAssign( ast::StmtAnnAssign { target: Box::new(set_context(target.into(), ast::ExprContext::Store)), annotation: Box::new(annotation.into()), @@ -149,7 +152,7 @@ ExpressionStatement: ast::Stmt = { simple, range: (location..end_location).into() }, - ) + )) }, }; diff --git a/crates/ruff_python_parser/src/python.rs b/crates/ruff_python_parser/src/python.rs index c109aa4b13..bfbd1475dd 100644 --- a/crates/ruff_python_parser/src/python.rs +++ b/crates/ruff_python_parser/src/python.rs @@ -1,5 +1,5 @@ // auto-generated: "lalrpop 0.20.0" -// sha3: c798bc6e7bd9950e88dd5d950470865a75b5ff0352f4fc7fb51f13147de6ba6c +// sha3: b8ac4a859b69d580e50733d39c96a3fe018f568e71e532ebb3153a19902e64e5 use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use ruff_python_ast::{self as ast, Int, IpyEscapeKind}; use crate::{ @@ -10,6 +10,7 @@ use crate::{ context::set_context, string::{StringType, concatenate_strings, parse_fstring_middle, parse_string_literal}, token::{self, StringKind}, + invalid, }; use lalrpop_util::ParseError; #[allow(unused_extern_crates)] @@ -33,6 +34,7 @@ mod __parse__Top { context::set_context, string::{StringType, concatenate_strings, parse_fstring_middle, parse_string_literal}, token::{self, StringKind}, + invalid, }; use lalrpop_util::ParseError; #[allow(unused_extern_crates)] @@ -13725,19 +13727,76 @@ mod __parse__Top { __reduce356(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>) } 357 => { - __reduce357(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>) + // ExpressionStatement = GenericList => ActionFn(1752); + let __sym0 = __pop_Variant15(__symbols); + let __start = __sym0.0; + let __end = __sym0.2; + let __nt = match super::__action1752::<>(source_code, mode, __sym0) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + __symbols.push((__start, __Symbol::Variant37(__nt), __end)); + (1, 137) } 358 => { - __reduce358(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>) + // ExpressionStatement = GenericList, AssignSuffix+ => ActionFn(1753); + assert!(__symbols.len() >= 2); + let __sym1 = __pop_Variant17(__symbols); + let __sym0 = __pop_Variant15(__symbols); + let __start = __sym0.0; + let __end = __sym1.2; + let __nt = match super::__action1753::<>(source_code, mode, __sym0, __sym1) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + __symbols.push((__start, __Symbol::Variant37(__nt), __end)); + (2, 137) } 359 => { - __reduce359(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>) + // ExpressionStatement = GenericList, AugAssign, TestListOrYieldExpr => ActionFn(1754); + assert!(__symbols.len() >= 3); + let __sym2 = __pop_Variant15(__symbols); + let __sym1 = __pop_Variant49(__symbols); + let __sym0 = __pop_Variant15(__symbols); + let __start = __sym0.0; + let __end = __sym2.2; + let __nt = match super::__action1754::<>(source_code, mode, __sym0, __sym1, __sym2) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + __symbols.push((__start, __Symbol::Variant37(__nt), __end)); + (3, 137) } 360 => { - __reduce360(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>) + // ExpressionStatement = Test<"all">, ":", Test<"all">, AssignSuffix => ActionFn(1531); + assert!(__symbols.len() >= 4); + let __sym3 = __pop_Variant15(__symbols); + let __sym2 = __pop_Variant15(__symbols); + let __sym1 = __pop_Variant0(__symbols); + let __sym0 = __pop_Variant15(__symbols); + let __start = __sym0.0; + let __end = __sym3.2; + let __nt = match super::__action1531::<>(source_code, mode, __sym0, __sym1, __sym2, __sym3) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + __symbols.push((__start, __Symbol::Variant37(__nt), __end)); + (4, 137) } 361 => { - __reduce361(source_code, mode, __lookahead_start, __symbols, core::marker::PhantomData::<()>) + // ExpressionStatement = Test<"all">, ":", Test<"all"> => ActionFn(1532); + assert!(__symbols.len() >= 3); + let __sym2 = __pop_Variant15(__symbols); + let __sym1 = __pop_Variant0(__symbols); + let __sym0 = __pop_Variant15(__symbols); + let __start = __sym0.0; + let __end = __sym2.2; + let __nt = match super::__action1532::<>(source_code, mode, __sym0, __sym1, __sym2) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + __symbols.push((__start, __Symbol::Variant37(__nt), __end)); + (3, 137) } 362 => { // FStringConversion = "!", name => ActionFn(800); @@ -24718,103 +24777,6 @@ mod __parse__Top { __symbols.push((__start, __Symbol::Variant15(__nt), __end)); (1, 136) } - pub(crate) fn __reduce357< - >( - source_code: &str, - mode: Mode, - __lookahead_start: Option<&TextSize>, - __symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>, - _: core::marker::PhantomData<()>, - ) -> (usize, usize) - { - // ExpressionStatement = GenericList => ActionFn(1752); - let __sym0 = __pop_Variant15(__symbols); - let __start = __sym0.0; - let __end = __sym0.2; - let __nt = super::__action1752::<>(source_code, mode, __sym0); - __symbols.push((__start, __Symbol::Variant37(__nt), __end)); - (1, 137) - } - pub(crate) fn __reduce358< - >( - source_code: &str, - mode: Mode, - __lookahead_start: Option<&TextSize>, - __symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>, - _: core::marker::PhantomData<()>, - ) -> (usize, usize) - { - // ExpressionStatement = GenericList, AssignSuffix+ => ActionFn(1753); - assert!(__symbols.len() >= 2); - let __sym1 = __pop_Variant17(__symbols); - let __sym0 = __pop_Variant15(__symbols); - let __start = __sym0.0; - let __end = __sym1.2; - let __nt = super::__action1753::<>(source_code, mode, __sym0, __sym1); - __symbols.push((__start, __Symbol::Variant37(__nt), __end)); - (2, 137) - } - pub(crate) fn __reduce359< - >( - source_code: &str, - mode: Mode, - __lookahead_start: Option<&TextSize>, - __symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>, - _: core::marker::PhantomData<()>, - ) -> (usize, usize) - { - // ExpressionStatement = GenericList, AugAssign, TestListOrYieldExpr => ActionFn(1754); - assert!(__symbols.len() >= 3); - let __sym2 = __pop_Variant15(__symbols); - let __sym1 = __pop_Variant49(__symbols); - let __sym0 = __pop_Variant15(__symbols); - let __start = __sym0.0; - let __end = __sym2.2; - let __nt = super::__action1754::<>(source_code, mode, __sym0, __sym1, __sym2); - __symbols.push((__start, __Symbol::Variant37(__nt), __end)); - (3, 137) - } - pub(crate) fn __reduce360< - >( - source_code: &str, - mode: Mode, - __lookahead_start: Option<&TextSize>, - __symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>, - _: core::marker::PhantomData<()>, - ) -> (usize, usize) - { - // ExpressionStatement = Test<"all">, ":", Test<"all">, AssignSuffix => ActionFn(1531); - assert!(__symbols.len() >= 4); - let __sym3 = __pop_Variant15(__symbols); - let __sym2 = __pop_Variant15(__symbols); - let __sym1 = __pop_Variant0(__symbols); - let __sym0 = __pop_Variant15(__symbols); - let __start = __sym0.0; - let __end = __sym3.2; - let __nt = super::__action1531::<>(source_code, mode, __sym0, __sym1, __sym2, __sym3); - __symbols.push((__start, __Symbol::Variant37(__nt), __end)); - (4, 137) - } - pub(crate) fn __reduce361< - >( - source_code: &str, - mode: Mode, - __lookahead_start: Option<&TextSize>, - __symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>, - _: core::marker::PhantomData<()>, - ) -> (usize, usize) - { - // ExpressionStatement = Test<"all">, ":", Test<"all"> => ActionFn(1532); - assert!(__symbols.len() >= 3); - let __sym2 = __pop_Variant15(__symbols); - let __sym1 = __pop_Variant0(__symbols); - let __sym0 = __pop_Variant15(__symbols); - let __start = __sym0.0; - let __end = __sym2.2; - let __nt = super::__action1532::<>(source_code, mode, __sym0, __sym1, __sym2); - __symbols.push((__start, __Symbol::Variant37(__nt), __end)); - (3, 137) - } pub(crate) fn __reduce363< >( source_code: &str, @@ -32789,14 +32751,14 @@ fn __action26< (_, expression, _): (TextSize, ast::ParenthesizedExpr, TextSize), (_, suffix, _): (TextSize, alloc::vec::Vec, TextSize), (_, end_location, _): (TextSize, TextSize, TextSize), -) -> ast::Stmt +) -> Result> { { // Just an expression, no assignment: if suffix.is_empty() { - ast::Stmt::Expr( + Ok(ast::Stmt::Expr( ast::StmtExpr { value: Box::new(expression.into()), range: (location..end_location).into() } - ) + )) } else { let mut targets = vec![set_context(expression.into(), ast::ExprContext::Store)]; let mut values = suffix; @@ -32806,10 +32768,10 @@ fn __action26< for target in values { targets.push(set_context(target.into(), ast::ExprContext::Store)); } - - ast::Stmt::Assign( + invalid::assignment_targets(&targets)?; + Ok(ast::Stmt::Assign( ast::StmtAssign { targets, value, range: (location..end_location).into() } - ) + )) } } } @@ -32825,17 +32787,18 @@ fn __action27< (_, op, _): (TextSize, ast::Operator, TextSize), (_, rhs, _): (TextSize, ast::ParenthesizedExpr, TextSize), (_, end_location, _): (TextSize, TextSize, TextSize), -) -> ast::Stmt +) -> Result> { { - ast::Stmt::AugAssign( + invalid::assignment_target(&target.expr)?; + Ok(ast::Stmt::AugAssign( ast::StmtAugAssign { target: Box::new(set_context(target.into(), ast::ExprContext::Store)), op, value: Box::new(rhs.into()), range: (location..end_location).into() }, - ) + )) } } @@ -32851,11 +32814,12 @@ fn __action28< (_, annotation, _): (TextSize, ast::ParenthesizedExpr, TextSize), (_, rhs, _): (TextSize, core::option::Option, TextSize), (_, end_location, _): (TextSize, TextSize, TextSize), -) -> ast::Stmt +) -> Result> { { let simple = target.expr.is_name_expr(); - ast::Stmt::AnnAssign( + invalid::assignment_target(&target.expr)?; + Ok(ast::Stmt::AnnAssign( ast::StmtAnnAssign { target: Box::new(set_context(target.into(), ast::ExprContext::Store)), annotation: Box::new(annotation.into()), @@ -32863,7 +32827,7 @@ fn __action28< simple, range: (location..end_location).into() }, - ) + )) } } @@ -48215,7 +48179,7 @@ fn __action797< __0: (TextSize, ast::ParenthesizedExpr, TextSize), __1: (TextSize, alloc::vec::Vec, TextSize), __2: (TextSize, TextSize, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __0.0; let __end0 = __0.0; @@ -48246,7 +48210,7 @@ fn __action798< __1: (TextSize, ast::Operator, TextSize), __2: (TextSize, ast::ParenthesizedExpr, TextSize), __3: (TextSize, TextSize, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __0.0; let __end0 = __0.0; @@ -48279,7 +48243,7 @@ fn __action799< __2: (TextSize, ast::ParenthesizedExpr, TextSize), __3: (TextSize, core::option::Option, TextSize), __4: (TextSize, TextSize, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __0.0; let __end0 = __0.0; @@ -64278,7 +64242,7 @@ fn __action1309< mode: Mode, __0: (TextSize, ast::ParenthesizedExpr, TextSize), __1: (TextSize, alloc::vec::Vec, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __1.2; let __end0 = __1.2; @@ -64307,7 +64271,7 @@ fn __action1310< __0: (TextSize, ast::ParenthesizedExpr, TextSize), __1: (TextSize, ast::Operator, TextSize), __2: (TextSize, ast::ParenthesizedExpr, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __2.2; let __end0 = __2.2; @@ -64338,7 +64302,7 @@ fn __action1311< __1: (TextSize, token::Tok, TextSize), __2: (TextSize, ast::ParenthesizedExpr, TextSize), __3: (TextSize, core::option::Option, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __3.2; let __end0 = __3.2; @@ -71035,7 +70999,7 @@ fn __action1529< source_code: &str, mode: Mode, __0: (TextSize, ast::ParenthesizedExpr, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __0.2; let __end0 = __0.2; @@ -71062,7 +71026,7 @@ fn __action1530< mode: Mode, __0: (TextSize, ast::ParenthesizedExpr, TextSize), __1: (TextSize, alloc::vec::Vec, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __1.0; let __end0 = __1.2; @@ -71090,7 +71054,7 @@ fn __action1531< __1: (TextSize, token::Tok, TextSize), __2: (TextSize, ast::ParenthesizedExpr, TextSize), __3: (TextSize, ast::ParenthesizedExpr, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __3.0; let __end0 = __3.2; @@ -71119,7 +71083,7 @@ fn __action1532< __0: (TextSize, ast::ParenthesizedExpr, TextSize), __1: (TextSize, token::Tok, TextSize), __2: (TextSize, ast::ParenthesizedExpr, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __2.2; let __end0 = __2.2; @@ -78391,7 +78355,7 @@ fn __action1752< source_code: &str, mode: Mode, __0: (TextSize, ast::ParenthesizedExpr, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __0.0; let __end0 = __0.2; @@ -78416,7 +78380,7 @@ fn __action1753< mode: Mode, __0: (TextSize, ast::ParenthesizedExpr, TextSize), __1: (TextSize, alloc::vec::Vec, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __0.0; let __end0 = __0.2; @@ -78443,7 +78407,7 @@ fn __action1754< __0: (TextSize, ast::ParenthesizedExpr, TextSize), __1: (TextSize, ast::Operator, TextSize), __2: (TextSize, ast::ParenthesizedExpr, TextSize), -) -> ast::Stmt +) -> Result> { let __start0 = __0.0; let __end0 = __0.2; diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_assignment_expr.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_assignment_expr.snap new file mode 100644 index 0000000000..58e38220f7 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_assignment_expr.snap @@ -0,0 +1,33 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Expr( + StmtExpr { + range: 0..8, + value: NamedExpr( + ExprNamedExpr { + range: 1..7, + target: Name( + ExprName { + range: 1..2, + id: "x", + ctx: Store, + }, + ), + value: NumberLiteral( + ExprNumberLiteral { + range: 6..7, + value: Int( + 5, + ), + }, + ), + }, + ), + }, + ), + ], +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_attribute_normal.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_attribute_normal.snap new file mode 100644 index 0000000000..feb0fedf4b --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_attribute_normal.snap @@ -0,0 +1,40 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Assign( + StmtAssign { + range: 0..12, + targets: [ + Attribute( + ExprAttribute { + range: 0..7, + value: Name( + ExprName { + range: 0..3, + id: "foo", + ctx: Load, + }, + ), + attr: Identifier { + id: "bar", + range: 4..7, + }, + ctx: Store, + }, + ), + ], + value: NumberLiteral( + ExprNumberLiteral { + range: 10..12, + value: Int( + 42, + ), + }, + ), + }, + ), + ], +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_attribute_weird.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_attribute_weird.snap new file mode 100644 index 0000000000..8848b18979 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_attribute_weird.snap @@ -0,0 +1,41 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Assign( + StmtAssign { + range: 0..12, + targets: [ + Attribute( + ExprAttribute { + range: 0..7, + value: StringLiteral( + ExprStringLiteral { + range: 0..5, + value: "foo", + unicode: false, + implicit_concatenated: false, + }, + ), + attr: Identifier { + id: "y", + range: 6..7, + }, + ctx: Store, + }, + ), + ], + value: NumberLiteral( + ExprNumberLiteral { + range: 10..12, + value: Int( + 42, + ), + }, + ), + }, + ), + ], +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_ipy_escape_command.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_ipy_escape_command.snap new file mode 100644 index 0000000000..8aec96f050 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_ipy_escape_command.snap @@ -0,0 +1,20 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + Module( + ModModule { + range: 0..9, + body: [ + IpyEscapeCommand( + StmtIpyEscapeCommand { + range: 0..9, + kind: Shell, + value: "foo = 42", + }, + ), + ], + }, + ), +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_list.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_list.snap new file mode 100644 index 0000000000..f2673d1c28 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_list.snap @@ -0,0 +1,76 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Assign( + StmtAssign { + range: 0..21, + targets: [ + List( + ExprList { + range: 0..9, + elts: [ + Name( + ExprName { + range: 1..2, + id: "x", + ctx: Store, + }, + ), + Name( + ExprName { + range: 4..5, + id: "y", + ctx: Store, + }, + ), + Name( + ExprName { + range: 7..8, + id: "z", + ctx: Store, + }, + ), + ], + ctx: Store, + }, + ), + ], + value: List( + ExprList { + range: 12..21, + elts: [ + NumberLiteral( + ExprNumberLiteral { + range: 13..14, + value: Int( + 1, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 16..17, + value: Int( + 2, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 19..20, + value: Int( + 3, + ), + }, + ), + ], + ctx: Load, + }, + ), + }, + ), + ], +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_name.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_name.snap new file mode 100644 index 0000000000..9bde5767f1 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_name.snap @@ -0,0 +1,30 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Assign( + StmtAssign { + range: 0..8, + targets: [ + Name( + ExprName { + range: 0..3, + id: "foo", + ctx: Store, + }, + ), + ], + value: NumberLiteral( + ExprNumberLiteral { + range: 6..8, + value: Int( + 42, + ), + }, + ), + }, + ), + ], +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_slice_normal.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_slice_normal.snap new file mode 100644 index 0000000000..c6a55fb48e --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_slice_normal.snap @@ -0,0 +1,70 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Assign( + StmtAssign { + range: 0..13, + targets: [ + Subscript( + ExprSubscript { + range: 0..6, + value: Name( + ExprName { + range: 0..1, + id: "x", + ctx: Load, + }, + ), + slice: Slice( + ExprSlice { + range: 2..5, + lower: Some( + NumberLiteral( + ExprNumberLiteral { + range: 2..3, + value: Int( + 1, + ), + }, + ), + ), + upper: Some( + NumberLiteral( + ExprNumberLiteral { + range: 4..5, + value: Int( + 2, + ), + }, + ), + ), + step: None, + }, + ), + ctx: Store, + }, + ), + ], + value: List( + ExprList { + range: 9..13, + elts: [ + NumberLiteral( + ExprNumberLiteral { + range: 10..12, + value: Int( + 42, + ), + }, + ), + ], + ctx: Load, + }, + ), + }, + ), + ], +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_slice_weird.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_slice_weird.snap new file mode 100644 index 0000000000..a48ebfcc5d --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_slice_weird.snap @@ -0,0 +1,71 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Assign( + StmtAssign { + range: 0..13, + targets: [ + Subscript( + ExprSubscript { + range: 0..6, + value: NumberLiteral( + ExprNumberLiteral { + range: 0..1, + value: Int( + 5, + ), + }, + ), + slice: Slice( + ExprSlice { + range: 2..5, + lower: Some( + NumberLiteral( + ExprNumberLiteral { + range: 2..3, + value: Int( + 1, + ), + }, + ), + ), + upper: Some( + NumberLiteral( + ExprNumberLiteral { + range: 4..5, + value: Int( + 2, + ), + }, + ), + ), + step: None, + }, + ), + ctx: Store, + }, + ), + ], + value: List( + ExprList { + range: 9..13, + elts: [ + NumberLiteral( + ExprNumberLiteral { + range: 10..12, + value: Int( + 42, + ), + }, + ), + ], + ctx: Load, + }, + ), + }, + ), + ], +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_starred.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_starred.snap new file mode 100644 index 0000000000..963ecc513b --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_starred.snap @@ -0,0 +1,36 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Assign( + StmtAssign { + range: 0..9, + targets: [ + Starred( + ExprStarred { + range: 0..4, + value: Name( + ExprName { + range: 1..4, + id: "foo", + ctx: Store, + }, + ), + ctx: Store, + }, + ), + ], + value: NumberLiteral( + ExprNumberLiteral { + range: 7..9, + value: Int( + 42, + ), + }, + ), + }, + ), + ], +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_subscript_normal.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_subscript_normal.snap new file mode 100644 index 0000000000..225358f190 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_subscript_normal.snap @@ -0,0 +1,44 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Assign( + StmtAssign { + range: 0..9, + targets: [ + Subscript( + ExprSubscript { + range: 0..4, + value: Name( + ExprName { + range: 0..1, + id: "x", + ctx: Load, + }, + ), + slice: NumberLiteral( + ExprNumberLiteral { + range: 2..3, + value: Int( + 0, + ), + }, + ), + ctx: Store, + }, + ), + ], + value: NumberLiteral( + ExprNumberLiteral { + range: 7..9, + value: Int( + 42, + ), + }, + ), + }, + ), + ], +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_subscript_weird.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_subscript_weird.snap new file mode 100644 index 0000000000..5073da87b7 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_subscript_weird.snap @@ -0,0 +1,45 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Assign( + StmtAssign { + range: 0..9, + targets: [ + Subscript( + ExprSubscript { + range: 0..4, + value: NumberLiteral( + ExprNumberLiteral { + range: 0..1, + value: Int( + 5, + ), + }, + ), + slice: NumberLiteral( + ExprNumberLiteral { + range: 2..3, + value: Int( + 0, + ), + }, + ), + ctx: Store, + }, + ), + ], + value: NumberLiteral( + ExprNumberLiteral { + range: 7..9, + value: Int( + 42, + ), + }, + ), + }, + ), + ], +) diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_tuple.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_tuple.snap new file mode 100644 index 0000000000..88e7d791f0 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__invalid__tests__ok_tuple.snap @@ -0,0 +1,76 @@ +--- +source: crates/ruff_python_parser/src/invalid.rs +expression: ast +--- +Ok( + [ + Assign( + StmtAssign { + range: 0..21, + targets: [ + Tuple( + ExprTuple { + range: 0..9, + elts: [ + Name( + ExprName { + range: 1..2, + id: "x", + ctx: Store, + }, + ), + Name( + ExprName { + range: 4..5, + id: "y", + ctx: Store, + }, + ), + Name( + ExprName { + range: 7..8, + id: "z", + ctx: Store, + }, + ), + ], + ctx: Store, + }, + ), + ], + value: Tuple( + ExprTuple { + range: 12..21, + elts: [ + NumberLiteral( + ExprNumberLiteral { + range: 13..14, + value: Int( + 1, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 16..17, + value: Int( + 2, + ), + }, + ), + NumberLiteral( + ExprNumberLiteral { + range: 19..20, + value: Int( + 3, + ), + }, + ), + ], + ctx: Load, + }, + ), + }, + ), + ], +)