From 1f1a1e81fadf2616bfe988c1124aa5eacd3132d2 Mon Sep 17 00:00:00 2001 From: jesco-absolut Date: Sat, 4 Jul 2026 01:30:29 -0400 Subject: [PATCH 1/3] Mark PYI061 fixes unsafe in Python files --- .../test/fixtures/flake8_pyi/PYI061.py | 5 + .../rules/redundant_none_literal.rs | 14 +- ...__flake8_pyi__tests__PYI061_PYI061.py.snap | 385 +++++++++-------- ..._flake8_pyi__tests__PYI061_PYI061.pyi.snap | 6 +- ...ke8_pyi__tests__py38_PYI061_PYI061.py.snap | 389 ++++++++++-------- ...e8_pyi__tests__py38_PYI061_PYI061.pyi.snap | 6 +- 6 files changed, 449 insertions(+), 356 deletions(-) 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 f3f23663d9cdf..87611ae5cc336 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py @@ -48,6 +48,11 @@ def good_func(arg1: Literal[int] | None): ... +# Regression test for https://github.com/astral-sh/ruff/issues/20729. +# Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. +options = Literal["foo", "bar", None] + + # From flake8-pyi Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | 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 b3e35c21c2336..d4ccea5ae01ca 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 @@ -39,7 +39,11 @@ use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ``` /// /// ## Fix safety and availability -/// This rule's fix is marked as safe unless the literal contains comments. +/// In Python files, this rule's fix is marked as unsafe because replacing +/// `Literal[...]` can change runtime-visible annotation objects, such as the +/// result of `typing.get_args`. +/// +/// In stub files, the fix is marked as safe unless the literal contains comments. /// /// There is currently no fix available when applying the fix would lead to /// a `TypeError` from an expression of the form `None | None` or when we @@ -62,10 +66,10 @@ impl Violation for RedundantNoneLiteral { match self.union_kind { UnionKind::NoUnion => "Use `None` rather than `Literal[None]`".to_string(), UnionKind::TypingOptional => { - "Use `Optional[Literal[...]]` rather than `Literal[None, ...]` ".to_string() + "Use `Optional[Literal[...]]` rather than `Literal[None, ...]`".to_string() } UnionKind::BitOr => { - "Use `Literal[...] | None` rather than `Literal[None, ...]` ".to_string() + "Use `Literal[...] | None` rather than `Literal[None, ...]`".to_string() } } } @@ -192,7 +196,9 @@ fn create_fix( } } - let applicability = if checker.comment_ranges().intersects(literal_expr.range()) { + let applicability = if checker.comment_ranges().intersects(literal_expr.range()) + || !checker.source_type.is_stub() + { Applicability::Unsafe } else { Applicability::Safe 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 64a0ea6d4dcea..2db442327b91c 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 @@ -15,6 +15,7 @@ help: Replace with `None` 4 + def func1(arg1: None): 5 | ... | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` --> PYI061.py:8:25 @@ -30,6 +31,7 @@ help: Replace with `None` 8 + def func2(arg1: None | int): 9 | ... | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` --> PYI061.py:12:24 @@ -45,8 +47,9 @@ help: Replace with `None` 12 + def func3() -> None: 13 | ... | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` --> PYI061.py:16:30 | 16 | def func4(arg1: Literal[int, None, float]): @@ -60,6 +63,7 @@ help: Replace with `Literal[...] | None` 16 + def func4(arg1: Literal[int, float] | None): 17 | ... | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` --> PYI061.py:20:25 @@ -75,6 +79,7 @@ help: Replace with `None` 20 + def func5(arg1: None): 21 | ... | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` --> PYI061.py:20:31 @@ -90,8 +95,9 @@ help: Replace with `None` 20 + def func5(arg1: None): 21 | ... | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` --> PYI061.py:26:5 | 24 | def func6(arg1: Literal[ @@ -157,340 +163,373 @@ help: Replace with `None` 42 + def func9(arg1: Union[None, None]): 43 | ... | +note: This is an unsafe fix and may change runtime behavior + +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:53:33 + | +51 | # Regression test for https://github.com/astral-sh/ruff/issues/20729. +52 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. +53 | options = Literal["foo", "bar", None] + | ^^^^ + | +help: Replace with `Literal[...] | None` + | +52 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. + - options = Literal["foo", "bar", None] +53 + options = Literal["foo", "bar"] | None +54 | + | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:52:9 + --> PYI061.py:57:9 | -51 | # From flake8-pyi -52 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +56 | # From flake8-pyi +57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" | ^^^^ -53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | help: Replace with `None` | -51 | # From flake8-pyi +56 | # From flake8-pyi - Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" -52 + None # Y061 None inside "Literal[]" expression. Replace with "None" -53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +57 + None # Y061 None inside "Literal[]" expression. Replace with "None" +58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:53:15 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:58:15 | -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" +56 | # From flake8-pyi +57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | ^^^^ -54 | -55 | ### +59 | +60 | ### | help: Replace with `Literal[...] | None` | -52 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" -53 + Literal[True] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" -54 | +58 + Literal[True] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +59 | | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:62:9 + --> PYI061.py:67:9 | -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" +65 | # If Y061 and Y062 both apply, but all the duplicate members are None, +66 | # only emit Y061... +67 | 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" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | help: Replace with `None` | -61 | # only emit Y061... +66 | # only emit Y061... - Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -62 + 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" +67 + None # Y061 None inside "Literal[]" expression. Replace with "None" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:62:15 + --> PYI061.py:67:15 | -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" +65 | # If Y061 and Y062 both apply, but all the duplicate members are None, +66 | # only emit Y061... +67 | 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" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | help: Replace with `None` | -61 | # only emit Y061... +66 | # only emit Y061... - Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -62 + 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" +67 + None # Y061 None inside "Literal[]" expression. Replace with "None" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:63:12 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:68:12 | -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" +66 | # only emit Y061... +67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | ^^^^ -64 | -65 | # ... but if Y061 and Y062 both apply +69 | +70 | # ... but if Y061 and Y062 both apply | help: Replace with `Literal[...] | None` | -62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -63 + Literal[1, "foo"] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -64 | +68 + Literal[1, "foo"] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +69 | | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:63:25 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:68:25 | -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" +66 | # only emit Y061... +67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | ^^^^ -64 | -65 | # ... but if Y061 and Y062 both apply +69 | +70 | # ... but if Y061 and Y062 both apply | help: Replace with `Literal[...] | None` | -62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -63 + Literal[1, "foo"] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -64 | +68 + Literal[1, "foo"] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +69 | | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:68:9 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:73:9 | -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" +71 | # and there are no None members in the Literal[] slice, +72 | # only emit Y062: +73 | Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" | ^^^^ | help: Replace with `Literal[...] | None` | -67 | # only emit Y062: +72 | # only emit Y062: - Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" -68 + Literal[True, True] | None # Y062 Duplicate "Literal[]" member "True" -69 | +73 + Literal[True, True] | None # Y062 Duplicate "Literal[]" member "True" +74 | | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:68:21 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:73:21 | -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" +71 | # and there are no None members in the Literal[] slice, +72 | # only emit Y062: +73 | Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" | ^^^^ | help: Replace with `Literal[...] | None` | -67 | # only emit Y062: +72 | # only emit Y062: - Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" -68 + Literal[True, True] | None # Y062 Duplicate "Literal[]" member "True" -69 | +73 + Literal[True, True] | None # Y062 Duplicate "Literal[]" member "True" +74 | | +note: This is an unsafe fix and may change runtime behavior PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:72:12 + --> PYI061.py:77:12 | -71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 -72 | x: Literal[None] | None +76 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +77 | x: Literal[None] | None | ^^^^ -73 | y: None | Literal[None] -74 | z: Union[Literal[None], None] +78 | y: None | Literal[None] +79 | z: Union[Literal[None], None] | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:73:19 + --> PYI061.py:78:19 | -71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 -72 | x: Literal[None] | None -73 | y: None | Literal[None] +76 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +77 | x: Literal[None] | None +78 | y: None | Literal[None] | ^^^^ -74 | z: Union[Literal[None], None] +79 | z: Union[Literal[None], None] | help: Replace with `None` PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:74:18 + --> PYI061.py:79:18 | -72 | x: Literal[None] | None -73 | y: None | Literal[None] -74 | z: Union[Literal[None], None] +77 | x: Literal[None] | None +78 | y: None | Literal[None] +79 | z: Union[Literal[None], None] | ^^^^ -75 | -76 | a: int | Literal[None] | None +80 | +81 | a: int | Literal[None] | None | help: Replace with `None` | -73 | y: None | Literal[None] +78 | y: None | Literal[None] - z: Union[Literal[None], None] -74 + z: Union[None, None] -75 | +79 + z: Union[None, None] +80 | | +note: This is an unsafe fix and may change runtime behavior PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:76:18 + --> PYI061.py:81:18 | -74 | z: Union[Literal[None], None] -75 | -76 | a: int | Literal[None] | None +79 | z: Union[Literal[None], None] +80 | +81 | a: int | Literal[None] | None | ^^^^ -77 | b: None | Literal[None] | None -78 | c: (None | Literal[None]) | None +82 | b: None | Literal[None] | None +83 | c: (None | Literal[None]) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:77:19 + --> PYI061.py:82:19 | -76 | a: int | Literal[None] | None -77 | b: None | Literal[None] | None +81 | a: int | Literal[None] | None +82 | b: None | Literal[None] | None | ^^^^ -78 | c: (None | Literal[None]) | None -79 | d: None | (Literal[None] | None) +83 | c: (None | Literal[None]) | None +84 | d: None | (Literal[None] | None) | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:78:20 + --> PYI061.py:83:20 | -76 | a: int | Literal[None] | None -77 | b: None | Literal[None] | None -78 | c: (None | Literal[None]) | None +81 | a: int | Literal[None] | None +82 | b: None | Literal[None] | None +83 | c: (None | Literal[None]) | None | ^^^^ -79 | d: None | (Literal[None] | None) -80 | e: None | ((None | Literal[None]) | None) | None +84 | d: None | (Literal[None] | None) +85 | e: None | ((None | Literal[None]) | None) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:79:20 + --> PYI061.py:84:20 | -77 | b: None | Literal[None] | None -78 | c: (None | Literal[None]) | None -79 | d: None | (Literal[None] | None) +82 | b: None | Literal[None] | None +83 | c: (None | Literal[None]) | None +84 | d: None | (Literal[None] | None) | ^^^^ -80 | e: None | ((None | Literal[None]) | None) | None +85 | e: None | ((None | Literal[None]) | None) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:80:28 + --> PYI061.py:85:28 | -78 | c: (None | Literal[None]) | None -79 | d: None | (Literal[None] | None) -80 | e: None | ((None | Literal[None]) | None) | None +83 | c: (None | Literal[None]) | None +84 | d: None | (Literal[None] | None) +85 | e: None | ((None | Literal[None]) | None) | None | ^^^^ -81 | -82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +86 | +87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) | help: Replace with `None` -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:83:18 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:88:18 | -82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) -83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ | ^^^^ -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | help: Replace with `Literal[...] | None` | -82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) - print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -83 + print((Literal[1] | None).__dict__) # Should become (Literal[1] | None).__dict__ -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +88 + print((Literal[1] | None).__dict__) # Should become (Literal[1] | None).__dict__ +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:84:18 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:89:18 | -82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) -83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() | ^^^^ -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | help: Replace with `Literal[...] | None` | -83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ - print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -84 + print((Literal[1] | None).method()) # Should become (Literal[1] | None).method() -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +89 + print((Literal[1] | None).method()) # Should become (Literal[1] | None).method() +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:85:18 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:90:18 | -83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | ^^^^ -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | help: Replace with `Literal[...] | None` | -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() - print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -85 + print((Literal[1] | None)[0]) # Should become (Literal[1] | None)[0] -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +90 + print((Literal[1] | None)[0]) # Should become (Literal[1] | None)[0] +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:86:18 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:91:18 | -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | ^^^^ -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | help: Replace with `Literal[...] | None` | -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] - print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -86 + print((Literal[1] | None) + 1) # Should become (Literal[1] | None) + 1 -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +91 + print((Literal[1] | None) + 1) # Should become (Literal[1] | None) + 1 +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:87:18 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:92:18 | -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | ^^^^ -88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | help: Replace with `Literal[...] | None` | -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 - print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -87 + print((Literal[1] | None) * 2) # Should become (Literal[1] | None) * 2 -88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +92 + print((Literal[1] | None) * 2) # Should become (Literal[1] | None) * 2 +93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:88:19 +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:93:19 | -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | ^^^^ | help: Replace with `Literal[...] | None` | -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 - print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ -88 + print((Literal[1] | None).__dict__) # Should become ((Literal[1] | None)).__dict__ +93 + print((Literal[1] | None).__dict__) # Should become ((Literal[1] | None)).__dict__ | +note: This is an unsafe fix and may change runtime behavior 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 64718817406fe..2dc7d76786916 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 @@ -43,7 +43,7 @@ help: Replace with `None` 11 | | -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` --> PYI061.pyi:13:30 | 13 | def func4(arg1: Literal[int, None, float]): ... @@ -85,7 +85,7 @@ help: Replace with `None` 17 | | -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` --> PYI061.pyi:21:5 | 19 | def func6(arg1: Literal[ @@ -165,7 +165,7 @@ help: Replace with `None` 43 | Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` --> PYI061.pyi:43:15 | 41 | # From flake8-pyi diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.py.snap index 993ed95ea88d5..28913ca10438e 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.py.snap @@ -15,6 +15,7 @@ help: Replace with `None` 4 + def func1(arg1: None): 5 | ... | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` --> PYI061.py:8:25 @@ -30,6 +31,7 @@ help: Replace with `None` 8 + def func2(arg1: None | int): 9 | ... | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` --> PYI061.py:12:24 @@ -45,8 +47,9 @@ help: Replace with `None` 12 + def func3() -> None: 13 | ... | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` --> PYI061.py:16:30 | 16 | def func4(arg1: Literal[int, None, float]): @@ -64,6 +67,7 @@ help: Replace with `Optional[Literal[...]]` 16 + def func4(arg1: Optional[Literal[int, float]]): 17 | ... | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` --> PYI061.py:20:25 @@ -79,6 +83,7 @@ help: Replace with `None` 20 + def func5(arg1: None): 21 | ... | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` --> PYI061.py:20:31 @@ -94,8 +99,9 @@ help: Replace with `None` 20 + def func5(arg1: None): 21 | ... | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` --> PYI061.py:26:5 | 24 | def func6(arg1: Literal[ @@ -165,32 +171,55 @@ help: Replace with `None` 42 + def func9(arg1: Union[None, None]): 43 | ... | +note: This is an unsafe fix and may change runtime behavior + +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:53:33 + | +51 | # Regression test for https://github.com/astral-sh/ruff/issues/20729. +52 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. +53 | options = Literal["foo", "bar", None] + | ^^^^ + | +help: Replace with `Optional[Literal[...]]` + | + - from typing import Literal, Union +1 + from typing import Literal, Union, Optional +2 | +-------------------------------------------------------------------------------- +52 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. + - options = Literal["foo", "bar", None] +53 + options = Optional[Literal["foo", "bar"]] +54 | + | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:52:9 + --> PYI061.py:57:9 | -51 | # From flake8-pyi -52 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +56 | # From flake8-pyi +57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" | ^^^^ -53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | help: Replace with `None` | -51 | # From flake8-pyi +56 | # From flake8-pyi - Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" -52 + None # Y061 None inside "Literal[]" expression. Replace with "None" -53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +57 + None # Y061 None inside "Literal[]" expression. Replace with "None" +58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:53:15 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:58:15 | -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" +56 | # From flake8-pyi +57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | ^^^^ -54 | -55 | ### +59 | +60 | ### | help: Replace with `Optional[Literal[...]]` | @@ -198,55 +227,58 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -52 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" -53 + Optional[Literal[True]] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" -54 | +58 + Optional[Literal[True]] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +59 | | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:62:9 + --> PYI061.py:67:9 | -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" +65 | # If Y061 and Y062 both apply, but all the duplicate members are None, +66 | # only emit Y061... +67 | 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" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | help: Replace with `None` | -61 | # only emit Y061... +66 | # only emit Y061... - Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -62 + 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" +67 + None # Y061 None inside "Literal[]" expression. Replace with "None" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | +note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:62:15 + --> PYI061.py:67:15 | -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" +65 | # If Y061 and Y062 both apply, but all the duplicate members are None, +66 | # only emit Y061... +67 | 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" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | help: Replace with `None` | -61 | # only emit Y061... +66 | # only emit Y061... - Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -62 + 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" +67 + None # Y061 None inside "Literal[]" expression. Replace with "None" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:63:12 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:68:12 | -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" +66 | # only emit Y061... +67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | ^^^^ -64 | -65 | # ... but if Y061 and Y062 both apply +69 | +70 | # ... but if Y061 and Y062 both apply | help: Replace with `Optional[Literal[...]]` | @@ -254,21 +286,22 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -63 + Optional[Literal[1, "foo"]] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -64 | +68 + Optional[Literal[1, "foo"]] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +69 | | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:63:25 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:68:25 | -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" +66 | # only emit Y061... +67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | ^^^^ -64 | -65 | # ... but if Y061 and Y062 both apply +69 | +70 | # ... but if Y061 and Y062 both apply | help: Replace with `Optional[Literal[...]]` | @@ -276,18 +309,19 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -63 + Optional[Literal[1, "foo"]] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -64 | +68 + Optional[Literal[1, "foo"]] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +69 | | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:68:9 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:73:9 | -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" +71 | # and there are no None members in the Literal[] slice, +72 | # only emit Y062: +73 | Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" | ^^^^ | help: Replace with `Optional[Literal[...]]` @@ -296,18 +330,19 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -67 | # only emit Y062: +72 | # only emit Y062: - Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" -68 + Optional[Literal[True, True]] # Y062 Duplicate "Literal[]" member "True" -69 | +73 + Optional[Literal[True, True]] # Y062 Duplicate "Literal[]" member "True" +74 | | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:68:21 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:73:21 | -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" +71 | # and there are no None members in the Literal[] slice, +72 | # only emit Y062: +73 | Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" | ^^^^ | help: Replace with `Optional[Literal[...]]` @@ -316,118 +351,120 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -67 | # only emit Y062: +72 | # only emit Y062: - Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" -68 + Optional[Literal[True, True]] # Y062 Duplicate "Literal[]" member "True" -69 | +73 + Optional[Literal[True, True]] # Y062 Duplicate "Literal[]" member "True" +74 | | +note: This is an unsafe fix and may change runtime behavior PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:72:12 + --> PYI061.py:77:12 | -71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 -72 | x: Literal[None] | None +76 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +77 | x: Literal[None] | None | ^^^^ -73 | y: None | Literal[None] -74 | z: Union[Literal[None], None] +78 | y: None | Literal[None] +79 | z: Union[Literal[None], None] | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:73:19 + --> PYI061.py:78:19 | -71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 -72 | x: Literal[None] | None -73 | y: None | Literal[None] +76 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +77 | x: Literal[None] | None +78 | y: None | Literal[None] | ^^^^ -74 | z: Union[Literal[None], None] +79 | z: Union[Literal[None], None] | help: Replace with `None` PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:74:18 + --> PYI061.py:79:18 | -72 | x: Literal[None] | None -73 | y: None | Literal[None] -74 | z: Union[Literal[None], None] +77 | x: Literal[None] | None +78 | y: None | Literal[None] +79 | z: Union[Literal[None], None] | ^^^^ -75 | -76 | a: int | Literal[None] | None +80 | +81 | a: int | Literal[None] | None | help: Replace with `None` | -73 | y: None | Literal[None] +78 | y: None | Literal[None] - z: Union[Literal[None], None] -74 + z: Union[None, None] -75 | +79 + z: Union[None, None] +80 | | +note: This is an unsafe fix and may change runtime behavior PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:76:18 + --> PYI061.py:81:18 | -74 | z: Union[Literal[None], None] -75 | -76 | a: int | Literal[None] | None +79 | z: Union[Literal[None], None] +80 | +81 | a: int | Literal[None] | None | ^^^^ -77 | b: None | Literal[None] | None -78 | c: (None | Literal[None]) | None +82 | b: None | Literal[None] | None +83 | c: (None | Literal[None]) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:77:19 + --> PYI061.py:82:19 | -76 | a: int | Literal[None] | None -77 | b: None | Literal[None] | None +81 | a: int | Literal[None] | None +82 | b: None | Literal[None] | None | ^^^^ -78 | c: (None | Literal[None]) | None -79 | d: None | (Literal[None] | None) +83 | c: (None | Literal[None]) | None +84 | d: None | (Literal[None] | None) | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:78:20 + --> PYI061.py:83:20 | -76 | a: int | Literal[None] | None -77 | b: None | Literal[None] | None -78 | c: (None | Literal[None]) | None +81 | a: int | Literal[None] | None +82 | b: None | Literal[None] | None +83 | c: (None | Literal[None]) | None | ^^^^ -79 | d: None | (Literal[None] | None) -80 | e: None | ((None | Literal[None]) | None) | None +84 | d: None | (Literal[None] | None) +85 | e: None | ((None | Literal[None]) | None) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:79:20 + --> PYI061.py:84:20 | -77 | b: None | Literal[None] | None -78 | c: (None | Literal[None]) | None -79 | d: None | (Literal[None] | None) +82 | b: None | Literal[None] | None +83 | c: (None | Literal[None]) | None +84 | d: None | (Literal[None] | None) | ^^^^ -80 | e: None | ((None | Literal[None]) | None) | None +85 | e: None | ((None | Literal[None]) | None) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:80:28 + --> PYI061.py:85:28 | -78 | c: (None | Literal[None]) | None -79 | d: None | (Literal[None] | None) -80 | e: None | ((None | Literal[None]) | None) | None +83 | c: (None | Literal[None]) | None +84 | d: None | (Literal[None] | None) +85 | e: None | ((None | Literal[None]) | None) | None | ^^^^ -81 | -82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +86 | +87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) | help: Replace with `None` -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:83:18 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:88:18 | -82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) -83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ | ^^^^ -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | help: Replace with `Optional[Literal[...]]` | @@ -435,21 +472,22 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) - print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -83 + print(Optional[Literal[1]].__dict__) # Should become (Literal[1] | None).__dict__ -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +88 + print(Optional[Literal[1]].__dict__) # Should become (Literal[1] | None).__dict__ +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:84:18 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:89:18 | -82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) -83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() | ^^^^ -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | help: Replace with `Optional[Literal[...]]` | @@ -457,21 +495,22 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ - print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -84 + print(Optional[Literal[1]].method()) # Should become (Literal[1] | None).method() -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +89 + print(Optional[Literal[1]].method()) # Should become (Literal[1] | None).method() +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:85:18 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:90:18 | -83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | ^^^^ -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | help: Replace with `Optional[Literal[...]]` | @@ -479,21 +518,22 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() - print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -85 + print(Optional[Literal[1]][0]) # Should become (Literal[1] | None)[0] -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +90 + print(Optional[Literal[1]][0]) # Should become (Literal[1] | None)[0] +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:86:18 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:91:18 | -84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | ^^^^ -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | help: Replace with `Optional[Literal[...]]` | @@ -501,20 +541,21 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] - print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -86 + print(Optional[Literal[1]] + 1) # Should become (Literal[1] | None) + 1 -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +91 + print(Optional[Literal[1]] + 1) # Should become (Literal[1] | None) + 1 +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:87:18 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:92:18 | -85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | ^^^^ -88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | help: Replace with `Optional[Literal[...]]` | @@ -522,18 +563,19 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 - print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -87 + print(Optional[Literal[1]] * 2) # Should become (Literal[1] | None) * 2 -88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +92 + print(Optional[Literal[1]] * 2) # Should become (Literal[1] | None) * 2 +93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | +note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:88:19 +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:93:19 | -86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | ^^^^ | help: Replace with `Optional[Literal[...]]` @@ -542,7 +584,8 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 - print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ -88 + print((Optional[Literal[1]]).__dict__) # Should become ((Literal[1] | None)).__dict__ +93 + print((Optional[Literal[1]]).__dict__) # Should become ((Literal[1] | None)).__dict__ | +note: This is an unsafe fix and may change runtime behavior diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.pyi.snap index 64718817406fe..2dc7d76786916 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.pyi.snap @@ -43,7 +43,7 @@ help: Replace with `None` 11 | | -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` --> PYI061.pyi:13:30 | 13 | def func4(arg1: Literal[int, None, float]): ... @@ -85,7 +85,7 @@ help: Replace with `None` 17 | | -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` --> PYI061.pyi:21:5 | 19 | def func6(arg1: Literal[ @@ -165,7 +165,7 @@ help: Replace with `None` 43 | Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` --> PYI061.pyi:43:15 | 41 | # From flake8-pyi From 2d3b585ad243c6da5284b9cdc7ff4122aaf523e1 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 8 Jul 2026 16:46:40 -0400 Subject: [PATCH 2/3] add --unsafe-fixes to retain cli snapshot --- crates/ruff/tests/cli/lint.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ruff/tests/cli/lint.rs b/crates/ruff/tests/cli/lint.rs index 65361b4f3568a..8e4b23aec639c 100644 --- a/crates/ruff/tests/cli/lint.rs +++ b/crates/ruff/tests/cli/lint.rs @@ -3645,6 +3645,7 @@ d: Literal[None,] | Literal[None] .args(["--stdin-filename", "test.py"]) .arg("--preview") .arg("--diff") + .arg("--unsafe-fixes") .arg("-") .pass_stdin(snippet), @" success: false From 0980cd28e9e2f369854f75be9e37aad215744f51 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 8 Jul 2026 17:36:34 -0400 Subject: [PATCH 3/3] move new test case to eof --- .../test/fixtures/flake8_pyi/PYI061.py | 9 +- ...__flake8_pyi__tests__PYI061_PYI061.py.snap | 356 ++++++++--------- ...ke8_pyi__tests__py38_PYI061_PYI061.py.snap | 364 +++++++++--------- 3 files changed, 366 insertions(+), 363 deletions(-) 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 87611ae5cc336..40e18a5b4b23b 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py @@ -48,11 +48,6 @@ def good_func(arg1: Literal[int] | None): ... -# Regression test for https://github.com/astral-sh/ruff/issues/20729. -# Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. -options = Literal["foo", "bar", None] - - # From flake8-pyi Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" @@ -91,3 +86,7 @@ def good_func(arg1: Literal[int] | None): print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ + +# Regression test for https://github.com/astral-sh/ruff/issues/20729. +# Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. +options = Literal["foo", "bar", None] 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 2db442327b91c..95ccdb7063d92 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 @@ -165,371 +165,373 @@ help: Replace with `None` | note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:53:33 - | -51 | # Regression test for https://github.com/astral-sh/ruff/issues/20729. -52 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. -53 | options = Literal["foo", "bar", None] - | ^^^^ - | -help: Replace with `Literal[...] | None` - | -52 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. - - options = Literal["foo", "bar", None] -53 + options = Literal["foo", "bar"] | None -54 | - | -note: This is an unsafe fix and may change runtime behavior - PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:57:9 + --> PYI061.py:52:9 | -56 | # From flake8-pyi -57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +51 | # From flake8-pyi +52 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" | ^^^^ -58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | help: Replace with `None` | -56 | # From flake8-pyi +51 | # From flake8-pyi - Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" -57 + None # Y061 None inside "Literal[]" expression. Replace with "None" -58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +52 + None # Y061 None inside "Literal[]" expression. Replace with "None" +53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:58:15 + --> PYI061.py:53:15 | -56 | # From flake8-pyi -57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" -58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | 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" | ^^^^ -59 | -60 | ### +54 | +55 | ### | help: Replace with `Literal[...] | None` | -57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +52 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" -58 + Literal[True] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" -59 | +53 + Literal[True] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +54 | | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:67:9 + --> PYI061.py:62:9 | -65 | # If Y061 and Y062 both apply, but all the duplicate members are None, -66 | # only emit Y061... -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace 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" | ^^^^ -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +63 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | help: Replace with `None` | -66 | # only emit Y061... +61 | # only emit Y061... - Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -67 + None # Y061 None inside "Literal[]" expression. Replace with "None" -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +62 + 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" | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:67:15 + --> PYI061.py:62:15 | -65 | # If Y061 and Y062 both apply, but all the duplicate members are None, -66 | # only emit Y061... -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace 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" | ^^^^ -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +63 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | help: Replace with `None` | -66 | # only emit Y061... +61 | # only emit Y061... - Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -67 + None # Y061 None inside "Literal[]" expression. Replace with "None" -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +62 + 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" | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:68:12 + --> PYI061.py:63:12 | -66 | # only emit Y061... -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | 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" | ^^^^ -69 | -70 | # ... but if Y061 and Y062 both apply +64 | +65 | # ... but if Y061 and Y062 both apply | help: Replace with `Literal[...] | None` | -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -68 + Literal[1, "foo"] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -69 | +63 + Literal[1, "foo"] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +64 | | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:68:25 + --> PYI061.py:63:25 | -66 | # only emit Y061... -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | 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" | ^^^^ -69 | -70 | # ... but if Y061 and Y062 both apply +64 | +65 | # ... but if Y061 and Y062 both apply | help: Replace with `Literal[...] | None` | -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -68 + Literal[1, "foo"] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -69 | +63 + Literal[1, "foo"] | None # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +64 | | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:73:9 + --> PYI061.py:68:9 | -71 | # and there are no None members in the Literal[] slice, -72 | # only emit Y062: -73 | 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" | ^^^^ | help: Replace with `Literal[...] | None` | -72 | # only emit Y062: +67 | # only emit Y062: - Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" -73 + Literal[True, True] | None # Y062 Duplicate "Literal[]" member "True" -74 | +68 + Literal[True, True] | None # Y062 Duplicate "Literal[]" member "True" +69 | | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:73:21 + --> PYI061.py:68:21 | -71 | # and there are no None members in the Literal[] slice, -72 | # only emit Y062: -73 | 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" | ^^^^ | help: Replace with `Literal[...] | None` | -72 | # only emit Y062: +67 | # only emit Y062: - Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" -73 + Literal[True, True] | None # Y062 Duplicate "Literal[]" member "True" -74 | +68 + Literal[True, True] | None # Y062 Duplicate "Literal[]" member "True" +69 | | note: This is an unsafe fix and may change runtime behavior PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:77:12 + --> PYI061.py:72:12 | -76 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 -77 | x: Literal[None] | None +71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +72 | x: Literal[None] | None | ^^^^ -78 | y: None | Literal[None] -79 | z: Union[Literal[None], None] +73 | y: None | Literal[None] +74 | z: Union[Literal[None], None] | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:78:19 + --> PYI061.py:73:19 | -76 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 -77 | x: Literal[None] | None -78 | y: None | Literal[None] +71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +72 | x: Literal[None] | None +73 | y: None | Literal[None] | ^^^^ -79 | z: Union[Literal[None], None] +74 | z: Union[Literal[None], None] | help: Replace with `None` PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:79:18 + --> PYI061.py:74:18 | -77 | x: Literal[None] | None -78 | y: None | Literal[None] -79 | z: Union[Literal[None], None] +72 | x: Literal[None] | None +73 | y: None | Literal[None] +74 | z: Union[Literal[None], None] | ^^^^ -80 | -81 | a: int | Literal[None] | None +75 | +76 | a: int | Literal[None] | None | help: Replace with `None` | -78 | y: None | Literal[None] +73 | y: None | Literal[None] - z: Union[Literal[None], None] -79 + z: Union[None, None] -80 | +74 + z: Union[None, None] +75 | | note: This is an unsafe fix and may change runtime behavior PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:81:18 + --> PYI061.py:76:18 | -79 | z: Union[Literal[None], None] -80 | -81 | a: int | Literal[None] | None +74 | z: Union[Literal[None], None] +75 | +76 | a: int | Literal[None] | None | ^^^^ -82 | b: None | Literal[None] | None -83 | c: (None | Literal[None]) | None +77 | b: None | Literal[None] | None +78 | c: (None | Literal[None]) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:82:19 + --> PYI061.py:77:19 | -81 | a: int | Literal[None] | None -82 | b: None | Literal[None] | None +76 | a: int | Literal[None] | None +77 | b: None | Literal[None] | None | ^^^^ -83 | c: (None | Literal[None]) | None -84 | d: None | (Literal[None] | None) +78 | c: (None | Literal[None]) | None +79 | d: None | (Literal[None] | None) | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:83:20 + --> PYI061.py:78:20 | -81 | a: int | Literal[None] | None -82 | b: None | Literal[None] | None -83 | c: (None | Literal[None]) | None +76 | a: int | Literal[None] | None +77 | b: None | Literal[None] | None +78 | c: (None | Literal[None]) | None | ^^^^ -84 | d: None | (Literal[None] | None) -85 | e: None | ((None | Literal[None]) | None) | None +79 | d: None | (Literal[None] | None) +80 | e: None | ((None | Literal[None]) | None) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:84:20 + --> PYI061.py:79:20 | -82 | b: None | Literal[None] | None -83 | c: (None | Literal[None]) | None -84 | d: None | (Literal[None] | None) +77 | b: None | Literal[None] | None +78 | c: (None | Literal[None]) | None +79 | d: None | (Literal[None] | None) | ^^^^ -85 | e: None | ((None | Literal[None]) | None) | None +80 | e: None | ((None | Literal[None]) | None) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:85:28 + --> PYI061.py:80:28 | -83 | c: (None | Literal[None]) | None -84 | d: None | (Literal[None] | None) -85 | e: None | ((None | Literal[None]) | None) | None +78 | c: (None | Literal[None]) | None +79 | d: None | (Literal[None] | None) +80 | e: None | ((None | Literal[None]) | None) | None | ^^^^ -86 | -87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +81 | +82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) | help: Replace with `None` PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:88:18 + --> PYI061.py:83:18 | -87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) -88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ | ^^^^ -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | help: Replace with `Literal[...] | None` | -87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) - print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -88 + print((Literal[1] | None).__dict__) # Should become (Literal[1] | None).__dict__ -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +83 + print((Literal[1] | None).__dict__) # Should become (Literal[1] | None).__dict__ +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:89:18 + --> PYI061.py:84:18 | -87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) -88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() | ^^^^ -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | help: Replace with `Literal[...] | None` | -88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ - print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -89 + print((Literal[1] | None).method()) # Should become (Literal[1] | None).method() -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +84 + print((Literal[1] | None).method()) # Should become (Literal[1] | None).method() +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:90:18 + --> PYI061.py:85:18 | -88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | ^^^^ -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | help: Replace with `Literal[...] | None` | -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() - print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -90 + print((Literal[1] | None)[0]) # Should become (Literal[1] | None)[0] -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +85 + print((Literal[1] | None)[0]) # Should become (Literal[1] | None)[0] +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:91:18 + --> PYI061.py:86:18 | -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | ^^^^ -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | help: Replace with `Literal[...] | None` | -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] - print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -91 + print((Literal[1] | None) + 1) # Should become (Literal[1] | None) + 1 -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +86 + print((Literal[1] | None) + 1) # Should become (Literal[1] | None) + 1 +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:92:18 + --> PYI061.py:87:18 | -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | ^^^^ -93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | help: Replace with `Literal[...] | None` | -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 - print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -92 + print((Literal[1] | None) * 2) # Should become (Literal[1] | None) * 2 -93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +87 + print((Literal[1] | None) * 2) # Should become (Literal[1] | None) * 2 +88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` - --> PYI061.py:93:19 + --> PYI061.py:88:19 | -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | ^^^^ +89 | +90 | # Regression test for https://github.com/astral-sh/ruff/issues/20729. | help: Replace with `Literal[...] | None` | -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 - print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ -93 + print((Literal[1] | None).__dict__) # Should become ((Literal[1] | None)).__dict__ +88 + print((Literal[1] | None).__dict__) # Should become ((Literal[1] | None)).__dict__ +89 | + | +note: This is an unsafe fix and may change runtime behavior + +PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` + --> PYI061.py:92:33 + | +90 | # Regression test for https://github.com/astral-sh/ruff/issues/20729. +91 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. +92 | options = Literal["foo", "bar", None] + | ^^^^ + | +help: Replace with `Literal[...] | None` + | +91 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. + - options = Literal["foo", "bar", None] +92 + options = Literal["foo", "bar"] | None | note: This is an unsafe fix and may change runtime behavior diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.py.snap index 28913ca10438e..ea86e80b20ef2 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.py.snap @@ -173,53 +173,32 @@ help: Replace with `None` | note: This is an unsafe fix and may change runtime behavior -PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:53:33 - | -51 | # Regression test for https://github.com/astral-sh/ruff/issues/20729. -52 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. -53 | options = Literal["foo", "bar", None] - | ^^^^ - | -help: Replace with `Optional[Literal[...]]` - | - - from typing import Literal, Union -1 + from typing import Literal, Union, Optional -2 | --------------------------------------------------------------------------------- -52 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. - - options = Literal["foo", "bar", None] -53 + options = Optional[Literal["foo", "bar"]] -54 | - | -note: This is an unsafe fix and may change runtime behavior - PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:57:9 + --> PYI061.py:52:9 | -56 | # From flake8-pyi -57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +51 | # From flake8-pyi +52 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" | ^^^^ -58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | help: Replace with `None` | -56 | # From flake8-pyi +51 | # From flake8-pyi - Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" -57 + None # Y061 None inside "Literal[]" expression. Replace with "None" -58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +52 + None # Y061 None inside "Literal[]" expression. Replace with "None" +53 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:58:15 + --> PYI061.py:53:15 | -56 | # From flake8-pyi -57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" -58 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | 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" | ^^^^ -59 | -60 | ### +54 | +55 | ### | help: Replace with `Optional[Literal[...]]` | @@ -227,58 +206,58 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -57 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +52 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" -58 + Optional[Literal[True]] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" -59 | +53 + Optional[Literal[True]] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +54 | | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:67:9 + --> PYI061.py:62:9 | -65 | # If Y061 and Y062 both apply, but all the duplicate members are None, -66 | # only emit Y061... -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace 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" | ^^^^ -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +63 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | help: Replace with `None` | -66 | # only emit Y061... +61 | # only emit Y061... - Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -67 + None # Y061 None inside "Literal[]" expression. Replace with "None" -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +62 + 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" | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:67:15 + --> PYI061.py:62:15 | -65 | # If Y061 and Y062 both apply, but all the duplicate members are None, -66 | # only emit Y061... -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace 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" | ^^^^ -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +63 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" | help: Replace with `None` | -66 | # only emit Y061... +61 | # only emit Y061... - Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -67 + None # Y061 None inside "Literal[]" expression. Replace with "None" -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +62 + 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" | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:68:12 + --> PYI061.py:63:12 | -66 | # only emit Y061... -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | 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" | ^^^^ -69 | -70 | # ... but if Y061 and Y062 both apply +64 | +65 | # ... but if Y061 and Y062 both apply | help: Replace with `Optional[Literal[...]]` | @@ -286,22 +265,22 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -68 + Optional[Literal[1, "foo"]] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -69 | +63 + Optional[Literal[1, "foo"]] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +64 | | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:68:25 + --> PYI061.py:63:25 | -66 | # only emit Y061... -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" -68 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | 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" | ^^^^ -69 | -70 | # ... but if Y061 and Y062 both apply +64 | +65 | # ... but if Y061 and Y062 both apply | help: Replace with `Optional[Literal[...]]` | @@ -309,19 +288,19 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -67 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" +62 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" - Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -68 + Optional[Literal[1, "foo"]] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" -69 | +63 + Optional[Literal[1, "foo"]] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" +64 | | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:73:9 + --> PYI061.py:68:9 | -71 | # and there are no None members in the Literal[] slice, -72 | # only emit Y062: -73 | 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" | ^^^^ | help: Replace with `Optional[Literal[...]]` @@ -330,19 +309,19 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -72 | # only emit Y062: +67 | # only emit Y062: - Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" -73 + Optional[Literal[True, True]] # Y062 Duplicate "Literal[]" member "True" -74 | +68 + Optional[Literal[True, True]] # Y062 Duplicate "Literal[]" member "True" +69 | | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:73:21 + --> PYI061.py:68:21 | -71 | # and there are no None members in the Literal[] slice, -72 | # only emit Y062: -73 | 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" | ^^^^ | help: Replace with `Optional[Literal[...]]` @@ -351,120 +330,120 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -72 | # only emit Y062: +67 | # only emit Y062: - Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" -73 + Optional[Literal[True, True]] # Y062 Duplicate "Literal[]" member "True" -74 | +68 + Optional[Literal[True, True]] # Y062 Duplicate "Literal[]" member "True" +69 | | note: This is an unsafe fix and may change runtime behavior PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:77:12 + --> PYI061.py:72:12 | -76 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 -77 | x: Literal[None] | None +71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +72 | x: Literal[None] | None | ^^^^ -78 | y: None | Literal[None] -79 | z: Union[Literal[None], None] +73 | y: None | Literal[None] +74 | z: Union[Literal[None], None] | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:78:19 + --> PYI061.py:73:19 | -76 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 -77 | x: Literal[None] | None -78 | y: None | Literal[None] +71 | # Regression tests for https://github.com/astral-sh/ruff/issues/14567 +72 | x: Literal[None] | None +73 | y: None | Literal[None] | ^^^^ -79 | z: Union[Literal[None], None] +74 | z: Union[Literal[None], None] | help: Replace with `None` PYI061 [*] Use `None` rather than `Literal[None]` - --> PYI061.py:79:18 + --> PYI061.py:74:18 | -77 | x: Literal[None] | None -78 | y: None | Literal[None] -79 | z: Union[Literal[None], None] +72 | x: Literal[None] | None +73 | y: None | Literal[None] +74 | z: Union[Literal[None], None] | ^^^^ -80 | -81 | a: int | Literal[None] | None +75 | +76 | a: int | Literal[None] | None | help: Replace with `None` | -78 | y: None | Literal[None] +73 | y: None | Literal[None] - z: Union[Literal[None], None] -79 + z: Union[None, None] -80 | +74 + z: Union[None, None] +75 | | note: This is an unsafe fix and may change runtime behavior PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:81:18 + --> PYI061.py:76:18 | -79 | z: Union[Literal[None], None] -80 | -81 | a: int | Literal[None] | None +74 | z: Union[Literal[None], None] +75 | +76 | a: int | Literal[None] | None | ^^^^ -82 | b: None | Literal[None] | None -83 | c: (None | Literal[None]) | None +77 | b: None | Literal[None] | None +78 | c: (None | Literal[None]) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:82:19 + --> PYI061.py:77:19 | -81 | a: int | Literal[None] | None -82 | b: None | Literal[None] | None +76 | a: int | Literal[None] | None +77 | b: None | Literal[None] | None | ^^^^ -83 | c: (None | Literal[None]) | None -84 | d: None | (Literal[None] | None) +78 | c: (None | Literal[None]) | None +79 | d: None | (Literal[None] | None) | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:83:20 + --> PYI061.py:78:20 | -81 | a: int | Literal[None] | None -82 | b: None | Literal[None] | None -83 | c: (None | Literal[None]) | None +76 | a: int | Literal[None] | None +77 | b: None | Literal[None] | None +78 | c: (None | Literal[None]) | None | ^^^^ -84 | d: None | (Literal[None] | None) -85 | e: None | ((None | Literal[None]) | None) | None +79 | d: None | (Literal[None] | None) +80 | e: None | ((None | Literal[None]) | None) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:84:20 + --> PYI061.py:79:20 | -82 | b: None | Literal[None] | None -83 | c: (None | Literal[None]) | None -84 | d: None | (Literal[None] | None) +77 | b: None | Literal[None] | None +78 | c: (None | Literal[None]) | None +79 | d: None | (Literal[None] | None) | ^^^^ -85 | e: None | ((None | Literal[None]) | None) | None +80 | e: None | ((None | Literal[None]) | None) | None | help: Replace with `None` PYI061 Use `None` rather than `Literal[None]` - --> PYI061.py:85:28 + --> PYI061.py:80:28 | -83 | c: (None | Literal[None]) | None -84 | d: None | (Literal[None] | None) -85 | e: None | ((None | Literal[None]) | None) | None +78 | c: (None | Literal[None]) | None +79 | d: None | (Literal[None] | None) +80 | e: None | ((None | Literal[None]) | None) | None | ^^^^ -86 | -87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +81 | +82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) | help: Replace with `None` PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:88:18 + --> PYI061.py:83:18 | -87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) -88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ | ^^^^ -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | help: Replace with `Optional[Literal[...]]` | @@ -472,22 +451,22 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) - print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -88 + print(Optional[Literal[1]].__dict__) # Should become (Literal[1] | None).__dict__ -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +83 + print(Optional[Literal[1]].__dict__) # Should become (Literal[1] | None).__dict__ +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:89:18 + --> PYI061.py:84:18 | -87 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) -88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265) +83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() | ^^^^ -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | help: Replace with `Optional[Literal[...]]` | @@ -495,22 +474,22 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ - print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -89 + print(Optional[Literal[1]].method()) # Should become (Literal[1] | None).method() -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +84 + print(Optional[Literal[1]].method()) # Should become (Literal[1] | None).method() +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:90:18 + --> PYI061.py:85:18 | -88 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__ +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] | ^^^^ -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | help: Replace with `Optional[Literal[...]]` | @@ -518,22 +497,22 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() - print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -90 + print(Optional[Literal[1]][0]) # Should become (Literal[1] | None)[0] -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +85 + print(Optional[Literal[1]][0]) # Should become (Literal[1] | None)[0] +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:91:18 + --> PYI061.py:86:18 | -89 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method() +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 | ^^^^ -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | help: Replace with `Optional[Literal[...]]` | @@ -541,21 +520,21 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] - print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -91 + print(Optional[Literal[1]] + 1) # Should become (Literal[1] | None) + 1 -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +86 + print(Optional[Literal[1]] + 1) # Should become (Literal[1] | None) + 1 +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:92:18 + --> PYI061.py:87:18 | -90 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0] +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 | ^^^^ -93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | help: Replace with `Optional[Literal[...]]` | @@ -563,20 +542,22 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 - print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -92 + print(Optional[Literal[1]] * 2) # Should become (Literal[1] | None) * 2 -93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +87 + print(Optional[Literal[1]] * 2) # Should become (Literal[1] | None) * 2 +88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | note: This is an unsafe fix and may change runtime behavior PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` - --> PYI061.py:93:19 + --> PYI061.py:88:19 | -91 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 -93 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ +86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1 +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ | ^^^^ +89 | +90 | # Regression test for https://github.com/astral-sh/ruff/issues/20729. | help: Replace with `Optional[Literal[...]]` | @@ -584,8 +565,29 @@ help: Replace with `Optional[Literal[...]]` 1 + from typing import Literal, Union, Optional 2 | -------------------------------------------------------------------------------- -92 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 +87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2 - print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__ -93 + print((Optional[Literal[1]]).__dict__) # Should become ((Literal[1] | None)).__dict__ +88 + print((Optional[Literal[1]]).__dict__) # Should become ((Literal[1] | None)).__dict__ +89 | + | +note: This is an unsafe fix and may change runtime behavior + +PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` + --> PYI061.py:92:33 + | +90 | # Regression test for https://github.com/astral-sh/ruff/issues/20729. +91 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. +92 | options = Literal["foo", "bar", None] + | ^^^^ + | +help: Replace with `Optional[Literal[...]]` + | + - from typing import Literal, Union +1 + from typing import Literal, Union, Optional +2 | +-------------------------------------------------------------------------------- +91 | # Rewriting this changes `typing.get_args(options)` at runtime, so the fix is unsafe. + - options = Literal["foo", "bar", None] +92 + options = Optional[Literal["foo", "bar"]] | note: This is an unsafe fix and may change runtime behavior