diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.py index 9e3b46c1f80c0..23074f9250f5d 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.py @@ -14,6 +14,12 @@ Literal[1, Literal[2], Literal[2]] # once t.Literal[1, t.Literal[2, t.Literal[1]]] # once typing_extensions.Literal[1, 1, 1] # twice +Literal[ + 1, # comment + Literal[ # another comment + 1 + ] +] # once # Ensure issue is only raised once, even on nested literals MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.pyi index 38d04bcd9df68..23074f9250f5d 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.pyi @@ -2,11 +2,11 @@ from typing import Literal import typing as t import typing_extensions -x: Literal[True, False, True, False] # PY062 twice here +x: Literal[True, False, True, False] # PYI062 twice here -y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1 +y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 -z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal +z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal Literal[1, Literal[1]] # once Literal[1, 2, Literal[1, 2]] # twice @@ -14,6 +14,12 @@ Literal[1, Literal[1], Literal[1]] # twice Literal[1, Literal[2], Literal[2]] # once t.Literal[1, t.Literal[2, t.Literal[1]]] # once typing_extensions.Literal[1, 1, 1] # twice +Literal[ + 1, # comment + Literal[ # another comment + 1 + ] +] # once # Ensure issue is only raised once, even on nested literals MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs index c3c1d7225b759..5b2c19fd2cc61 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use rustc_hash::FxHashSet; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::{self as ast, Expr, ExprContext}; @@ -28,8 +28,10 @@ use crate::checkers::ast::Checker; /// ``` /// /// ## Fix safety -/// This rule's fix is marked as safe; however, the fix will flatten nested -/// literals into a single top-level literal. +/// This rule's fix is marked as safe, unless the type annotation contains comments. +/// +/// Note that the fix will flatten nested literals into a single top-level +/// literal. /// /// ## References /// - [Python documentation: `typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal) @@ -73,33 +75,39 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &mut Checker, expr: &'a Expr // Traverse the literal, collect all diagnostic members. traverse_literal(&mut check_for_duplicate_members, checker.semantic(), expr); + if diagnostics.is_empty() { + return; + } + // If there's at least one diagnostic, create a fix to remove the duplicate members. - if !diagnostics.is_empty() { - if let Expr::Subscript(subscript) = expr { - let subscript = Expr::Subscript(ast::ExprSubscript { - slice: Box::new(if let [elt] = unique_nodes.as_slice() { - (*elt).clone() - } else { - Expr::Tuple(ast::ExprTuple { - elts: unique_nodes.into_iter().cloned().collect(), - range: TextRange::default(), - ctx: ExprContext::Load, - parenthesized: false, - }) - }), - value: subscript.value.clone(), - range: TextRange::default(), - ctx: ExprContext::Load, - }); - let fix = Fix::safe_edit(Edit::range_replacement( - checker.generator().expr(&subscript), - expr.range(), - )); - for diagnostic in &mut diagnostics { - diagnostic.set_fix(fix.clone()); - } + if let Expr::Subscript(subscript) = expr { + let subscript = Expr::Subscript(ast::ExprSubscript { + slice: Box::new(if let [elt] = unique_nodes.as_slice() { + (*elt).clone() + } else { + Expr::Tuple(ast::ExprTuple { + elts: unique_nodes.into_iter().cloned().collect(), + range: TextRange::default(), + ctx: ExprContext::Load, + parenthesized: false, + }) + }), + value: subscript.value.clone(), + range: TextRange::default(), + ctx: ExprContext::Load, + }); + let fix = Fix::applicable_edit( + Edit::range_replacement(checker.generator().expr(&subscript), expr.range()), + if checker.comment_ranges().intersects(expr.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }, + ); + for diagnostic in &mut diagnostics { + diagnostic.set_fix(fix.clone()); } - } + }; checker.diagnostics.append(&mut diagnostics); } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI062_PYI062.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI062_PYI062.py.snap index c73ae4f46e55b..1470b5190fde6 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI062_PYI062.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI062_PYI062.py.snap @@ -207,7 +207,7 @@ PYI062.py:14:32: PYI062 [*] Duplicate literal member `2` 14 |+Literal[1, 2] # once 15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once 16 16 | typing_extensions.Literal[1, 1, 1] # twice -17 17 | +17 17 | Literal[ PYI062.py:15:37: PYI062 [*] Duplicate literal member `1` | @@ -216,6 +216,7 @@ PYI062.py:15:37: PYI062 [*] Duplicate literal member `1` 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once | ^ PYI062 16 | typing_extensions.Literal[1, 1, 1] # twice +17 | Literal[ | = help: Remove duplicates @@ -226,8 +227,8 @@ PYI062.py:15:37: PYI062 [*] Duplicate literal member `1` 15 |-t.Literal[1, t.Literal[2, t.Literal[1]]] # once 15 |+t.Literal[1, 2] # once 16 16 | typing_extensions.Literal[1, 1, 1] # twice -17 17 | -18 18 | # Ensure issue is only raised once, even on nested literals +17 17 | Literal[ +18 18 | 1, # comment PYI062.py:16:30: PYI062 [*] Duplicate literal member `1` | @@ -235,8 +236,8 @@ PYI062.py:16:30: PYI062 [*] Duplicate literal member `1` 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once 16 | typing_extensions.Literal[1, 1, 1] # twice | ^ PYI062 -17 | -18 | # Ensure issue is only raised once, even on nested literals +17 | Literal[ +18 | 1, # comment | = help: Remove duplicates @@ -246,9 +247,9 @@ PYI062.py:16:30: PYI062 [*] Duplicate literal member `1` 15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once 16 |-typing_extensions.Literal[1, 1, 1] # twice 16 |+typing_extensions.Literal[1] # twice -17 17 | -18 18 | # Ensure issue is only raised once, even on nested literals -19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 +17 17 | Literal[ +18 18 | 1, # comment +19 19 | Literal[ # another comment PYI062.py:16:33: PYI062 [*] Duplicate literal member `1` | @@ -256,8 +257,8 @@ PYI062.py:16:33: PYI062 [*] Duplicate literal member `1` 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once 16 | typing_extensions.Literal[1, 1, 1] # twice | ^ PYI062 -17 | -18 | # Ensure issue is only raised once, even on nested literals +17 | Literal[ +18 | 1, # comment | = help: Remove duplicates @@ -267,25 +268,51 @@ PYI062.py:16:33: PYI062 [*] Duplicate literal member `1` 15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once 16 |-typing_extensions.Literal[1, 1, 1] # twice 16 |+typing_extensions.Literal[1] # twice -17 17 | -18 18 | # Ensure issue is only raised once, even on nested literals -19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 +17 17 | Literal[ +18 18 | 1, # comment +19 19 | Literal[ # another comment -PYI062.py:19:46: PYI062 [*] Duplicate literal member `True` +PYI062.py:20:9: PYI062 [*] Duplicate literal member `1` | -18 | # Ensure issue is only raised once, even on nested literals -19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 +18 | 1, # comment +19 | Literal[ # another comment +20 | 1 + | ^ PYI062 +21 | ] +22 | ] # once + | + = help: Remove duplicates + +ℹ Unsafe fix +14 14 | Literal[1, Literal[2], Literal[2]] # once +15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once +16 16 | typing_extensions.Literal[1, 1, 1] # twice +17 |-Literal[ +18 |- 1, # comment +19 |- Literal[ # another comment +20 |- 1 +21 |- ] +22 |-] # once + 17 |+Literal[1] # once +23 18 | +24 19 | # Ensure issue is only raised once, even on nested literals +25 20 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 + +PYI062.py:25:46: PYI062 [*] Duplicate literal member `True` + | +24 | # Ensure issue is only raised once, even on nested literals +25 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 | ^^^^ PYI062 -20 | -21 | n: Literal["No", "duplicates", "here", 1, "1"] +26 | +27 | n: Literal["No", "duplicates", "here", 1, "1"] | = help: Remove duplicates ℹ Safe fix -16 16 | typing_extensions.Literal[1, 1, 1] # twice -17 17 | -18 18 | # Ensure issue is only raised once, even on nested literals -19 |-MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 - 19 |+MyType = Literal["foo", True, False, "bar"] # PYI062 -20 20 | -21 21 | n: Literal["No", "duplicates", "here", 1, "1"] +22 22 | ] # once +23 23 | +24 24 | # Ensure issue is only raised once, even on nested literals +25 |-MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 + 25 |+MyType = Literal["foo", True, False, "bar"] # PYI062 +26 26 | +27 27 | n: Literal["No", "duplicates", "here", 1, "1"] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI062_PYI062.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI062_PYI062.pyi.snap index 0a96dea0cec1e..af053f2bb533f 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI062_PYI062.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI062_PYI062.pyi.snap @@ -5,10 +5,10 @@ PYI062.pyi:5:25: PYI062 [*] Duplicate literal member `True` | 3 | import typing_extensions 4 | -5 | x: Literal[True, False, True, False] # PY062 twice here +5 | x: Literal[True, False, True, False] # PYI062 twice here | ^^^^ PYI062 6 | -7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1 +7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 | = help: Remove duplicates @@ -16,20 +16,20 @@ PYI062.pyi:5:25: PYI062 [*] Duplicate literal member `True` 2 2 | import typing as t 3 3 | import typing_extensions 4 4 | -5 |-x: Literal[True, False, True, False] # PY062 twice here - 5 |+x: Literal[True, False] # PY062 twice here +5 |-x: Literal[True, False, True, False] # PYI062 twice here + 5 |+x: Literal[True, False] # PYI062 twice here 6 6 | -7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1 +7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 8 8 | PYI062.pyi:5:31: PYI062 [*] Duplicate literal member `False` | 3 | import typing_extensions 4 | -5 | x: Literal[True, False, True, False] # PY062 twice here +5 | x: Literal[True, False, True, False] # PYI062 twice here | ^^^^^ PYI062 6 | -7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1 +7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 | = help: Remove duplicates @@ -37,38 +37,38 @@ PYI062.pyi:5:31: PYI062 [*] Duplicate literal member `False` 2 2 | import typing as t 3 3 | import typing_extensions 4 4 | -5 |-x: Literal[True, False, True, False] # PY062 twice here - 5 |+x: Literal[True, False] # PY062 twice here +5 |-x: Literal[True, False, True, False] # PYI062 twice here + 5 |+x: Literal[True, False] # PYI062 twice here 6 6 | -7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1 +7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 8 8 | PYI062.pyi:7:45: PYI062 [*] Duplicate literal member `1` | -5 | x: Literal[True, False, True, False] # PY062 twice here +5 | x: Literal[True, False, True, False] # PYI062 twice here 6 | -7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1 +7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 | ^ PYI062 8 | -9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal +9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal | = help: Remove duplicates ℹ Safe fix 4 4 | -5 5 | x: Literal[True, False, True, False] # PY062 twice here +5 5 | x: Literal[True, False, True, False] # PYI062 twice here 6 6 | -7 |-y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1 - 7 |+y: Literal[1, print("hello"), 3, 4] # PY062 on the last 1 +7 |-y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 + 7 |+y: Literal[1, print("hello"), 3, 4] # PYI062 on the last 1 8 8 | -9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal +9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal 10 10 | PYI062.pyi:9:33: PYI062 [*] Duplicate literal member `{1, 3, 5}` | - 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1 + 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 8 | - 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal + 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal | ^^^^^^^ PYI062 10 | 11 | Literal[1, Literal[1]] # once @@ -77,17 +77,17 @@ PYI062.pyi:9:33: PYI062 [*] Duplicate literal member `{1, 3, 5}` ℹ Safe fix 6 6 | -7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1 +7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 8 8 | -9 |-z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal - 9 |+z: Literal[{1, 3, 5}, "foobar"] # PY062 on the set literal +9 |-z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal + 9 |+z: Literal[{1, 3, 5}, "foobar"] # PYI062 on the set literal 10 10 | 11 11 | Literal[1, Literal[1]] # once 12 12 | Literal[1, 2, Literal[1, 2]] # twice PYI062.pyi:11:20: PYI062 [*] Duplicate literal member `1` | - 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal + 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal 10 | 11 | Literal[1, Literal[1]] # once | ^ PYI062 @@ -98,7 +98,7 @@ PYI062.pyi:11:20: PYI062 [*] Duplicate literal member `1` ℹ Safe fix 8 8 | -9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal +9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal 10 10 | 11 |-Literal[1, Literal[1]] # once 11 |+Literal[1] # once @@ -117,7 +117,7 @@ PYI062.pyi:12:23: PYI062 [*] Duplicate literal member `1` = help: Remove duplicates ℹ Safe fix -9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal +9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal 10 10 | 11 11 | Literal[1, Literal[1]] # once 12 |-Literal[1, 2, Literal[1, 2]] # twice @@ -137,7 +137,7 @@ PYI062.pyi:12:26: PYI062 [*] Duplicate literal member `2` = help: Remove duplicates ℹ Safe fix -9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal +9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal 10 10 | 11 11 | Literal[1, Literal[1]] # once 12 |-Literal[1, 2, Literal[1, 2]] # twice @@ -207,7 +207,7 @@ PYI062.pyi:14:32: PYI062 [*] Duplicate literal member `2` 14 |+Literal[1, 2] # once 15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once 16 16 | typing_extensions.Literal[1, 1, 1] # twice -17 17 | +17 17 | Literal[ PYI062.pyi:15:37: PYI062 [*] Duplicate literal member `1` | @@ -216,6 +216,7 @@ PYI062.pyi:15:37: PYI062 [*] Duplicate literal member `1` 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once | ^ PYI062 16 | typing_extensions.Literal[1, 1, 1] # twice +17 | Literal[ | = help: Remove duplicates @@ -226,8 +227,8 @@ PYI062.pyi:15:37: PYI062 [*] Duplicate literal member `1` 15 |-t.Literal[1, t.Literal[2, t.Literal[1]]] # once 15 |+t.Literal[1, 2] # once 16 16 | typing_extensions.Literal[1, 1, 1] # twice -17 17 | -18 18 | # Ensure issue is only raised once, even on nested literals +17 17 | Literal[ +18 18 | 1, # comment PYI062.pyi:16:30: PYI062 [*] Duplicate literal member `1` | @@ -235,8 +236,8 @@ PYI062.pyi:16:30: PYI062 [*] Duplicate literal member `1` 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once 16 | typing_extensions.Literal[1, 1, 1] # twice | ^ PYI062 -17 | -18 | # Ensure issue is only raised once, even on nested literals +17 | Literal[ +18 | 1, # comment | = help: Remove duplicates @@ -246,9 +247,9 @@ PYI062.pyi:16:30: PYI062 [*] Duplicate literal member `1` 15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once 16 |-typing_extensions.Literal[1, 1, 1] # twice 16 |+typing_extensions.Literal[1] # twice -17 17 | -18 18 | # Ensure issue is only raised once, even on nested literals -19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 +17 17 | Literal[ +18 18 | 1, # comment +19 19 | Literal[ # another comment PYI062.pyi:16:33: PYI062 [*] Duplicate literal member `1` | @@ -256,8 +257,8 @@ PYI062.pyi:16:33: PYI062 [*] Duplicate literal member `1` 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once 16 | typing_extensions.Literal[1, 1, 1] # twice | ^ PYI062 -17 | -18 | # Ensure issue is only raised once, even on nested literals +17 | Literal[ +18 | 1, # comment | = help: Remove duplicates @@ -267,25 +268,51 @@ PYI062.pyi:16:33: PYI062 [*] Duplicate literal member `1` 15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once 16 |-typing_extensions.Literal[1, 1, 1] # twice 16 |+typing_extensions.Literal[1] # twice -17 17 | -18 18 | # Ensure issue is only raised once, even on nested literals -19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 +17 17 | Literal[ +18 18 | 1, # comment +19 19 | Literal[ # another comment -PYI062.pyi:19:46: PYI062 [*] Duplicate literal member `True` +PYI062.pyi:20:9: PYI062 [*] Duplicate literal member `1` | -18 | # Ensure issue is only raised once, even on nested literals -19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 +18 | 1, # comment +19 | Literal[ # another comment +20 | 1 + | ^ PYI062 +21 | ] +22 | ] # once + | + = help: Remove duplicates + +ℹ Unsafe fix +14 14 | Literal[1, Literal[2], Literal[2]] # once +15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once +16 16 | typing_extensions.Literal[1, 1, 1] # twice +17 |-Literal[ +18 |- 1, # comment +19 |- Literal[ # another comment +20 |- 1 +21 |- ] +22 |-] # once + 17 |+Literal[1] # once +23 18 | +24 19 | # Ensure issue is only raised once, even on nested literals +25 20 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 + +PYI062.pyi:25:46: PYI062 [*] Duplicate literal member `True` + | +24 | # Ensure issue is only raised once, even on nested literals +25 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 | ^^^^ PYI062 -20 | -21 | n: Literal["No", "duplicates", "here", 1, "1"] +26 | +27 | n: Literal["No", "duplicates", "here", 1, "1"] | = help: Remove duplicates ℹ Safe fix -16 16 | typing_extensions.Literal[1, 1, 1] # twice -17 17 | -18 18 | # Ensure issue is only raised once, even on nested literals -19 |-MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 - 19 |+MyType = Literal["foo", True, False, "bar"] # PYI062 -20 20 | -21 21 | n: Literal["No", "duplicates", "here", 1, "1"] +22 22 | ] # once +23 23 | +24 24 | # Ensure issue is only raised once, even on nested literals +25 |-MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 + 25 |+MyType = Literal["foo", True, False, "bar"] # PYI062 +26 26 | +27 27 | n: Literal["No", "duplicates", "here", 1, "1"]