From b595346213c5343c27fc8ff152f15b85c7b7d44d Mon Sep 17 00:00:00 2001 From: Dylan <53534755+dylwil3@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:30:29 -0500 Subject: [PATCH] [ruff] Do not remove parens for tuples with starred expressions in Python <=3.10 `RUF031` (#12784) --- .../resources/test/fixtures/ruff/RUF031.py | 7 +- .../fixtures/ruff/RUF031_prefer_parens.py | 7 +- crates/ruff_linter/src/rules/ruff/mod.rs | 16 ++ ...rectly_parenthesized_tuple_in_subscript.rs | 12 +- ..._rules__ruff__tests__RUF031_RUF031.py.snap | 16 ++ ...remove_parentheses_starred_expr_py310.snap | 171 ++++++++++++++++++ 6 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__no_remove_parentheses_starred_expr_py310.snap diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF031.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF031.py index 4000930038..e2f638c128 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF031.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF031.py @@ -28,4 +28,9 @@ d[1,] d[(1,)] d[()] # empty tuples should be ignored d[:,] # slices in the subscript lead to syntax error if parens are added -d[1,2,:] \ No newline at end of file +d[1,2,:] + +# Should keep these parentheses in +# Python <=3.10 to avoid syntax error. +# https://github.com/astral-sh/ruff/issues/12776 +d[(*foo,bar)] \ No newline at end of file diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF031_prefer_parens.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF031_prefer_parens.py index f546515a74..dfe462aaea 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF031_prefer_parens.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF031_prefer_parens.py @@ -28,4 +28,9 @@ d[(1,)] d[()] # empty tuples should be ignored d[:,] # slices in the subscript lead to syntax error if parens are added -d[1,2,:] \ No newline at end of file +d[1,2,:] + +# Should keep these parentheses in +# Python <=3.10 to avoid syntax error. +# https://github.com/astral-sh/ruff/issues/12776 +d[(*foo,bar)] \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 975122c9f6..0fcb746b82 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -84,6 +84,22 @@ mod tests { Ok(()) } + #[test] + fn no_remove_parentheses_starred_expr_py310() -> Result<()> { + let diagnostics = test_path( + Path::new("ruff/RUF031.py"), + &LinterSettings { + ruff: super::settings::Settings { + parenthesize_tuple_in_subscript: false, + }, + target_version: PythonVersion::Py310, + ..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript) + }, + )?; + assert_messages!(diagnostics); + Ok(()) + } + #[test_case(Path::new("RUF013_0.py"))] #[test_case(Path::new("RUF013_1.py"))] fn implicit_optional_py39(path: &Path) -> Result<()> { diff --git a/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs b/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs index 473a0e2f5f..2c2005e30c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/incorrectly_parenthesized_tuple_in_subscript.rs @@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{Expr, ExprSubscript}; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; +use crate::{checkers::ast::Checker, settings::types::PythonVersion}; /// ## What it does /// Checks for consistent style regarding whether nonempty tuples in subscripts @@ -68,6 +68,16 @@ pub(crate) fn subscript_with_parenthesized_tuple(checker: &mut Checker, subscrip if prefer_parentheses && tuple_subscript.elts.iter().any(Expr::is_slice_expr) { return; } + // Removing parentheses in the presence of unpacking leads + // to a syntax error in Python 3.10. + // This is no longer a syntax error starting in Python 3.11 + // see https://peps.python.org/pep-0646/#change-1-star-expressions-in-indexes + if checker.settings.target_version <= PythonVersion::Py310 + && !prefer_parentheses + && tuple_subscript.elts.iter().any(Expr::is_starred_expr) + { + return; + } let locator = checker.locator(); let source_range = subscript.slice.range(); let new_source = if prefer_parentheses { diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF031_RUF031.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF031_RUF031.py.snap index 4dbad809a6..214b56cd0c 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF031_RUF031.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF031_RUF031.py.snap @@ -169,3 +169,19 @@ RUF031.py:28:3: RUF031 [*] Avoid parentheses for tuples in subscripts. 29 29 | d[()] # empty tuples should be ignored 30 30 | d[:,] # slices in the subscript lead to syntax error if parens are added 31 31 | d[1,2,:] + +RUF031.py:36:3: RUF031 [*] Avoid parentheses for tuples in subscripts. + | +34 | # Python <=3.10 to avoid syntax error. +35 | # https://github.com/astral-sh/ruff/issues/12776 +36 | d[(*foo,bar)] + | ^^^^^^^^^^ RUF031 + | + = help: Remove the parentheses. + +ℹ Safe fix +33 33 | # Should keep these parentheses in +34 34 | # Python <=3.10 to avoid syntax error. +35 35 | # https://github.com/astral-sh/ruff/issues/12776 +36 |-d[(*foo,bar)] + 36 |+d[*foo,bar] diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__no_remove_parentheses_starred_expr_py310.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__no_remove_parentheses_starred_expr_py310.snap new file mode 100644 index 0000000000..4dbad809a6 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__no_remove_parentheses_starred_expr_py310.snap @@ -0,0 +1,171 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF031.py:2:3: RUF031 [*] Avoid parentheses for tuples in subscripts. + | +1 | d = {(1,2):"a",(3,4):"b",(5,6,7):"c",(8,):"d"} +2 | d[(1,2)] + | ^^^^^ RUF031 +3 | d[( +4 | 1, + | + = help: Remove the parentheses. + +ℹ Safe fix +1 1 | d = {(1,2):"a",(3,4):"b",(5,6,7):"c",(8,):"d"} +2 |-d[(1,2)] + 2 |+d[1,2] +3 3 | d[( +4 4 | 1, +5 5 | 2 + +RUF031.py:3:3: RUF031 [*] Avoid parentheses for tuples in subscripts. + | +1 | d = {(1,2):"a",(3,4):"b",(5,6,7):"c",(8,):"d"} +2 | d[(1,2)] +3 | d[( + | ___^ +4 | | 1, +5 | | 2 +6 | | )] + | |_^ RUF031 +7 | d[ +8 | 1, + | + = help: Remove the parentheses. + +ℹ Safe fix +1 1 | d = {(1,2):"a",(3,4):"b",(5,6,7):"c",(8,):"d"} +2 2 | d[(1,2)] +3 |-d[( + 3 |+d[ +4 4 | 1, +5 5 | 2 +6 |-)] + 6 |+] +7 7 | d[ +8 8 | 1, +9 9 | 2 + +RUF031.py:11:3: RUF031 [*] Avoid parentheses for tuples in subscripts. + | + 9 | 2 +10 | ] +11 | d[(2,4)] + | ^^^^^ RUF031 +12 | d[(5,6,7)] +13 | d[(8,)] + | + = help: Remove the parentheses. + +ℹ Safe fix +8 8 | 1, +9 9 | 2 +10 10 | ] +11 |-d[(2,4)] + 11 |+d[2,4] +12 12 | d[(5,6,7)] +13 13 | d[(8,)] +14 14 | d[tuple(1,2)] + +RUF031.py:12:3: RUF031 [*] Avoid parentheses for tuples in subscripts. + | +10 | ] +11 | d[(2,4)] +12 | d[(5,6,7)] + | ^^^^^^^ RUF031 +13 | d[(8,)] +14 | d[tuple(1,2)] + | + = help: Remove the parentheses. + +ℹ Safe fix +9 9 | 2 +10 10 | ] +11 11 | d[(2,4)] +12 |-d[(5,6,7)] + 12 |+d[5,6,7] +13 13 | d[(8,)] +14 14 | d[tuple(1,2)] +15 15 | d[tuple(8)] + +RUF031.py:13:3: RUF031 [*] Avoid parentheses for tuples in subscripts. + | +11 | d[(2,4)] +12 | d[(5,6,7)] +13 | d[(8,)] + | ^^^^ RUF031 +14 | d[tuple(1,2)] +15 | d[tuple(8)] + | + = help: Remove the parentheses. + +ℹ Safe fix +10 10 | ] +11 11 | d[(2,4)] +12 12 | d[(5,6,7)] +13 |-d[(8,)] + 13 |+d[8,] +14 14 | d[tuple(1,2)] +15 15 | d[tuple(8)] +16 16 | d[1,2] + +RUF031.py:20:3: RUF031 [*] Avoid parentheses for tuples in subscripts. + | +18 | d[5,6,7] +19 | e = {((1,2),(3,4)):"a"} +20 | e[((1,2),(3,4))] + | ^^^^^^^^^^^^^ RUF031 +21 | e[(1,2),(3,4)] + | + = help: Remove the parentheses. + +ℹ Safe fix +17 17 | d[3,4] +18 18 | d[5,6,7] +19 19 | e = {((1,2),(3,4)):"a"} +20 |-e[((1,2),(3,4))] +21 20 | e[(1,2),(3,4)] + 21 |+e[(1,2),(3,4)] +22 22 | +23 23 | token_features[ +24 24 | (window_position, feature_name) + +RUF031.py:24:5: RUF031 [*] Avoid parentheses for tuples in subscripts. + | +23 | token_features[ +24 | (window_position, feature_name) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF031 +25 | ] = self._extract_raw_features_from_token + | + = help: Remove the parentheses. + +ℹ Safe fix +21 21 | e[(1,2),(3,4)] +22 22 | +23 23 | token_features[ +24 |- (window_position, feature_name) + 24 |+ window_position, feature_name +25 25 | ] = self._extract_raw_features_from_token +26 26 | +27 27 | d[1,] + +RUF031.py:28:3: RUF031 [*] Avoid parentheses for tuples in subscripts. + | +27 | d[1,] +28 | d[(1,)] + | ^^^^ RUF031 +29 | d[()] # empty tuples should be ignored +30 | d[:,] # slices in the subscript lead to syntax error if parens are added + | + = help: Remove the parentheses. + +ℹ Safe fix +25 25 | ] = self._extract_raw_features_from_token +26 26 | +27 27 | d[1,] +28 |-d[(1,)] + 28 |+d[1,] +29 29 | d[()] # empty tuples should be ignored +30 30 | d[:,] # slices in the subscript lead to syntax error if parens are added +31 31 | d[1,2,:]