From cbecb0f18dae74de932a749f55d3fe676ee4d684 Mon Sep 17 00:00:00 2001 From: soundsonacid Date: Thu, 17 Jul 2025 18:20:34 -0500 Subject: [PATCH 1/7] [pyi108]: skip if all Union members are None --- .../test/fixtures/flake8_pyi/PYI016_2.py | 3 +++ crates/ruff_linter/src/rules/flake8_pyi/mod.rs | 1 + .../flake8_pyi/rules/duplicate_union_member.rs | 15 ++++++++++++++- ...es__flake8_pyi__tests__PYI016_PYI016_2.py.snap | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016_2.py create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016_2.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016_2.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016_2.py new file mode 100644 index 0000000000000..2b96e868727c6 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016_2.py @@ -0,0 +1,3 @@ +# This is a regression test for https://github.com/astral-sh/ruff/issues/19403 +from typing import Union +isinstance(None, Union[None, None]) \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index a065946483ae6..ffc8d4b4d3e36 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -41,6 +41,7 @@ mod tests { #[test_case(Rule::DuplicateLiteralMember, Path::new("PYI062.pyi"))] #[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.py"))] #[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.pyi"))] + #[test_case(Rule::DuplicateUnionMember, Path::new("PYI016_2.py"))] #[test_case(Rule::EllipsisInNonEmptyClassBody, Path::new("PYI013.py"))] #[test_case(Rule::EllipsisInNonEmptyClassBody, Path::new("PYI013.pyi"))] #[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.py"))] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs index 33812cebee09d..a1dee7925ae70 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs @@ -87,7 +87,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { DuplicateUnionMember { duplicate_name: checker.generator().expr(virtual_expr), }, - // Use the real expression's range for diagnostics, + // Use the real expression's range for diagnostics. expr.range(), )); } @@ -104,6 +104,19 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { return; } + // Do not reduce `Union[None, ... None]` to avoid introducing a `TypeError` unintentionally + // e.g. `isinstance(None, Union[None, None])`, if reduced to `isinstance(None, None)`, causes + // `TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union` to throw at runtime + if unique_nodes + .iter() + .all(|n| matches!(n, Expr::NoneLiteral(_))) + { + for diagnostic in diagnostics { + diagnostic.defuse(); + } + return; + } + // Mark [`Fix`] as unsafe when comments are in range. let applicability = if checker.comment_ranges().intersects(expr.range()) { Applicability::Unsafe diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016_2.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016_2.py.snap new file mode 100644 index 0000000000000..f0a4f44d2b294 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016_2.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- + From cc511803a69c9a95caefc4e1e702c036601398c8 Mon Sep 17 00:00:00 2001 From: soundsonacid Date: Fri, 18 Jul 2025 10:36:46 -0500 Subject: [PATCH 2/7] (fix): resolve test failures by not bailing if reducing Optional --- .../src/rules/flake8_pyi/rules/duplicate_union_member.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs index a1dee7925ae70..c49d982d121de 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs @@ -64,6 +64,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { let mut diagnostics = Vec::new(); let mut union_type = UnionKind::TypingUnion; + let mut optional_present = false; // Adds a member to `literal_exprs` if it is a `Literal` annotation let mut check_for_duplicate_members = |expr: &'a Expr, parent: &'a Expr| { if matches!(parent, Expr::BinOp(_)) { @@ -74,6 +75,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { && is_optional_type(checker, expr) { // If the union member is an `Optional`, add a virtual `None` literal. + optional_present = true; &VIRTUAL_NONE_LITERAL } else { expr @@ -106,10 +108,14 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { // Do not reduce `Union[None, ... None]` to avoid introducing a `TypeError` unintentionally // e.g. `isinstance(None, Union[None, None])`, if reduced to `isinstance(None, None)`, causes - // `TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union` to throw at runtime + // `TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union` to throw at + // in cases where we are reducing a `typing.Optional[None]`, our only unique node will be a `NoneLiteral`, + // and we want to flag on that pursuant to identifying that `typing.Optional[None]` is semantically equivalent to + // `None`, which is also covered by this lint. if unique_nodes .iter() .all(|n| matches!(n, Expr::NoneLiteral(_))) + && !optional_present { for diagnostic in diagnostics { diagnostic.defuse(); From fdaa8d35b7b7404df6da848125f0f3b4e6f81b5c Mon Sep 17 00:00:00 2001 From: soundsonacid Date: Fri, 18 Jul 2025 11:25:25 -0500 Subject: [PATCH 3/7] (chore): move PYI016_2.py into existing PYI016.py --- .../resources/test/fixtures/flake8_pyi/PYI016.py | 4 ++++ .../resources/test/fixtures/flake8_pyi/PYI016_2.py | 3 --- crates/ruff_linter/src/rules/flake8_pyi/mod.rs | 1 - ...linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap | 7 +++++++ ...nter__rules__flake8_pyi__tests__PYI016_PYI016_2.py.snap | 4 ---- 5 files changed, 11 insertions(+), 8 deletions(-) delete mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016_2.py delete mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016_2.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py index 89efd61e8f86c..b8f424128c38e 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py @@ -142,3 +142,7 @@ def func2() -> str | str: # PYI016: Duplicate union member `str` # avoid reporting twice field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] field49: typing.Optional[complex | complex] | complex + +# Regression test for https://github.com/astral-sh/ruff/issues/19403 +# Should be OK +isinstance(None, typing.Union[None, None]) \ No newline at end of file diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016_2.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016_2.py deleted file mode 100644 index 2b96e868727c6..0000000000000 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016_2.py +++ /dev/null @@ -1,3 +0,0 @@ -# This is a regression test for https://github.com/astral-sh/ruff/issues/19403 -from typing import Union -isinstance(None, Union[None, None]) \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index ffc8d4b4d3e36..a065946483ae6 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -41,7 +41,6 @@ mod tests { #[test_case(Rule::DuplicateLiteralMember, Path::new("PYI062.pyi"))] #[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.py"))] #[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.pyi"))] - #[test_case(Rule::DuplicateUnionMember, Path::new("PYI016_2.py"))] #[test_case(Rule::EllipsisInNonEmptyClassBody, Path::new("PYI013.py"))] #[test_case(Rule::EllipsisInNonEmptyClassBody, Path::new("PYI013.pyi"))] #[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.py"))] diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap index 8a51694b4a9b7..9f1937197a5c6 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap @@ -974,6 +974,8 @@ PYI016.py:143:61: PYI016 [*] Duplicate union member `complex` 143 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] 143 |+field48: typing.Union[typing.Optional[complex], complex] 144 144 | field49: typing.Optional[complex | complex] | complex +145 145 | +146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 PYI016.py:144:36: PYI016 [*] Duplicate union member `complex` | @@ -981,6 +983,8 @@ PYI016.py:144:36: PYI016 [*] Duplicate union member `complex` 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] 144 | field49: typing.Optional[complex | complex] | complex | ^^^^^^^ PYI016 +145 | +146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 | = help: Remove duplicate union member `complex` @@ -990,3 +994,6 @@ PYI016.py:144:36: PYI016 [*] Duplicate union member `complex` 143 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] 144 |-field49: typing.Optional[complex | complex] | complex 144 |+field49: typing.Optional[complex] | complex +145 145 | +146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 +147 147 | # Should be OK diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016_2.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016_2.py.snap deleted file mode 100644 index f0a4f44d2b294..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016_2.py.snap +++ /dev/null @@ -1,4 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs ---- - From a6dbde7fceeb315f689c5c0106f82811733bc7d1 Mon Sep 17 00:00:00 2001 From: soundsonacid Date: Fri, 18 Jul 2025 11:36:05 -0500 Subject: [PATCH 4/7] (fix): update snapshots --- ...ake8_pyi__tests__preview__PYI016_PYI016.py.snap | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.py.snap index 8b49997fcc5f9..0a45a19d865e9 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.py.snap @@ -1162,6 +1162,8 @@ PYI016.py:143:61: PYI016 [*] Duplicate union member `complex` 143 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] 143 |+field48: typing.Union[None, complex] 144 144 | field49: typing.Optional[complex | complex] | complex +145 145 | +146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 PYI016.py:143:72: PYI016 [*] Duplicate union member `complex` | @@ -1179,6 +1181,8 @@ PYI016.py:143:72: PYI016 [*] Duplicate union member `complex` 143 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] 143 |+field48: typing.Union[None, complex] 144 144 | field49: typing.Optional[complex | complex] | complex +145 145 | +146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 PYI016.py:144:36: PYI016 [*] Duplicate union member `complex` | @@ -1186,6 +1190,8 @@ PYI016.py:144:36: PYI016 [*] Duplicate union member `complex` 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] 144 | field49: typing.Optional[complex | complex] | complex | ^^^^^^^ PYI016 +145 | +146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 | = help: Remove duplicate union member `complex` @@ -1195,6 +1201,9 @@ PYI016.py:144:36: PYI016 [*] Duplicate union member `complex` 143 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] 144 |-field49: typing.Optional[complex | complex] | complex 144 |+field49: None | complex +145 145 | +146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 +147 147 | # Should be OK PYI016.py:144:47: PYI016 [*] Duplicate union member `complex` | @@ -1202,6 +1211,8 @@ PYI016.py:144:47: PYI016 [*] Duplicate union member `complex` 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] 144 | field49: typing.Optional[complex | complex] | complex | ^^^^^^^ PYI016 +145 | +146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 | = help: Remove duplicate union member `complex` @@ -1211,3 +1222,6 @@ PYI016.py:144:47: PYI016 [*] Duplicate union member `complex` 143 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] 144 |-field49: typing.Optional[complex | complex] | complex 144 |+field49: None | complex +145 145 | +146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 +147 147 | # Should be OK From 4c0a10c7cec5ed49bff0bbd02bfe1e8ee4a20f08 Mon Sep 17 00:00:00 2001 From: soundsonacid Date: Tue, 22 Jul 2025 09:11:52 -0500 Subject: [PATCH 5/7] (chore): pr review --- .../resources/test/fixtures/flake8_pyi/PYI016.py | 2 +- .../flake8_pyi/rules/duplicate_union_member.rs | 10 ++-------- ..._rules__flake8_pyi__tests__PYI016_PYI016.py.snap | 11 ++++++++++- ...lake8_pyi__tests__preview__PYI016_PYI016.py.snap | 13 +++++++++++-- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py index b8f424128c38e..74eaf356d5be2 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py @@ -144,5 +144,5 @@ def func2() -> str | str: # PYI016: Duplicate union member `str` field49: typing.Optional[complex | complex] | complex # Regression test for https://github.com/astral-sh/ruff/issues/19403 -# Should be OK +# Should throw duplicate union member but not fix isinstance(None, typing.Union[None, None]) \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs index c49d982d121de..1c89c99611ac9 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs @@ -108,18 +108,12 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { // Do not reduce `Union[None, ... None]` to avoid introducing a `TypeError` unintentionally // e.g. `isinstance(None, Union[None, None])`, if reduced to `isinstance(None, None)`, causes - // `TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union` to throw at - // in cases where we are reducing a `typing.Optional[None]`, our only unique node will be a `NoneLiteral`, - // and we want to flag on that pursuant to identifying that `typing.Optional[None]` is semantically equivalent to - // `None`, which is also covered by this lint. + // `TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union` to throw. if unique_nodes .iter() - .all(|n| matches!(n, Expr::NoneLiteral(_))) + .all(|expr| Expr::is_none_literal_expr(expr)) && !optional_present { - for diagnostic in diagnostics { - diagnostic.defuse(); - } return; } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap index 9f1937197a5c6..62c0d69f78ec9 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.py.snap @@ -996,4 +996,13 @@ PYI016.py:144:36: PYI016 [*] Duplicate union member `complex` 144 |+field49: typing.Optional[complex] | complex 145 145 | 146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 -147 147 | # Should be OK +147 147 | # Should throw duplicate union member but not fix + +PYI016.py:148:37: PYI016 Duplicate union member `None` + | +146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 +147 | # Should throw duplicate union member but not fix +148 | isinstance(None, typing.Union[None, None]) + | ^^^^ PYI016 + | + = help: Remove duplicate union member `None` diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.py.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.py.snap index 0a45a19d865e9..a1212a7b5bd4d 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.py.snap @@ -1203,7 +1203,7 @@ PYI016.py:144:36: PYI016 [*] Duplicate union member `complex` 144 |+field49: None | complex 145 145 | 146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 -147 147 | # Should be OK +147 147 | # Should throw duplicate union member but not fix PYI016.py:144:47: PYI016 [*] Duplicate union member `complex` | @@ -1224,4 +1224,13 @@ PYI016.py:144:47: PYI016 [*] Duplicate union member `complex` 144 |+field49: None | complex 145 145 | 146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 -147 147 | # Should be OK +147 147 | # Should throw duplicate union member but not fix + +PYI016.py:148:37: PYI016 Duplicate union member `None` + | +146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403 +147 | # Should throw duplicate union member but not fix +148 | isinstance(None, typing.Union[None, None]) + | ^^^^ PYI016 + | + = help: Remove duplicate union member `None` From c21de9b372c85deab6654c565ec97f4add516c07 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Tue, 22 Jul 2025 12:57:19 -0400 Subject: [PATCH 6/7] tiny cleanup --- .../src/rules/flake8_pyi/rules/duplicate_union_member.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs index 1c89c99611ac9..f21e2dfab6548 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs @@ -111,7 +111,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { // `TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union` to throw. if unique_nodes .iter() - .all(|expr| Expr::is_none_literal_expr(expr)) + .all(|expr| expr.is_none_literal_expr()) && !optional_present { return; From df777f6bb31af4694df7b98a6272b3141b0e1cf2 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 22 Jul 2025 12:59:54 -0400 Subject: [PATCH 7/7] fmt --- .../src/rules/flake8_pyi/rules/duplicate_union_member.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs index f21e2dfab6548..2fefb8e01dc7c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_union_member.rs @@ -109,11 +109,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { // Do not reduce `Union[None, ... None]` to avoid introducing a `TypeError` unintentionally // e.g. `isinstance(None, Union[None, None])`, if reduced to `isinstance(None, None)`, causes // `TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union` to throw. - if unique_nodes - .iter() - .all(|expr| expr.is_none_literal_expr()) - && !optional_present - { + if unique_nodes.iter().all(|expr| expr.is_none_literal_expr()) && !optional_present { return; }