From 0ffde3afb9a22d230bfd9168467d92b0a7106e75 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 8 Aug 2024 19:36:06 +0100 Subject: [PATCH] `RUF031`: Ignore unparenthesized tuples in subscripts when the subscript is obviously a type annotation or type alias --- .../resources/test/fixtures/ruff/RUF031.py | 10 +++++++++- .../test/fixtures/ruff/RUF031_prefer_parens.py | 10 +++++++++- ...orrectly_parenthesized_tuple_in_subscript.rs | 17 +++++++++++++++++ ...r__rules__ruff__tests__RUF031_RUF031.py.snap | 9 +++++++-- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF031.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF031.py index e2f638c128ef3..e784125680ffc 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF031.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF031.py @@ -33,4 +33,12 @@ # 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 +d[(*foo,bar)] + +x: dict[str, int] # tuples inside type annotations should never be altered + +import typing + +type Y = typing.Literal[1, 2] +Z: typing.TypeAlias = dict[int, int] +class Foo(dict[str, int]): pass 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 dfe462aaea1a2..0d9afff34828a 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 @@ -33,4 +33,12 @@ # 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 +d[(*foo,bar)] + +x: dict[str, int] # tuples inside type annotations should never be altered + +import typing + +type Y = typing.Literal[1, 2] +Z: typing.TypeAlias = dict[int, int] +class Foo(dict[str, int]): pass 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 2c2005e30c896..028173455e8b8 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 @@ -13,6 +13,10 @@ use crate::{checkers::ast::Checker, settings::types::PythonVersion}; /// [`lint.ruff.parenthesize-tuple-in-subscript`]. By default, the use of /// parentheses is considered a violation. /// +/// This rule is not applied inside "typing contexts" (type annotations, +/// type aliases and subscripted class bases), as these have their own specific +/// conventions around them. +/// /// ## Why is this bad? /// It is good to be consistent and, depending on the codebase, one or the other /// convention may be preferred. @@ -58,16 +62,20 @@ impl AlwaysFixableViolation for IncorrectlyParenthesizedTupleInSubscript { /// RUF031 pub(crate) fn subscript_with_parenthesized_tuple(checker: &mut Checker, subscript: &ExprSubscript) { let prefer_parentheses = checker.settings.ruff.parenthesize_tuple_in_subscript; + let Some(tuple_subscript) = subscript.slice.as_tuple_expr() else { return; }; + if tuple_subscript.parenthesized == prefer_parentheses || tuple_subscript.elts.is_empty() { return; } + // Adding parentheses in the presence of a slice leads to a syntax error. 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 @@ -78,6 +86,14 @@ pub(crate) fn subscript_with_parenthesized_tuple(checker: &mut Checker, subscrip { return; } + + // subscripts in annotations, type definitions or class bases are typing subscripts. + // These have their own special conventions; skip applying the rule in these cases. + let semantic = checker.semantic(); + if semantic.in_annotation() || semantic.in_type_definition() || semantic.in_class_base() { + return; + } + let locator = checker.locator(); let source_range = subscript.slice.range(); let new_source = if prefer_parentheses { @@ -86,6 +102,7 @@ pub(crate) fn subscript_with_parenthesized_tuple(checker: &mut Checker, subscrip locator.slice(source_range)[1..source_range.len().to_usize() - 1].to_string() }; let edit = Edit::range_replacement(new_source, source_range); + checker.diagnostics.push( Diagnostic::new( IncorrectlyParenthesizedTupleInSubscript { 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 214b56cd0cf69..b3ca339ba13a6 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 @@ -174,8 +174,10 @@ 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)] +36 | d[(*foo,bar)] | ^^^^^^^^^^ RUF031 +37 | +38 | x: dict[str, int] # tuples inside type annotations should never be altered | = help: Remove the parentheses. @@ -183,5 +185,8 @@ RUF031.py:36:3: RUF031 [*] Avoid parentheses for tuples in subscripts. 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)] 36 |+d[*foo,bar] +37 37 | +38 38 | x: dict[str, int] # tuples inside type annotations should never be altered +39 39 |