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`