diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py index aa7d8d3734..51baabe892 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py @@ -1,4 +1,4 @@ -from typing import Literal +from typing import Literal, Union def func1(arg1: Literal[None]): @@ -17,7 +17,7 @@ def func4(arg1: Literal[int, None, float]): ... -def func5(arg1: Literal[None, None]): +def func5(arg1: Literal[None, None]): ... @@ -25,13 +25,21 @@ def func6(arg1: Literal[ "hello", None # Comment 1 , "world" - ]): + ]): ... def func7(arg1: Literal[ None # Comment 1 - ]): + ]): + ... + + +def func8(arg1: Literal[None] | None): + ... + + +def func9(arg1: Union[Literal[None], None]): ... @@ -58,3 +66,16 @@ Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replac # and there are no None members in the Literal[] slice, # only emit Y062: Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" + + +# Regression tests for https://github.com/astral-sh/ruff/issues/14567 +x: Literal[None] | None +y: None | Literal[None] +z: Union[Literal[None], None] + +a: int | Literal[None] | None +b: None | Literal[None] | None +c: (None | Literal[None]) | None +d: None | (Literal[None] | None) +e: None | ((None | Literal[None]) | None) | None +f: Literal[None] | Literal[None] diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.pyi index a43ab1460d..ed879dd646 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.pyi @@ -1,4 +1,4 @@ -from typing import Literal +from typing import Literal, Union def func1(arg1: Literal[None]): ... @@ -28,6 +28,12 @@ def func7(arg1: Literal[ ]): ... +def func8(arg1: Literal[None] | None):... + + +def func9(arg1: Union[Literal[None], None]): ... + + # OK def good_func(arg1: Literal[int] | None): ... @@ -35,3 +41,16 @@ def good_func(arg1: Literal[int] | None): ... # From flake8-pyi Literal[None] # PYI061 None inside "Literal[]" expression. Replace with "None" Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None" + + +# Regression tests for https://github.com/astral-sh/ruff/issues/14567 +x: Literal[None] | None +y: None | Literal[None] +z: Union[Literal[None], None] + +a: int | Literal[None] | None +b: None | Literal[None] | None +c: (None | Literal[None]) | None +d: None | (Literal[None] | None) +e: None | ((None | Literal[None]) | None) | None +f: Literal[None] | Literal[None] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs index da8066943f..9a23fff1b8 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs @@ -1,7 +1,10 @@ use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::{Expr, ExprNoneLiteral}; -use ruff_python_semantic::analyze::typing::traverse_literal; +use ruff_python_ast::{Expr, ExprBinOp, ExprNoneLiteral, ExprSubscript, Operator}; +use ruff_python_semantic::{ + analyze::typing::{traverse_literal, traverse_union}, + SemanticModel, +}; use ruff_text_size::Ranged; use smallvec::SmallVec; @@ -31,6 +34,9 @@ use crate::checkers::ast::Checker; /// Literal[1, 2, 3, "foo", 5] | None /// ``` /// +/// ## Fix safety +/// This rule's fix is marked as safe unless the literal contains comments. +/// /// ## References /// - [Typing documentation: Legal parameters for `Literal` at type check time](https://typing.readthedocs.io/en/latest/spec/literal.html#legal-parameters-for-literal-at-type-check-time) #[violation] @@ -87,14 +93,16 @@ pub(crate) fn redundant_none_literal<'a>(checker: &mut Checker, literal_expr: &' let fix = if other_literal_elements_seen { None } else { - Some(Fix::applicable_edit( - Edit::range_replacement("None".to_string(), literal_expr.range()), - if checker.comment_ranges().intersects(literal_expr.range()) { - Applicability::Unsafe - } else { - Applicability::Safe - }, - )) + create_fix_edit(checker.semantic(), literal_expr).map(|edit| { + Fix::applicable_edit( + edit, + if checker.comment_ranges().intersects(literal_expr.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }, + ) + }) }; for none_expr in none_exprs { @@ -110,3 +118,47 @@ pub(crate) fn redundant_none_literal<'a>(checker: &mut Checker, literal_expr: &' checker.diagnostics.push(diagnostic); } } + +fn create_fix_edit(semantic: &SemanticModel, literal_expr: &Expr) -> Option { + let mut enclosing_union = None; + for expr in semantic.current_expressions().skip(1).take_while(|expr| { + matches!( + expr, + Expr::BinOp(ExprBinOp { + op: Operator::BitOr, + .. + }) + ) + }) { + enclosing_union = Some(expr); + } + + let mut is_fixable = true; + if let Some(enclosing_union) = enclosing_union { + traverse_union( + &mut |expr, _| { + if matches!(expr, Expr::NoneLiteral(_)) { + is_fixable = false; + } + if expr != literal_expr { + if let Expr::Subscript(ExprSubscript { value, slice, .. }) = expr { + if semantic.match_typing_expr(value, "Literal") + && matches!(**slice, Expr::NoneLiteral(_)) + { + is_fixable = false; + } + } + } + }, + semantic, + enclosing_union, + ); + } + + // Avoid producing code that would raise an exception when + // `Literal[None] | None` would be fixed to `None | None`. + // Instead do not provide a fix. No action needed for `typing.Union`, + // as `Union[None, None]` is valid Python. + // See https://github.com/astral-sh/ruff/issues/14567. + is_fixable.then(|| Edit::range_replacement("None".to_string(), literal_expr.range())) +} diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI061_PYI061.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI061_PYI061.py.snap index 41a074e440..380b81d37b 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI061_PYI061.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI061_PYI061.py.snap @@ -10,7 +10,7 @@ PYI061.py:4:25: PYI061 [*] `Literal[None]` can be replaced with `None` = help: Replace with `None` ℹ Safe fix -1 1 | from typing import Literal +1 1 | from typing import Literal, Union 2 2 | 3 3 | 4 |-def func1(arg1: Literal[None]): @@ -65,7 +65,7 @@ PYI061.py:16:30: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] PYI061.py:20:25: PYI061 [*] `Literal[None]` can be replaced with `None` | -20 | def func5(arg1: Literal[None, None]): +20 | def func5(arg1: Literal[None, None]): | ^^^^ PYI061 21 | ... | @@ -75,15 +75,15 @@ PYI061.py:20:25: PYI061 [*] `Literal[None]` can be replaced with `None` 17 17 | ... 18 18 | 19 19 | -20 |-def func5(arg1: Literal[None, None]): - 20 |+def func5(arg1: None): +20 |-def func5(arg1: Literal[None, None]): + 20 |+def func5(arg1: None): 21 21 | ... 22 22 | 23 23 | PYI061.py:20:31: PYI061 [*] `Literal[None]` can be replaced with `None` | -20 | def func5(arg1: Literal[None, None]): +20 | def func5(arg1: Literal[None, None]): | ^^^^ PYI061 21 | ... | @@ -93,8 +93,8 @@ PYI061.py:20:31: PYI061 [*] `Literal[None]` can be replaced with `None` 17 17 | ... 18 18 | 19 19 | -20 |-def func5(arg1: Literal[None, None]): - 20 |+def func5(arg1: None): +20 |-def func5(arg1: Literal[None, None]): + 20 |+def func5(arg1: None): 21 21 | ... 22 22 | 23 23 | @@ -106,7 +106,7 @@ PYI061.py:26:5: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | 26 | None # Comment 1 | ^^^^ PYI061 27 | , "world" -28 | ]): +28 | ]): | = help: Replace with `Literal[...] | None` @@ -115,7 +115,7 @@ PYI061.py:33:5: PYI061 [*] `Literal[None]` can be replaced with `None` 32 | def func7(arg1: Literal[ 33 | None # Comment 1 | ^^^^ PYI061 -34 | ]): +34 | ]): 35 | ... | = help: Replace with `None` @@ -126,118 +126,256 @@ PYI061.py:33:5: PYI061 [*] `Literal[None]` can be replaced with `None` 31 31 | 32 |-def func7(arg1: Literal[ 33 |- None # Comment 1 -34 |- ]): - 32 |+def func7(arg1: None): +34 |- ]): + 32 |+def func7(arg1: None): 35 33 | ... 36 34 | 37 35 | -PYI061.py:44:9: PYI061 [*] `Literal[None]` can be replaced with `None` +PYI061.py:38:25: PYI061 `Literal[None]` can be replaced with `None` | -43 | # From flake8-pyi -44 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" - | ^^^^ PYI061 -45 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" - | - = help: Replace with `None` - -ℹ Safe fix -41 41 | -42 42 | -43 43 | # From flake8-pyi -44 |-Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" - 44 |+None # Y061 None inside "Literal[]" expression. Replace with "None" -45 45 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" -46 46 | -47 47 | ### - -PYI061.py:45:15: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` - | -43 | # From flake8-pyi -44 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" -45 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" - | ^^^^ PYI061 -46 | -47 | ### - | - = help: Replace with `Literal[...] | None` - -PYI061.py:54:9: PYI061 [*] `Literal[None]` can be replaced with `None` - | -52 | # If Y061 and Y062 both apply, but all the duplicate members are None, -53 | # only emit Y061... -54 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - | ^^^^ PYI061 -55 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" - | - = help: Replace with `None` - -ℹ Safe fix -51 51 | -52 52 | # If Y061 and Y062 both apply, but all the duplicate members are None, -53 53 | # only emit Y061... -54 |-Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - 54 |+None # Y061 None inside "Literal[]" expression. Replace with "None" -55 55 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -56 56 | -57 57 | # ... but if Y061 and Y062 both apply - -PYI061.py:54:15: PYI061 [*] `Literal[None]` can be replaced with `None` - | -52 | # If Y061 and Y062 both apply, but all the duplicate members are None, -53 | # only emit Y061... -54 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - | ^^^^ PYI061 -55 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" - | - = help: Replace with `None` - -ℹ Safe fix -51 51 | -52 52 | # If Y061 and Y062 both apply, but all the duplicate members are None, -53 53 | # only emit Y061... -54 |-Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - 54 |+None # Y061 None inside "Literal[]" expression. Replace with "None" -55 55 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -56 56 | -57 57 | # ... but if Y061 and Y062 both apply - -PYI061.py:55:12: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` - | -53 | # only emit Y061... -54 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -55 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" - | ^^^^ PYI061 -56 | -57 | # ... but if Y061 and Y062 both apply - | - = help: Replace with `Literal[...] | None` - -PYI061.py:55:25: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` - | -53 | # only emit Y061... -54 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -55 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +38 | def func8(arg1: Literal[None] | None): | ^^^^ PYI061 -56 | -57 | # ... but if Y061 and Y062 both apply +39 | ... + | + = help: Replace with `None` + +PYI061.py:42:31: PYI061 [*] `Literal[None]` can be replaced with `None` + | +42 | def func9(arg1: Union[Literal[None], None]): + | ^^^^ PYI061 +43 | ... + | + = help: Replace with `None` + +ℹ Safe fix +39 39 | ... +40 40 | +41 41 | +42 |-def func9(arg1: Union[Literal[None], None]): + 42 |+def func9(arg1: Union[None, None]): +43 43 | ... +44 44 | +45 45 | + +PYI061.py:52:9: PYI061 [*] `Literal[None]` can be replaced with `None` + | +51 | # From flake8-pyi +52 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" + | ^^^^ PYI061 +53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" + | + = help: Replace with `None` + +ℹ Safe fix +49 49 | +50 50 | +51 51 | # From flake8-pyi +52 |-Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" + 52 |+None # Y061 None inside "Literal[]" expression. Replace with "None" +53 53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +54 54 | +55 55 | ### + +PYI061.py:53:15: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` + | +51 | # From flake8-pyi +52 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" + | ^^^^ PYI061 +54 | +55 | ### | = help: Replace with `Literal[...] | None` -PYI061.py:60:9: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` +PYI061.py:62:9: PYI061 [*] `Literal[None]` can be replaced with `None` | -58 | # and there are no None members in the Literal[] slice, -59 | # only emit Y062: -60 | Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" +60 | # If Y061 and Y062 both apply, but all the duplicate members are None, +61 | # only emit Y061... +62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" + | ^^^^ PYI061 +63 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" + | + = help: Replace with `None` + +ℹ Safe fix +59 59 | +60 60 | # If Y061 and Y062 both apply, but all the duplicate members are None, +61 61 | # only emit Y061... +62 |-Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" + 62 |+None # Y061 None inside "Literal[]" expression. Replace with "None" +63 63 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +64 64 | +65 65 | # ... but if Y061 and Y062 both apply + +PYI061.py:62:15: PYI061 [*] `Literal[None]` can be replaced with `None` + | +60 | # If Y061 and Y062 both apply, but all the duplicate members are None, +61 | # only emit Y061... +62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" + | ^^^^ PYI061 +63 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" + | + = help: Replace with `None` + +ℹ Safe fix +59 59 | +60 60 | # If Y061 and Y062 both apply, but all the duplicate members are None, +61 61 | # only emit Y061... +62 |-Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" + 62 |+None # Y061 None inside "Literal[]" expression. Replace with "None" +63 63 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +64 64 | +65 65 | # ... but if Y061 and Y062 both apply + +PYI061.py:63:12: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` + | +61 | # only emit Y061... +62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +63 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" + | ^^^^ PYI061 +64 | +65 | # ... but if Y061 and Y062 both apply + | + = help: Replace with `Literal[...] | None` + +PYI061.py:63:25: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` + | +61 | # only emit Y061... +62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +63 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" + | ^^^^ PYI061 +64 | +65 | # ... but if Y061 and Y062 both apply + | + = help: Replace with `Literal[...] | None` + +PYI061.py:68:9: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` + | +66 | # and there are no None members in the Literal[] slice, +67 | # only emit Y062: +68 | Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" | ^^^^ PYI061 | = help: Replace with `Literal[...] | None` -PYI061.py:60:21: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` +PYI061.py:68:21: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` | -58 | # and there are no None members in the Literal[] slice, -59 | # only emit Y062: -60 | Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" +66 | # and there are no None members in the Literal[] slice, +67 | # only emit Y062: +68 | Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" | ^^^^ PYI061 | = help: Replace with `Literal[...] | None` + +PYI061.py:72:12: PYI061 `Literal[None]` can be replaced with `None` + | +71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +72 | x: Literal[None] | None + | ^^^^ PYI061 +73 | y: None | Literal[None] +74 | z: Union[Literal[None], None] + | + = help: Replace with `None` + +PYI061.py:73:19: PYI061 `Literal[None]` can be replaced with `None` + | +71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +72 | x: Literal[None] | None +73 | y: None | Literal[None] + | ^^^^ PYI061 +74 | z: Union[Literal[None], None] + | + = help: Replace with `None` + +PYI061.py:74:18: PYI061 [*] `Literal[None]` can be replaced with `None` + | +72 | x: Literal[None] | None +73 | y: None | Literal[None] +74 | z: Union[Literal[None], None] + | ^^^^ PYI061 +75 | +76 | a: int | Literal[None] | None + | + = help: Replace with `None` + +ℹ Safe fix +71 71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +72 72 | x: Literal[None] | None +73 73 | y: None | Literal[None] +74 |-z: Union[Literal[None], None] + 74 |+z: Union[None, None] +75 75 | +76 76 | a: int | Literal[None] | None +77 77 | b: None | Literal[None] | None + +PYI061.py:76:18: PYI061 `Literal[None]` can be replaced with `None` + | +74 | z: Union[Literal[None], None] +75 | +76 | a: int | Literal[None] | None + | ^^^^ PYI061 +77 | b: None | Literal[None] | None +78 | c: (None | Literal[None]) | None + | + = help: Replace with `None` + +PYI061.py:77:19: PYI061 `Literal[None]` can be replaced with `None` + | +76 | a: int | Literal[None] | None +77 | b: None | Literal[None] | None + | ^^^^ PYI061 +78 | c: (None | Literal[None]) | None +79 | d: None | (Literal[None] | None) + | + = help: Replace with `None` + +PYI061.py:78:20: PYI061 `Literal[None]` can be replaced with `None` + | +76 | a: int | Literal[None] | None +77 | b: None | Literal[None] | None +78 | c: (None | Literal[None]) | None + | ^^^^ PYI061 +79 | d: None | (Literal[None] | None) +80 | e: None | ((None | Literal[None]) | None) | None + | + = help: Replace with `None` + +PYI061.py:79:20: PYI061 `Literal[None]` can be replaced with `None` + | +77 | b: None | Literal[None] | None +78 | c: (None | Literal[None]) | None +79 | d: None | (Literal[None] | None) + | ^^^^ PYI061 +80 | e: None | ((None | Literal[None]) | None) | None +81 | f: Literal[None] | Literal[None] + | + = help: Replace with `None` + +PYI061.py:80:28: PYI061 `Literal[None]` can be replaced with `None` + | +78 | c: (None | Literal[None]) | None +79 | d: None | (Literal[None] | None) +80 | e: None | ((None | Literal[None]) | None) | None + | ^^^^ PYI061 +81 | f: Literal[None] | Literal[None] + | + = help: Replace with `None` + +PYI061.py:81:12: PYI061 `Literal[None]` can be replaced with `None` + | +79 | d: None | (Literal[None] | None) +80 | e: None | ((None | Literal[None]) | None) | None +81 | f: Literal[None] | Literal[None] + | ^^^^ PYI061 + | + = help: Replace with `None` + +PYI061.py:81:28: PYI061 `Literal[None]` can be replaced with `None` + | +79 | d: None | (Literal[None] | None) +80 | e: None | ((None | Literal[None]) | None) | None +81 | f: Literal[None] | Literal[None] + | ^^^^ PYI061 + | + = help: Replace with `None` diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI061_PYI061.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI061_PYI061.pyi.snap index e7ca825f64..d1b695f73d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI061_PYI061.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI061_PYI061.pyi.snap @@ -9,7 +9,7 @@ PYI061.pyi:4:25: PYI061 [*] `Literal[None]` can be replaced with `None` = help: Replace with `None` ℹ Safe fix -1 1 | from typing import Literal +1 1 | from typing import Literal, Union 2 2 | 3 3 | 4 |-def func1(arg1: Literal[None]): ... @@ -123,30 +123,168 @@ PYI061.pyi:27:5: PYI061 [*] `Literal[None]` can be replaced with `None` 26 |+def func7(arg1: None): ... 29 27 | 30 28 | -31 29 | # OK +31 29 | def func8(arg1: Literal[None] | None):... -PYI061.pyi:36:9: PYI061 [*] `Literal[None]` can be replaced with `None` +PYI061.pyi:31:25: PYI061 `Literal[None]` can be replaced with `None` | -35 | # From flake8-pyi -36 | Literal[None] # PYI061 None inside "Literal[]" expression. Replace with "None" - | ^^^^ PYI061 -37 | Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +31 | def func8(arg1: Literal[None] | None):... + | ^^^^ PYI061 + | + = help: Replace with `None` + +PYI061.pyi:34:31: PYI061 [*] `Literal[None]` can be replaced with `None` + | +34 | def func9(arg1: Union[Literal[None], None]): ... + | ^^^^ PYI061 | = help: Replace with `None` ℹ Safe fix +31 31 | def func8(arg1: Literal[None] | None):... +32 32 | 33 33 | -34 34 | -35 35 | # From flake8-pyi -36 |-Literal[None] # PYI061 None inside "Literal[]" expression. Replace with "None" - 36 |+None # PYI061 None inside "Literal[]" expression. Replace with "None" -37 37 | Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +34 |-def func9(arg1: Union[Literal[None], None]): ... + 34 |+def func9(arg1: Union[None, None]): ... +35 35 | +36 36 | +37 37 | # OK -PYI061.pyi:37:15: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` +PYI061.pyi:42:9: PYI061 [*] `Literal[None]` can be replaced with `None` | -35 | # From flake8-pyi -36 | Literal[None] # PYI061 None inside "Literal[]" expression. Replace with "None" -37 | Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +41 | # From flake8-pyi +42 | Literal[None] # PYI061 None inside "Literal[]" expression. Replace with "None" + | ^^^^ PYI061 +43 | Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None" + | + = help: Replace with `None` + +ℹ Safe fix +39 39 | +40 40 | +41 41 | # From flake8-pyi +42 |-Literal[None] # PYI061 None inside "Literal[]" expression. Replace with "None" + 42 |+None # PYI061 None inside "Literal[]" expression. Replace with "None" +43 43 | Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +44 44 | +45 45 | + +PYI061.pyi:43:15: PYI061 `Literal[None, ...]` can be replaced with `Literal[...] | None` + | +41 | # From flake8-pyi +42 | Literal[None] # PYI061 None inside "Literal[]" expression. Replace with "None" +43 | Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | ^^^^ PYI061 | = help: Replace with `Literal[...] | None` + +PYI061.pyi:47:12: PYI061 `Literal[None]` can be replaced with `None` + | +46 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +47 | x: Literal[None] | None + | ^^^^ PYI061 +48 | y: None | Literal[None] +49 | z: Union[Literal[None], None] + | + = help: Replace with `None` + +PYI061.pyi:48:19: PYI061 `Literal[None]` can be replaced with `None` + | +46 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +47 | x: Literal[None] | None +48 | y: None | Literal[None] + | ^^^^ PYI061 +49 | z: Union[Literal[None], None] + | + = help: Replace with `None` + +PYI061.pyi:49:18: PYI061 [*] `Literal[None]` can be replaced with `None` + | +47 | x: Literal[None] | None +48 | y: None | Literal[None] +49 | z: Union[Literal[None], None] + | ^^^^ PYI061 +50 | +51 | a: int | Literal[None] | None + | + = help: Replace with `None` + +ℹ Safe fix +46 46 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +47 47 | x: Literal[None] | None +48 48 | y: None | Literal[None] +49 |-z: Union[Literal[None], None] + 49 |+z: Union[None, None] +50 50 | +51 51 | a: int | Literal[None] | None +52 52 | b: None | Literal[None] | None + +PYI061.pyi:51:18: PYI061 `Literal[None]` can be replaced with `None` + | +49 | z: Union[Literal[None], None] +50 | +51 | a: int | Literal[None] | None + | ^^^^ PYI061 +52 | b: None | Literal[None] | None +53 | c: (None | Literal[None]) | None + | + = help: Replace with `None` + +PYI061.pyi:52:19: PYI061 `Literal[None]` can be replaced with `None` + | +51 | a: int | Literal[None] | None +52 | b: None | Literal[None] | None + | ^^^^ PYI061 +53 | c: (None | Literal[None]) | None +54 | d: None | (Literal[None] | None) + | + = help: Replace with `None` + +PYI061.pyi:53:20: PYI061 `Literal[None]` can be replaced with `None` + | +51 | a: int | Literal[None] | None +52 | b: None | Literal[None] | None +53 | c: (None | Literal[None]) | None + | ^^^^ PYI061 +54 | d: None | (Literal[None] | None) +55 | e: None | ((None | Literal[None]) | None) | None + | + = help: Replace with `None` + +PYI061.pyi:54:20: PYI061 `Literal[None]` can be replaced with `None` + | +52 | b: None | Literal[None] | None +53 | c: (None | Literal[None]) | None +54 | d: None | (Literal[None] | None) + | ^^^^ PYI061 +55 | e: None | ((None | Literal[None]) | None) | None +56 | f: Literal[None] | Literal[None] + | + = help: Replace with `None` + +PYI061.pyi:55:28: PYI061 `Literal[None]` can be replaced with `None` + | +53 | c: (None | Literal[None]) | None +54 | d: None | (Literal[None] | None) +55 | e: None | ((None | Literal[None]) | None) | None + | ^^^^ PYI061 +56 | f: Literal[None] | Literal[None] + | + = help: Replace with `None` + +PYI061.pyi:56:12: PYI061 `Literal[None]` can be replaced with `None` + | +54 | d: None | (Literal[None] | None) +55 | e: None | ((None | Literal[None]) | None) | None +56 | f: Literal[None] | Literal[None] + | ^^^^ PYI061 + | + = help: Replace with `None` + +PYI061.pyi:56:28: PYI061 `Literal[None]` can be replaced with `None` + | +54 | d: None | (Literal[None] | None) +55 | e: None | ((None | Literal[None]) | None) | None +56 | f: Literal[None] | Literal[None] + | ^^^^ PYI061 + | + = help: Replace with `None`