From b004cd6588a801021d77f2e143677e3ea2a7ccbe Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Thu, 27 Mar 2025 01:55:41 +0530 Subject: [PATCH 01/19] wip: Add autofix for PYI051 This commit tries to fix https://github.com/astral-sh/ruff/issues/14185 and is a work in progress. It's able to achieve the basic goal: ```python from typing import Literal x: Literal["A", "B", b"c"] | str ``` will be refactored to: ```python from typing import Literal x: Literal[ b"c"] | str ``` It does have a lot of edge cases to handle (infact they're not even edgecases). --- .../rules/redundant_literal_union.rs | 75 +++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index bfcfcef7ca..5f96e6df55 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -2,12 +2,12 @@ use std::fmt; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::{self as ast, Expr, LiteralExpressionRef}; +use ruff_python_ast::{self as ast, Expr, ExprContext, LiteralExpressionRef}; use ruff_python_semantic::analyze::typing::traverse_union; use ruff_python_semantic::SemanticModel; -use ruff_text_size::Ranged; +use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; @@ -61,11 +61,13 @@ impl Violation for RedundantLiteralUnion { pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { let mut typing_literal_exprs = Vec::new(); let mut builtin_types_in_union = FxHashSet::default(); + let mut literal_expr: Option = None; // Adds a member to `literal_exprs` for each value in a `Literal`, and any builtin types // to `builtin_types_in_union`. let mut func = |expr: &'a Expr, _parent: &'a Expr| { if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { + literal_expr = Some(expr.clone()); if checker.semantic().match_typing_expr(value, "Literal") { if let Expr::Tuple(tuple) = &**slice { typing_literal_exprs.extend(tuple); @@ -84,13 +86,16 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { traverse_union(&mut func, checker.semantic(), union); + let mut diagnostics = Vec::new(); + let mut non_redundant_literal_exprs = Vec::new(); + for typing_literal_expr in typing_literal_exprs { let Some(literal_type) = match_literal_type(typing_literal_expr) else { continue; }; if builtin_types_in_union.contains(&literal_type) { - checker.report_diagnostic(Diagnostic::new( + let diagnostic = Diagnostic::new( RedundantLiteralUnion { literal: SourceCodeSnippet::from_str( checker.locator().slice(typing_literal_expr), @@ -98,9 +103,69 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { builtin_type: literal_type, }, typing_literal_expr.range(), - )); + ); + + diagnostics.push(diagnostic); + } else { + non_redundant_literal_exprs.push(typing_literal_expr); } } + + let Some(literal_expr) = literal_expr else { + return; + }; + + if non_redundant_literal_exprs.is_empty() { + let fix = Fix::applicable_edit( + Edit::range_deletion(literal_expr.range()), + Applicability::Unsafe, + ); + + for diagnostic in &mut diagnostics { + diagnostic.set_fix(fix.clone()); + } + + checker.report_diagnostics(diagnostics); + return; + } + + let Expr::Subscript(ast::ExprSubscript { + value: literal_subscript, + .. + }) = literal_expr.clone() + else { + return; + }; + + let new_literal_expr = Expr::Subscript(ast::ExprSubscript { + value: literal_subscript, + range: TextRange::default(), + ctx: ExprContext::Load, + slice: Box::new(if non_redundant_literal_exprs.len() > 1 { + Expr::Tuple(ast::ExprTuple { + elts: non_redundant_literal_exprs.into_iter().cloned().collect(), + range: TextRange::default(), + ctx: ExprContext::Load, + parenthesized: true, + }) + } else { + non_redundant_literal_exprs[0].clone() + }), + }); + + let fix = Fix::applicable_edit( + Edit::range_replacement( + checker.generator().expr(&new_literal_expr), + literal_expr.range(), + ), + Applicability::Safe, + ); + + for diagnostic in &mut diagnostics { + diagnostic.set_fix(fix.clone()); + } + + checker.report_diagnostics(diagnostics); } #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] From c22d06ced32d99e9ee02409b0de850e5fd1ca552 Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Mon, 31 Mar 2025 00:18:45 +0530 Subject: [PATCH 02/19] feat(PYI051): Implement PYI051 autofix --- .../rules/redundant_literal_union.rs | 117 ++++++++++-------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 5f96e6df55..3f9ef7d42c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -1,8 +1,9 @@ use std::fmt; +use std::iter::zip; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, Violation}; +use ruff_diagnostics::{Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{self as ast, Expr, ExprContext, LiteralExpressionRef}; use ruff_python_semantic::analyze::typing::traverse_union; @@ -61,14 +62,20 @@ impl Violation for RedundantLiteralUnion { pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { let mut typing_literal_exprs = Vec::new(); let mut builtin_types_in_union = FxHashSet::default(); - let mut literal_expr: Option = None; + let mut literal_subscript = None; + let mut literal_exprs = Vec::new(); // Adds a member to `literal_exprs` for each value in a `Literal`, and any builtin types // to `builtin_types_in_union`. let mut func = |expr: &'a Expr, _parent: &'a Expr| { if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { - literal_expr = Some(expr.clone()); if checker.semantic().match_typing_expr(value, "Literal") { + literal_exprs.push(expr); + + if literal_subscript.is_none() { + literal_subscript = Some(value.as_ref()); + } + if let Expr::Tuple(tuple) = &**slice { typing_literal_exprs.extend(tuple); } else { @@ -87,7 +94,8 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { traverse_union(&mut func, checker.semantic(), union); let mut diagnostics = Vec::new(); - let mut non_redundant_literal_exprs = Vec::new(); + let mut non_redundant_literal_types = Vec::new(); + let mut redundant_literal_types = Vec::new(); for typing_literal_expr in typing_literal_exprs { let Some(literal_type) = match_literal_type(typing_literal_expr) else { @@ -95,7 +103,8 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { }; if builtin_types_in_union.contains(&literal_type) { - let diagnostic = Diagnostic::new( + redundant_literal_types.push(typing_literal_expr); + diagnostics.push(Diagnostic::new( RedundantLiteralUnion { literal: SourceCodeSnippet::from_str( checker.locator().slice(typing_literal_expr), @@ -103,66 +112,74 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { builtin_type: literal_type, }, typing_literal_expr.range(), - ); - - diagnostics.push(diagnostic); + )); } else { - non_redundant_literal_exprs.push(typing_literal_expr); + non_redundant_literal_types.push(typing_literal_expr); } } - let Some(literal_expr) = literal_expr else { - return; - }; + let mut non_redundant_literal_type_groups = Vec::new(); - if non_redundant_literal_exprs.is_empty() { - let fix = Fix::applicable_edit( - Edit::range_deletion(literal_expr.range()), - Applicability::Unsafe, - ); + // Group all the non-redundant literal types together based on the `Literals` + let mut func = |expr: &'a Expr, _parent: &'a Expr| { + if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { + if checker.semantic().match_typing_expr(value, "Literal") { + let mut group = Vec::new(); - for diagnostic in &mut diagnostics { - diagnostic.set_fix(fix.clone()); + if let Expr::Tuple(tuple) = &**slice { + for tuple_slice in tuple { + if non_redundant_literal_types.contains(&tuple_slice) { + group.push(tuple_slice); + } + } + } else { + if non_redundant_literal_types.contains(&slice.as_ref()) { + group.push(slice); + } + } + + non_redundant_literal_type_groups.push(group); + } } + }; - checker.report_diagnostics(diagnostics); - return; - } + traverse_union(&mut func, checker.semantic(), union); - let Expr::Subscript(ast::ExprSubscript { - value: literal_subscript, - .. - }) = literal_expr.clone() - else { + let Some(literal_subscript) = literal_subscript else { return; }; - let new_literal_expr = Expr::Subscript(ast::ExprSubscript { - value: literal_subscript, - range: TextRange::default(), - ctx: ExprContext::Load, - slice: Box::new(if non_redundant_literal_exprs.len() > 1 { - Expr::Tuple(ast::ExprTuple { - elts: non_redundant_literal_exprs.into_iter().cloned().collect(), + for (diagnostic, (group, literal_expr)) in zip( + &mut diagnostics, + zip(non_redundant_literal_type_groups, literal_exprs), + ) { + if group.is_empty() { + // This contains a `Literal` that has to be deleted + let fix = Fix::safe_edit(Edit::range_deletion(literal_expr.range())); + diagnostic.set_fix(fix); + } else { + let new_literal_expr = Expr::Subscript(ast::ExprSubscript { + value: Box::new(literal_subscript.clone()), range: TextRange::default(), ctx: ExprContext::Load, - parenthesized: true, - }) - } else { - non_redundant_literal_exprs[0].clone() - }), - }); + slice: Box::new(if group.len() > 1 { + Expr::Tuple(ast::ExprTuple { + elts: group.into_iter().cloned().collect(), + range: TextRange::default(), + ctx: ExprContext::Load, + parenthesized: true, + }) + } else { + group[0].clone() + }), + }); - let fix = Fix::applicable_edit( - Edit::range_replacement( - checker.generator().expr(&new_literal_expr), - literal_expr.range(), - ), - Applicability::Safe, - ); - - for diagnostic in &mut diagnostics { - diagnostic.set_fix(fix.clone()); + let fix = Fix::safe_edit(Edit::range_replacement( + checker.generator().expr(&new_literal_expr), + literal_expr.range(), + )); + diagnostic.set_fix(fix); + } } checker.report_diagnostics(diagnostics); From d8bb71b3f166f8857c5b00f73d5a4ea2bddf82ec Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Tue, 1 Apr 2025 00:07:23 +0530 Subject: [PATCH 03/19] feat(PYI051): Add complete autofix implementation for PYI051 --- .../rules/redundant_literal_union.rs | 166 ++++++++++++++---- 1 file changed, 131 insertions(+), 35 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 3f9ef7d42c..b23b49eb61 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -1,17 +1,21 @@ use std::fmt; -use std::iter::zip; +use ruff_python_ast::name::Name; use rustc_hash::FxHashSet; use ruff_diagnostics::{Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::{self as ast, Expr, ExprContext, LiteralExpressionRef}; +use ruff_python_ast::{ + self as ast, Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, LiteralExpressionRef, + Operator, PythonVersion, +}; use ruff_python_semantic::analyze::typing::traverse_union; use ruff_python_semantic::SemanticModel; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::importer::ImportRequest; /// ## What it does /// Checks for redundant unions between a `Literal` and a builtin supertype of @@ -64,6 +68,12 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { let mut builtin_types_in_union = FxHashSet::default(); let mut literal_subscript = None; let mut literal_exprs = Vec::new(); + let union_type = + if (checker.target_version() >= PythonVersion::PY310) || checker.source_type.is_stub() { + UnionKind::PEP604 + } else { + UnionKind::TypingUnion + }; // Adds a member to `literal_exprs` for each value in a `Literal`, and any builtin types // to `builtin_types_in_union`. @@ -118,7 +128,7 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { } } - let mut non_redundant_literal_type_groups = Vec::new(); + let mut new_literal_expr_types: Vec> = Vec::new(); // Group all the non-redundant literal types together based on the `Literals` let mut func = |expr: &'a Expr, _parent: &'a Expr| { @@ -137,10 +147,16 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { group.push(slice); } } - - non_redundant_literal_type_groups.push(group); + if !group.is_empty() { + new_literal_expr_types.push(LiteralExprType::NonRedundantTypes(group.clone())); + } } + return; } + let Some(_) = match_builtin_type(expr, checker.semantic()) else { + return; + }; + new_literal_expr_types.push(LiteralExprType::BuiltinType(expr)); }; traverse_union(&mut func, checker.semantic(), union); @@ -149,42 +165,122 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { return; }; - for (diagnostic, (group, literal_expr)) in zip( - &mut diagnostics, - zip(non_redundant_literal_type_groups, literal_exprs), - ) { - if group.is_empty() { - // This contains a `Literal` that has to be deleted - let fix = Fix::safe_edit(Edit::range_deletion(literal_expr.range())); - diagnostic.set_fix(fix); - } else { - let new_literal_expr = Expr::Subscript(ast::ExprSubscript { - value: Box::new(literal_subscript.clone()), - range: TextRange::default(), - ctx: ExprContext::Load, - slice: Box::new(if group.len() > 1 { - Expr::Tuple(ast::ExprTuple { - elts: group.into_iter().cloned().collect(), - range: TextRange::default(), - ctx: ExprContext::Load, - parenthesized: true, - }) - } else { - group[0].clone() - }), - }); - - let fix = Fix::safe_edit(Edit::range_replacement( - checker.generator().expr(&new_literal_expr), - literal_expr.range(), - )); - diagnostic.set_fix(fix); + // This generates new individual `Literal` exprs and builtins + let mut new_exprs = Vec::new(); + for group in new_literal_expr_types { + match group { + LiteralExprType::BuiltinType(expr) => { + new_exprs.push(expr.to_owned()); + } + LiteralExprType::NonRedundantTypes(group) => { + let new_literal_expr = Expr::Subscript(ast::ExprSubscript { + value: Box::new(literal_subscript.clone()), + range: TextRange::default(), + ctx: ExprContext::Load, + slice: Box::new(if group.len() > 1 { + Expr::Tuple(ast::ExprTuple { + elts: group.into_iter().cloned().collect(), + range: TextRange::default(), + ctx: ExprContext::Load, + parenthesized: true, + }) + } else { + group[0].clone() + }), + }); + new_exprs.push(new_literal_expr); + } } } + // Now concatenate the exprs based on the union type + let fix = match union_type { + UnionKind::PEP604 => generate_pep604_fix(checker, new_exprs, union), + UnionKind::TypingUnion => generate_typing_union_fix(checker, new_exprs, union), + }; + + for diagnostic in &mut diagnostics { + diagnostic.set_fix(fix.clone()); + } + checker.report_diagnostics(diagnostics); } +fn generate_pep604_fix(checker: &Checker, new_exprs: Vec, union: &Expr) -> Fix { + let new_expr = new_exprs + .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), + range: TextRange::default(), + })) + } else { + Some(right) + } + }) + .unwrap(); + + Fix::safe_edit(Edit::range_replacement( + checker.generator().expr(&new_expr), + union.range(), + )) +} + +fn generate_typing_union_fix(checker: &Checker, new_exprs: Vec, union: &Expr) -> Fix { + let (import_edit, binding) = checker + .importer() + .get_or_import_symbol( + &ImportRequest::import_from("typing", "Union"), + union.range().start(), + checker.semantic(), + ) + .expect("Error importing"); + + // 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 new_exprs.len() > 1 { + Expr::Tuple(ast::ExprTuple { + elts: new_exprs, + range: TextRange::default(), + ctx: ExprContext::Load, + parenthesized: true, + }) + } else { + new_exprs[0].clone() + }), + ctx: ExprContext::Load, + }); + + Fix::applicable_edits( + Edit::range_replacement(checker.generator().expr(&new_expr), union.range()), + [import_edit], + ruff_diagnostics::Applicability::Safe, + ) +} + +#[derive(Debug, Clone)] +enum LiteralExprType<'a> { + NonRedundantTypes(Vec<&'a Expr>), + BuiltinType(&'a Expr), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum UnionKind { + /// E.g., `typing.Union[int, str]` + TypingUnion, + /// E.g., `int | str` + PEP604, +} + #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] enum ExprType { Int, From e2e87381cc18fcb060091ee59bc10a13751c2cfe Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Tue, 1 Apr 2025 00:18:16 +0530 Subject: [PATCH 04/19] fix(PYI051): Make FixAvailable::Always --- .../src/rules/flake8_pyi/rules/redundant_literal_union.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index b23b49eb61..1d74f3d9ed 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -3,7 +3,7 @@ use std::fmt; use ruff_python_ast::name::Name; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Diagnostic, Edit, Fix, Violation}; +use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{ self as ast, Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, LiteralExpressionRef, @@ -48,6 +48,8 @@ pub(crate) struct RedundantLiteralUnion { } impl Violation for RedundantLiteralUnion { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Always; + #[derive_message_formats] fn message(&self) -> String { let RedundantLiteralUnion { From 2a0042870262016ee9f4349e4cd8638ac688cd4a Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Tue, 1 Apr 2025 10:54:30 +0530 Subject: [PATCH 05/19] fix(PYI051): Add `fix_title` --- .../flake8_pyi/rules/redundant_literal_union.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 1d74f3d9ed..6fdce3714f 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -62,6 +62,20 @@ impl Violation for RedundantLiteralUnion { format!("`Literal` is redundant in a union with `{builtin_type}`") } } + + fn fix_title(&self) -> Option { + let RedundantLiteralUnion { + literal, + builtin_type, + } = self; + if let Some(literal) = literal.full_display() { + Some(format!( + "Replace `Literal[{literal}] | {builtin_type}` with `{builtin_type}`" + )) + } else { + Some(format!("Replace with `{builtin_type}`")) + } + } } /// PYI051 From 66ca56fc51c42b8fd1367126dd00a10ba20f751d Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Tue, 1 Apr 2025 10:54:42 +0530 Subject: [PATCH 06/19] chore(PYI051): Update snapshots --- ...__flake8_pyi__tests__PYI051_PYI051.py.snap | 156 ++++++++++++++++-- ..._flake8_pyi__tests__PYI051_PYI051.pyi.snap | 156 ++++++++++++++++-- 2 files changed, 288 insertions(+), 24 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap index 84850e38e7..d33b9d4260 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI051.py:4:18: PYI051 `Literal["foo"]` is redundant in a union with `str` +PYI051.py:4:18: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | 2 | from typing import Literal, TypeAlias, Union 3 | @@ -10,8 +10,19 @@ PYI051.py:4:18: PYI051 `Literal["foo"]` is redundant in a union with `str` 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] | + = help: Replace `Literal["foo"] | str` with `str` -PYI051.py:5:37: PYI051 `Literal[b"bar"]` is redundant in a union with `bytes` +ℹ Safe fix +1 1 | import typing +2 2 | from typing import Literal, TypeAlias, Union +3 3 | +4 |-A: str | Literal["foo"] + 4 |+A: str +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + +PYI051.py:5:37: PYI051 [*] `Literal[b"bar"]` is redundant in a union with `bytes` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -19,8 +30,19 @@ PYI051.py:5:37: PYI051 `Literal[b"bar"]` is redundant in a union with `bytes` 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] | + = help: Replace `Literal[b"bar"] | bytes` with `bytes` -PYI051.py:5:45: PYI051 `Literal[b"foo"]` is redundant in a union with `bytes` +ℹ Safe fix +2 2 | from typing import Literal, TypeAlias, Union +3 3 | +4 4 | A: str | Literal["foo"] +5 |-B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] + 5 |+B: TypeAlias = bytes | str +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + +PYI051.py:5:45: PYI051 [*] `Literal[b"foo"]` is redundant in a union with `bytes` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -28,8 +50,19 @@ PYI051.py:5:45: PYI051 `Literal[b"foo"]` is redundant in a union with `bytes` 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] | + = help: Replace `Literal[b"foo"] | bytes` with `bytes` -PYI051.py:6:37: PYI051 `Literal[5]` is redundant in a union with `int` +ℹ Safe fix +2 2 | from typing import Literal, TypeAlias, Union +3 3 | +4 4 | A: str | Literal["foo"] +5 |-B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] + 5 |+B: TypeAlias = bytes | str +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + +PYI051.py:6:37: PYI051 [*] `Literal[5]` is redundant in a union with `int` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -38,8 +71,19 @@ PYI051.py:6:37: PYI051 `Literal[5]` is redundant in a union with `int` 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] | + = help: Replace `Literal[5] | int` with `int` -PYI051.py:6:67: PYI051 `Literal["foo"]` is redundant in a union with `str` +ℹ Safe fix +3 3 | +4 4 | A: str | Literal["foo"] +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 |-C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] + 6 |+C: TypeAlias = int | str +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + +PYI051.py:6:67: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -48,8 +92,19 @@ PYI051.py:6:67: PYI051 `Literal["foo"]` is redundant in a union with `str` 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] | + = help: Replace `Literal["foo"] | str` with `str` -PYI051.py:7:37: PYI051 `Literal[b"str_bytes"]` is redundant in a union with `bytes` +ℹ Safe fix +3 3 | +4 4 | A: str | Literal["foo"] +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 |-C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] + 6 |+C: TypeAlias = int | str +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + +PYI051.py:7:37: PYI051 [*] `Literal[b"str_bytes"]` is redundant in a union with `bytes` | 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] @@ -58,8 +113,19 @@ PYI051.py:7:37: PYI051 `Literal[b"str_bytes"]` is redundant in a union with `byt 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | + = help: Replace `Literal[b"str_bytes"] | bytes` with `bytes` -PYI051.py:7:51: PYI051 `Literal[42]` is redundant in a union with `int` +ℹ Safe fix +4 4 | A: str | Literal["foo"] +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 |-D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + 7 |+D: TypeAlias = bytes | int +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + +PYI051.py:7:51: PYI051 [*] `Literal[42]` is redundant in a union with `int` | 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] @@ -68,8 +134,19 @@ PYI051.py:7:51: PYI051 `Literal[42]` is redundant in a union with `int` 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | + = help: Replace `Literal[42] | int` with `int` -PYI051.py:8:76: PYI051 `Literal["foo"]` is redundant in a union with `str` +ℹ Safe fix +4 4 | A: str | Literal["foo"] +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 |-D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + 7 |+D: TypeAlias = bytes | int +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + +PYI051.py:8:76: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] @@ -78,8 +155,19 @@ PYI051.py:8:76: PYI051 `Literal["foo"]` is redundant in a union with `str` 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | + = help: Replace `Literal["foo"] | str` with `str` -PYI051.py:9:81: PYI051 `Literal["foo"]` is redundant in a union with `str` +ℹ Safe fix +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 |-E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + 8 |+E: TypeAlias = str +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +11 11 | + +PYI051.py:9:81: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] @@ -87,8 +175,19 @@ PYI051.py:9:81: PYI051 `Literal["foo"]` is redundant in a union with `str` | ^^^^^ PYI051 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | + = help: Replace `Literal["foo"] | str` with `str` -PYI051.py:10:69: PYI051 `Literal["foo"]` is redundant in a union with `str` +ℹ Safe fix +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 |-F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + 9 |+F: TypeAlias = str | int +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +11 11 | +12 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... + +PYI051.py:10:69: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] @@ -97,8 +196,19 @@ PYI051.py:10:69: PYI051 `Literal["foo"]` is redundant in a union with `str` 11 | 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... | + = help: Replace `Literal["foo"] | str` with `str` -PYI051.py:12:31: PYI051 `Literal[1J]` is redundant in a union with `complex` +ℹ Safe fix +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 |-G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + 10 |+G: str | int +11 11 | +12 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... +13 13 | + +PYI051.py:12:31: PYI051 [*] `Literal[1J]` is redundant in a union with `complex` | 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 11 | @@ -107,8 +217,19 @@ PYI051.py:12:31: PYI051 `Literal[1J]` is redundant in a union with `complex` 13 | 14 | # OK | + = help: Replace `Literal[1J] | complex` with `complex` -PYI051.py:12:53: PYI051 `Literal[3.14]` is redundant in a union with `float` +ℹ Safe fix +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +11 11 | +12 |-def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... + 12 |+def func(x: complex, y: Union[Literal[3.14], float]): ... +13 13 | +14 14 | # OK +15 15 | A: Literal["foo"] + +PYI051.py:12:53: PYI051 [*] `Literal[3.14]` is redundant in a union with `float` | 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 11 | @@ -117,3 +238,14 @@ PYI051.py:12:53: PYI051 `Literal[3.14]` is redundant in a union with `float` 13 | 14 | # OK | + = help: Replace `Literal[3.14] | float` with `float` + +ℹ Safe fix +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +11 11 | +12 |-def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... + 12 |+def func(x: complex | Literal[1J], y: float): ... +13 13 | +14 14 | # OK +15 15 | A: Literal["foo"] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap index cf1f213ca1..01244067e2 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI051.pyi:4:18: PYI051 `Literal["foo"]` is redundant in a union with `str` +PYI051.pyi:4:18: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | 2 | from typing import Literal, TypeAlias, Union 3 | @@ -10,8 +10,19 @@ PYI051.pyi:4:18: PYI051 `Literal["foo"]` is redundant in a union with `str` 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] | + = help: Replace `Literal["foo"] | str` with `str` -PYI051.pyi:5:37: PYI051 `Literal[b"bar"]` is redundant in a union with `bytes` +ℹ Safe fix +1 1 | import typing +2 2 | from typing import Literal, TypeAlias, Union +3 3 | +4 |-A: str | Literal["foo"] + 4 |+A: str +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + +PYI051.pyi:5:37: PYI051 [*] `Literal[b"bar"]` is redundant in a union with `bytes` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -19,8 +30,19 @@ PYI051.pyi:5:37: PYI051 `Literal[b"bar"]` is redundant in a union with `bytes` 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] | + = help: Replace `Literal[b"bar"] | bytes` with `bytes` -PYI051.pyi:5:45: PYI051 `Literal[b"foo"]` is redundant in a union with `bytes` +ℹ Safe fix +2 2 | from typing import Literal, TypeAlias, Union +3 3 | +4 4 | A: str | Literal["foo"] +5 |-B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] + 5 |+B: TypeAlias = bytes | str +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + +PYI051.pyi:5:45: PYI051 [*] `Literal[b"foo"]` is redundant in a union with `bytes` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -28,8 +50,19 @@ PYI051.pyi:5:45: PYI051 `Literal[b"foo"]` is redundant in a union with `bytes` 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] | + = help: Replace `Literal[b"foo"] | bytes` with `bytes` -PYI051.pyi:6:37: PYI051 `Literal[5]` is redundant in a union with `int` +ℹ Safe fix +2 2 | from typing import Literal, TypeAlias, Union +3 3 | +4 4 | A: str | Literal["foo"] +5 |-B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] + 5 |+B: TypeAlias = bytes | str +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + +PYI051.pyi:6:37: PYI051 [*] `Literal[5]` is redundant in a union with `int` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -38,8 +71,19 @@ PYI051.pyi:6:37: PYI051 `Literal[5]` is redundant in a union with `int` 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] | + = help: Replace `Literal[5] | int` with `int` -PYI051.pyi:6:67: PYI051 `Literal["foo"]` is redundant in a union with `str` +ℹ Safe fix +3 3 | +4 4 | A: str | Literal["foo"] +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 |-C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] + 6 |+C: TypeAlias = int | str +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + +PYI051.pyi:6:67: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -48,8 +92,19 @@ PYI051.pyi:6:67: PYI051 `Literal["foo"]` is redundant in a union with `str` 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] | + = help: Replace `Literal["foo"] | str` with `str` -PYI051.pyi:7:37: PYI051 `Literal[b"str_bytes"]` is redundant in a union with `bytes` +ℹ Safe fix +3 3 | +4 4 | A: str | Literal["foo"] +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 |-C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] + 6 |+C: TypeAlias = int | str +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + +PYI051.pyi:7:37: PYI051 [*] `Literal[b"str_bytes"]` is redundant in a union with `bytes` | 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] @@ -58,8 +113,19 @@ PYI051.pyi:7:37: PYI051 `Literal[b"str_bytes"]` is redundant in a union with `by 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | + = help: Replace `Literal[b"str_bytes"] | bytes` with `bytes` -PYI051.pyi:7:51: PYI051 `Literal[42]` is redundant in a union with `int` +ℹ Safe fix +4 4 | A: str | Literal["foo"] +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 |-D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + 7 |+D: TypeAlias = bytes | int +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + +PYI051.pyi:7:51: PYI051 [*] `Literal[42]` is redundant in a union with `int` | 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] @@ -68,8 +134,19 @@ PYI051.pyi:7:51: PYI051 `Literal[42]` is redundant in a union with `int` 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | + = help: Replace `Literal[42] | int` with `int` -PYI051.pyi:8:76: PYI051 `Literal["foo"]` is redundant in a union with `str` +ℹ Safe fix +4 4 | A: str | Literal["foo"] +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 |-D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + 7 |+D: TypeAlias = bytes | int +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + +PYI051.pyi:8:76: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] @@ -78,8 +155,19 @@ PYI051.pyi:8:76: PYI051 `Literal["foo"]` is redundant in a union with `str` 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | + = help: Replace `Literal["foo"] | str` with `str` -PYI051.pyi:9:81: PYI051 `Literal["foo"]` is redundant in a union with `str` +ℹ Safe fix +5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 |-E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + 8 |+E: TypeAlias = str +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +11 11 | + +PYI051.pyi:9:81: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] @@ -87,8 +175,19 @@ PYI051.pyi:9:81: PYI051 `Literal["foo"]` is redundant in a union with `str` | ^^^^^ PYI051 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | + = help: Replace `Literal["foo"] | str` with `str` -PYI051.pyi:10:69: PYI051 `Literal["foo"]` is redundant in a union with `str` +ℹ Safe fix +6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 |-F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + 9 |+F: TypeAlias = str | int +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +11 11 | +12 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... + +PYI051.pyi:10:69: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] @@ -97,8 +196,19 @@ PYI051.pyi:10:69: PYI051 `Literal["foo"]` is redundant in a union with `str` 11 | 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... | + = help: Replace `Literal["foo"] | str` with `str` -PYI051.pyi:12:31: PYI051 `Literal[1J]` is redundant in a union with `complex` +ℹ Safe fix +7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 |-G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + 10 |+G: str | int +11 11 | +12 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... +13 13 | + +PYI051.pyi:12:31: PYI051 [*] `Literal[1J]` is redundant in a union with `complex` | 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 11 | @@ -107,8 +217,19 @@ PYI051.pyi:12:31: PYI051 `Literal[1J]` is redundant in a union with `complex` 13 | 14 | # OK | + = help: Replace `Literal[1J] | complex` with `complex` -PYI051.pyi:12:53: PYI051 `Literal[3.14]` is redundant in a union with `float` +ℹ Safe fix +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +11 11 | +12 |-def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... + 12 |+def func(x: complex, y: Union[Literal[3.14], float]): ... +13 13 | +14 14 | # OK +15 15 | A: Literal["foo"] + +PYI051.pyi:12:53: PYI051 [*] `Literal[3.14]` is redundant in a union with `float` | 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 11 | @@ -117,3 +238,14 @@ PYI051.pyi:12:53: PYI051 `Literal[3.14]` is redundant in a union with `float` 13 | 14 | # OK | + = help: Replace `Literal[3.14] | float` with `float` + +ℹ Safe fix +9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +11 11 | +12 |-def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... + 12 |+def func(x: complex | Literal[1J], y: float): ... +13 13 | +14 14 | # OK +15 15 | A: Literal["foo"] From 709230b2086ce739dc3015dd03f0ad3228d1e33c Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Fri, 4 Apr 2025 02:06:58 +0530 Subject: [PATCH 07/19] feat(PYI051): Add proper applicability and fix title based on `union_kind` This commit does two things: 1. Correct applicability based on the comment ranges. 2. Improve `fix_title` based on the `union_kind` This is not a proper fix yet as the fix title can be ambiguous when `Union[...]` is used instead of `typing.Union[...]`. The fix title is currently hardcoded to `typing.Union[...]` and can be confusing to the user. --- .../rules/redundant_literal_union.rs | 75 ++++++++++++++----- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 6fdce3714f..2b0a6ab5cc 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -1,9 +1,9 @@ -use std::fmt; +use std::fmt::{self, format}; use ruff_python_ast::name::Name; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{ self as ast, Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, LiteralExpressionRef, @@ -45,6 +45,7 @@ use crate::importer::ImportRequest; pub(crate) struct RedundantLiteralUnion { literal: SourceCodeSnippet, builtin_type: ExprType, + union_kind: UnionKind, } impl Violation for RedundantLiteralUnion { @@ -55,6 +56,7 @@ impl Violation for RedundantLiteralUnion { let RedundantLiteralUnion { literal, builtin_type, + .. } = self; if let Some(literal) = literal.full_display() { format!("`Literal[{literal}]` is redundant in a union with `{builtin_type}`") @@ -67,11 +69,17 @@ impl Violation for RedundantLiteralUnion { let RedundantLiteralUnion { literal, builtin_type, + union_kind, } = self; if let Some(literal) = literal.full_display() { - Some(format!( - "Replace `Literal[{literal}] | {builtin_type}` with `{builtin_type}`" - )) + match union_kind { + UnionKind::TypingUnion => Some(format!( + "Replace `typing.Union[Literal[{literal}], {builtin_type}]` with `{builtin_type}`" + )), + UnionKind::PEP604 => Some(format!( + "Replace `Literal[{literal}] | {builtin_type}` with `{builtin_type}`" + )), + } } else { Some(format!("Replace with `{builtin_type}`")) } @@ -84,8 +92,8 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { let mut builtin_types_in_union = FxHashSet::default(); let mut literal_subscript = None; let mut literal_exprs = Vec::new(); - let union_type = - if (checker.target_version() >= PythonVersion::PY310) || checker.source_type.is_stub() { + let union_kind = + if checker.target_version() >= PythonVersion::PY310 || checker.source_type.is_stub() { UnionKind::PEP604 } else { UnionKind::TypingUnion @@ -136,6 +144,7 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { checker.locator().slice(typing_literal_expr), ), builtin_type: literal_type, + union_kind, }, typing_literal_expr.range(), )); @@ -210,9 +219,17 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { } // Now concatenate the exprs based on the union type - let fix = match union_type { - UnionKind::PEP604 => generate_pep604_fix(checker, new_exprs, union), - UnionKind::TypingUnion => generate_typing_union_fix(checker, new_exprs, union), + let applicability = if checker.comment_ranges().intersects(union.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + + let fix = match union_kind { + UnionKind::PEP604 => generate_pep604_fix(checker, new_exprs, union, applicability), + UnionKind::TypingUnion => { + generate_typing_union_fix(checker, new_exprs, union, applicability) + } }; for diagnostic in &mut diagnostics { @@ -222,7 +239,19 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { checker.report_diagnostics(diagnostics); } -fn generate_pep604_fix(checker: &Checker, new_exprs: Vec, union: &Expr) -> Fix { +fn generate_pep604_fix( + checker: &Checker, + new_exprs: Vec, + union: &Expr, + applicability: Applicability, +) -> Fix { + if new_exprs.len() == 1 { + return Fix::applicable_edit( + Edit::range_replacement(checker.generator().expr(&new_exprs[0]), union.range()), + applicability, + ); + } + let new_expr = new_exprs .into_iter() .fold(None, |acc: Option, right: Expr| { @@ -239,13 +268,25 @@ fn generate_pep604_fix(checker: &Checker, new_exprs: Vec, union: &Expr) -> }) .unwrap(); - Fix::safe_edit(Edit::range_replacement( - checker.generator().expr(&new_expr), - union.range(), - )) + Fix::applicable_edit( + Edit::range_replacement(checker.generator().expr(&new_expr), union.range()), + applicability, + ) } -fn generate_typing_union_fix(checker: &Checker, new_exprs: Vec, union: &Expr) -> Fix { +fn generate_typing_union_fix( + checker: &Checker, + new_exprs: Vec, + union: &Expr, + applicability: Applicability, +) -> Fix { + if new_exprs.len() == 1 { + return Fix::applicable_edit( + Edit::range_replacement(checker.generator().expr(&new_exprs[0]), union.range()), + applicability, + ); + } + let (import_edit, binding) = checker .importer() .get_or_import_symbol( @@ -279,7 +320,7 @@ fn generate_typing_union_fix(checker: &Checker, new_exprs: Vec, union: &Ex Fix::applicable_edits( Edit::range_replacement(checker.generator().expr(&new_expr), union.range()), [import_edit], - ruff_diagnostics::Applicability::Safe, + applicability, ) } From 298f32a0369db35a3ef26399796a2b138324b509 Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Fri, 4 Apr 2025 02:13:54 +0530 Subject: [PATCH 08/19] fix: Remove unused import --- .../src/rules/flake8_pyi/rules/redundant_literal_union.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 2b0a6ab5cc..794ba95326 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -1,4 +1,4 @@ -use std::fmt::{self, format}; +use std::fmt; use ruff_python_ast::name::Name; use rustc_hash::FxHashSet; From cbf01b50ea76bf653edd6776b1cecff7c95c7f2b Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Fri, 4 Apr 2025 18:44:11 +0530 Subject: [PATCH 09/19] refactor(PYI051): Avoid `unwrap`ing in union fixes --- .../rules/redundant_literal_union.rs | 91 +++++++++---------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 794ba95326..f134ea10ba 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -1,5 +1,6 @@ use std::fmt; +use anyhow::Result; use ruff_python_ast::name::Name; use rustc_hash::FxHashSet; @@ -225,15 +226,20 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { Applicability::Safe }; - let fix = match union_kind { - UnionKind::PEP604 => generate_pep604_fix(checker, new_exprs, union, applicability), - UnionKind::TypingUnion => { - generate_typing_union_fix(checker, new_exprs, union, applicability) - } - }; - for diagnostic in &mut diagnostics { - diagnostic.set_fix(fix.clone()); + match union_kind { + UnionKind::PEP604 => diagnostic.try_set_optional_fix(|| { + Ok(generate_pep604_fix( + checker, + &new_exprs, + union, + applicability, + )) + }), + UnionKind::TypingUnion => diagnostic.try_set_optional_fix(|| { + generate_typing_union_fix(checker, &new_exprs, union, applicability) + }), + }; } checker.report_diagnostics(diagnostics); @@ -241,60 +247,47 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { fn generate_pep604_fix( checker: &Checker, - new_exprs: Vec, + new_exprs: &[Expr], union: &Expr, applicability: Applicability, -) -> Fix { +) -> Option { if new_exprs.len() == 1 { - return Fix::applicable_edit( + return Some(Fix::applicable_edit( Edit::range_replacement(checker.generator().expr(&new_exprs[0]), union.range()), applicability, - ); + )); } - let new_expr = new_exprs - .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), - range: TextRange::default(), - })) - } else { - Some(right) - } - }) - .unwrap(); + let new_expr = new_exprs.iter().fold(None, |acc, right| { + 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()) + } + })?; - Fix::applicable_edit( + Some(Fix::applicable_edit( Edit::range_replacement(checker.generator().expr(&new_expr), union.range()), applicability, - ) + )) } fn generate_typing_union_fix( checker: &Checker, - new_exprs: Vec, + new_exprs: &[Expr], union: &Expr, applicability: Applicability, -) -> Fix { - if new_exprs.len() == 1 { - return Fix::applicable_edit( - Edit::range_replacement(checker.generator().expr(&new_exprs[0]), union.range()), - applicability, - ); - } - - let (import_edit, binding) = checker - .importer() - .get_or_import_symbol( - &ImportRequest::import_from("typing", "Union"), - union.range().start(), - checker.semantic(), - ) - .expect("Error importing"); +) -> Result> { + let (import_edit, binding) = checker.importer().get_or_import_symbol( + &ImportRequest::import_from("typing", "Union"), + union.range().start(), + checker.semantic(), + )?; // Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]` let new_expr = Expr::Subscript(ExprSubscript { @@ -306,7 +299,7 @@ fn generate_typing_union_fix( })), slice: Box::new(if new_exprs.len() > 1 { Expr::Tuple(ast::ExprTuple { - elts: new_exprs, + elts: new_exprs.to_vec(), range: TextRange::default(), ctx: ExprContext::Load, parenthesized: true, @@ -317,11 +310,11 @@ fn generate_typing_union_fix( ctx: ExprContext::Load, }); - Fix::applicable_edits( + Ok(Some(Fix::applicable_edits( Edit::range_replacement(checker.generator().expr(&new_expr), union.range()), [import_edit], applicability, - ) + ))) } #[derive(Debug, Clone)] From 2a02f3d251693ad443914d1d9fb83ecd421d4297 Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Fri, 4 Apr 2025 18:51:43 +0530 Subject: [PATCH 10/19] refactor(PYI051): Use `get()` instead of index access --- .../src/rules/flake8_pyi/rules/redundant_literal_union.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index f134ea10ba..42ab257010 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -305,7 +305,10 @@ fn generate_typing_union_fix( parenthesized: true, }) } else { - new_exprs[0].clone() + new_exprs + .first() + .expect("should have at least one new_expr") + .clone() }), ctx: ExprContext::Load, }); From 27563892e0f228d29006985de1616d3c6f1cff56 Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Sat, 5 Apr 2025 00:14:04 +0530 Subject: [PATCH 11/19] refactor(PYI051): Move literal subscript assignment up --- .../rules/flake8_pyi/rules/redundant_literal_union.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 42ab257010..a66449d064 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -128,6 +128,10 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { traverse_union(&mut func, checker.semantic(), union); + let Some(literal_subscript) = literal_subscript else { + return; + }; + let mut diagnostics = Vec::new(); let mut non_redundant_literal_types = Vec::new(); let mut redundant_literal_types = Vec::new(); @@ -187,10 +191,6 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { traverse_union(&mut func, checker.semantic(), union); - let Some(literal_subscript) = literal_subscript else { - return; - }; - // This generates new individual `Literal` exprs and builtins let mut new_exprs = Vec::new(); for group in new_literal_expr_types { @@ -239,7 +239,7 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { UnionKind::TypingUnion => diagnostic.try_set_optional_fix(|| { generate_typing_union_fix(checker, &new_exprs, union, applicability) }), - }; + } } checker.report_diagnostics(diagnostics); From c748f32482d003810e14ed1dad9ca5a7c8667288 Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Sat, 5 Apr 2025 00:23:48 +0530 Subject: [PATCH 12/19] refactor(PYI051): Replace index access with `first()` --- .../src/rules/flake8_pyi/rules/redundant_literal_union.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index a66449d064..faef5511e8 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -211,7 +211,11 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { parenthesized: true, }) } else { - group[0].clone() + group + .first() + .expect("should have at least one new_expr") + .to_owned() + .clone() }), }); new_exprs.push(new_literal_expr); From b1b5d7162d69673ee42a680feed7f63c8ddf282d Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Sat, 5 Apr 2025 00:36:08 +0530 Subject: [PATCH 13/19] refactor(PYI051): Remove unused redundant literal exprs vec --- .../src/rules/flake8_pyi/rules/redundant_literal_union.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index faef5511e8..31a3c69963 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -134,7 +134,6 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { let mut diagnostics = Vec::new(); let mut non_redundant_literal_types = Vec::new(); - let mut redundant_literal_types = Vec::new(); for typing_literal_expr in typing_literal_exprs { let Some(literal_type) = match_literal_type(typing_literal_expr) else { @@ -142,7 +141,6 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { }; if builtin_types_in_union.contains(&literal_type) { - redundant_literal_types.push(typing_literal_expr); diagnostics.push(Diagnostic::new( RedundantLiteralUnion { literal: SourceCodeSnippet::from_str( From 3f20aca4d74924dbde90e584f94f9666418d0c92 Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Wed, 9 Apr 2025 23:55:56 +0530 Subject: [PATCH 14/19] refactor: Better early-return handling --- .../rules/redundant_literal_union.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 31a3c69963..73f94fcbdc 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -209,11 +209,10 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { parenthesized: true, }) } else { - group - .first() - .expect("should have at least one new_expr") - .to_owned() - .clone() + let Some(group) = group.first() else { + return; + }; + group.to_owned().clone() }), }); new_exprs.push(new_literal_expr); @@ -221,7 +220,6 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { } } - // Now concatenate the exprs based on the union type let applicability = if checker.comment_ranges().intersects(union.range()) { Applicability::Unsafe } else { @@ -307,10 +305,10 @@ fn generate_typing_union_fix( parenthesized: true, }) } else { - new_exprs - .first() - .expect("should have at least one new_expr") - .clone() + let Some(new_exprs) = new_exprs.first() else { + return Ok(None); + }; + new_exprs.clone() }), ctx: ExprContext::Load, }); From 899c89823d039b44dd3ca7fcc87dbeb9c73c03ec Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Thu, 8 May 2025 02:24:03 +0530 Subject: [PATCH 15/19] chore: Add suggested changes --- .../rules/redundant_literal_union.rs | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 73f94fcbdc..f85e4cfe65 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -8,7 +8,7 @@ use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Vi use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_ast::{ self as ast, Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, LiteralExpressionRef, - Operator, PythonVersion, + Operator, }; use ruff_python_semantic::analyze::typing::traverse_union; use ruff_python_semantic::SemanticModel; @@ -93,12 +93,19 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { let mut builtin_types_in_union = FxHashSet::default(); let mut literal_subscript = None; let mut literal_exprs = Vec::new(); - let union_kind = - if checker.target_version() >= PythonVersion::PY310 || checker.source_type.is_stub() { - UnionKind::PEP604 - } else { + let subscript = union.as_subscript_expr(); + let union_kind = match subscript { + Some(subscript) => { + if !checker + .semantic() + .match_typing_expr(&subscript.value, "Union") + { + return; + } UnionKind::TypingUnion - }; + } + None => UnionKind::PEP604, + }; // Adds a member to `literal_exprs` for each value in a `Literal`, and any builtin types // to `builtin_types_in_union`. @@ -156,6 +163,11 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { } } + if checker.settings.preview.is_disabled() { + checker.report_diagnostics(diagnostics); + return; + } + let mut new_literal_expr_types: Vec> = Vec::new(); // Group all the non-redundant literal types together based on the `Literals` @@ -251,12 +263,12 @@ fn generate_pep604_fix( union: &Expr, applicability: Applicability, ) -> Option { - if new_exprs.len() == 1 { + if let [new_expr] = new_exprs { return Some(Fix::applicable_edit( - Edit::range_replacement(checker.generator().expr(&new_exprs[0]), union.range()), + Edit::range_replacement(checker.generator().expr(new_expr), union.range()), applicability, )); - } + }; let new_expr = new_exprs.iter().fold(None, |acc, right| { if let Some(left) = acc { From c7d14edd4a5e80e407b6e717f3d0c4a5ca930a32 Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Thu, 8 May 2025 02:52:17 +0530 Subject: [PATCH 16/19] fix: Make FixAvailability::Sometime to pass tests --- .../src/rules/flake8_pyi/rules/redundant_literal_union.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index f85e4cfe65..577481fc5c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -50,7 +50,7 @@ pub(crate) struct RedundantLiteralUnion { } impl Violation for RedundantLiteralUnion { - const FIX_AVAILABILITY: FixAvailability = FixAvailability::Always; + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; #[derive_message_formats] fn message(&self) -> String { From cf9492119beca34f6acc6e9f1312546498a34abd Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Thu, 8 May 2025 02:52:27 +0530 Subject: [PATCH 17/19] chore: Update snapshots --- ...__flake8_pyi__tests__PYI051_PYI051.py.snap | 164 +++--------------- ..._flake8_pyi__tests__PYI051_PYI051.pyi.snap | 164 +++--------------- 2 files changed, 44 insertions(+), 284 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap index d33b9d4260..7005a12f96 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI051.py:4:18: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` +PYI051.py:4:18: PYI051 `Literal["foo"]` is redundant in a union with `str` | 2 | from typing import Literal, TypeAlias, Union 3 | @@ -12,17 +12,7 @@ PYI051.py:4:18: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | = help: Replace `Literal["foo"] | str` with `str` -ℹ Safe fix -1 1 | import typing -2 2 | from typing import Literal, TypeAlias, Union -3 3 | -4 |-A: str | Literal["foo"] - 4 |+A: str -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] - -PYI051.py:5:37: PYI051 [*] `Literal[b"bar"]` is redundant in a union with `bytes` +PYI051.py:5:37: PYI051 `Literal[b"bar"]` is redundant in a union with `bytes` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -30,19 +20,9 @@ PYI051.py:5:37: PYI051 [*] `Literal[b"bar"]` is redundant in a union with `bytes 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] | - = help: Replace `Literal[b"bar"] | bytes` with `bytes` + = help: Replace `typing.Union[Literal[b"bar"], bytes]` with `bytes` -ℹ Safe fix -2 2 | from typing import Literal, TypeAlias, Union -3 3 | -4 4 | A: str | Literal["foo"] -5 |-B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] - 5 |+B: TypeAlias = bytes | str -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] - -PYI051.py:5:45: PYI051 [*] `Literal[b"foo"]` is redundant in a union with `bytes` +PYI051.py:5:45: PYI051 `Literal[b"foo"]` is redundant in a union with `bytes` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -50,19 +30,9 @@ PYI051.py:5:45: PYI051 [*] `Literal[b"foo"]` is redundant in a union with `bytes 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] | - = help: Replace `Literal[b"foo"] | bytes` with `bytes` + = help: Replace `typing.Union[Literal[b"foo"], bytes]` with `bytes` -ℹ Safe fix -2 2 | from typing import Literal, TypeAlias, Union -3 3 | -4 4 | A: str | Literal["foo"] -5 |-B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] - 5 |+B: TypeAlias = bytes | str -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] - -PYI051.py:6:37: PYI051 [*] `Literal[5]` is redundant in a union with `int` +PYI051.py:6:37: PYI051 `Literal[5]` is redundant in a union with `int` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -71,19 +41,9 @@ PYI051.py:6:37: PYI051 [*] `Literal[5]` is redundant in a union with `int` 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] | - = help: Replace `Literal[5] | int` with `int` + = help: Replace `typing.Union[Literal[5], int]` with `int` -ℹ Safe fix -3 3 | -4 4 | A: str | Literal["foo"] -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 |-C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] - 6 |+C: TypeAlias = int | str -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - -PYI051.py:6:67: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` +PYI051.py:6:67: PYI051 `Literal["foo"]` is redundant in a union with `str` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -92,19 +52,9 @@ PYI051.py:6:67: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] | - = help: Replace `Literal["foo"] | str` with `str` + = help: Replace `typing.Union[Literal["foo"], str]` with `str` -ℹ Safe fix -3 3 | -4 4 | A: str | Literal["foo"] -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 |-C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] - 6 |+C: TypeAlias = int | str -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - -PYI051.py:7:37: PYI051 [*] `Literal[b"str_bytes"]` is redundant in a union with `bytes` +PYI051.py:7:37: PYI051 `Literal[b"str_bytes"]` is redundant in a union with `bytes` | 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] @@ -113,19 +63,9 @@ PYI051.py:7:37: PYI051 [*] `Literal[b"str_bytes"]` is redundant in a union with 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `Literal[b"str_bytes"] | bytes` with `bytes` + = help: Replace `typing.Union[Literal[b"str_bytes"], bytes]` with `bytes` -ℹ Safe fix -4 4 | A: str | Literal["foo"] -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 |-D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] - 7 |+D: TypeAlias = bytes | int -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - -PYI051.py:7:51: PYI051 [*] `Literal[42]` is redundant in a union with `int` +PYI051.py:7:51: PYI051 `Literal[42]` is redundant in a union with `int` | 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] @@ -134,19 +74,9 @@ PYI051.py:7:51: PYI051 [*] `Literal[42]` is redundant in a union with `int` 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `Literal[42] | int` with `int` + = help: Replace `typing.Union[Literal[42], int]` with `int` -ℹ Safe fix -4 4 | A: str | Literal["foo"] -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 |-D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] - 7 |+D: TypeAlias = bytes | int -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - -PYI051.py:8:76: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` +PYI051.py:8:76: PYI051 `Literal["foo"]` is redundant in a union with `str` | 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] @@ -155,19 +85,9 @@ PYI051.py:8:76: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `Literal["foo"] | str` with `str` + = help: Replace `typing.Union[Literal["foo"], str]` with `str` -ℹ Safe fix -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 |-E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] - 8 |+E: TypeAlias = str -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -11 11 | - -PYI051.py:9:81: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` +PYI051.py:9:81: PYI051 `Literal["foo"]` is redundant in a union with `str` | 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] @@ -175,19 +95,9 @@ PYI051.py:9:81: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | ^^^^^ PYI051 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `Literal["foo"] | str` with `str` + = help: Replace `typing.Union[Literal["foo"], str]` with `str` -ℹ Safe fix -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 |-F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - 9 |+F: TypeAlias = str | int -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -11 11 | -12 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... - -PYI051.py:10:69: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` +PYI051.py:10:69: PYI051 `Literal["foo"]` is redundant in a union with `str` | 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] @@ -196,19 +106,9 @@ PYI051.py:10:69: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` 11 | 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... | - = help: Replace `Literal["foo"] | str` with `str` + = help: Replace `typing.Union[Literal["foo"], str]` with `str` -ℹ Safe fix -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 |-G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - 10 |+G: str | int -11 11 | -12 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... -13 13 | - -PYI051.py:12:31: PYI051 [*] `Literal[1J]` is redundant in a union with `complex` +PYI051.py:12:31: PYI051 `Literal[1J]` is redundant in a union with `complex` | 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 11 | @@ -219,17 +119,7 @@ PYI051.py:12:31: PYI051 [*] `Literal[1J]` is redundant in a union with `complex` | = help: Replace `Literal[1J] | complex` with `complex` -ℹ Safe fix -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -11 11 | -12 |-def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... - 12 |+def func(x: complex, y: Union[Literal[3.14], float]): ... -13 13 | -14 14 | # OK -15 15 | A: Literal["foo"] - -PYI051.py:12:53: PYI051 [*] `Literal[3.14]` is redundant in a union with `float` +PYI051.py:12:53: PYI051 `Literal[3.14]` is redundant in a union with `float` | 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 11 | @@ -238,14 +128,4 @@ PYI051.py:12:53: PYI051 [*] `Literal[3.14]` is redundant in a union with `float` 13 | 14 | # OK | - = help: Replace `Literal[3.14] | float` with `float` - -ℹ Safe fix -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -11 11 | -12 |-def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... - 12 |+def func(x: complex | Literal[1J], y: float): ... -13 13 | -14 14 | # OK -15 15 | A: Literal["foo"] + = help: Replace `typing.Union[Literal[3.14], float]` with `float` diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap index 01244067e2..30ffcff449 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs --- -PYI051.pyi:4:18: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` +PYI051.pyi:4:18: PYI051 `Literal["foo"]` is redundant in a union with `str` | 2 | from typing import Literal, TypeAlias, Union 3 | @@ -12,17 +12,7 @@ PYI051.pyi:4:18: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | = help: Replace `Literal["foo"] | str` with `str` -ℹ Safe fix -1 1 | import typing -2 2 | from typing import Literal, TypeAlias, Union -3 3 | -4 |-A: str | Literal["foo"] - 4 |+A: str -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] - -PYI051.pyi:5:37: PYI051 [*] `Literal[b"bar"]` is redundant in a union with `bytes` +PYI051.pyi:5:37: PYI051 `Literal[b"bar"]` is redundant in a union with `bytes` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -30,19 +20,9 @@ PYI051.pyi:5:37: PYI051 [*] `Literal[b"bar"]` is redundant in a union with `byte 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] | - = help: Replace `Literal[b"bar"] | bytes` with `bytes` + = help: Replace `typing.Union[Literal[b"bar"], bytes]` with `bytes` -ℹ Safe fix -2 2 | from typing import Literal, TypeAlias, Union -3 3 | -4 4 | A: str | Literal["foo"] -5 |-B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] - 5 |+B: TypeAlias = bytes | str -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] - -PYI051.pyi:5:45: PYI051 [*] `Literal[b"foo"]` is redundant in a union with `bytes` +PYI051.pyi:5:45: PYI051 `Literal[b"foo"]` is redundant in a union with `bytes` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -50,19 +30,9 @@ PYI051.pyi:5:45: PYI051 [*] `Literal[b"foo"]` is redundant in a union with `byte 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] | - = help: Replace `Literal[b"foo"] | bytes` with `bytes` + = help: Replace `typing.Union[Literal[b"foo"], bytes]` with `bytes` -ℹ Safe fix -2 2 | from typing import Literal, TypeAlias, Union -3 3 | -4 4 | A: str | Literal["foo"] -5 |-B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] - 5 |+B: TypeAlias = bytes | str -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] - -PYI051.pyi:6:37: PYI051 [*] `Literal[5]` is redundant in a union with `int` +PYI051.pyi:6:37: PYI051 `Literal[5]` is redundant in a union with `int` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -71,19 +41,9 @@ PYI051.pyi:6:37: PYI051 [*] `Literal[5]` is redundant in a union with `int` 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] | - = help: Replace `Literal[5] | int` with `int` + = help: Replace `typing.Union[Literal[5], int]` with `int` -ℹ Safe fix -3 3 | -4 4 | A: str | Literal["foo"] -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 |-C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] - 6 |+C: TypeAlias = int | str -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - -PYI051.pyi:6:67: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` +PYI051.pyi:6:67: PYI051 `Literal["foo"]` is redundant in a union with `str` | 4 | A: str | Literal["foo"] 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] @@ -92,19 +52,9 @@ PYI051.pyi:6:67: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] | - = help: Replace `Literal["foo"] | str` with `str` + = help: Replace `typing.Union[Literal["foo"], str]` with `str` -ℹ Safe fix -3 3 | -4 4 | A: str | Literal["foo"] -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 |-C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] - 6 |+C: TypeAlias = int | str -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - -PYI051.pyi:7:37: PYI051 [*] `Literal[b"str_bytes"]` is redundant in a union with `bytes` +PYI051.pyi:7:37: PYI051 `Literal[b"str_bytes"]` is redundant in a union with `bytes` | 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] @@ -113,19 +63,9 @@ PYI051.pyi:7:37: PYI051 [*] `Literal[b"str_bytes"]` is redundant in a union with 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `Literal[b"str_bytes"] | bytes` with `bytes` + = help: Replace `typing.Union[Literal[b"str_bytes"], bytes]` with `bytes` -ℹ Safe fix -4 4 | A: str | Literal["foo"] -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 |-D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] - 7 |+D: TypeAlias = bytes | int -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - -PYI051.pyi:7:51: PYI051 [*] `Literal[42]` is redundant in a union with `int` +PYI051.pyi:7:51: PYI051 `Literal[42]` is redundant in a union with `int` | 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] @@ -134,19 +74,9 @@ PYI051.pyi:7:51: PYI051 [*] `Literal[42]` is redundant in a union with `int` 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `Literal[42] | int` with `int` + = help: Replace `typing.Union[Literal[42], int]` with `int` -ℹ Safe fix -4 4 | A: str | Literal["foo"] -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 |-D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] - 7 |+D: TypeAlias = bytes | int -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - -PYI051.pyi:8:76: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` +PYI051.pyi:8:76: PYI051 `Literal["foo"]` is redundant in a union with `str` | 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] @@ -155,19 +85,9 @@ PYI051.pyi:8:76: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `Literal["foo"] | str` with `str` + = help: Replace `typing.Union[Literal["foo"], str]` with `str` -ℹ Safe fix -5 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 |-E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] - 8 |+E: TypeAlias = str -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -11 11 | - -PYI051.pyi:9:81: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` +PYI051.pyi:9:81: PYI051 `Literal["foo"]` is redundant in a union with `str` | 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] @@ -175,19 +95,9 @@ PYI051.pyi:9:81: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` | ^^^^^ PYI051 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `Literal["foo"] | str` with `str` + = help: Replace `typing.Union[Literal["foo"], str]` with `str` -ℹ Safe fix -6 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 |-F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - 9 |+F: TypeAlias = str | int -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -11 11 | -12 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... - -PYI051.pyi:10:69: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` +PYI051.pyi:10:69: PYI051 `Literal["foo"]` is redundant in a union with `str` | 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] @@ -196,19 +106,9 @@ PYI051.pyi:10:69: PYI051 [*] `Literal["foo"]` is redundant in a union with `str` 11 | 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... | - = help: Replace `Literal["foo"] | str` with `str` + = help: Replace `typing.Union[Literal["foo"], str]` with `str` -ℹ Safe fix -7 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] -8 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 |-G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] - 10 |+G: str | int -11 11 | -12 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... -13 13 | - -PYI051.pyi:12:31: PYI051 [*] `Literal[1J]` is redundant in a union with `complex` +PYI051.pyi:12:31: PYI051 `Literal[1J]` is redundant in a union with `complex` | 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 11 | @@ -219,17 +119,7 @@ PYI051.pyi:12:31: PYI051 [*] `Literal[1J]` is redundant in a union with `complex | = help: Replace `Literal[1J] | complex` with `complex` -ℹ Safe fix -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -11 11 | -12 |-def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... - 12 |+def func(x: complex, y: Union[Literal[3.14], float]): ... -13 13 | -14 14 | # OK -15 15 | A: Literal["foo"] - -PYI051.pyi:12:53: PYI051 [*] `Literal[3.14]` is redundant in a union with `float` +PYI051.pyi:12:53: PYI051 `Literal[3.14]` is redundant in a union with `float` | 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 11 | @@ -238,14 +128,4 @@ PYI051.pyi:12:53: PYI051 [*] `Literal[3.14]` is redundant in a union with `float 13 | 14 | # OK | - = help: Replace `Literal[3.14] | float` with `float` - -ℹ Safe fix -9 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -10 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] -11 11 | -12 |-def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... - 12 |+def func(x: complex | Literal[1J], y: float): ... -13 13 | -14 14 | # OK -15 15 | A: Literal["foo"] + = help: Replace `typing.Union[Literal[3.14], float]` with `float` From c19c5d77d121c358a2fadf82b4adeffa56776d7b Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Thu, 8 May 2025 03:09:57 +0530 Subject: [PATCH 18/19] fix: Remove redundant semicolon --- .../src/rules/flake8_pyi/rules/redundant_literal_union.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 577481fc5c..3259fdbecc 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -268,7 +268,7 @@ fn generate_pep604_fix( Edit::range_replacement(checker.generator().expr(new_expr), union.range()), applicability, )); - }; + } let new_expr = new_exprs.iter().fold(None, |acc, right| { if let Some(left) = acc { From e73b3a52b6ed3f8e72e13a40433e4e5d0d862011 Mon Sep 17 00:00:00 2001 From: Chandra Kiran G Date: Mon, 3 Nov 2025 15:17:44 +0530 Subject: [PATCH 19/19] Fix: Update snapshots and code --- .../rules/redundant_literal_union.rs | 57 ++++--- ...__flake8_pyi__tests__PYI051_PYI051.py.snap | 24 +-- ...ke8_pyi__tests__PYI051_PYI051.pyi.snap.new | 144 ++++++++++++++++++ 3 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap.new diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs index 8a5801a12d..b731faf7c4 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_literal_union.rs @@ -4,20 +4,20 @@ use anyhow::Result; use ruff_python_ast::name::Name; use rustc_hash::FxHashSet; -use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; -use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_diagnostics::{Applicability, Edit, Fix}; +use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::{ self as ast, Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, LiteralExpressionRef, Operator, }; -use ruff_python_semantic::analyze::typing::traverse_union; use ruff_python_semantic::SemanticModel; +use ruff_python_semantic::analyze::typing::traverse_union; use ruff_text_size::{Ranged, TextRange}; -use crate::Violation; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; use crate::importer::ImportRequest; +use crate::{FixAvailability, Violation}; /// ## What it does /// Checks for redundant unions between a `Literal` and a builtin supertype of @@ -78,7 +78,7 @@ impl Violation for RedundantLiteralUnion { match union_kind { UnionKind::TypingUnion => Some(format!( "Replace `typing.Union[Literal[{literal}], {builtin_type}]` with `{builtin_type}`" - )), + )), UnionKind::PEP604 => Some(format!( "Replace `Literal[{literal}] | {builtin_type}` with `{builtin_type}`" )), @@ -141,7 +141,7 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { return; }; - let mut diagnostics = Vec::new(); + let mut diagnostics: Vec<(RedundantLiteralUnion, TextRange)> = Vec::new(); let mut non_redundant_literal_types = Vec::new(); for typing_literal_expr in typing_literal_exprs { @@ -150,7 +150,7 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { }; if builtin_types_in_union.contains(&literal_type) { - diagnostics.push(Diagnostic::new( + diagnostics.push(( RedundantLiteralUnion { literal: SourceCodeSnippet::from_str( checker.locator().slice(typing_literal_expr), @@ -165,8 +165,10 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { } } - if checker.settings.preview.is_disabled() { - checker.report_diagnostics(diagnostics); + if checker.settings().preview.is_disabled() { + for (kind, range) in diagnostics { + let _ = checker.report_diagnostic(kind, range); + } return; } @@ -212,11 +214,13 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { } LiteralExprType::NonRedundantTypes(group) => { let new_literal_expr = Expr::Subscript(ast::ExprSubscript { + node_index: Default::default(), value: Box::new(literal_subscript.clone()), range: TextRange::default(), ctx: ExprContext::Load, slice: Box::new(if group.len() > 1 { Expr::Tuple(ast::ExprTuple { + node_index: Default::default(), elts: group.into_iter().cloned().collect(), range: TextRange::default(), ctx: ExprContext::Load, @@ -240,23 +244,26 @@ pub(crate) fn redundant_literal_union<'a>(checker: &Checker, union: &'a Expr) { Applicability::Safe }; - for diagnostic in &mut diagnostics { + for (kind, range) in diagnostics { + let mut diagnostic = checker.report_diagnostic(kind, range); match union_kind { - UnionKind::PEP604 => diagnostic.try_set_optional_fix(|| { - Ok(generate_pep604_fix( - checker, - &new_exprs, - union, - applicability, - )) - }), - UnionKind::TypingUnion => diagnostic.try_set_optional_fix(|| { - generate_typing_union_fix(checker, &new_exprs, union, applicability) - }), + UnionKind::PEP604 => { + diagnostic.try_set_optional_fix(|| -> anyhow::Result> { + Ok(generate_pep604_fix( + checker, + &new_exprs, + union, + applicability, + )) + }); + } + UnionKind::TypingUnion => { + diagnostic.try_set_optional_fix(|| -> anyhow::Result> { + generate_typing_union_fix(checker, &new_exprs, union, applicability) + }); + } } } - - checker.report_diagnostics(diagnostics); } fn generate_pep604_fix( @@ -275,6 +282,7 @@ fn generate_pep604_fix( let new_expr = new_exprs.iter().fold(None, |acc, right| { if let Some(left) = acc { Some(Expr::BinOp(ExprBinOp { + node_index: Default::default(), left: Box::new(left), op: Operator::BitOr, right: Box::new(right.clone()), @@ -305,14 +313,17 @@ fn generate_typing_union_fix( // Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]` let new_expr = Expr::Subscript(ExprSubscript { + node_index: Default::default(), range: TextRange::default(), value: Box::new(Expr::Name(ExprName { + node_index: Default::default(), id: Name::new(binding), ctx: ExprContext::Store, range: TextRange::default(), })), slice: Box::new(if new_exprs.len() > 1 { Expr::Tuple(ast::ExprTuple { + node_index: Default::default(), elts: new_exprs.to_vec(), range: TextRange::default(), ctx: ExprContext::Load, diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap index 0d2fde341a..6398b03de4 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.py.snap @@ -11,7 +11,7 @@ PYI051 `Literal["foo"]` is redundant in a union with `str` 5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] | - = help: Replace `Literal["foo"] | str` with `str` +help: Replace `Literal["foo"] | str` with `str` PYI051 `Literal[b"bar"]` is redundant in a union with `bytes` --> PYI051.py:5:37 @@ -22,7 +22,7 @@ PYI051 `Literal[b"bar"]` is redundant in a union with `bytes` 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] | - = help: Replace `typing.Union[Literal[b"bar"], bytes]` with `bytes` +help: Replace `typing.Union[Literal[b"bar"], bytes]` with `bytes` PYI051 `Literal[b"foo"]` is redundant in a union with `bytes` --> PYI051.py:5:45 @@ -33,7 +33,7 @@ PYI051 `Literal[b"foo"]` is redundant in a union with `bytes` 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] | - = help: Replace `typing.Union[Literal[b"foo"], bytes]` with `bytes` +help: Replace `typing.Union[Literal[b"foo"], bytes]` with `bytes` PYI051 `Literal[5]` is redundant in a union with `int` --> PYI051.py:6:37 @@ -45,7 +45,7 @@ PYI051 `Literal[5]` is redundant in a union with `int` 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] | - = help: Replace `typing.Union[Literal[5], int]` with `int` +help: Replace `typing.Union[Literal[5], int]` with `int` PYI051 `Literal["foo"]` is redundant in a union with `str` --> PYI051.py:6:67 @@ -57,7 +57,7 @@ PYI051 `Literal["foo"]` is redundant in a union with `str` 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] | - = help: Replace `typing.Union[Literal["foo"], str]` with `str` +help: Replace `typing.Union[Literal["foo"], str]` with `str` PYI051 `Literal[b"str_bytes"]` is redundant in a union with `bytes` --> PYI051.py:7:37 @@ -69,7 +69,7 @@ PYI051 `Literal[b"str_bytes"]` is redundant in a union with `bytes` 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `typing.Union[Literal[b"str_bytes"], bytes]` with `bytes` +help: Replace `typing.Union[Literal[b"str_bytes"], bytes]` with `bytes` PYI051 `Literal[42]` is redundant in a union with `int` --> PYI051.py:7:51 @@ -81,7 +81,7 @@ PYI051 `Literal[42]` is redundant in a union with `int` 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `typing.Union[Literal[42], int]` with `int` +help: Replace `typing.Union[Literal[42], int]` with `int` PYI051 `Literal["foo"]` is redundant in a union with `str` --> PYI051.py:8:76 @@ -93,7 +93,7 @@ PYI051 `Literal["foo"]` is redundant in a union with `str` 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `typing.Union[Literal["foo"], str]` with `str` +help: Replace `typing.Union[Literal["foo"], str]` with `str` PYI051 `Literal["foo"]` is redundant in a union with `str` --> PYI051.py:9:81 @@ -104,7 +104,7 @@ PYI051 `Literal["foo"]` is redundant in a union with `str` | ^^^^^ 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] | - = help: Replace `typing.Union[Literal["foo"], str]` with `str` +help: Replace `typing.Union[Literal["foo"], str]` with `str` PYI051 `Literal["foo"]` is redundant in a union with `str` --> PYI051.py:10:69 @@ -116,7 +116,7 @@ PYI051 `Literal["foo"]` is redundant in a union with `str` 11 | 12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... | - = help: Replace `typing.Union[Literal["foo"], str]` with `str` +help: Replace `typing.Union[Literal["foo"], str]` with `str` PYI051 `Literal[1J]` is redundant in a union with `complex` --> PYI051.py:12:31 @@ -128,7 +128,7 @@ PYI051 `Literal[1J]` is redundant in a union with `complex` 13 | 14 | # OK | - = help: Replace `Literal[1J] | complex` with `complex` +help: Replace `Literal[1J] | complex` with `complex` PYI051 `Literal[3.14]` is redundant in a union with `float` --> PYI051.py:12:53 @@ -140,4 +140,4 @@ PYI051 `Literal[3.14]` is redundant in a union with `float` 13 | 14 | # OK | - = help: Replace `typing.Union[Literal[3.14], float]` with `float` +help: Replace `typing.Union[Literal[3.14], float]` with `float` diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap.new b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap.new new file mode 100644 index 0000000000..9ae50bab50 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI051_PYI051.pyi.snap.new @@ -0,0 +1,144 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +assertion_line: 137 +--- +PYI051 `Literal["foo"]` is redundant in a union with `str` + --> PYI051.pyi:4:18 + | +2 | from typing import Literal, TypeAlias, Union +3 | +4 | A: str | Literal["foo"] + | ^^^^^ +5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] + | +help: Replace `Literal["foo"] | str` with `str` + +PYI051 `Literal[b"bar"]` is redundant in a union with `bytes` + --> PYI051.pyi:5:37 + | +4 | A: str | Literal["foo"] +5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] + | ^^^^^^ +6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + | +help: Replace `typing.Union[Literal[b"bar"], bytes]` with `bytes` + +PYI051 `Literal[b"foo"]` is redundant in a union with `bytes` + --> PYI051.pyi:5:45 + | +4 | A: str | Literal["foo"] +5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] + | ^^^^^^ +6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + | +help: Replace `typing.Union[Literal[b"foo"], bytes]` with `bytes` + +PYI051 `Literal[5]` is redundant in a union with `int` + --> PYI051.pyi:6:37 + | +4 | A: str | Literal["foo"] +5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] + | ^ +7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + | +help: Replace `typing.Union[Literal[5], int]` with `int` + +PYI051 `Literal["foo"]` is redundant in a union with `str` + --> PYI051.pyi:6:67 + | +4 | A: str | Literal["foo"] +5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] + | ^^^^^ +7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] +8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + | +help: Replace `typing.Union[Literal["foo"], str]` with `str` + +PYI051 `Literal[b"str_bytes"]` is redundant in a union with `bytes` + --> PYI051.pyi:7:37 + | +5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + | ^^^^^^^^^^^^ +8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + | +help: Replace `typing.Union[Literal[b"str_bytes"], bytes]` with `bytes` + +PYI051 `Literal[42]` is redundant in a union with `int` + --> PYI051.pyi:7:51 + | +5 | B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str] +6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] +7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + | ^^ +8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] +9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + | +help: Replace `typing.Union[Literal[42], int]` with `int` + +PYI051 `Literal["foo"]` is redundant in a union with `str` + --> PYI051.pyi:8:76 + | + 6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]] + 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + | ^^^^^ + 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + | +help: Replace `typing.Union[Literal["foo"], str]` with `str` + +PYI051 `Literal["foo"]` is redundant in a union with `str` + --> PYI051.pyi:9:81 + | + 7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int] + 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + | ^^^^^ +10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + | +help: Replace `typing.Union[Literal["foo"], str]` with `str` + +PYI051 `Literal["foo"]` is redundant in a union with `str` + --> PYI051.pyi:10:69 + | + 8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]] + 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] + | ^^^^^ +11 | +12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... + | +help: Replace `typing.Union[Literal["foo"], str]` with `str` + +PYI051 `Literal[1J]` is redundant in a union with `complex` + --> PYI051.pyi:12:31 + | +10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +11 | +12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... + | ^^ +13 | +14 | # OK + | +help: Replace `Literal[1J] | complex` with `complex` + +PYI051 `Literal[3.14]` is redundant in a union with `float` + --> PYI051.pyi:12:53 + | +10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] +11 | +12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ... + | ^^^^ +13 | +14 | # OK + | +help: Replace `typing.Union[Literal[3.14], float]` with `float`