From 6baf63d24d54862f7ce510ed9f60fc6c39dfb7b2 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Mon, 11 Nov 2024 13:31:19 +0100 Subject: [PATCH 1/7] [`flake8-pyi`] Implement autofix for `redundant-numeric-union` (`PYI041`) --- .../test/fixtures/flake8_pyi/PYI041.py | 29 ++ .../test/fixtures/flake8_pyi/PYI041.pyi | 21 ++ .../rules/redundant_numeric_union.rs | 262 +++++++++++++++--- ...__flake8_pyi__tests__PYI041_PYI041.py.snap | 201 +++++++++++++- ..._flake8_pyi__tests__PYI041_PYI041.pyi.snap | 201 +++++++++++++- 5 files changed, 642 insertions(+), 72 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.py index d1349b3ee3a0f..8675cc5464904 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.py @@ -39,9 +39,38 @@ async def f4(**kwargs: int | int | float) -> None: ... +def f5( + arg: Union[ # comment + float, # another + complex, int] + ) -> None: + ... + +def f6( + arg: ( + int | # comment + float | # another + complex + ) + ) -> None: + ... + + class Foo: def good(self, arg: int) -> None: ... def bad(self, arg: int | float | complex) -> None: ... + + def bad2(self, arg: int | Union[float, complex]) -> None: + ... + + def bad3(self, arg: Union[Union[float, complex], int]) -> None: + ... + + def bad4(self, arg: Union[float | complex, int]) -> None: + ... + + def bad5(self, arg: int | (float | complex)) -> None: + ... diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.pyi index 07c2bd3fd67a3..f45d741a5d81b 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI041.pyi @@ -32,8 +32,29 @@ def f3(arg1: int, *args: Union[int | int | float]) -> None: ... # PYI041 async def f4(**kwargs: int | int | float) -> None: ... # PYI041 +def f5( + arg: Union[ # comment + float, # another + complex, int] + ) -> None: ... # PYI041 + +def f6( + arg: ( + int | # comment + float | # another + complex + ) + ) -> None: ... # PYI041 class Foo: def good(self, arg: int) -> None: ... def bad(self, arg: int | float | complex) -> None: ... # PYI041 + + def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 + + def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 + + def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 + + def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041 diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index 39251a474170b..24c967118bec6 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -1,10 +1,17 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use bitflags::bitflags; + +use anyhow::Result; + +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::{AnyParameterRef, Expr, Parameters}; +use ruff_python_ast::{ + name::Name, AnyParameterRef, Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, ExprTuple, + Operator, Parameters, +}; use ruff_python_semantic::analyze::typing::traverse_union; -use ruff_text_size::Ranged; +use ruff_text_size::{Ranged, TextRange}; -use crate::checkers::ast::Checker; +use crate::{checkers::ast::Checker, importer::ImportRequest}; /// ## What it does /// Checks for parameter annotations that contain redundant unions between @@ -37,6 +44,13 @@ use crate::checkers::ast::Checker; /// def foo(x: float | str) -> None: ... /// ``` /// +/// ## Fix safety +/// This rule's fix is marked as safe for most cases; however, the fix will +/// nested unions type expressions into a single top-level union. +/// +/// The fix is marked as unsafe when comments are present within the type +/// expression. +/// /// ## References /// - [Python documentation: The numeric tower](https://docs.python.org/3/library/numbers.html#the-numeric-tower) /// - [PEP 484: The numeric tower](https://peps.python.org/pep-0484/#the-numeric-tower) @@ -47,16 +61,21 @@ pub struct RedundantNumericUnion { redundancy: Redundancy, } -impl Violation for RedundantNumericUnion { +impl AlwaysFixableViolation for RedundantNumericUnion { #[derive_message_formats] fn message(&self) -> String { let (subtype, supertype) = match self.redundancy { + Redundancy::IntFloatComplex => ("int | float", "complex"), Redundancy::FloatComplex => ("float", "complex"), Redundancy::IntComplex => ("int", "complex"), Redundancy::IntFloat => ("int", "float"), }; format!("Use `{supertype}` instead of `{subtype} | {supertype}`") } + + fn fix_title(&self) -> String { + "Remove duplicates".to_string() + } } /// PYI041 @@ -66,57 +85,212 @@ pub(crate) fn redundant_numeric_union(checker: &mut Checker, parameters: &Parame } } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -enum Redundancy { - FloatComplex, - IntComplex, - IntFloat, -} - -fn check_annotation(checker: &mut Checker, annotation: &Expr) { - let mut has_float = false; - let mut has_complex = false; - let mut has_int = false; +fn check_annotation<'a>(checker: &mut Checker, annotation: &'a Expr) { + let mut numeric_flags = NumericFlags::empty(); let mut find_numeric_type = |expr: &Expr, _parent: &Expr| { let Some(builtin_type) = checker.semantic().resolve_builtin_symbol(expr) else { return; }; - match builtin_type { - "int" => has_int = true, - "float" => has_float = true, - "complex" => has_complex = true, - _ => {} - } + numeric_flags.seen_builtin_type(builtin_type); }; + // Traverse the union, and remember which numeric types are found. traverse_union(&mut find_numeric_type, checker.semantic(), annotation); - if has_complex { - if has_float { - checker.diagnostics.push(Diagnostic::new( - RedundantNumericUnion { - redundancy: Redundancy::FloatComplex, - }, - annotation.range(), - )); + let Some(redundancy) = Redundancy::from_numeric_flags(numeric_flags) else { + return; + }; + + // Traverse the union a second time to construct the fix. + let mut necessary_nodes: Vec<&Expr> = Vec::new(); + + let mut union_type = UnionKind::TypingUnion; + let mut remove_numeric_type = |expr: &'a Expr, parent: &'a Expr| { + let Some(builtin_type) = checker.semantic().resolve_builtin_symbol(expr) else { + // Keep type annotations that are not numeric. + necessary_nodes.push(expr); + return; + }; + + if matches!(parent, Expr::BinOp(_)) { + union_type = UnionKind::PEP604; + } + + // `int` is always dropped, since `float` or `complex` must be present. + // `float` is only dropped if `complex`` is present. + if (builtin_type == "float" && !numeric_flags.contains(NumericFlags::COMPLEX)) + || (builtin_type != "float" && builtin_type != "int") + { + necessary_nodes.push(expr); } + }; - if has_int { - checker.diagnostics.push(Diagnostic::new( - RedundantNumericUnion { - redundancy: Redundancy::IntComplex, - }, - annotation.range(), - )); + // Traverse the union, and remember which numeric types are found. + traverse_union(&mut remove_numeric_type, checker.semantic(), annotation); + + // Mark [`Fix`] as unsafe when comments are in range. + let applicability = if checker.comment_ranges().intersects(annotation.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + + // Generate the flattened fix once. + let fix = if let &[edit_expr] = necessary_nodes.as_slice() { + // Generate a [`Fix`] for a single type expression, e.g. `int`. + Fix::applicable_edit( + Edit::range_replacement(checker.generator().expr(edit_expr), annotation.range()), + applicability, + ) + } else { + match union_type { + UnionKind::PEP604 => { + generate_pep604_fix(checker, necessary_nodes, annotation, applicability) + } + UnionKind::TypingUnion => { + generate_union_fix(checker, necessary_nodes, annotation, applicability) + .ok() + .unwrap() + } } - } else if has_float && has_int { - checker.diagnostics.push(Diagnostic::new( - RedundantNumericUnion { - redundancy: Redundancy::IntFloat, - }, - annotation.range(), - )); + }; + + checker.diagnostics.push( + Diagnostic::new(RedundantNumericUnion { redundancy }, annotation.range()).with_fix(fix), + ); +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum Redundancy { + IntFloatComplex, + FloatComplex, + IntComplex, + IntFloat, +} + +impl Redundancy { + pub(super) fn from_numeric_flags(numeric_flags: NumericFlags) -> Option { + match numeric_flags.bits() { + // NumericFlags::INT | NumericFlags::FLOAT | NumericFlags::COMPLEX + 0b0111 => Some(Self::IntFloatComplex), + // NumericFlags::FLOAT | NumericFlags::COMPLEX + 0b0110 => Some(Self::FloatComplex), + // NumericFlags::INT | NumericFlags::COMPLEX + 0b0101 => Some(Self::IntComplex), + // NumericFlags::INT | NumericFlags::FLOAT + 0b0011 => Some(Self::IntFloat), + _ => None, + } + } +} + +bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub(super) struct NumericFlags: u8 { + /// `int` + const INT = 1 << 0; + /// `float` + const FLOAT = 1 << 1; + /// `complex` + const COMPLEX = 1 << 2; + } +} + +impl NumericFlags { + pub(super) fn seen_builtin_type(&mut self, name: &str) { + let flag: NumericFlags = match name { + "int" => NumericFlags::INT, + "float" => NumericFlags::FLOAT, + "complex" => NumericFlags::COMPLEX, + _ => { + return; + } + }; + self.insert(flag); } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum UnionKind { + /// E.g., `typing.Union[int, str]` + TypingUnion, + /// E.g., `int | str` + PEP604, +} + +// Generate a [`Fix`] for two or more type expressions, e.g. `int | float | complex`. +fn generate_pep604_fix( + checker: &Checker, + nodes: Vec<&Expr>, + annotation: &Expr, + applicability: Applicability, +) -> Fix { + debug_assert!(nodes.len() >= 2, "At least two nodes required"); + + let new_expr = nodes + .into_iter() + .fold(None, |acc: Option, right: &Expr| { + if let Some(left) = acc { + Some(Expr::BinOp(ExprBinOp { + left: Box::new(left), + op: Operator::BitOr, + right: Box::new(right.clone()), + range: TextRange::default(), + })) + } else { + Some(right.clone()) + } + }) + .unwrap(); + + Fix::applicable_edit( + Edit::range_replacement(checker.generator().expr(&new_expr), annotation.range()), + applicability, + ) +} + +// Generate a [`Fix`] for two or more type expresisons, e.g. `typing.Union[int, float, complex]`. +fn generate_union_fix( + checker: &Checker, + nodes: Vec<&Expr>, + annotation: &Expr, + applicability: Applicability, +) -> Result { + debug_assert!(nodes.len() >= 2, "At least two nodes required"); + + // Request `typing.Union` + let (import_edit, binding) = checker.importer().get_or_import_symbol( + &ImportRequest::import_from("typing", "Union"), + annotation.start(), + checker.semantic(), + )?; + + // Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]` + let new_expr = Expr::Subscript(ExprSubscript { + range: TextRange::default(), + value: Box::new(Expr::Name(ExprName { + id: Name::new(binding), + ctx: ExprContext::Store, + range: TextRange::default(), + })), + slice: Box::new(if let [elt] = nodes.as_slice() { + (*elt).clone() + } else { + Expr::Tuple(ExprTuple { + elts: nodes.into_iter().cloned().collect(), + range: TextRange::default(), + ctx: ExprContext::Load, + parenthesized: false, + }) + }), + ctx: ExprContext::Load, + }); + + Ok(Fix::applicable_edits( + Edit::range_replacement(checker.generator().expr(&new_expr), annotation.range()), + [import_edit], + applicability, + )) +} diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap index e1721adde5913..27f37b80a66d9 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap @@ -1,50 +1,221 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI041.py:22:14: PYI041 Use `float` instead of `int | float` +PYI041.py:22:14: PYI041 [*] Use `float` instead of `int | float` | 22 | def f0(arg1: float | int) -> None: | ^^^^^^^^^^^ PYI041 23 | ... | + = help: Remove duplicates -PYI041.py:26:30: PYI041 Use `complex` instead of `float | complex` +ℹ Safe fix +19 19 | ... +20 20 | +21 21 | +22 |-def f0(arg1: float | int) -> None: + 22 |+def f0(arg1: float) -> None: +23 23 | ... +24 24 | +25 25 | + +PYI041.py:26:30: PYI041 [*] Use `complex` instead of `float | complex` | 26 | def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 27 | ... | + = help: Remove duplicates + +ℹ Safe fix +23 23 | ... +24 24 | +25 25 | +26 |-def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: + 26 |+def f1(arg1: float, *, arg2: list[str] | type[bool] | complex) -> None: +27 27 | ... +28 28 | +29 29 | -PYI041.py:30:28: PYI041 Use `float` instead of `int | float` +PYI041.py:30:28: PYI041 [*] Use `float` instead of `int | float` | 30 | def f2(arg1: int, /, arg2: int | int | float) -> None: | ^^^^^^^^^^^^^^^^^ PYI041 31 | ... | + = help: Remove duplicates -PYI041.py:38:24: PYI041 Use `float` instead of `int | float` +ℹ Safe fix +27 27 | ... +28 28 | +29 29 | +30 |-def f2(arg1: int, /, arg2: int | int | float) -> None: + 30 |+def f2(arg1: int, /, arg2: float) -> None: +31 31 | ... +32 32 | +33 33 | + +PYI041.py:38:24: PYI041 [*] Use `float` instead of `int | float` | 38 | async def f4(**kwargs: int | int | float) -> None: | ^^^^^^^^^^^^^^^^^ PYI041 39 | ... | + = help: Remove duplicates + +ℹ Safe fix +35 35 | ... +36 36 | +37 37 | +38 |-async def f4(**kwargs: int | int | float) -> None: + 38 |+async def f4(**kwargs: float) -> None: +39 39 | ... +40 40 | +41 41 | -PYI041.py:46:24: PYI041 Use `complex` instead of `float | complex` +PYI041.py:43:10: PYI041 [*] Use `complex` instead of `int | float | complex` | -44 | ... -45 | -46 | def bad(self, arg: int | float | complex) -> None: - | ^^^^^^^^^^^^^^^^^^^^^ PYI041 -47 | ... +42 | def f5( +43 | arg: Union[ # comment + | __________^ +44 | | float, # another +45 | | complex, int] + | |_____________________^ PYI041 +46 | ) -> None: +47 | ... | + = help: Remove duplicates + +ℹ Unsafe fix +40 40 | +41 41 | +42 42 | def f5( +43 |- arg: Union[ # comment +44 |- float, # another +45 |- complex, int] + 43 |+ arg: complex +46 44 | ) -> None: +47 45 | ... +48 46 | -PYI041.py:46:24: PYI041 Use `complex` instead of `int | complex` +PYI041.py:51:9: PYI041 [*] Use `complex` instead of `int | float | complex` + | +49 | def f6( +50 | arg: ( +51 | int | # comment + | _________^ +52 | | float | # another +53 | | complex + | |_______________^ PYI041 +54 | ) +55 | ) -> None: | -44 | ... -45 | -46 | def bad(self, arg: int | float | complex) -> None: + = help: Remove duplicates + +ℹ Unsafe fix +48 48 | +49 49 | def f6( +50 50 | arg: ( +51 |- int | # comment +52 |- float | # another +53 51 | complex +54 52 | ) +55 53 | ) -> None: + +PYI041.py:63:24: PYI041 [*] Use `complex` instead of `int | float | complex` + | +61 | ... +62 | +63 | def bad(self, arg: int | float | complex) -> None: | ^^^^^^^^^^^^^^^^^^^^^ PYI041 -47 | ... +64 | ... | + = help: Remove duplicates +ℹ Safe fix +60 60 | def good(self, arg: int) -> None: +61 61 | ... +62 62 | +63 |- def bad(self, arg: int | float | complex) -> None: + 63 |+ def bad(self, arg: complex) -> None: +64 64 | ... +65 65 | +66 66 | def bad2(self, arg: int | Union[float, complex]) -> None: + +PYI041.py:66:25: PYI041 [*] Use `complex` instead of `int | float | complex` + | +64 | ... +65 | +66 | def bad2(self, arg: int | Union[float, complex]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +67 | ... + | + = help: Remove duplicates + +ℹ Safe fix +63 63 | def bad(self, arg: int | float | complex) -> None: +64 64 | ... +65 65 | +66 |- def bad2(self, arg: int | Union[float, complex]) -> None: + 66 |+ def bad2(self, arg: complex) -> None: +67 67 | ... +68 68 | +69 69 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: + +PYI041.py:69:25: PYI041 [*] Use `complex` instead of `int | float | complex` + | +67 | ... +68 | +69 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +70 | ... + | + = help: Remove duplicates + +ℹ Safe fix +66 66 | def bad2(self, arg: int | Union[float, complex]) -> None: +67 67 | ... +68 68 | +69 |- def bad3(self, arg: Union[Union[float, complex], int]) -> None: + 69 |+ def bad3(self, arg: complex) -> None: +70 70 | ... +71 71 | +72 72 | def bad4(self, arg: Union[float | complex, int]) -> None: + +PYI041.py:72:25: PYI041 [*] Use `complex` instead of `int | float | complex` + | +70 | ... +71 | +72 | def bad4(self, arg: Union[float | complex, int]) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +73 | ... + | + = help: Remove duplicates + +ℹ Safe fix +69 69 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: +70 70 | ... +71 71 | +72 |- def bad4(self, arg: Union[float | complex, int]) -> None: + 72 |+ def bad4(self, arg: complex) -> None: +73 73 | ... +74 74 | +75 75 | def bad5(self, arg: int | (float | complex)) -> None: + +PYI041.py:75:25: PYI041 [*] Use `complex` instead of `int | float | complex` + | +73 | ... +74 | +75 | def bad5(self, arg: int | (float | complex)) -> None: + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +76 | ... + | + = help: Remove duplicates +ℹ Safe fix +72 72 | def bad4(self, arg: Union[float | complex, int]) -> None: +73 73 | ... +74 74 | +75 |- def bad5(self, arg: int | (float | complex)) -> None: + 75 |+ def bad5(self, arg: complex) -> None: +76 76 | ... diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap index 914faa51566ad..da818608b107e 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap @@ -1,44 +1,219 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI041.pyi:21:14: PYI041 Use `float` instead of `int | float` +PYI041.pyi:21:14: PYI041 [*] Use `float` instead of `int | float` | 21 | def f0(arg1: float | int) -> None: ... # PYI041 | ^^^^^^^^^^^ PYI041 | + = help: Remove duplicates -PYI041.pyi:24:30: PYI041 Use `complex` instead of `float | complex` +ℹ Safe fix +18 18 | def good2(arg: int, arg2: int | bool) -> None: ... +19 19 | +20 20 | +21 |-def f0(arg1: float | int) -> None: ... # PYI041 + 21 |+def f0(arg1: float) -> None: ... # PYI041 +22 22 | +23 23 | +24 24 | def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041 + +PYI041.pyi:24:30: PYI041 [*] Use `complex` instead of `float | complex` | 24 | def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 | + = help: Remove duplicates + +ℹ Safe fix +21 21 | def f0(arg1: float | int) -> None: ... # PYI041 +22 22 | +23 23 | +24 |-def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041 + 24 |+def f1(arg1: float, *, arg2: list[str] | type[bool] | complex) -> None: ... # PYI041 +25 25 | +26 26 | +27 27 | def f2(arg1: int, /, arg2: int | int | float) -> None: ... # PYI041 -PYI041.pyi:27:28: PYI041 Use `float` instead of `int | float` +PYI041.pyi:27:28: PYI041 [*] Use `float` instead of `int | float` | 27 | def f2(arg1: int, /, arg2: int | int | float) -> None: ... # PYI041 | ^^^^^^^^^^^^^^^^^ PYI041 | + = help: Remove duplicates -PYI041.pyi:33:24: PYI041 Use `float` instead of `int | float` +ℹ Safe fix +24 24 | def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041 +25 25 | +26 26 | +27 |-def f2(arg1: int, /, arg2: int | int | float) -> None: ... # PYI041 + 27 |+def f2(arg1: int, /, arg2: float) -> None: ... # PYI041 +28 28 | +29 29 | +30 30 | def f3(arg1: int, *args: Union[int | int | float]) -> None: ... # PYI041 + +PYI041.pyi:33:24: PYI041 [*] Use `float` instead of `int | float` | 33 | async def f4(**kwargs: int | int | float) -> None: ... # PYI041 | ^^^^^^^^^^^^^^^^^ PYI041 +34 | +35 | def f5( | + = help: Remove duplicates + +ℹ Safe fix +30 30 | def f3(arg1: int, *args: Union[int | int | float]) -> None: ... # PYI041 +31 31 | +32 32 | +33 |-async def f4(**kwargs: int | int | float) -> None: ... # PYI041 + 33 |+async def f4(**kwargs: float) -> None: ... # PYI041 +34 34 | +35 35 | def f5( +36 36 | arg: Union[ # comment -PYI041.pyi:39:24: PYI041 Use `complex` instead of `float | complex` +PYI041.pyi:36:10: PYI041 [*] Use `complex` instead of `int | float | complex` | -37 | def good(self, arg: int) -> None: ... -38 | -39 | def bad(self, arg: int | float | complex) -> None: ... # PYI041 - | ^^^^^^^^^^^^^^^^^^^^^ PYI041 +35 | def f5( +36 | arg: Union[ # comment + | __________^ +37 | | float, # another +38 | | complex, int] + | |_____________________^ PYI041 +39 | ) -> None: ... # PYI041 | + = help: Remove duplicates + +ℹ Unsafe fix +33 33 | async def f4(**kwargs: int | int | float) -> None: ... # PYI041 +34 34 | +35 35 | def f5( +36 |- arg: Union[ # comment +37 |- float, # another +38 |- complex, int] + 36 |+ arg: complex +39 37 | ) -> None: ... # PYI041 +40 38 | +41 39 | def f6( -PYI041.pyi:39:24: PYI041 Use `complex` instead of `int | complex` +PYI041.pyi:43:9: PYI041 [*] Use `complex` instead of `int | float | complex` + | +41 | def f6( +42 | arg: ( +43 | int | # comment + | _________^ +44 | | float | # another +45 | | complex + | |_______________^ PYI041 +46 | ) +47 | ) -> None: ... # PYI041 | -37 | def good(self, arg: int) -> None: ... -38 | -39 | def bad(self, arg: int | float | complex) -> None: ... # PYI041 + = help: Remove duplicates + +ℹ Unsafe fix +40 40 | +41 41 | def f6( +42 42 | arg: ( +43 |- int | # comment +44 |- float | # another +45 43 | complex +46 44 | ) +47 45 | ) -> None: ... # PYI041 + +PYI041.pyi:52:24: PYI041 [*] Use `complex` instead of `int | float | complex` + | +50 | def good(self, arg: int) -> None: ... +51 | +52 | def bad(self, arg: int | float | complex) -> None: ... # PYI041 | ^^^^^^^^^^^^^^^^^^^^^ PYI041 +53 | +54 | def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 | + = help: Remove duplicates +ℹ Safe fix +49 49 | class Foo: +50 50 | def good(self, arg: int) -> None: ... +51 51 | +52 |- def bad(self, arg: int | float | complex) -> None: ... # PYI041 + 52 |+ def bad(self, arg: complex) -> None: ... # PYI041 +53 53 | +54 54 | def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 +55 55 | + +PYI041.pyi:54:25: PYI041 [*] Use `complex` instead of `int | float | complex` + | +52 | def bad(self, arg: int | float | complex) -> None: ... # PYI041 +53 | +54 | def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +55 | +56 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 + | + = help: Remove duplicates + +ℹ Safe fix +51 51 | +52 52 | def bad(self, arg: int | float | complex) -> None: ... # PYI041 +53 53 | +54 |- def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 + 54 |+ def bad2(self, arg: complex) -> None: ... # PYI041 +55 55 | +56 56 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 +57 57 | + +PYI041.pyi:56:25: PYI041 [*] Use `complex` instead of `int | float | complex` + | +54 | def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 +55 | +56 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +57 | +58 | def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 + | + = help: Remove duplicates + +ℹ Safe fix +53 53 | +54 54 | def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 +55 55 | +56 |- def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 + 56 |+ def bad3(self, arg: complex) -> None: ... # PYI041 +57 57 | +58 58 | def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 +59 59 | + +PYI041.pyi:58:25: PYI041 [*] Use `complex` instead of `int | float | complex` + | +56 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 +57 | +58 | def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 +59 | +60 | def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041 + | + = help: Remove duplicates + +ℹ Safe fix +55 55 | +56 56 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 +57 57 | +58 |- def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 + 58 |+ def bad4(self, arg: complex) -> None: ... # PYI041 +59 59 | +60 60 | def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041 + +PYI041.pyi:60:25: PYI041 [*] Use `complex` instead of `int | float | complex` + | +58 | def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 +59 | +60 | def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041 + | ^^^^^^^^^^^^^^^^^^^^^^^ PYI041 + | + = help: Remove duplicates +ℹ Safe fix +57 57 | +58 58 | def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 +59 59 | +60 |- def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041 + 60 |+ def bad5(self, arg: complex) -> None: ... # PYI041 From c7d492221562fe4791cef3ce99ef995bf48f506d Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Mon, 11 Nov 2024 15:22:32 +0100 Subject: [PATCH 2/7] Fix available under `--preview` --- .../rules/redundant_numeric_union.rs | 65 +++++---- ...__flake8_pyi__tests__PYI041_PYI041.py.snap | 132 ++---------------- ..._flake8_pyi__tests__PYI041_PYI041.pyi.snap | 130 ++--------------- 3 files changed, 57 insertions(+), 270 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index 24c967118bec6..ef1994293e4fb 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -2,7 +2,7 @@ use bitflags::bitflags; use anyhow::Result; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{ name::Name, AnyParameterRef, Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, ExprTuple, @@ -61,7 +61,10 @@ pub struct RedundantNumericUnion { redundancy: Redundancy, } -impl AlwaysFixableViolation for RedundantNumericUnion { +impl Violation for RedundantNumericUnion { + // Always fixable, but currently under preview. + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { let (subtype, supertype) = match self.redundancy { @@ -73,8 +76,8 @@ impl AlwaysFixableViolation for RedundantNumericUnion { format!("Use `{supertype}` instead of `{subtype} | {supertype}`") } - fn fix_title(&self) -> String { - "Remove duplicates".to_string() + fn fix_title(&self) -> Option { + Some("Remove duplicates".to_string()) } } @@ -130,36 +133,38 @@ fn check_annotation<'a>(checker: &mut Checker, annotation: &'a Expr) { // Traverse the union, and remember which numeric types are found. traverse_union(&mut remove_numeric_type, checker.semantic(), annotation); - // Mark [`Fix`] as unsafe when comments are in range. - let applicability = if checker.comment_ranges().intersects(annotation.range()) { - Applicability::Unsafe - } else { - Applicability::Safe - }; + let mut diagnostic = Diagnostic::new(RedundantNumericUnion { redundancy }, annotation.range()); + if checker.settings.preview.is_enabled() { + // Mark [`Fix`] as unsafe when comments are in range. + let applicability = if checker.comment_ranges().intersects(annotation.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }; - // Generate the flattened fix once. - let fix = if let &[edit_expr] = necessary_nodes.as_slice() { - // Generate a [`Fix`] for a single type expression, e.g. `int`. - Fix::applicable_edit( - Edit::range_replacement(checker.generator().expr(edit_expr), annotation.range()), - applicability, - ) - } else { - match union_type { - UnionKind::PEP604 => { - generate_pep604_fix(checker, necessary_nodes, annotation, applicability) - } - UnionKind::TypingUnion => { - generate_union_fix(checker, necessary_nodes, annotation, applicability) - .ok() - .unwrap() + // Generate the flattened fix once. + let fix = if let &[edit_expr] = necessary_nodes.as_slice() { + // Generate a [`Fix`] for a single type expression, e.g. `int`. + Fix::applicable_edit( + Edit::range_replacement(checker.generator().expr(edit_expr), annotation.range()), + applicability, + ) + } else { + match union_type { + UnionKind::PEP604 => { + generate_pep604_fix(checker, necessary_nodes, annotation, applicability) + } + UnionKind::TypingUnion => { + generate_union_fix(checker, necessary_nodes, annotation, applicability) + .ok() + .unwrap() + } } - } + }; + diagnostic.set_fix(fix); }; - checker.diagnostics.push( - Diagnostic::new(RedundantNumericUnion { redundancy }, annotation.range()).with_fix(fix), - ); + checker.diagnostics.push(diagnostic); } #[derive(Debug, Clone, Copy, Eq, PartialEq)] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap index 27f37b80a66d9..2b2c4a76f0891 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI041.py:22:14: PYI041 [*] Use `float` instead of `int | float` +PYI041.py:22:14: PYI041 Use `float` instead of `int | float` | 22 | def f0(arg1: float | int) -> None: | ^^^^^^^^^^^ PYI041 @@ -9,17 +9,7 @@ PYI041.py:22:14: PYI041 [*] Use `float` instead of `int | float` | = help: Remove duplicates -ℹ Safe fix -19 19 | ... -20 20 | -21 21 | -22 |-def f0(arg1: float | int) -> None: - 22 |+def f0(arg1: float) -> None: -23 23 | ... -24 24 | -25 25 | - -PYI041.py:26:30: PYI041 [*] Use `complex` instead of `float | complex` +PYI041.py:26:30: PYI041 Use `complex` instead of `float | complex` | 26 | def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 @@ -27,17 +17,7 @@ PYI041.py:26:30: PYI041 [*] Use `complex` instead of `float | complex` | = help: Remove duplicates -ℹ Safe fix -23 23 | ... -24 24 | -25 25 | -26 |-def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: - 26 |+def f1(arg1: float, *, arg2: list[str] | type[bool] | complex) -> None: -27 27 | ... -28 28 | -29 29 | - -PYI041.py:30:28: PYI041 [*] Use `float` instead of `int | float` +PYI041.py:30:28: PYI041 Use `float` instead of `int | float` | 30 | def f2(arg1: int, /, arg2: int | int | float) -> None: | ^^^^^^^^^^^^^^^^^ PYI041 @@ -45,17 +25,7 @@ PYI041.py:30:28: PYI041 [*] Use `float` instead of `int | float` | = help: Remove duplicates -ℹ Safe fix -27 27 | ... -28 28 | -29 29 | -30 |-def f2(arg1: int, /, arg2: int | int | float) -> None: - 30 |+def f2(arg1: int, /, arg2: float) -> None: -31 31 | ... -32 32 | -33 33 | - -PYI041.py:38:24: PYI041 [*] Use `float` instead of `int | float` +PYI041.py:38:24: PYI041 Use `float` instead of `int | float` | 38 | async def f4(**kwargs: int | int | float) -> None: | ^^^^^^^^^^^^^^^^^ PYI041 @@ -63,17 +33,7 @@ PYI041.py:38:24: PYI041 [*] Use `float` instead of `int | float` | = help: Remove duplicates -ℹ Safe fix -35 35 | ... -36 36 | -37 37 | -38 |-async def f4(**kwargs: int | int | float) -> None: - 38 |+async def f4(**kwargs: float) -> None: -39 39 | ... -40 40 | -41 41 | - -PYI041.py:43:10: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.py:43:10: PYI041 Use `complex` instead of `int | float | complex` | 42 | def f5( 43 | arg: Union[ # comment @@ -86,19 +46,7 @@ PYI041.py:43:10: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Unsafe fix -40 40 | -41 41 | -42 42 | def f5( -43 |- arg: Union[ # comment -44 |- float, # another -45 |- complex, int] - 43 |+ arg: complex -46 44 | ) -> None: -47 45 | ... -48 46 | - -PYI041.py:51:9: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.py:51:9: PYI041 Use `complex` instead of `int | float | complex` | 49 | def f6( 50 | arg: ( @@ -112,17 +60,7 @@ PYI041.py:51:9: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Unsafe fix -48 48 | -49 49 | def f6( -50 50 | arg: ( -51 |- int | # comment -52 |- float | # another -53 51 | complex -54 52 | ) -55 53 | ) -> None: - -PYI041.py:63:24: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.py:63:24: PYI041 Use `complex` instead of `int | float | complex` | 61 | ... 62 | @@ -132,17 +70,7 @@ PYI041.py:63:24: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Safe fix -60 60 | def good(self, arg: int) -> None: -61 61 | ... -62 62 | -63 |- def bad(self, arg: int | float | complex) -> None: - 63 |+ def bad(self, arg: complex) -> None: -64 64 | ... -65 65 | -66 66 | def bad2(self, arg: int | Union[float, complex]) -> None: - -PYI041.py:66:25: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.py:66:25: PYI041 Use `complex` instead of `int | float | complex` | 64 | ... 65 | @@ -152,17 +80,7 @@ PYI041.py:66:25: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Safe fix -63 63 | def bad(self, arg: int | float | complex) -> None: -64 64 | ... -65 65 | -66 |- def bad2(self, arg: int | Union[float, complex]) -> None: - 66 |+ def bad2(self, arg: complex) -> None: -67 67 | ... -68 68 | -69 69 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: - -PYI041.py:69:25: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.py:69:25: PYI041 Use `complex` instead of `int | float | complex` | 67 | ... 68 | @@ -172,17 +90,7 @@ PYI041.py:69:25: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Safe fix -66 66 | def bad2(self, arg: int | Union[float, complex]) -> None: -67 67 | ... -68 68 | -69 |- def bad3(self, arg: Union[Union[float, complex], int]) -> None: - 69 |+ def bad3(self, arg: complex) -> None: -70 70 | ... -71 71 | -72 72 | def bad4(self, arg: Union[float | complex, int]) -> None: - -PYI041.py:72:25: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.py:72:25: PYI041 Use `complex` instead of `int | float | complex` | 70 | ... 71 | @@ -192,17 +100,7 @@ PYI041.py:72:25: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Safe fix -69 69 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: -70 70 | ... -71 71 | -72 |- def bad4(self, arg: Union[float | complex, int]) -> None: - 72 |+ def bad4(self, arg: complex) -> None: -73 73 | ... -74 74 | -75 75 | def bad5(self, arg: int | (float | complex)) -> None: - -PYI041.py:75:25: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.py:75:25: PYI041 Use `complex` instead of `int | float | complex` | 73 | ... 74 | @@ -211,11 +109,3 @@ PYI041.py:75:25: PYI041 [*] Use `complex` instead of `int | float | complex` 76 | ... | = help: Remove duplicates - -ℹ Safe fix -72 72 | def bad4(self, arg: Union[float | complex, int]) -> None: -73 73 | ... -74 74 | -75 |- def bad5(self, arg: int | (float | complex)) -> None: - 75 |+ def bad5(self, arg: complex) -> None: -76 76 | ... diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap index da818608b107e..f5015af26bfa7 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap @@ -1,58 +1,28 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI041.pyi:21:14: PYI041 [*] Use `float` instead of `int | float` +PYI041.pyi:21:14: PYI041 Use `float` instead of `int | float` | 21 | def f0(arg1: float | int) -> None: ... # PYI041 | ^^^^^^^^^^^ PYI041 | = help: Remove duplicates -ℹ Safe fix -18 18 | def good2(arg: int, arg2: int | bool) -> None: ... -19 19 | -20 20 | -21 |-def f0(arg1: float | int) -> None: ... # PYI041 - 21 |+def f0(arg1: float) -> None: ... # PYI041 -22 22 | -23 23 | -24 24 | def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041 - -PYI041.pyi:24:30: PYI041 [*] Use `complex` instead of `float | complex` +PYI041.pyi:24:30: PYI041 Use `complex` instead of `float | complex` | 24 | def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 | = help: Remove duplicates -ℹ Safe fix -21 21 | def f0(arg1: float | int) -> None: ... # PYI041 -22 22 | -23 23 | -24 |-def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041 - 24 |+def f1(arg1: float, *, arg2: list[str] | type[bool] | complex) -> None: ... # PYI041 -25 25 | -26 26 | -27 27 | def f2(arg1: int, /, arg2: int | int | float) -> None: ... # PYI041 - -PYI041.pyi:27:28: PYI041 [*] Use `float` instead of `int | float` +PYI041.pyi:27:28: PYI041 Use `float` instead of `int | float` | 27 | def f2(arg1: int, /, arg2: int | int | float) -> None: ... # PYI041 | ^^^^^^^^^^^^^^^^^ PYI041 | = help: Remove duplicates -ℹ Safe fix -24 24 | def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041 -25 25 | -26 26 | -27 |-def f2(arg1: int, /, arg2: int | int | float) -> None: ... # PYI041 - 27 |+def f2(arg1: int, /, arg2: float) -> None: ... # PYI041 -28 28 | -29 29 | -30 30 | def f3(arg1: int, *args: Union[int | int | float]) -> None: ... # PYI041 - -PYI041.pyi:33:24: PYI041 [*] Use `float` instead of `int | float` +PYI041.pyi:33:24: PYI041 Use `float` instead of `int | float` | 33 | async def f4(**kwargs: int | int | float) -> None: ... # PYI041 | ^^^^^^^^^^^^^^^^^ PYI041 @@ -61,17 +31,7 @@ PYI041.pyi:33:24: PYI041 [*] Use `float` instead of `int | float` | = help: Remove duplicates -ℹ Safe fix -30 30 | def f3(arg1: int, *args: Union[int | int | float]) -> None: ... # PYI041 -31 31 | -32 32 | -33 |-async def f4(**kwargs: int | int | float) -> None: ... # PYI041 - 33 |+async def f4(**kwargs: float) -> None: ... # PYI041 -34 34 | -35 35 | def f5( -36 36 | arg: Union[ # comment - -PYI041.pyi:36:10: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.pyi:36:10: PYI041 Use `complex` instead of `int | float | complex` | 35 | def f5( 36 | arg: Union[ # comment @@ -83,19 +43,7 @@ PYI041.pyi:36:10: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Unsafe fix -33 33 | async def f4(**kwargs: int | int | float) -> None: ... # PYI041 -34 34 | -35 35 | def f5( -36 |- arg: Union[ # comment -37 |- float, # another -38 |- complex, int] - 36 |+ arg: complex -39 37 | ) -> None: ... # PYI041 -40 38 | -41 39 | def f6( - -PYI041.pyi:43:9: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.pyi:43:9: PYI041 Use `complex` instead of `int | float | complex` | 41 | def f6( 42 | arg: ( @@ -109,17 +57,7 @@ PYI041.pyi:43:9: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Unsafe fix -40 40 | -41 41 | def f6( -42 42 | arg: ( -43 |- int | # comment -44 |- float | # another -45 43 | complex -46 44 | ) -47 45 | ) -> None: ... # PYI041 - -PYI041.pyi:52:24: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.pyi:52:24: PYI041 Use `complex` instead of `int | float | complex` | 50 | def good(self, arg: int) -> None: ... 51 | @@ -130,17 +68,7 @@ PYI041.pyi:52:24: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Safe fix -49 49 | class Foo: -50 50 | def good(self, arg: int) -> None: ... -51 51 | -52 |- def bad(self, arg: int | float | complex) -> None: ... # PYI041 - 52 |+ def bad(self, arg: complex) -> None: ... # PYI041 -53 53 | -54 54 | def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 -55 55 | - -PYI041.pyi:54:25: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.pyi:54:25: PYI041 Use `complex` instead of `int | float | complex` | 52 | def bad(self, arg: int | float | complex) -> None: ... # PYI041 53 | @@ -151,17 +79,7 @@ PYI041.pyi:54:25: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Safe fix -51 51 | -52 52 | def bad(self, arg: int | float | complex) -> None: ... # PYI041 -53 53 | -54 |- def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 - 54 |+ def bad2(self, arg: complex) -> None: ... # PYI041 -55 55 | -56 56 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 -57 57 | - -PYI041.pyi:56:25: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.pyi:56:25: PYI041 Use `complex` instead of `int | float | complex` | 54 | def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 55 | @@ -172,17 +90,7 @@ PYI041.pyi:56:25: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Safe fix -53 53 | -54 54 | def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 -55 55 | -56 |- def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 - 56 |+ def bad3(self, arg: complex) -> None: ... # PYI041 -57 57 | -58 58 | def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 -59 59 | - -PYI041.pyi:58:25: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.pyi:58:25: PYI041 Use `complex` instead of `int | float | complex` | 56 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 57 | @@ -193,16 +101,7 @@ PYI041.pyi:58:25: PYI041 [*] Use `complex` instead of `int | float | complex` | = help: Remove duplicates -ℹ Safe fix -55 55 | -56 56 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 -57 57 | -58 |- def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 - 58 |+ def bad4(self, arg: complex) -> None: ... # PYI041 -59 59 | -60 60 | def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041 - -PYI041.pyi:60:25: PYI041 [*] Use `complex` instead of `int | float | complex` +PYI041.pyi:60:25: PYI041 Use `complex` instead of `int | float | complex` | 58 | def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 59 | @@ -210,10 +109,3 @@ PYI041.pyi:60:25: PYI041 [*] Use `complex` instead of `int | float | complex` | ^^^^^^^^^^^^^^^^^^^^^^^ PYI041 | = help: Remove duplicates - -ℹ Safe fix -57 57 | -58 58 | def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 -59 59 | -60 |- def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041 - 60 |+ def bad5(self, arg: complex) -> None: ... # PYI041 From 9136f8137d56c08a252e730447ef00300ebf57c3 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Tue, 12 Nov 2024 16:11:56 +0100 Subject: [PATCH 3/7] Tweaks. --- .../rules/redundant_numeric_union.rs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index ef1994293e4fb..4a625c2906cf6 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -130,7 +130,7 @@ fn check_annotation<'a>(checker: &mut Checker, annotation: &'a Expr) { } }; - // Traverse the union, and remember which numeric types are found. + // Traverse the union a second time to construct a [`Fix`]. traverse_union(&mut remove_numeric_type, checker.semantic(), annotation); let mut diagnostic = Diagnostic::new(RedundantNumericUnion { redundancy }, annotation.range()); @@ -177,16 +177,16 @@ enum Redundancy { impl Redundancy { pub(super) fn from_numeric_flags(numeric_flags: NumericFlags) -> Option { - match numeric_flags.bits() { - // NumericFlags::INT | NumericFlags::FLOAT | NumericFlags::COMPLEX - 0b0111 => Some(Self::IntFloatComplex), - // NumericFlags::FLOAT | NumericFlags::COMPLEX - 0b0110 => Some(Self::FloatComplex), - // NumericFlags::INT | NumericFlags::COMPLEX - 0b0101 => Some(Self::IntComplex), - // NumericFlags::INT | NumericFlags::FLOAT - 0b0011 => Some(Self::IntFloat), - _ => None, + if numeric_flags == NumericFlags::INT | NumericFlags::FLOAT | NumericFlags::COMPLEX { + Some(Self::IntFloatComplex) + } else if numeric_flags == NumericFlags::FLOAT | NumericFlags::COMPLEX { + Some(Self::FloatComplex) + } else if numeric_flags == NumericFlags::INT | NumericFlags::COMPLEX { + Some(Self::IntComplex) + } else if numeric_flags == NumericFlags::FLOAT | NumericFlags::INT { + Some(Self::IntFloat) + } else { + None } } } @@ -280,16 +280,12 @@ fn generate_union_fix( ctx: ExprContext::Store, range: TextRange::default(), })), - slice: Box::new(if let [elt] = nodes.as_slice() { - (*elt).clone() - } else { - Expr::Tuple(ExprTuple { - elts: nodes.into_iter().cloned().collect(), - range: TextRange::default(), - ctx: ExprContext::Load, - parenthesized: false, - }) - }), + slice: Box::new(Expr::Tuple(ExprTuple { + elts: nodes.into_iter().cloned().collect(), + range: TextRange::default(), + ctx: ExprContext::Load, + parenthesized: false, + })), ctx: ExprContext::Load, }); From 3bd5574442e146895b064ab25e237fa9ebf4f8d9 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Tue, 12 Nov 2024 16:45:45 +0100 Subject: [PATCH 4/7] Typo. --- .../src/rules/flake8_pyi/rules/redundant_numeric_union.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index 4a625c2906cf6..0bbb7b0c4c84a 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -46,7 +46,7 @@ use crate::{checkers::ast::Checker, importer::ImportRequest}; /// /// ## Fix safety /// This rule's fix is marked as safe for most cases; however, the fix will -/// nested unions type expressions into a single top-level union. +/// flatten nested unions type expressions into a single top-level union. /// /// The fix is marked as unsafe when comments are present within the type /// expression. From 1af2c5f6d7bab694f572b76e3242e0f5e2787632 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Wed, 13 Nov 2024 11:23:49 +0100 Subject: [PATCH 5/7] Update crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs Co-authored-by: Micha Reiser --- .../src/rules/flake8_pyi/rules/redundant_numeric_union.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index 0bbb7b0c4c84a..a84a8e361a81f 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -77,7 +77,7 @@ impl Violation for RedundantNumericUnion { } fn fix_title(&self) -> Option { - Some("Remove duplicates".to_string()) + Some("Remove redundant type".to_string()) } } From 9cdc46d3151de0b8a248da8fa35207595be4bd67 Mon Sep 17 00:00:00 2001 From: Simon Brugman Date: Thu, 14 Nov 2024 09:12:37 +0100 Subject: [PATCH 6/7] Update snapshots --- ...__flake8_pyi__tests__PYI041_PYI041.py.snap | 22 +++++++++---------- ..._flake8_pyi__tests__PYI041_PYI041.pyi.snap | 22 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap index 2b2c4a76f0891..4e3c5c893017d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.py.snap @@ -7,7 +7,7 @@ PYI041.py:22:14: PYI041 Use `float` instead of `int | float` | ^^^^^^^^^^^ PYI041 23 | ... | - = help: Remove duplicates + = help: Remove redundant type PYI041.py:26:30: PYI041 Use `complex` instead of `float | complex` | @@ -15,7 +15,7 @@ PYI041.py:26:30: PYI041 Use `complex` instead of `float | complex` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 27 | ... | - = help: Remove duplicates + = help: Remove redundant type PYI041.py:30:28: PYI041 Use `float` instead of `int | float` | @@ -23,7 +23,7 @@ PYI041.py:30:28: PYI041 Use `float` instead of `int | float` | ^^^^^^^^^^^^^^^^^ PYI041 31 | ... | - = help: Remove duplicates + = help: Remove redundant type PYI041.py:38:24: PYI041 Use `float` instead of `int | float` | @@ -31,7 +31,7 @@ PYI041.py:38:24: PYI041 Use `float` instead of `int | float` | ^^^^^^^^^^^^^^^^^ PYI041 39 | ... | - = help: Remove duplicates + = help: Remove redundant type PYI041.py:43:10: PYI041 Use `complex` instead of `int | float | complex` | @@ -44,7 +44,7 @@ PYI041.py:43:10: PYI041 Use `complex` instead of `int | float | complex` 46 | ) -> None: 47 | ... | - = help: Remove duplicates + = help: Remove redundant type PYI041.py:51:9: PYI041 Use `complex` instead of `int | float | complex` | @@ -58,7 +58,7 @@ PYI041.py:51:9: PYI041 Use `complex` instead of `int | float | complex` 54 | ) 55 | ) -> None: | - = help: Remove duplicates + = help: Remove redundant type PYI041.py:63:24: PYI041 Use `complex` instead of `int | float | complex` | @@ -68,7 +68,7 @@ PYI041.py:63:24: PYI041 Use `complex` instead of `int | float | complex` | ^^^^^^^^^^^^^^^^^^^^^ PYI041 64 | ... | - = help: Remove duplicates + = help: Remove redundant type PYI041.py:66:25: PYI041 Use `complex` instead of `int | float | complex` | @@ -78,7 +78,7 @@ PYI041.py:66:25: PYI041 Use `complex` instead of `int | float | complex` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 67 | ... | - = help: Remove duplicates + = help: Remove redundant type PYI041.py:69:25: PYI041 Use `complex` instead of `int | float | complex` | @@ -88,7 +88,7 @@ PYI041.py:69:25: PYI041 Use `complex` instead of `int | float | complex` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 70 | ... | - = help: Remove duplicates + = help: Remove redundant type PYI041.py:72:25: PYI041 Use `complex` instead of `int | float | complex` | @@ -98,7 +98,7 @@ PYI041.py:72:25: PYI041 Use `complex` instead of `int | float | complex` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 73 | ... | - = help: Remove duplicates + = help: Remove redundant type PYI041.py:75:25: PYI041 Use `complex` instead of `int | float | complex` | @@ -108,4 +108,4 @@ PYI041.py:75:25: PYI041 Use `complex` instead of `int | float | complex` | ^^^^^^^^^^^^^^^^^^^^^^^ PYI041 76 | ... | - = help: Remove duplicates + = help: Remove redundant type diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap index f5015af26bfa7..479a879ed72d7 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI041_PYI041.pyi.snap @@ -6,21 +6,21 @@ PYI041.pyi:21:14: PYI041 Use `float` instead of `int | float` 21 | def f0(arg1: float | int) -> None: ... # PYI041 | ^^^^^^^^^^^ PYI041 | - = help: Remove duplicates + = help: Remove redundant type PYI041.pyi:24:30: PYI041 Use `complex` instead of `float | complex` | 24 | def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041 | - = help: Remove duplicates + = help: Remove redundant type PYI041.pyi:27:28: PYI041 Use `float` instead of `int | float` | 27 | def f2(arg1: int, /, arg2: int | int | float) -> None: ... # PYI041 | ^^^^^^^^^^^^^^^^^ PYI041 | - = help: Remove duplicates + = help: Remove redundant type PYI041.pyi:33:24: PYI041 Use `float` instead of `int | float` | @@ -29,7 +29,7 @@ PYI041.pyi:33:24: PYI041 Use `float` instead of `int | float` 34 | 35 | def f5( | - = help: Remove duplicates + = help: Remove redundant type PYI041.pyi:36:10: PYI041 Use `complex` instead of `int | float | complex` | @@ -41,7 +41,7 @@ PYI041.pyi:36:10: PYI041 Use `complex` instead of `int | float | complex` | |_____________________^ PYI041 39 | ) -> None: ... # PYI041 | - = help: Remove duplicates + = help: Remove redundant type PYI041.pyi:43:9: PYI041 Use `complex` instead of `int | float | complex` | @@ -55,7 +55,7 @@ PYI041.pyi:43:9: PYI041 Use `complex` instead of `int | float | complex` 46 | ) 47 | ) -> None: ... # PYI041 | - = help: Remove duplicates + = help: Remove redundant type PYI041.pyi:52:24: PYI041 Use `complex` instead of `int | float | complex` | @@ -66,7 +66,7 @@ PYI041.pyi:52:24: PYI041 Use `complex` instead of `int | float | complex` 53 | 54 | def bad2(self, arg: int | Union[float, complex]) -> None: ... # PYI041 | - = help: Remove duplicates + = help: Remove redundant type PYI041.pyi:54:25: PYI041 Use `complex` instead of `int | float | complex` | @@ -77,7 +77,7 @@ PYI041.pyi:54:25: PYI041 Use `complex` instead of `int | float | complex` 55 | 56 | def bad3(self, arg: Union[Union[float, complex], int]) -> None: ... # PYI041 | - = help: Remove duplicates + = help: Remove redundant type PYI041.pyi:56:25: PYI041 Use `complex` instead of `int | float | complex` | @@ -88,7 +88,7 @@ PYI041.pyi:56:25: PYI041 Use `complex` instead of `int | float | complex` 57 | 58 | def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041 | - = help: Remove duplicates + = help: Remove redundant type PYI041.pyi:58:25: PYI041 Use `complex` instead of `int | float | complex` | @@ -99,7 +99,7 @@ PYI041.pyi:58:25: PYI041 Use `complex` instead of `int | float | complex` 59 | 60 | def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041 | - = help: Remove duplicates + = help: Remove redundant type PYI041.pyi:60:25: PYI041 Use `complex` instead of `int | float | complex` | @@ -108,4 +108,4 @@ PYI041.pyi:60:25: PYI041 Use `complex` instead of `int | float | complex` 60 | def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041 | ^^^^^^^^^^^^^^^^^^^^^^^ PYI041 | - = help: Remove duplicates + = help: Remove redundant type From a3c9a6fc47f4f8ab179ee4a01d4fa290df777ad1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 16 Nov 2024 13:08:11 -0500 Subject: [PATCH 7/7] Tweak --- .../rules/flake8_pyi/rules/duplicate_literal_member.rs | 4 ++-- .../src/rules/flake8_pyi/rules/redundant_numeric_union.rs | 7 +++---- .../src/rules/flake8_pyi/rules/unnecessary_type_union.rs | 8 +++----- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs index 5b2c19fd2cc61..cc67e93ad0bfb 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs @@ -30,8 +30,8 @@ use crate::checkers::ast::Checker; /// ## Fix safety /// This rule's fix is marked as safe, unless the type annotation contains comments. /// -/// Note that the fix will flatten nested literals into a single top-level -/// literal. +/// Note that while the fix may flatten nested literals into a single top-level literal, +/// the semantics of the annotation will remain unchanged. /// /// ## References /// - [Python documentation: `typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs index a84a8e361a81f..508fdbb7fff79 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_numeric_union.rs @@ -45,11 +45,10 @@ use crate::{checkers::ast::Checker, importer::ImportRequest}; /// ``` /// /// ## Fix safety -/// This rule's fix is marked as safe for most cases; however, the fix will -/// flatten nested unions type expressions into a single top-level union. +/// This rule's fix is marked as safe, unless the type annotation contains comments. /// -/// The fix is marked as unsafe when comments are present within the type -/// expression. +/// Note that while the fix may flatten nested unions into a single top-level union, +/// the semantics of the annotation will remain unchanged. /// /// ## References /// - [Python documentation: The numeric tower](https://docs.python.org/3/library/numbers.html#the-numeric-tower) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs index 459d37a649b19..0a498264c856d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unnecessary_type_union.rs @@ -27,12 +27,10 @@ use crate::checkers::ast::Checker; /// ``` /// /// ## Fix safety +/// This rule's fix is marked as safe, unless the type annotation contains comments. /// -/// This rule's fix is marked as safe in most cases; however, the fix will -/// flatten nested unions type expressions into a single top-level union. -/// -/// The fix is marked as unsafe when comments are present within the type -/// expression. +/// Note that while the fix may flatten nested unions into a single top-level union, +/// the semantics of the annotation will remain unchanged. #[violation] pub struct UnnecessaryTypeUnion { members: Vec,