From 6802c4702f5c0000333e2ca559f2c5df14a11736 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Fri, 27 Jun 2025 15:43:11 +0000 Subject: [PATCH] [`flake8-pyi`] Expand `Optional[A]` to `A | None` (`PYI016`) (#18572) ## Summary Under preview :test_tube: I've expanded rule `PYI016` to also flag type union duplicates containing `None` and `Optional`. ## Test Plan Examples/tests have been added. I've made sure that the existing examples did not change unless preview is enabled. ## Relevant Issues * https://github.com/astral-sh/ruff/issues/18508 (discussing introducing/extending a rule to flag `Optional[None]`) * https://github.com/astral-sh/ruff/issues/18546 (where I discussed this addition with @AlexWaygood) --------- Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Co-authored-by: Brent Westbrook --- .../test/fixtures/flake8_pyi/PYI016.py | 24 +- .../test/fixtures/flake8_pyi/PYI016.pyi | 22 + .../src/checkers/ast/analyze/expression.rs | 14 +- crates/ruff_linter/src/preview.rs | 5 + .../ruff_linter/src/rules/flake8_pyi/mod.rs | 20 + .../rules/duplicate_union_member.rs | 46 +- ...__flake8_pyi__tests__PYI016_PYI016.py.snap | 77 +- ..._flake8_pyi__tests__PYI016_PYI016.pyi.snap | 81 +- ...pyi__tests__preview__PYI016_PYI016.py.snap | 1213 +++++++++++++++++ ...yi__tests__preview__PYI016_PYI016.pyi.snap | 1194 ++++++++++++++++ crates/ruff_python_ast/src/node_index.rs | 4 +- .../src/analyze/typing.rs | 58 +- crates/ruff_python_semantic/src/model.rs | 14 +- 13 files changed, 2747 insertions(+), 25 deletions(-) create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.py.snap create mode 100644 crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.pyi.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 75633e423b..89efd61e8f 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.py @@ -119,4 +119,26 @@ field35: "int | str | int" # Error # Technically, this falls into the domain of the rule but it is an unlikely edge case, # only works if you have from `__future__ import annotations` at the top of the file, # and stringified annotations are discouraged in stub files. -field36: "int | str" | int # Ok \ No newline at end of file +field36: "int | str" | int # Ok + +# https://github.com/astral-sh/ruff/issues/18546 +# Expand Optional[T] to Union[T, None] +# OK +field37: typing.Optional[int] +field38: typing.Union[int, None] +# equivalent to None +field39: typing.Optional[None] +# equivalent to int | None +field40: typing.Union[typing.Optional[int], None] +field41: typing.Optional[typing.Union[int, None]] +field42: typing.Union[typing.Optional[int], typing.Optional[int]] +field43: typing.Optional[int] | None +field44: typing.Optional[int | None] +field45: typing.Optional[int] | typing.Optional[int] +# equivalent to int | dict | None +field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +field47: typing.Optional[int] | typing.Optional[dict] + +# avoid reporting twice +field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +field49: typing.Optional[complex | complex] | complex diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.pyi index a4ff51f5de..07ed45bbd3 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI016.pyi @@ -111,3 +111,25 @@ field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error # Test case for mixed union type field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + +# https://github.com/astral-sh/ruff/issues/18546 +# Expand Optional[T] to Union[T, None] +# OK +field37: typing.Optional[int] +field38: typing.Union[int, None] +# equivalent to None +field39: typing.Optional[None] +# equivalent to int | None +field40: typing.Union[typing.Optional[int], None] +field41: typing.Optional[typing.Union[int, None]] +field42: typing.Union[typing.Optional[int], typing.Optional[int]] +field43: typing.Optional[int] | None +field44: typing.Optional[int | None] +field45: typing.Optional[int] | typing.Optional[int] +# equivalent to int | dict | None +field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +field47: typing.Optional[int] | typing.Optional[dict] + +# avoid reporting twice +field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +field49: typing.Optional[complex | complex] | complex diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 668d6d578e..a1f854e4d5 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -7,6 +7,7 @@ use ruff_python_semantic::analyze::typing; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::preview::is_optional_as_none_in_union_enabled; use crate::registry::Rule; use crate::rules::{ airflow, flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, @@ -90,7 +91,13 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { if checker.is_rule_enabled(Rule::UnnecessaryLiteralUnion) { flake8_pyi::rules::unnecessary_literal_union(checker, expr); } - if checker.is_rule_enabled(Rule::DuplicateUnionMember) { + if checker.is_rule_enabled(Rule::DuplicateUnionMember) + // Avoid duplicate checks inside `Optional` + && !( + is_optional_as_none_in_union_enabled(checker.settings()) + && checker.semantic.inside_optional() + ) + { flake8_pyi::rules::duplicate_union_member(checker, expr); } if checker.is_rule_enabled(Rule::RedundantLiteralUnion) { @@ -1430,6 +1437,11 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { if !checker.semantic.in_nested_union() { if checker.is_rule_enabled(Rule::DuplicateUnionMember) && checker.semantic.in_type_definition() + // Avoid duplicate checks inside `Optional` + && !( + is_optional_as_none_in_union_enabled(checker.settings()) + && checker.semantic.inside_optional() + ) { flake8_pyi::rules::duplicate_union_member(checker, expr); } diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 097941f7e0..3f826aee02 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -90,6 +90,11 @@ pub(crate) const fn is_ignore_init_files_in_useless_alias_enabled( settings.preview.is_enabled() } +// https://github.com/astral-sh/ruff/pull/18572 +pub(crate) const fn is_optional_as_none_in_union_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + // https://github.com/astral-sh/ruff/pull/18547 pub(crate) const fn is_invalid_async_mock_access_check_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() diff --git a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs index c90aca969b..a065946483 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs @@ -11,6 +11,7 @@ mod tests { use crate::registry::Rule; use crate::rules::pep8_naming; + use crate::settings::types::PreviewMode; use crate::test::test_path; use crate::{assert_diagnostics, settings}; @@ -172,4 +173,23 @@ mod tests { assert_diagnostics!(snapshot, diagnostics); Ok(()) } + + #[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.py"))] + #[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.pyi"))] + fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "preview__{}_{}", + rule_code.noqa_code(), + path.to_string_lossy() + ); + let diagnostics = test_path( + Path::new("flake8_pyi").join(path).as_path(), + &settings::LinterSettings { + preview: PreviewMode::Enabled, + ..settings::LinterSettings::for_rule(rule_code) + }, + )?; + assert_diagnostics!(snapshot, diagnostics); + Ok(()) + } } 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 27c6d41c78..33812cebee 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 @@ -1,17 +1,16 @@ -use std::collections::HashSet; - use rustc_hash::FxHashSet; +use std::collections::HashSet; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; -use ruff_python_ast::{Expr, ExprBinOp, Operator, PythonVersion}; -use ruff_python_semantic::analyze::typing::traverse_union; -use ruff_text_size::{Ranged, TextRange}; - -use crate::checkers::ast::Checker; -use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; +use ruff_python_ast::{AtomicNodeIndex, Expr, ExprBinOp, ExprNoneLiteral, Operator, PythonVersion}; +use ruff_python_semantic::analyze::typing::{traverse_union, traverse_union_and_optional}; +use ruff_text_size::{Ranged, TextRange, TextSize}; use super::generate_union_fix; +use crate::checkers::ast::Checker; +use crate::preview::is_optional_as_none_in_union_enabled; +use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for duplicate union members. @@ -71,21 +70,35 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) { union_type = UnionKind::PEP604; } + let virtual_expr = if is_optional_as_none_in_union_enabled(checker.settings()) + && is_optional_type(checker, expr) + { + // If the union member is an `Optional`, add a virtual `None` literal. + &VIRTUAL_NONE_LITERAL + } else { + expr + }; + // If we've already seen this union member, raise a violation. - if seen_nodes.insert(expr.into()) { - unique_nodes.push(expr); + if seen_nodes.insert(virtual_expr.into()) { + unique_nodes.push(virtual_expr); } else { diagnostics.push(checker.report_diagnostic( DuplicateUnionMember { - duplicate_name: checker.generator().expr(expr), + duplicate_name: checker.generator().expr(virtual_expr), }, + // Use the real expression's range for diagnostics, expr.range(), )); } }; // Traverse the union, collect all diagnostic members - traverse_union(&mut check_for_duplicate_members, checker.semantic(), expr); + if is_optional_as_none_in_union_enabled(checker.settings()) { + traverse_union_and_optional(&mut check_for_duplicate_members, checker.semantic(), expr); + } else { + traverse_union(&mut check_for_duplicate_members, checker.semantic(), expr); + } if diagnostics.is_empty() { return; @@ -178,3 +191,12 @@ fn generate_pep604_fix( applicability, ) } + +static VIRTUAL_NONE_LITERAL: Expr = Expr::NoneLiteral(ExprNoneLiteral { + node_index: AtomicNodeIndex::dummy(), + range: TextRange::new(TextSize::new(0), TextSize::new(0)), +}); + +fn is_optional_type(checker: &Checker, expr: &Expr) -> bool { + checker.semantic().match_typing_expr(expr, "Optional") +} 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 3d671d859c..8a51694b4a 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 @@ -914,4 +914,79 @@ PYI016.py:115:23: PYI016 [*] Duplicate union member `int` 115 |+field35: "int | str" # Error 116 116 | 117 117 | -118 118 | +118 118 | + +PYI016.py:134:45: PYI016 [*] Duplicate union member `typing.Optional[int]` + | +132 | field40: typing.Union[typing.Optional[int], None] +133 | field41: typing.Optional[typing.Union[int, None]] +134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] + | ^^^^^^^^^^^^^^^^^^^^ PYI016 +135 | field43: typing.Optional[int] | None +136 | field44: typing.Optional[int | None] + | + = help: Remove duplicate union member `typing.Optional[int]` + +ℹ Safe fix +131 131 | # equivalent to int | None +132 132 | field40: typing.Union[typing.Optional[int], None] +133 133 | field41: typing.Optional[typing.Union[int, None]] +134 |-field42: typing.Union[typing.Optional[int], typing.Optional[int]] + 134 |+field42: typing.Optional[int] +135 135 | field43: typing.Optional[int] | None +136 136 | field44: typing.Optional[int | None] +137 137 | field45: typing.Optional[int] | typing.Optional[int] + +PYI016.py:137:33: PYI016 [*] Duplicate union member `typing.Optional[int]` + | +135 | field43: typing.Optional[int] | None +136 | field44: typing.Optional[int | None] +137 | field45: typing.Optional[int] | typing.Optional[int] + | ^^^^^^^^^^^^^^^^^^^^ PYI016 +138 | # equivalent to int | dict | None +139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + | + = help: Remove duplicate union member `typing.Optional[int]` + +ℹ Safe fix +134 134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +135 135 | field43: typing.Optional[int] | None +136 136 | field44: typing.Optional[int | None] +137 |-field45: typing.Optional[int] | typing.Optional[int] + 137 |+field45: typing.Optional[int] +138 138 | # equivalent to int | dict | None +139 139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +140 140 | field47: typing.Optional[int] | typing.Optional[dict] + +PYI016.py:143:61: PYI016 [*] Duplicate union member `complex` + | +142 | # avoid reporting twice +143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + | ^^^^^^^ PYI016 +144 | field49: typing.Optional[complex | complex] | complex + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +140 140 | field47: typing.Optional[int] | typing.Optional[dict] +141 141 | +142 142 | # avoid reporting twice +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 + +PYI016.py:144:36: PYI016 [*] Duplicate union member `complex` + | +142 | # avoid reporting twice +143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +144 | field49: typing.Optional[complex | complex] | complex + | ^^^^^^^ PYI016 + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +141 141 | +142 142 | # avoid reporting twice +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 diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.pyi.snap index 0f14b06acb..cd37334068 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI016_PYI016.pyi.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs -snapshot_kind: text --- PYI016.pyi:7:15: PYI016 [*] Duplicate union member `str` | @@ -883,6 +882,8 @@ PYI016.pyi:113:61: PYI016 [*] Duplicate union member `list[int]` 112 | # Test case for mixed union type 113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error | ^^^^^^^^^ PYI016 +114 | +115 | # https://github.com/astral-sh/ruff/issues/18546 | = help: Remove duplicate union member `list[int]` @@ -892,3 +893,81 @@ PYI016.pyi:113:61: PYI016 [*] Duplicate union member `list[int]` 112 112 | # Test case for mixed union type 113 |-field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error 113 |+field34: typing.Union[list[int], str, bytes] # Error +114 114 | +115 115 | # https://github.com/astral-sh/ruff/issues/18546 +116 116 | # Expand Optional[T] to Union[T, None] + +PYI016.pyi:125:45: PYI016 [*] Duplicate union member `typing.Optional[int]` + | +123 | field40: typing.Union[typing.Optional[int], None] +124 | field41: typing.Optional[typing.Union[int, None]] +125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] + | ^^^^^^^^^^^^^^^^^^^^ PYI016 +126 | field43: typing.Optional[int] | None +127 | field44: typing.Optional[int | None] + | + = help: Remove duplicate union member `typing.Optional[int]` + +ℹ Safe fix +122 122 | # equivalent to int | None +123 123 | field40: typing.Union[typing.Optional[int], None] +124 124 | field41: typing.Optional[typing.Union[int, None]] +125 |-field42: typing.Union[typing.Optional[int], typing.Optional[int]] + 125 |+field42: typing.Optional[int] +126 126 | field43: typing.Optional[int] | None +127 127 | field44: typing.Optional[int | None] +128 128 | field45: typing.Optional[int] | typing.Optional[int] + +PYI016.pyi:128:33: PYI016 [*] Duplicate union member `typing.Optional[int]` + | +126 | field43: typing.Optional[int] | None +127 | field44: typing.Optional[int | None] +128 | field45: typing.Optional[int] | typing.Optional[int] + | ^^^^^^^^^^^^^^^^^^^^ PYI016 +129 | # equivalent to int | dict | None +130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + | + = help: Remove duplicate union member `typing.Optional[int]` + +ℹ Safe fix +125 125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +126 126 | field43: typing.Optional[int] | None +127 127 | field44: typing.Optional[int | None] +128 |-field45: typing.Optional[int] | typing.Optional[int] + 128 |+field45: typing.Optional[int] +129 129 | # equivalent to int | dict | None +130 130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +131 131 | field47: typing.Optional[int] | typing.Optional[dict] + +PYI016.pyi:134:61: PYI016 [*] Duplicate union member `complex` + | +133 | # avoid reporting twice +134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + | ^^^^^^^ PYI016 +135 | field49: typing.Optional[complex | complex] | complex + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +131 131 | field47: typing.Optional[int] | typing.Optional[dict] +132 132 | +133 133 | # avoid reporting twice +134 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + 134 |+field48: typing.Union[typing.Optional[complex], complex] +135 135 | field49: typing.Optional[complex | complex] | complex + +PYI016.pyi:135:36: PYI016 [*] Duplicate union member `complex` + | +133 | # avoid reporting twice +134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +135 | field49: typing.Optional[complex | complex] | complex + | ^^^^^^^ PYI016 + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +132 132 | +133 133 | # avoid reporting twice +134 134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +135 |-field49: typing.Optional[complex | complex] | complex + 135 |+field49: typing.Optional[complex] | complex 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 new file mode 100644 index 0000000000..8b49997fcc --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.py.snap @@ -0,0 +1,1213 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- +PYI016.py:7:15: PYI016 [*] Duplicate union member `str` + | +6 | # Should emit for duplicate field types. +7 | field2: str | str # PYI016: Duplicate union member `str` + | ^^^ PYI016 +8 | +9 | # Should emit for union types in arguments. + | + = help: Remove duplicate union member `str` + +ℹ Safe fix +4 4 | field1: str +5 5 | +6 6 | # Should emit for duplicate field types. +7 |-field2: str | str # PYI016: Duplicate union member `str` + 7 |+field2: str # PYI016: Duplicate union member `str` +8 8 | +9 9 | # Should emit for union types in arguments. +10 10 | def func1(arg1: int | int): # PYI016: Duplicate union member `int` + +PYI016.py:10:23: PYI016 [*] Duplicate union member `int` + | + 9 | # Should emit for union types in arguments. +10 | def func1(arg1: int | int): # PYI016: Duplicate union member `int` + | ^^^ PYI016 +11 | print(arg1) + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +7 7 | field2: str | str # PYI016: Duplicate union member `str` +8 8 | +9 9 | # Should emit for union types in arguments. +10 |-def func1(arg1: int | int): # PYI016: Duplicate union member `int` + 10 |+def func1(arg1: int): # PYI016: Duplicate union member `int` +11 11 | print(arg1) +12 12 | +13 13 | # Should emit for unions in return types. + +PYI016.py:14:22: PYI016 [*] Duplicate union member `str` + | +13 | # Should emit for unions in return types. +14 | def func2() -> str | str: # PYI016: Duplicate union member `str` + | ^^^ PYI016 +15 | return "my string" + | + = help: Remove duplicate union member `str` + +ℹ Safe fix +11 11 | print(arg1) +12 12 | +13 13 | # Should emit for unions in return types. +14 |-def func2() -> str | str: # PYI016: Duplicate union member `str` + 14 |+def func2() -> str: # PYI016: Duplicate union member `str` +15 15 | return "my string" +16 16 | +17 17 | # Should emit in longer unions, even if not directly adjacent. + +PYI016.py:18:15: PYI016 [*] Duplicate union member `str` + | +17 | # Should emit in longer unions, even if not directly adjacent. +18 | field3: str | str | int # PYI016: Duplicate union member `str` + | ^^^ PYI016 +19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 | field5: str | int | str # PYI016: Duplicate union member `str` + | + = help: Remove duplicate union member `str` + +ℹ Safe fix +15 15 | return "my string" +16 16 | +17 17 | # Should emit in longer unions, even if not directly adjacent. +18 |-field3: str | str | int # PYI016: Duplicate union member `str` + 18 |+field3: str | int # PYI016: Duplicate union member `str` +19 19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 20 | field5: str | int | str # PYI016: Duplicate union member `str` +21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` + +PYI016.py:19:15: PYI016 [*] Duplicate union member `int` + | +17 | # Should emit in longer unions, even if not directly adjacent. +18 | field3: str | str | int # PYI016: Duplicate union member `str` +19 | field4: int | int | str # PYI016: Duplicate union member `int` + | ^^^ PYI016 +20 | field5: str | int | str # PYI016: Duplicate union member `str` +21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +16 16 | +17 17 | # Should emit in longer unions, even if not directly adjacent. +18 18 | field3: str | str | int # PYI016: Duplicate union member `str` +19 |-field4: int | int | str # PYI016: Duplicate union member `int` + 19 |+field4: int | str # PYI016: Duplicate union member `int` +20 20 | field5: str | int | str # PYI016: Duplicate union member `str` +21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` +22 22 | + +PYI016.py:20:21: PYI016 [*] Duplicate union member `str` + | +18 | field3: str | str | int # PYI016: Duplicate union member `str` +19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 | field5: str | int | str # PYI016: Duplicate union member `str` + | ^^^ PYI016 +21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` + | + = help: Remove duplicate union member `str` + +ℹ Safe fix +17 17 | # Should emit in longer unions, even if not directly adjacent. +18 18 | field3: str | str | int # PYI016: Duplicate union member `str` +19 19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 |-field5: str | int | str # PYI016: Duplicate union member `str` + 20 |+field5: str | int # PYI016: Duplicate union member `str` +21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` +22 22 | +23 23 | # Shouldn't emit for non-type unions. + +PYI016.py:21:28: PYI016 [*] Duplicate union member `int` + | +19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 | field5: str | int | str # PYI016: Duplicate union member `str` +21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` + | ^^^ PYI016 +22 | +23 | # Shouldn't emit for non-type unions. + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +18 18 | field3: str | str | int # PYI016: Duplicate union member `str` +19 19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 20 | field5: str | int | str # PYI016: Duplicate union member `str` +21 |-field6: int | bool | str | int # PYI016: Duplicate union member `int` + 21 |+field6: int | bool | str # PYI016: Duplicate union member `int` +22 22 | +23 23 | # Shouldn't emit for non-type unions. +24 24 | field7 = str | str + +PYI016.py:27:22: PYI016 [*] Duplicate union member `int` + | +26 | # Should emit for strangely-bracketed unions. +27 | field8: int | (str | int) # PYI016: Duplicate union member `int` + | ^^^ PYI016 +28 | +29 | # Should handle user brackets when fixing. + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +24 24 | field7 = str | str +25 25 | +26 26 | # Should emit for strangely-bracketed unions. +27 |-field8: int | (str | int) # PYI016: Duplicate union member `int` + 27 |+field8: int | str # PYI016: Duplicate union member `int` +28 28 | +29 29 | # Should handle user brackets when fixing. +30 30 | field9: int | (int | str) # PYI016: Duplicate union member `int` + +PYI016.py:30:16: PYI016 [*] Duplicate union member `int` + | +29 | # Should handle user brackets when fixing. +30 | field9: int | (int | str) # PYI016: Duplicate union member `int` + | ^^^ PYI016 +31 | field10: (str | int) | str # PYI016: Duplicate union member `str` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +27 27 | field8: int | (str | int) # PYI016: Duplicate union member `int` +28 28 | +29 29 | # Should handle user brackets when fixing. +30 |-field9: int | (int | str) # PYI016: Duplicate union member `int` + 30 |+field9: int | str # PYI016: Duplicate union member `int` +31 31 | field10: (str | int) | str # PYI016: Duplicate union member `str` +32 32 | +33 33 | # Should emit for nested unions. + +PYI016.py:31:24: PYI016 [*] Duplicate union member `str` + | +29 | # Should handle user brackets when fixing. +30 | field9: int | (int | str) # PYI016: Duplicate union member `int` +31 | field10: (str | int) | str # PYI016: Duplicate union member `str` + | ^^^ PYI016 +32 | +33 | # Should emit for nested unions. + | + = help: Remove duplicate union member `str` + +ℹ Safe fix +28 28 | +29 29 | # Should handle user brackets when fixing. +30 30 | field9: int | (int | str) # PYI016: Duplicate union member `int` +31 |-field10: (str | int) | str # PYI016: Duplicate union member `str` + 31 |+field10: str | int # PYI016: Duplicate union member `str` +32 32 | +33 33 | # Should emit for nested unions. +34 34 | field11: dict[int | int, str] + +PYI016.py:34:21: PYI016 [*] Duplicate union member `int` + | +33 | # Should emit for nested unions. +34 | field11: dict[int | int, str] + | ^^^ PYI016 +35 | +36 | # Should emit for unions with more than two cases + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +31 31 | field10: (str | int) | str # PYI016: Duplicate union member `str` +32 32 | +33 33 | # Should emit for nested unions. +34 |-field11: dict[int | int, str] + 34 |+field11: dict[int, str] +35 35 | +36 36 | # Should emit for unions with more than two cases +37 37 | field12: int | int | int # Error + +PYI016.py:37:16: PYI016 [*] Duplicate union member `int` + | +36 | # Should emit for unions with more than two cases +37 | field12: int | int | int # Error + | ^^^ PYI016 +38 | field13: int | int | int | int # Error + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +34 34 | field11: dict[int | int, str] +35 35 | +36 36 | # Should emit for unions with more than two cases +37 |-field12: int | int | int # Error + 37 |+field12: int # Error +38 38 | field13: int | int | int | int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent + +PYI016.py:37:22: PYI016 [*] Duplicate union member `int` + | +36 | # Should emit for unions with more than two cases +37 | field12: int | int | int # Error + | ^^^ PYI016 +38 | field13: int | int | int | int # Error + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +34 34 | field11: dict[int | int, str] +35 35 | +36 36 | # Should emit for unions with more than two cases +37 |-field12: int | int | int # Error + 37 |+field12: int # Error +38 38 | field13: int | int | int | int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent + +PYI016.py:38:16: PYI016 [*] Duplicate union member `int` + | +36 | # Should emit for unions with more than two cases +37 | field12: int | int | int # Error +38 | field13: int | int | int | int # Error + | ^^^ PYI016 +39 | +40 | # Should emit for unions with more than two cases, even if not directly adjacent + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +35 35 | +36 36 | # Should emit for unions with more than two cases +37 37 | field12: int | int | int # Error +38 |-field13: int | int | int | int # Error + 38 |+field13: int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 41 | field14: int | int | str | int # Error + +PYI016.py:38:22: PYI016 [*] Duplicate union member `int` + | +36 | # Should emit for unions with more than two cases +37 | field12: int | int | int # Error +38 | field13: int | int | int | int # Error + | ^^^ PYI016 +39 | +40 | # Should emit for unions with more than two cases, even if not directly adjacent + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +35 35 | +36 36 | # Should emit for unions with more than two cases +37 37 | field12: int | int | int # Error +38 |-field13: int | int | int | int # Error + 38 |+field13: int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 41 | field14: int | int | str | int # Error + +PYI016.py:38:28: PYI016 [*] Duplicate union member `int` + | +36 | # Should emit for unions with more than two cases +37 | field12: int | int | int # Error +38 | field13: int | int | int | int # Error + | ^^^ PYI016 +39 | +40 | # Should emit for unions with more than two cases, even if not directly adjacent + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +35 35 | +36 36 | # Should emit for unions with more than two cases +37 37 | field12: int | int | int # Error +38 |-field13: int | int | int | int # Error + 38 |+field13: int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 41 | field14: int | int | str | int # Error + +PYI016.py:41:16: PYI016 [*] Duplicate union member `int` + | +40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 | field14: int | int | str | int # Error + | ^^^ PYI016 +42 | +43 | # Should emit for duplicate literal types; also covered by PYI030 + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +38 38 | field13: int | int | int | int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 |-field14: int | int | str | int # Error + 41 |+field14: int | str # Error +42 42 | +43 43 | # Should emit for duplicate literal types; also covered by PYI030 +44 44 | field15: typing.Literal[1] | typing.Literal[1] # Error + +PYI016.py:41:28: PYI016 [*] Duplicate union member `int` + | +40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 | field14: int | int | str | int # Error + | ^^^ PYI016 +42 | +43 | # Should emit for duplicate literal types; also covered by PYI030 + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +38 38 | field13: int | int | int | int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 |-field14: int | int | str | int # Error + 41 |+field14: int | str # Error +42 42 | +43 43 | # Should emit for duplicate literal types; also covered by PYI030 +44 44 | field15: typing.Literal[1] | typing.Literal[1] # Error + +PYI016.py:44:30: PYI016 [*] Duplicate union member `typing.Literal[1]` + | +43 | # Should emit for duplicate literal types; also covered by PYI030 +44 | field15: typing.Literal[1] | typing.Literal[1] # Error + | ^^^^^^^^^^^^^^^^^ PYI016 +45 | +46 | # Shouldn't emit if in new parent type + | + = help: Remove duplicate union member `typing.Literal[1]` + +ℹ Safe fix +41 41 | field14: int | int | str | int # Error +42 42 | +43 43 | # Should emit for duplicate literal types; also covered by PYI030 +44 |-field15: typing.Literal[1] | typing.Literal[1] # Error + 44 |+field15: typing.Literal[1] # Error +45 45 | +46 46 | # Shouldn't emit if in new parent type +47 47 | field16: int | dict[int, str] # OK + +PYI016.py:57:5: PYI016 [*] Duplicate union member `set[int]` + | +55 | int # foo +56 | ], +57 | / set[ +58 | | int # bar +59 | | ], + | |_____^ PYI016 +60 | ] # Error, newline and comment will not be emitted in message + | + = help: Remove duplicate union member `set[int]` + +ℹ Unsafe fix +50 50 | field17: dict[int, int] # OK +51 51 | +52 52 | # Should emit in cases with newlines +53 |-field18: typing.Union[ +54 |- set[ +55 |- int # foo +56 |- ], +57 |- set[ +58 |- int # bar +59 |- ], +60 |-] # Error, newline and comment will not be emitted in message + 53 |+field18: set[int] # Error, newline and comment will not be emitted in message +61 54 | +62 55 | # Should emit in cases with `typing.Union` instead of `|` +63 56 | field19: typing.Union[int, int] # Error + +PYI016.py:63:28: PYI016 [*] Duplicate union member `int` + | +62 | # Should emit in cases with `typing.Union` instead of `|` +63 | field19: typing.Union[int, int] # Error + | ^^^ PYI016 +64 | +65 | # Should emit in cases with nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +60 60 | ] # Error, newline and comment will not be emitted in message +61 61 | +62 62 | # Should emit in cases with `typing.Union` instead of `|` +63 |-field19: typing.Union[int, int] # Error + 63 |+field19: int # Error +64 64 | +65 65 | # Should emit in cases with nested `typing.Union` +66 66 | field20: typing.Union[int, typing.Union[int, str]] # Error + +PYI016.py:66:41: PYI016 [*] Duplicate union member `int` + | +65 | # Should emit in cases with nested `typing.Union` +66 | field20: typing.Union[int, typing.Union[int, str]] # Error + | ^^^ PYI016 +67 | +68 | # Should emit in cases with mixed `typing.Union` and `|` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +63 63 | field19: typing.Union[int, int] # Error +64 64 | +65 65 | # Should emit in cases with nested `typing.Union` +66 |-field20: typing.Union[int, typing.Union[int, str]] # Error + 66 |+field20: typing.Union[int, str] # Error +67 67 | +68 68 | # Should emit in cases with mixed `typing.Union` and `|` +69 69 | field21: typing.Union[int, int | str] # Error + +PYI016.py:69:28: PYI016 [*] Duplicate union member `int` + | +68 | # Should emit in cases with mixed `typing.Union` and `|` +69 | field21: typing.Union[int, int | str] # Error + | ^^^ PYI016 +70 | +71 | # Should emit only once in cases with multiple nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +66 66 | field20: typing.Union[int, typing.Union[int, str]] # Error +67 67 | +68 68 | # Should emit in cases with mixed `typing.Union` and `|` +69 |-field21: typing.Union[int, int | str] # Error + 69 |+field21: int | str # Error +70 70 | +71 71 | # Should emit only once in cases with multiple nested `typing.Union` +72 72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + +PYI016.py:72:41: PYI016 [*] Duplicate union member `int` + | +71 | # Should emit only once in cases with multiple nested `typing.Union` +72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + | ^^^ PYI016 +73 | +74 | # Should emit in cases with newlines + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +69 69 | field21: typing.Union[int, int | str] # Error +70 70 | +71 71 | # Should emit only once in cases with multiple nested `typing.Union` +72 |-field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + 72 |+field22: int # Error +73 73 | +74 74 | # Should emit in cases with newlines +75 75 | field23: set[ # foo + +PYI016.py:72:59: PYI016 [*] Duplicate union member `int` + | +71 | # Should emit only once in cases with multiple nested `typing.Union` +72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + | ^^^ PYI016 +73 | +74 | # Should emit in cases with newlines + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +69 69 | field21: typing.Union[int, int | str] # Error +70 70 | +71 71 | # Should emit only once in cases with multiple nested `typing.Union` +72 |-field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + 72 |+field22: int # Error +73 73 | +74 74 | # Should emit in cases with newlines +75 75 | field23: set[ # foo + +PYI016.py:72:64: PYI016 [*] Duplicate union member `int` + | +71 | # Should emit only once in cases with multiple nested `typing.Union` +72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + | ^^^ PYI016 +73 | +74 | # Should emit in cases with newlines + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +69 69 | field21: typing.Union[int, int | str] # Error +70 70 | +71 71 | # Should emit only once in cases with multiple nested `typing.Union` +72 |-field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + 72 |+field22: int # Error +73 73 | +74 74 | # Should emit in cases with newlines +75 75 | field23: set[ # foo + +PYI016.py:76:12: PYI016 [*] Duplicate union member `set[int]` + | +74 | # Should emit in cases with newlines +75 | field23: set[ # foo +76 | int] | set[int] + | ^^^^^^^^ PYI016 +77 | +78 | # Should emit twice (once for each `int` in the nested union, both of which are + | + = help: Remove duplicate union member `set[int]` + +ℹ Unsafe fix +72 72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error +73 73 | +74 74 | # Should emit in cases with newlines +75 |-field23: set[ # foo +76 |- int] | set[int] + 75 |+field23: set[int] +77 76 | +78 77 | # Should emit twice (once for each `int` in the nested union, both of which are +79 78 | # duplicates of the outer `int`), but not three times (which would indicate that + +PYI016.py:81:41: PYI016 [*] Duplicate union member `int` + | +79 | # duplicates of the outer `int`), but not three times (which would indicate that +80 | # we incorrectly re-checked the nested union). +81 | field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +82 | +83 | # Should emit twice (once for each `int` in the nested union, both of which are + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +78 78 | # Should emit twice (once for each `int` in the nested union, both of which are +79 79 | # duplicates of the outer `int`), but not three times (which would indicate that +80 80 | # we incorrectly re-checked the nested union). +81 |-field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int` + 81 |+field24: int # PYI016: Duplicate union member `int` +82 82 | +83 83 | # Should emit twice (once for each `int` in the nested union, both of which are +84 84 | # duplicates of the outer `int`), but not three times (which would indicate that + +PYI016.py:81:46: PYI016 [*] Duplicate union member `int` + | +79 | # duplicates of the outer `int`), but not three times (which would indicate that +80 | # we incorrectly re-checked the nested union). +81 | field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +82 | +83 | # Should emit twice (once for each `int` in the nested union, both of which are + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +78 78 | # Should emit twice (once for each `int` in the nested union, both of which are +79 79 | # duplicates of the outer `int`), but not three times (which would indicate that +80 80 | # we incorrectly re-checked the nested union). +81 |-field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int` + 81 |+field24: int # PYI016: Duplicate union member `int` +82 82 | +83 83 | # Should emit twice (once for each `int` in the nested union, both of which are +84 84 | # duplicates of the outer `int`), but not three times (which would indicate that + +PYI016.py:86:28: PYI016 [*] Duplicate union member `int` + | +84 | # duplicates of the outer `int`), but not three times (which would indicate that +85 | # we incorrectly re-checked the nested union). +86 | field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +87 | +88 | # Should emit in cases with nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +83 83 | # Should emit twice (once for each `int` in the nested union, both of which are +84 84 | # duplicates of the outer `int`), but not three times (which would indicate that +85 85 | # we incorrectly re-checked the nested union). +86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int` + 86 |+field25: int # PYI016: Duplicate union member `int` +87 87 | +88 88 | # Should emit in cases with nested `typing.Union` +89 89 | field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int` + +PYI016.py:86:34: PYI016 [*] Duplicate union member `int` + | +84 | # duplicates of the outer `int`), but not three times (which would indicate that +85 | # we incorrectly re-checked the nested union). +86 | field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +87 | +88 | # Should emit in cases with nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +83 83 | # Should emit twice (once for each `int` in the nested union, both of which are +84 84 | # duplicates of the outer `int`), but not three times (which would indicate that +85 85 | # we incorrectly re-checked the nested union). +86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int` + 86 |+field25: int # PYI016: Duplicate union member `int` +87 87 | +88 88 | # Should emit in cases with nested `typing.Union` +89 89 | field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int` + +PYI016.py:89:41: PYI016 [*] Duplicate union member `int` + | +88 | # Should emit in cases with nested `typing.Union` +89 | field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +90 | +91 | # Should emit in cases with nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +86 86 | field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int` +87 87 | +88 88 | # Should emit in cases with nested `typing.Union` +89 |-field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int` + 89 |+field26: int # PYI016: Duplicate union member `int` +90 90 | +91 91 | # Should emit in cases with nested `typing.Union` +92 92 | field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int` + +PYI016.py:92:54: PYI016 [*] Duplicate union member `int` + | +91 | # Should emit in cases with nested `typing.Union` +92 | field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +93 | +94 | # Should emit in cases with mixed `typing.Union` and `|` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +89 89 | field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int` +90 90 | +91 91 | # Should emit in cases with nested `typing.Union` +92 |-field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int` + 92 |+field27: int # PYI016: Duplicate union member `int` +93 93 | +94 94 | # Should emit in cases with mixed `typing.Union` and `|` +95 95 | field28: typing.Union[int | int] # Error + +PYI016.py:95:29: PYI016 [*] Duplicate union member `int` + | +94 | # Should emit in cases with mixed `typing.Union` and `|` +95 | field28: typing.Union[int | int] # Error + | ^^^ PYI016 +96 | +97 | # Should emit twice in cases with multiple nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +92 92 | field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int` +93 93 | +94 94 | # Should emit in cases with mixed `typing.Union` and `|` +95 |-field28: typing.Union[int | int] # Error + 95 |+field28: int # Error +96 96 | +97 97 | # Should emit twice in cases with multiple nested `typing.Union` +98 98 | field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error + +PYI016.py:98:54: PYI016 [*] Duplicate union member `int` + | + 97 | # Should emit twice in cases with multiple nested `typing.Union` + 98 | field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error + | ^^^ PYI016 + 99 | +100 | # Should emit once in cases with multiple nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +95 95 | field28: typing.Union[int | int] # Error +96 96 | +97 97 | # Should emit twice in cases with multiple nested `typing.Union` +98 |-field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error + 98 |+field29: int # Error +99 99 | +100 100 | # Should emit once in cases with multiple nested `typing.Union` +101 101 | field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error + +PYI016.py:98:59: PYI016 [*] Duplicate union member `int` + | + 97 | # Should emit twice in cases with multiple nested `typing.Union` + 98 | field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error + | ^^^ PYI016 + 99 | +100 | # Should emit once in cases with multiple nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +95 95 | field28: typing.Union[int | int] # Error +96 96 | +97 97 | # Should emit twice in cases with multiple nested `typing.Union` +98 |-field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error + 98 |+field29: int # Error +99 99 | +100 100 | # Should emit once in cases with multiple nested `typing.Union` +101 101 | field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error + +PYI016.py:101:54: PYI016 [*] Duplicate union member `int` + | +100 | # Should emit once in cases with multiple nested `typing.Union` +101 | field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error + | ^^^ PYI016 +102 | +103 | # Should emit once, and fix to `typing.Union[float, int]` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +98 98 | field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error +99 99 | +100 100 | # Should emit once in cases with multiple nested `typing.Union` +101 |-field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error + 101 |+field30: typing.Union[int, str] # Error +102 102 | +103 103 | # Should emit once, and fix to `typing.Union[float, int]` +104 104 | field31: typing.Union[float, typing.Union[int | int]] # Error + +PYI016.py:104:49: PYI016 [*] Duplicate union member `int` + | +103 | # Should emit once, and fix to `typing.Union[float, int]` +104 | field31: typing.Union[float, typing.Union[int | int]] # Error + | ^^^ PYI016 +105 | +106 | # Should emit once, and fix to `typing.Union[float, int]` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +101 101 | field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error +102 102 | +103 103 | # Should emit once, and fix to `typing.Union[float, int]` +104 |-field31: typing.Union[float, typing.Union[int | int]] # Error + 104 |+field31: float | int # Error +105 105 | +106 106 | # Should emit once, and fix to `typing.Union[float, int]` +107 107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error + +PYI016.py:107:49: PYI016 [*] Duplicate union member `int` + | +106 | # Should emit once, and fix to `typing.Union[float, int]` +107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error + | ^^^ PYI016 +108 | +109 | # Test case for mixed union type fix + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +104 104 | field31: typing.Union[float, typing.Union[int | int]] # Error +105 105 | +106 106 | # Should emit once, and fix to `typing.Union[float, int]` +107 |-field32: typing.Union[float, typing.Union[int | int | int]] # Error + 107 |+field32: float | int # Error +108 108 | +109 109 | # Test case for mixed union type fix +110 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + +PYI016.py:107:55: PYI016 [*] Duplicate union member `int` + | +106 | # Should emit once, and fix to `typing.Union[float, int]` +107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error + | ^^^ PYI016 +108 | +109 | # Test case for mixed union type fix + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +104 104 | field31: typing.Union[float, typing.Union[int | int]] # Error +105 105 | +106 106 | # Should emit once, and fix to `typing.Union[float, int]` +107 |-field32: typing.Union[float, typing.Union[int | int | int]] # Error + 107 |+field32: float | int # Error +108 108 | +109 109 | # Test case for mixed union type fix +110 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + +PYI016.py:110:42: PYI016 [*] Duplicate union member `int` + | +109 | # Test case for mixed union type fix +110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + | ^^^ PYI016 +111 | +112 | # Test case for mixed union type + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +107 107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error +108 108 | +109 109 | # Test case for mixed union type fix +110 |-field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + 110 |+field33: int # Error +111 111 | +112 112 | # Test case for mixed union type +113 113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + +PYI016.py:110:62: PYI016 [*] Duplicate union member `int` + | +109 | # Test case for mixed union type fix +110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + | ^^^ PYI016 +111 | +112 | # Test case for mixed union type + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +107 107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error +108 108 | +109 109 | # Test case for mixed union type fix +110 |-field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + 110 |+field33: int # Error +111 111 | +112 112 | # Test case for mixed union type +113 113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + +PYI016.py:110:68: PYI016 [*] Duplicate union member `int` + | +109 | # Test case for mixed union type fix +110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + | ^^^ PYI016 +111 | +112 | # Test case for mixed union type + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +107 107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error +108 108 | +109 109 | # Test case for mixed union type fix +110 |-field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + 110 |+field33: int # Error +111 111 | +112 112 | # Test case for mixed union type +113 113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + +PYI016.py:113:61: PYI016 [*] Duplicate union member `list[int]` + | +112 | # Test case for mixed union type +113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + | ^^^^^^^^^ PYI016 +114 | +115 | field35: "int | str | int" # Error + | + = help: Remove duplicate union member `list[int]` + +ℹ Safe fix +110 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error +111 111 | +112 112 | # Test case for mixed union type +113 |-field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + 113 |+field34: typing.Union[list[int], str, bytes] # Error +114 114 | +115 115 | field35: "int | str | int" # Error +116 116 | + +PYI016.py:115:23: PYI016 [*] Duplicate union member `int` + | +113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error +114 | +115 | field35: "int | str | int" # Error + | ^^^ PYI016 + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +112 112 | # Test case for mixed union type +113 113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error +114 114 | +115 |-field35: "int | str | int" # Error + 115 |+field35: "int | str" # Error +116 116 | +117 117 | +118 118 | + +PYI016.py:130:26: PYI016 [*] Duplicate union member `None` + | +128 | field38: typing.Union[int, None] +129 | # equivalent to None +130 | field39: typing.Optional[None] + | ^^^^ PYI016 +131 | # equivalent to int | None +132 | field40: typing.Union[typing.Optional[int], None] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +127 127 | field37: typing.Optional[int] +128 128 | field38: typing.Union[int, None] +129 129 | # equivalent to None +130 |-field39: typing.Optional[None] + 130 |+field39: None +131 131 | # equivalent to int | None +132 132 | field40: typing.Union[typing.Optional[int], None] +133 133 | field41: typing.Optional[typing.Union[int, None]] + +PYI016.py:132:45: PYI016 [*] Duplicate union member `None` + | +130 | field39: typing.Optional[None] +131 | # equivalent to int | None +132 | field40: typing.Union[typing.Optional[int], None] + | ^^^^ PYI016 +133 | field41: typing.Optional[typing.Union[int, None]] +134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +129 129 | # equivalent to None +130 130 | field39: typing.Optional[None] +131 131 | # equivalent to int | None +132 |-field40: typing.Union[typing.Optional[int], None] + 132 |+field40: typing.Union[None, int] +133 133 | field41: typing.Optional[typing.Union[int, None]] +134 134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +135 135 | field43: typing.Optional[int] | None + +PYI016.py:133:44: PYI016 [*] Duplicate union member `None` + | +131 | # equivalent to int | None +132 | field40: typing.Union[typing.Optional[int], None] +133 | field41: typing.Optional[typing.Union[int, None]] + | ^^^^ PYI016 +134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +135 | field43: typing.Optional[int] | None + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +130 130 | field39: typing.Optional[None] +131 131 | # equivalent to int | None +132 132 | field40: typing.Union[typing.Optional[int], None] +133 |-field41: typing.Optional[typing.Union[int, None]] + 133 |+field41: typing.Union[None, int] +134 134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +135 135 | field43: typing.Optional[int] | None +136 136 | field44: typing.Optional[int | None] + +PYI016.py:134:45: PYI016 [*] Duplicate union member `None` + | +132 | field40: typing.Union[typing.Optional[int], None] +133 | field41: typing.Optional[typing.Union[int, None]] +134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] + | ^^^^^^^^^^^^^^^ PYI016 +135 | field43: typing.Optional[int] | None +136 | field44: typing.Optional[int | None] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +131 131 | # equivalent to int | None +132 132 | field40: typing.Union[typing.Optional[int], None] +133 133 | field41: typing.Optional[typing.Union[int, None]] +134 |-field42: typing.Union[typing.Optional[int], typing.Optional[int]] + 134 |+field42: typing.Union[None, int] +135 135 | field43: typing.Optional[int] | None +136 136 | field44: typing.Optional[int | None] +137 137 | field45: typing.Optional[int] | typing.Optional[int] + +PYI016.py:134:61: PYI016 [*] Duplicate union member `int` + | +132 | field40: typing.Union[typing.Optional[int], None] +133 | field41: typing.Optional[typing.Union[int, None]] +134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] + | ^^^ PYI016 +135 | field43: typing.Optional[int] | None +136 | field44: typing.Optional[int | None] + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +131 131 | # equivalent to int | None +132 132 | field40: typing.Union[typing.Optional[int], None] +133 133 | field41: typing.Optional[typing.Union[int, None]] +134 |-field42: typing.Union[typing.Optional[int], typing.Optional[int]] + 134 |+field42: typing.Union[None, int] +135 135 | field43: typing.Optional[int] | None +136 136 | field44: typing.Optional[int | None] +137 137 | field45: typing.Optional[int] | typing.Optional[int] + +PYI016.py:135:33: PYI016 [*] Duplicate union member `None` + | +133 | field41: typing.Optional[typing.Union[int, None]] +134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +135 | field43: typing.Optional[int] | None + | ^^^^ PYI016 +136 | field44: typing.Optional[int | None] +137 | field45: typing.Optional[int] | typing.Optional[int] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +132 132 | field40: typing.Union[typing.Optional[int], None] +133 133 | field41: typing.Optional[typing.Union[int, None]] +134 134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +135 |-field43: typing.Optional[int] | None + 135 |+field43: None | int +136 136 | field44: typing.Optional[int | None] +137 137 | field45: typing.Optional[int] | typing.Optional[int] +138 138 | # equivalent to int | dict | None + +PYI016.py:136:32: PYI016 [*] Duplicate union member `None` + | +134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +135 | field43: typing.Optional[int] | None +136 | field44: typing.Optional[int | None] + | ^^^^ PYI016 +137 | field45: typing.Optional[int] | typing.Optional[int] +138 | # equivalent to int | dict | None + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +133 133 | field41: typing.Optional[typing.Union[int, None]] +134 134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +135 135 | field43: typing.Optional[int] | None +136 |-field44: typing.Optional[int | None] + 136 |+field44: None | int +137 137 | field45: typing.Optional[int] | typing.Optional[int] +138 138 | # equivalent to int | dict | None +139 139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + +PYI016.py:137:33: PYI016 [*] Duplicate union member `None` + | +135 | field43: typing.Optional[int] | None +136 | field44: typing.Optional[int | None] +137 | field45: typing.Optional[int] | typing.Optional[int] + | ^^^^^^^^^^^^^^^ PYI016 +138 | # equivalent to int | dict | None +139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +134 134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +135 135 | field43: typing.Optional[int] | None +136 136 | field44: typing.Optional[int | None] +137 |-field45: typing.Optional[int] | typing.Optional[int] + 137 |+field45: typing.Union[None, int] +138 138 | # equivalent to int | dict | None +139 139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +140 140 | field47: typing.Optional[int] | typing.Optional[dict] + +PYI016.py:137:49: PYI016 [*] Duplicate union member `int` + | +135 | field43: typing.Optional[int] | None +136 | field44: typing.Optional[int | None] +137 | field45: typing.Optional[int] | typing.Optional[int] + | ^^^ PYI016 +138 | # equivalent to int | dict | None +139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +134 134 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +135 135 | field43: typing.Optional[int] | None +136 136 | field44: typing.Optional[int | None] +137 |-field45: typing.Optional[int] | typing.Optional[int] + 137 |+field45: typing.Union[None, int] +138 138 | # equivalent to int | dict | None +139 139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +140 140 | field47: typing.Optional[int] | typing.Optional[dict] + +PYI016.py:139:45: PYI016 [*] Duplicate union member `None` + | +137 | field45: typing.Optional[int] | typing.Optional[int] +138 | # equivalent to int | dict | None +139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + | ^^^^^^^^^^^^^^^ PYI016 +140 | field47: typing.Optional[int] | typing.Optional[dict] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +136 136 | field44: typing.Optional[int | None] +137 137 | field45: typing.Optional[int] | typing.Optional[int] +138 138 | # equivalent to int | dict | None +139 |-field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + 139 |+field46: typing.Union[None, int, dict] +140 140 | field47: typing.Optional[int] | typing.Optional[dict] +141 141 | +142 142 | # avoid reporting twice + +PYI016.py:140:33: PYI016 [*] Duplicate union member `None` + | +138 | # equivalent to int | dict | None +139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +140 | field47: typing.Optional[int] | typing.Optional[dict] + | ^^^^^^^^^^^^^^^ PYI016 +141 | +142 | # avoid reporting twice + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +137 137 | field45: typing.Optional[int] | typing.Optional[int] +138 138 | # equivalent to int | dict | None +139 139 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +140 |-field47: typing.Optional[int] | typing.Optional[dict] + 140 |+field47: typing.Union[None, int, dict] +141 141 | +142 142 | # avoid reporting twice +143 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + +PYI016.py:143:61: PYI016 [*] Duplicate union member `complex` + | +142 | # avoid reporting twice +143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + | ^^^^^^^ PYI016 +144 | field49: typing.Optional[complex | complex] | complex + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +140 140 | field47: typing.Optional[int] | typing.Optional[dict] +141 141 | +142 142 | # avoid reporting twice +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 + +PYI016.py:143:72: PYI016 [*] Duplicate union member `complex` + | +142 | # avoid reporting twice +143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + | ^^^^^^^ PYI016 +144 | field49: typing.Optional[complex | complex] | complex + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +140 140 | field47: typing.Optional[int] | typing.Optional[dict] +141 141 | +142 142 | # avoid reporting twice +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 + +PYI016.py:144:36: PYI016 [*] Duplicate union member `complex` + | +142 | # avoid reporting twice +143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +144 | field49: typing.Optional[complex | complex] | complex + | ^^^^^^^ PYI016 + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +141 141 | +142 142 | # avoid reporting twice +143 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +144 |-field49: typing.Optional[complex | complex] | complex + 144 |+field49: None | complex + +PYI016.py:144:47: PYI016 [*] Duplicate union member `complex` + | +142 | # avoid reporting twice +143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +144 | field49: typing.Optional[complex | complex] | complex + | ^^^^^^^ PYI016 + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +141 141 | +142 142 | # avoid reporting twice +143 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +144 |-field49: typing.Optional[complex | complex] | complex + 144 |+field49: None | complex diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.pyi.snap new file mode 100644 index 0000000000..4af0da5a50 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__preview__PYI016_PYI016.pyi.snap @@ -0,0 +1,1194 @@ +--- +source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs +--- +PYI016.pyi:7:15: PYI016 [*] Duplicate union member `str` + | +6 | # Should emit for duplicate field types. +7 | field2: str | str # PYI016: Duplicate union member `str` + | ^^^ PYI016 +8 | +9 | # Should emit for union types in arguments. + | + = help: Remove duplicate union member `str` + +ℹ Safe fix +4 4 | field1: str +5 5 | +6 6 | # Should emit for duplicate field types. +7 |-field2: str | str # PYI016: Duplicate union member `str` + 7 |+field2: str # PYI016: Duplicate union member `str` +8 8 | +9 9 | # Should emit for union types in arguments. +10 10 | def func1(arg1: int | int): # PYI016: Duplicate union member `int` + +PYI016.pyi:10:23: PYI016 [*] Duplicate union member `int` + | + 9 | # Should emit for union types in arguments. +10 | def func1(arg1: int | int): # PYI016: Duplicate union member `int` + | ^^^ PYI016 +11 | print(arg1) + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +7 7 | field2: str | str # PYI016: Duplicate union member `str` +8 8 | +9 9 | # Should emit for union types in arguments. +10 |-def func1(arg1: int | int): # PYI016: Duplicate union member `int` + 10 |+def func1(arg1: int): # PYI016: Duplicate union member `int` +11 11 | print(arg1) +12 12 | +13 13 | # Should emit for unions in return types. + +PYI016.pyi:14:22: PYI016 [*] Duplicate union member `str` + | +13 | # Should emit for unions in return types. +14 | def func2() -> str | str: # PYI016: Duplicate union member `str` + | ^^^ PYI016 +15 | return "my string" + | + = help: Remove duplicate union member `str` + +ℹ Safe fix +11 11 | print(arg1) +12 12 | +13 13 | # Should emit for unions in return types. +14 |-def func2() -> str | str: # PYI016: Duplicate union member `str` + 14 |+def func2() -> str: # PYI016: Duplicate union member `str` +15 15 | return "my string" +16 16 | +17 17 | # Should emit in longer unions, even if not directly adjacent. + +PYI016.pyi:18:15: PYI016 [*] Duplicate union member `str` + | +17 | # Should emit in longer unions, even if not directly adjacent. +18 | field3: str | str | int # PYI016: Duplicate union member `str` + | ^^^ PYI016 +19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 | field5: str | int | str # PYI016: Duplicate union member `str` + | + = help: Remove duplicate union member `str` + +ℹ Safe fix +15 15 | return "my string" +16 16 | +17 17 | # Should emit in longer unions, even if not directly adjacent. +18 |-field3: str | str | int # PYI016: Duplicate union member `str` + 18 |+field3: str | int # PYI016: Duplicate union member `str` +19 19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 20 | field5: str | int | str # PYI016: Duplicate union member `str` +21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` + +PYI016.pyi:19:15: PYI016 [*] Duplicate union member `int` + | +17 | # Should emit in longer unions, even if not directly adjacent. +18 | field3: str | str | int # PYI016: Duplicate union member `str` +19 | field4: int | int | str # PYI016: Duplicate union member `int` + | ^^^ PYI016 +20 | field5: str | int | str # PYI016: Duplicate union member `str` +21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +16 16 | +17 17 | # Should emit in longer unions, even if not directly adjacent. +18 18 | field3: str | str | int # PYI016: Duplicate union member `str` +19 |-field4: int | int | str # PYI016: Duplicate union member `int` + 19 |+field4: int | str # PYI016: Duplicate union member `int` +20 20 | field5: str | int | str # PYI016: Duplicate union member `str` +21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` +22 22 | + +PYI016.pyi:20:21: PYI016 [*] Duplicate union member `str` + | +18 | field3: str | str | int # PYI016: Duplicate union member `str` +19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 | field5: str | int | str # PYI016: Duplicate union member `str` + | ^^^ PYI016 +21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` + | + = help: Remove duplicate union member `str` + +ℹ Safe fix +17 17 | # Should emit in longer unions, even if not directly adjacent. +18 18 | field3: str | str | int # PYI016: Duplicate union member `str` +19 19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 |-field5: str | int | str # PYI016: Duplicate union member `str` + 20 |+field5: str | int # PYI016: Duplicate union member `str` +21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` +22 22 | +23 23 | # Shouldn't emit for non-type unions. + +PYI016.pyi:21:28: PYI016 [*] Duplicate union member `int` + | +19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 | field5: str | int | str # PYI016: Duplicate union member `str` +21 | field6: int | bool | str | int # PYI016: Duplicate union member `int` + | ^^^ PYI016 +22 | +23 | # Shouldn't emit for non-type unions. + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +18 18 | field3: str | str | int # PYI016: Duplicate union member `str` +19 19 | field4: int | int | str # PYI016: Duplicate union member `int` +20 20 | field5: str | int | str # PYI016: Duplicate union member `str` +21 |-field6: int | bool | str | int # PYI016: Duplicate union member `int` + 21 |+field6: int | bool | str # PYI016: Duplicate union member `int` +22 22 | +23 23 | # Shouldn't emit for non-type unions. +24 24 | field7 = str | str + +PYI016.pyi:27:22: PYI016 [*] Duplicate union member `int` + | +26 | # Should emit for strangely-bracketed unions. +27 | field8: int | (str | int) # PYI016: Duplicate union member `int` + | ^^^ PYI016 +28 | +29 | # Should handle user brackets when fixing. + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +24 24 | field7 = str | str +25 25 | +26 26 | # Should emit for strangely-bracketed unions. +27 |-field8: int | (str | int) # PYI016: Duplicate union member `int` + 27 |+field8: int | str # PYI016: Duplicate union member `int` +28 28 | +29 29 | # Should handle user brackets when fixing. +30 30 | field9: int | (int | str) # PYI016: Duplicate union member `int` + +PYI016.pyi:30:16: PYI016 [*] Duplicate union member `int` + | +29 | # Should handle user brackets when fixing. +30 | field9: int | (int | str) # PYI016: Duplicate union member `int` + | ^^^ PYI016 +31 | field10: (str | int) | str # PYI016: Duplicate union member `str` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +27 27 | field8: int | (str | int) # PYI016: Duplicate union member `int` +28 28 | +29 29 | # Should handle user brackets when fixing. +30 |-field9: int | (int | str) # PYI016: Duplicate union member `int` + 30 |+field9: int | str # PYI016: Duplicate union member `int` +31 31 | field10: (str | int) | str # PYI016: Duplicate union member `str` +32 32 | +33 33 | # Should emit for nested unions. + +PYI016.pyi:31:24: PYI016 [*] Duplicate union member `str` + | +29 | # Should handle user brackets when fixing. +30 | field9: int | (int | str) # PYI016: Duplicate union member `int` +31 | field10: (str | int) | str # PYI016: Duplicate union member `str` + | ^^^ PYI016 +32 | +33 | # Should emit for nested unions. + | + = help: Remove duplicate union member `str` + +ℹ Safe fix +28 28 | +29 29 | # Should handle user brackets when fixing. +30 30 | field9: int | (int | str) # PYI016: Duplicate union member `int` +31 |-field10: (str | int) | str # PYI016: Duplicate union member `str` + 31 |+field10: str | int # PYI016: Duplicate union member `str` +32 32 | +33 33 | # Should emit for nested unions. +34 34 | field11: dict[int | int, str] + +PYI016.pyi:34:21: PYI016 [*] Duplicate union member `int` + | +33 | # Should emit for nested unions. +34 | field11: dict[int | int, str] + | ^^^ PYI016 +35 | +36 | # Should emit for unions with more than two cases + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +31 31 | field10: (str | int) | str # PYI016: Duplicate union member `str` +32 32 | +33 33 | # Should emit for nested unions. +34 |-field11: dict[int | int, str] + 34 |+field11: dict[int, str] +35 35 | +36 36 | # Should emit for unions with more than two cases +37 37 | field12: int | int | int # Error + +PYI016.pyi:37:16: PYI016 [*] Duplicate union member `int` + | +36 | # Should emit for unions with more than two cases +37 | field12: int | int | int # Error + | ^^^ PYI016 +38 | field13: int | int | int | int # Error + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +34 34 | field11: dict[int | int, str] +35 35 | +36 36 | # Should emit for unions with more than two cases +37 |-field12: int | int | int # Error + 37 |+field12: int # Error +38 38 | field13: int | int | int | int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent + +PYI016.pyi:37:22: PYI016 [*] Duplicate union member `int` + | +36 | # Should emit for unions with more than two cases +37 | field12: int | int | int # Error + | ^^^ PYI016 +38 | field13: int | int | int | int # Error + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +34 34 | field11: dict[int | int, str] +35 35 | +36 36 | # Should emit for unions with more than two cases +37 |-field12: int | int | int # Error + 37 |+field12: int # Error +38 38 | field13: int | int | int | int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent + +PYI016.pyi:38:16: PYI016 [*] Duplicate union member `int` + | +36 | # Should emit for unions with more than two cases +37 | field12: int | int | int # Error +38 | field13: int | int | int | int # Error + | ^^^ PYI016 +39 | +40 | # Should emit for unions with more than two cases, even if not directly adjacent + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +35 35 | +36 36 | # Should emit for unions with more than two cases +37 37 | field12: int | int | int # Error +38 |-field13: int | int | int | int # Error + 38 |+field13: int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 41 | field14: int | int | str | int # Error + +PYI016.pyi:38:22: PYI016 [*] Duplicate union member `int` + | +36 | # Should emit for unions with more than two cases +37 | field12: int | int | int # Error +38 | field13: int | int | int | int # Error + | ^^^ PYI016 +39 | +40 | # Should emit for unions with more than two cases, even if not directly adjacent + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +35 35 | +36 36 | # Should emit for unions with more than two cases +37 37 | field12: int | int | int # Error +38 |-field13: int | int | int | int # Error + 38 |+field13: int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 41 | field14: int | int | str | int # Error + +PYI016.pyi:38:28: PYI016 [*] Duplicate union member `int` + | +36 | # Should emit for unions with more than two cases +37 | field12: int | int | int # Error +38 | field13: int | int | int | int # Error + | ^^^ PYI016 +39 | +40 | # Should emit for unions with more than two cases, even if not directly adjacent + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +35 35 | +36 36 | # Should emit for unions with more than two cases +37 37 | field12: int | int | int # Error +38 |-field13: int | int | int | int # Error + 38 |+field13: int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 41 | field14: int | int | str | int # Error + +PYI016.pyi:41:16: PYI016 [*] Duplicate union member `int` + | +40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 | field14: int | int | str | int # Error + | ^^^ PYI016 +42 | +43 | # Should emit for duplicate literal types; also covered by PYI030 + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +38 38 | field13: int | int | int | int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 |-field14: int | int | str | int # Error + 41 |+field14: int | str # Error +42 42 | +43 43 | # Should emit for duplicate literal types; also covered by PYI030 +44 44 | field15: typing.Literal[1] | typing.Literal[1] # Error + +PYI016.pyi:41:28: PYI016 [*] Duplicate union member `int` + | +40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 | field14: int | int | str | int # Error + | ^^^ PYI016 +42 | +43 | # Should emit for duplicate literal types; also covered by PYI030 + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +38 38 | field13: int | int | int | int # Error +39 39 | +40 40 | # Should emit for unions with more than two cases, even if not directly adjacent +41 |-field14: int | int | str | int # Error + 41 |+field14: int | str # Error +42 42 | +43 43 | # Should emit for duplicate literal types; also covered by PYI030 +44 44 | field15: typing.Literal[1] | typing.Literal[1] # Error + +PYI016.pyi:44:30: PYI016 [*] Duplicate union member `typing.Literal[1]` + | +43 | # Should emit for duplicate literal types; also covered by PYI030 +44 | field15: typing.Literal[1] | typing.Literal[1] # Error + | ^^^^^^^^^^^^^^^^^ PYI016 +45 | +46 | # Shouldn't emit if in new parent type + | + = help: Remove duplicate union member `typing.Literal[1]` + +ℹ Safe fix +41 41 | field14: int | int | str | int # Error +42 42 | +43 43 | # Should emit for duplicate literal types; also covered by PYI030 +44 |-field15: typing.Literal[1] | typing.Literal[1] # Error + 44 |+field15: typing.Literal[1] # Error +45 45 | +46 46 | # Shouldn't emit if in new parent type +47 47 | field16: int | dict[int, str] # OK + +PYI016.pyi:57:5: PYI016 [*] Duplicate union member `set[int]` + | +55 | int # foo +56 | ], +57 | / set[ +58 | | int # bar +59 | | ], + | |_____^ PYI016 +60 | ] # Error, newline and comment will not be emitted in message + | + = help: Remove duplicate union member `set[int]` + +ℹ Unsafe fix +50 50 | field17: dict[int, int] # OK +51 51 | +52 52 | # Should emit in cases with newlines +53 |-field18: typing.Union[ +54 |- set[ +55 |- int # foo +56 |- ], +57 |- set[ +58 |- int # bar +59 |- ], +60 |-] # Error, newline and comment will not be emitted in message + 53 |+field18: set[int] # Error, newline and comment will not be emitted in message +61 54 | +62 55 | # Should emit in cases with `typing.Union` instead of `|` +63 56 | field19: typing.Union[int, int] # Error + +PYI016.pyi:63:28: PYI016 [*] Duplicate union member `int` + | +62 | # Should emit in cases with `typing.Union` instead of `|` +63 | field19: typing.Union[int, int] # Error + | ^^^ PYI016 +64 | +65 | # Should emit in cases with nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +60 60 | ] # Error, newline and comment will not be emitted in message +61 61 | +62 62 | # Should emit in cases with `typing.Union` instead of `|` +63 |-field19: typing.Union[int, int] # Error + 63 |+field19: int # Error +64 64 | +65 65 | # Should emit in cases with nested `typing.Union` +66 66 | field20: typing.Union[int, typing.Union[int, str]] # Error + +PYI016.pyi:66:41: PYI016 [*] Duplicate union member `int` + | +65 | # Should emit in cases with nested `typing.Union` +66 | field20: typing.Union[int, typing.Union[int, str]] # Error + | ^^^ PYI016 +67 | +68 | # Should emit in cases with mixed `typing.Union` and `|` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +63 63 | field19: typing.Union[int, int] # Error +64 64 | +65 65 | # Should emit in cases with nested `typing.Union` +66 |-field20: typing.Union[int, typing.Union[int, str]] # Error + 66 |+field20: typing.Union[int, str] # Error +67 67 | +68 68 | # Should emit in cases with mixed `typing.Union` and `|` +69 69 | field21: typing.Union[int, int | str] # Error + +PYI016.pyi:69:28: PYI016 [*] Duplicate union member `int` + | +68 | # Should emit in cases with mixed `typing.Union` and `|` +69 | field21: typing.Union[int, int | str] # Error + | ^^^ PYI016 +70 | +71 | # Should emit only once in cases with multiple nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +66 66 | field20: typing.Union[int, typing.Union[int, str]] # Error +67 67 | +68 68 | # Should emit in cases with mixed `typing.Union` and `|` +69 |-field21: typing.Union[int, int | str] # Error + 69 |+field21: int | str # Error +70 70 | +71 71 | # Should emit only once in cases with multiple nested `typing.Union` +72 72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + +PYI016.pyi:72:41: PYI016 [*] Duplicate union member `int` + | +71 | # Should emit only once in cases with multiple nested `typing.Union` +72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + | ^^^ PYI016 +73 | +74 | # Should emit in cases with newlines + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +69 69 | field21: typing.Union[int, int | str] # Error +70 70 | +71 71 | # Should emit only once in cases with multiple nested `typing.Union` +72 |-field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + 72 |+field22: int # Error +73 73 | +74 74 | # Should emit in cases with newlines +75 75 | field23: set[ # foo + +PYI016.pyi:72:59: PYI016 [*] Duplicate union member `int` + | +71 | # Should emit only once in cases with multiple nested `typing.Union` +72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + | ^^^ PYI016 +73 | +74 | # Should emit in cases with newlines + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +69 69 | field21: typing.Union[int, int | str] # Error +70 70 | +71 71 | # Should emit only once in cases with multiple nested `typing.Union` +72 |-field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + 72 |+field22: int # Error +73 73 | +74 74 | # Should emit in cases with newlines +75 75 | field23: set[ # foo + +PYI016.pyi:72:64: PYI016 [*] Duplicate union member `int` + | +71 | # Should emit only once in cases with multiple nested `typing.Union` +72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + | ^^^ PYI016 +73 | +74 | # Should emit in cases with newlines + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +69 69 | field21: typing.Union[int, int | str] # Error +70 70 | +71 71 | # Should emit only once in cases with multiple nested `typing.Union` +72 |-field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error + 72 |+field22: int # Error +73 73 | +74 74 | # Should emit in cases with newlines +75 75 | field23: set[ # foo + +PYI016.pyi:76:12: PYI016 [*] Duplicate union member `set[int]` + | +74 | # Should emit in cases with newlines +75 | field23: set[ # foo +76 | int] | set[int] + | ^^^^^^^^ PYI016 +77 | +78 | # Should emit twice (once for each `int` in the nested union, both of which are + | + = help: Remove duplicate union member `set[int]` + +ℹ Unsafe fix +72 72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error +73 73 | +74 74 | # Should emit in cases with newlines +75 |-field23: set[ # foo +76 |- int] | set[int] + 75 |+field23: set[int] +77 76 | +78 77 | # Should emit twice (once for each `int` in the nested union, both of which are +79 78 | # duplicates of the outer `int`), but not three times (which would indicate that + +PYI016.pyi:81:41: PYI016 [*] Duplicate union member `int` + | +79 | # duplicates of the outer `int`), but not three times (which would indicate that +80 | # we incorrectly re-checked the nested union). +81 | field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +82 | +83 | # Should emit twice (once for each `int` in the nested union, both of which are + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +78 78 | # Should emit twice (once for each `int` in the nested union, both of which are +79 79 | # duplicates of the outer `int`), but not three times (which would indicate that +80 80 | # we incorrectly re-checked the nested union). +81 |-field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int` + 81 |+field24: int # PYI016: Duplicate union member `int` +82 82 | +83 83 | # Should emit twice (once for each `int` in the nested union, both of which are +84 84 | # duplicates of the outer `int`), but not three times (which would indicate that + +PYI016.pyi:81:46: PYI016 [*] Duplicate union member `int` + | +79 | # duplicates of the outer `int`), but not three times (which would indicate that +80 | # we incorrectly re-checked the nested union). +81 | field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +82 | +83 | # Should emit twice (once for each `int` in the nested union, both of which are + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +78 78 | # Should emit twice (once for each `int` in the nested union, both of which are +79 79 | # duplicates of the outer `int`), but not three times (which would indicate that +80 80 | # we incorrectly re-checked the nested union). +81 |-field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int` + 81 |+field24: int # PYI016: Duplicate union member `int` +82 82 | +83 83 | # Should emit twice (once for each `int` in the nested union, both of which are +84 84 | # duplicates of the outer `int`), but not three times (which would indicate that + +PYI016.pyi:86:28: PYI016 [*] Duplicate union member `int` + | +84 | # duplicates of the outer `int`), but not three times (which would indicate that +85 | # we incorrectly re-checked the nested union). +86 | field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +87 | +88 | # Should emit in cases with nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +83 83 | # Should emit twice (once for each `int` in the nested union, both of which are +84 84 | # duplicates of the outer `int`), but not three times (which would indicate that +85 85 | # we incorrectly re-checked the nested union). +86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int` + 86 |+field25: int # PYI016: Duplicate union member `int` +87 87 | +88 88 | # Should emit in cases with nested `typing.Union` +89 89 | field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int` + +PYI016.pyi:86:34: PYI016 [*] Duplicate union member `int` + | +84 | # duplicates of the outer `int`), but not three times (which would indicate that +85 | # we incorrectly re-checked the nested union). +86 | field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +87 | +88 | # Should emit in cases with nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +83 83 | # Should emit twice (once for each `int` in the nested union, both of which are +84 84 | # duplicates of the outer `int`), but not three times (which would indicate that +85 85 | # we incorrectly re-checked the nested union). +86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int` + 86 |+field25: int # PYI016: Duplicate union member `int` +87 87 | +88 88 | # Should emit in cases with nested `typing.Union` +89 89 | field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int` + +PYI016.pyi:89:41: PYI016 [*] Duplicate union member `int` + | +88 | # Should emit in cases with nested `typing.Union` +89 | field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +90 | +91 | # Should emit in cases with nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +86 86 | field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int` +87 87 | +88 88 | # Should emit in cases with nested `typing.Union` +89 |-field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int` + 89 |+field26: int # PYI016: Duplicate union member `int` +90 90 | +91 91 | # Should emit in cases with nested `typing.Union` +92 92 | field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int` + +PYI016.pyi:92:54: PYI016 [*] Duplicate union member `int` + | +91 | # Should emit in cases with nested `typing.Union` +92 | field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int` + | ^^^ PYI016 +93 | +94 | # Should emit in cases with mixed `typing.Union` and `|` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +89 89 | field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int` +90 90 | +91 91 | # Should emit in cases with nested `typing.Union` +92 |-field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int` + 92 |+field27: int # PYI016: Duplicate union member `int` +93 93 | +94 94 | # Should emit in cases with mixed `typing.Union` and `|` +95 95 | field28: typing.Union[int | int] # Error + +PYI016.pyi:95:29: PYI016 [*] Duplicate union member `int` + | +94 | # Should emit in cases with mixed `typing.Union` and `|` +95 | field28: typing.Union[int | int] # Error + | ^^^ PYI016 +96 | +97 | # Should emit twice in cases with multiple nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +92 92 | field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int` +93 93 | +94 94 | # Should emit in cases with mixed `typing.Union` and `|` +95 |-field28: typing.Union[int | int] # Error + 95 |+field28: int # Error +96 96 | +97 97 | # Should emit twice in cases with multiple nested `typing.Union` +98 98 | field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error + +PYI016.pyi:98:54: PYI016 [*] Duplicate union member `int` + | + 97 | # Should emit twice in cases with multiple nested `typing.Union` + 98 | field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error + | ^^^ PYI016 + 99 | +100 | # Should emit once in cases with multiple nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +95 95 | field28: typing.Union[int | int] # Error +96 96 | +97 97 | # Should emit twice in cases with multiple nested `typing.Union` +98 |-field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error + 98 |+field29: int # Error +99 99 | +100 100 | # Should emit once in cases with multiple nested `typing.Union` +101 101 | field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error + +PYI016.pyi:98:59: PYI016 [*] Duplicate union member `int` + | + 97 | # Should emit twice in cases with multiple nested `typing.Union` + 98 | field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error + | ^^^ PYI016 + 99 | +100 | # Should emit once in cases with multiple nested `typing.Union` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +95 95 | field28: typing.Union[int | int] # Error +96 96 | +97 97 | # Should emit twice in cases with multiple nested `typing.Union` +98 |-field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error + 98 |+field29: int # Error +99 99 | +100 100 | # Should emit once in cases with multiple nested `typing.Union` +101 101 | field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error + +PYI016.pyi:101:54: PYI016 [*] Duplicate union member `int` + | +100 | # Should emit once in cases with multiple nested `typing.Union` +101 | field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error + | ^^^ PYI016 +102 | +103 | # Should emit once, and fix to `typing.Union[float, int]` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +98 98 | field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error +99 99 | +100 100 | # Should emit once in cases with multiple nested `typing.Union` +101 |-field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error + 101 |+field30: typing.Union[int, str] # Error +102 102 | +103 103 | # Should emit once, and fix to `typing.Union[float, int]` +104 104 | field31: typing.Union[float, typing.Union[int | int]] # Error + +PYI016.pyi:104:49: PYI016 [*] Duplicate union member `int` + | +103 | # Should emit once, and fix to `typing.Union[float, int]` +104 | field31: typing.Union[float, typing.Union[int | int]] # Error + | ^^^ PYI016 +105 | +106 | # Should emit once, and fix to `typing.Union[float, int]` + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +101 101 | field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error +102 102 | +103 103 | # Should emit once, and fix to `typing.Union[float, int]` +104 |-field31: typing.Union[float, typing.Union[int | int]] # Error + 104 |+field31: float | int # Error +105 105 | +106 106 | # Should emit once, and fix to `typing.Union[float, int]` +107 107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error + +PYI016.pyi:107:49: PYI016 [*] Duplicate union member `int` + | +106 | # Should emit once, and fix to `typing.Union[float, int]` +107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error + | ^^^ PYI016 +108 | +109 | # Test case for mixed union type fix + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +104 104 | field31: typing.Union[float, typing.Union[int | int]] # Error +105 105 | +106 106 | # Should emit once, and fix to `typing.Union[float, int]` +107 |-field32: typing.Union[float, typing.Union[int | int | int]] # Error + 107 |+field32: float | int # Error +108 108 | +109 109 | # Test case for mixed union type fix +110 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + +PYI016.pyi:107:55: PYI016 [*] Duplicate union member `int` + | +106 | # Should emit once, and fix to `typing.Union[float, int]` +107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error + | ^^^ PYI016 +108 | +109 | # Test case for mixed union type fix + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +104 104 | field31: typing.Union[float, typing.Union[int | int]] # Error +105 105 | +106 106 | # Should emit once, and fix to `typing.Union[float, int]` +107 |-field32: typing.Union[float, typing.Union[int | int | int]] # Error + 107 |+field32: float | int # Error +108 108 | +109 109 | # Test case for mixed union type fix +110 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + +PYI016.pyi:110:42: PYI016 [*] Duplicate union member `int` + | +109 | # Test case for mixed union type fix +110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + | ^^^ PYI016 +111 | +112 | # Test case for mixed union type + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +107 107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error +108 108 | +109 109 | # Test case for mixed union type fix +110 |-field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + 110 |+field33: int # Error +111 111 | +112 112 | # Test case for mixed union type +113 113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + +PYI016.pyi:110:62: PYI016 [*] Duplicate union member `int` + | +109 | # Test case for mixed union type fix +110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + | ^^^ PYI016 +111 | +112 | # Test case for mixed union type + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +107 107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error +108 108 | +109 109 | # Test case for mixed union type fix +110 |-field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + 110 |+field33: int # Error +111 111 | +112 112 | # Test case for mixed union type +113 113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + +PYI016.pyi:110:68: PYI016 [*] Duplicate union member `int` + | +109 | # Test case for mixed union type fix +110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + | ^^^ PYI016 +111 | +112 | # Test case for mixed union type + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +107 107 | field32: typing.Union[float, typing.Union[int | int | int]] # Error +108 108 | +109 109 | # Test case for mixed union type fix +110 |-field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error + 110 |+field33: int # Error +111 111 | +112 112 | # Test case for mixed union type +113 113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + +PYI016.pyi:113:61: PYI016 [*] Duplicate union member `list[int]` + | +112 | # Test case for mixed union type +113 | field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + | ^^^^^^^^^ PYI016 +114 | +115 | # https://github.com/astral-sh/ruff/issues/18546 + | + = help: Remove duplicate union member `list[int]` + +ℹ Safe fix +110 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error +111 111 | +112 112 | # Test case for mixed union type +113 |-field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error + 113 |+field34: typing.Union[list[int], str, bytes] # Error +114 114 | +115 115 | # https://github.com/astral-sh/ruff/issues/18546 +116 116 | # Expand Optional[T] to Union[T, None] + +PYI016.pyi:121:26: PYI016 [*] Duplicate union member `None` + | +119 | field38: typing.Union[int, None] +120 | # equivalent to None +121 | field39: typing.Optional[None] + | ^^^^ PYI016 +122 | # equivalent to int | None +123 | field40: typing.Union[typing.Optional[int], None] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +118 118 | field37: typing.Optional[int] +119 119 | field38: typing.Union[int, None] +120 120 | # equivalent to None +121 |-field39: typing.Optional[None] + 121 |+field39: None +122 122 | # equivalent to int | None +123 123 | field40: typing.Union[typing.Optional[int], None] +124 124 | field41: typing.Optional[typing.Union[int, None]] + +PYI016.pyi:123:45: PYI016 [*] Duplicate union member `None` + | +121 | field39: typing.Optional[None] +122 | # equivalent to int | None +123 | field40: typing.Union[typing.Optional[int], None] + | ^^^^ PYI016 +124 | field41: typing.Optional[typing.Union[int, None]] +125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +120 120 | # equivalent to None +121 121 | field39: typing.Optional[None] +122 122 | # equivalent to int | None +123 |-field40: typing.Union[typing.Optional[int], None] + 123 |+field40: typing.Union[None, int] +124 124 | field41: typing.Optional[typing.Union[int, None]] +125 125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +126 126 | field43: typing.Optional[int] | None + +PYI016.pyi:124:44: PYI016 [*] Duplicate union member `None` + | +122 | # equivalent to int | None +123 | field40: typing.Union[typing.Optional[int], None] +124 | field41: typing.Optional[typing.Union[int, None]] + | ^^^^ PYI016 +125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +126 | field43: typing.Optional[int] | None + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +121 121 | field39: typing.Optional[None] +122 122 | # equivalent to int | None +123 123 | field40: typing.Union[typing.Optional[int], None] +124 |-field41: typing.Optional[typing.Union[int, None]] + 124 |+field41: typing.Union[None, int] +125 125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +126 126 | field43: typing.Optional[int] | None +127 127 | field44: typing.Optional[int | None] + +PYI016.pyi:125:45: PYI016 [*] Duplicate union member `None` + | +123 | field40: typing.Union[typing.Optional[int], None] +124 | field41: typing.Optional[typing.Union[int, None]] +125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] + | ^^^^^^^^^^^^^^^ PYI016 +126 | field43: typing.Optional[int] | None +127 | field44: typing.Optional[int | None] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +122 122 | # equivalent to int | None +123 123 | field40: typing.Union[typing.Optional[int], None] +124 124 | field41: typing.Optional[typing.Union[int, None]] +125 |-field42: typing.Union[typing.Optional[int], typing.Optional[int]] + 125 |+field42: typing.Union[None, int] +126 126 | field43: typing.Optional[int] | None +127 127 | field44: typing.Optional[int | None] +128 128 | field45: typing.Optional[int] | typing.Optional[int] + +PYI016.pyi:125:61: PYI016 [*] Duplicate union member `int` + | +123 | field40: typing.Union[typing.Optional[int], None] +124 | field41: typing.Optional[typing.Union[int, None]] +125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] + | ^^^ PYI016 +126 | field43: typing.Optional[int] | None +127 | field44: typing.Optional[int | None] + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +122 122 | # equivalent to int | None +123 123 | field40: typing.Union[typing.Optional[int], None] +124 124 | field41: typing.Optional[typing.Union[int, None]] +125 |-field42: typing.Union[typing.Optional[int], typing.Optional[int]] + 125 |+field42: typing.Union[None, int] +126 126 | field43: typing.Optional[int] | None +127 127 | field44: typing.Optional[int | None] +128 128 | field45: typing.Optional[int] | typing.Optional[int] + +PYI016.pyi:126:33: PYI016 [*] Duplicate union member `None` + | +124 | field41: typing.Optional[typing.Union[int, None]] +125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +126 | field43: typing.Optional[int] | None + | ^^^^ PYI016 +127 | field44: typing.Optional[int | None] +128 | field45: typing.Optional[int] | typing.Optional[int] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +123 123 | field40: typing.Union[typing.Optional[int], None] +124 124 | field41: typing.Optional[typing.Union[int, None]] +125 125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +126 |-field43: typing.Optional[int] | None + 126 |+field43: None | int +127 127 | field44: typing.Optional[int | None] +128 128 | field45: typing.Optional[int] | typing.Optional[int] +129 129 | # equivalent to int | dict | None + +PYI016.pyi:127:32: PYI016 [*] Duplicate union member `None` + | +125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +126 | field43: typing.Optional[int] | None +127 | field44: typing.Optional[int | None] + | ^^^^ PYI016 +128 | field45: typing.Optional[int] | typing.Optional[int] +129 | # equivalent to int | dict | None + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +124 124 | field41: typing.Optional[typing.Union[int, None]] +125 125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +126 126 | field43: typing.Optional[int] | None +127 |-field44: typing.Optional[int | None] + 127 |+field44: None | int +128 128 | field45: typing.Optional[int] | typing.Optional[int] +129 129 | # equivalent to int | dict | None +130 130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + +PYI016.pyi:128:33: PYI016 [*] Duplicate union member `None` + | +126 | field43: typing.Optional[int] | None +127 | field44: typing.Optional[int | None] +128 | field45: typing.Optional[int] | typing.Optional[int] + | ^^^^^^^^^^^^^^^ PYI016 +129 | # equivalent to int | dict | None +130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +125 125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +126 126 | field43: typing.Optional[int] | None +127 127 | field44: typing.Optional[int | None] +128 |-field45: typing.Optional[int] | typing.Optional[int] + 128 |+field45: typing.Union[None, int] +129 129 | # equivalent to int | dict | None +130 130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +131 131 | field47: typing.Optional[int] | typing.Optional[dict] + +PYI016.pyi:128:49: PYI016 [*] Duplicate union member `int` + | +126 | field43: typing.Optional[int] | None +127 | field44: typing.Optional[int | None] +128 | field45: typing.Optional[int] | typing.Optional[int] + | ^^^ PYI016 +129 | # equivalent to int | dict | None +130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + | + = help: Remove duplicate union member `int` + +ℹ Safe fix +125 125 | field42: typing.Union[typing.Optional[int], typing.Optional[int]] +126 126 | field43: typing.Optional[int] | None +127 127 | field44: typing.Optional[int | None] +128 |-field45: typing.Optional[int] | typing.Optional[int] + 128 |+field45: typing.Union[None, int] +129 129 | # equivalent to int | dict | None +130 130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +131 131 | field47: typing.Optional[int] | typing.Optional[dict] + +PYI016.pyi:130:45: PYI016 [*] Duplicate union member `None` + | +128 | field45: typing.Optional[int] | typing.Optional[int] +129 | # equivalent to int | dict | None +130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + | ^^^^^^^^^^^^^^^ PYI016 +131 | field47: typing.Optional[int] | typing.Optional[dict] + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +127 127 | field44: typing.Optional[int | None] +128 128 | field45: typing.Optional[int] | typing.Optional[int] +129 129 | # equivalent to int | dict | None +130 |-field46: typing.Union[typing.Optional[int], typing.Optional[dict]] + 130 |+field46: typing.Union[None, int, dict] +131 131 | field47: typing.Optional[int] | typing.Optional[dict] +132 132 | +133 133 | # avoid reporting twice + +PYI016.pyi:131:33: PYI016 [*] Duplicate union member `None` + | +129 | # equivalent to int | dict | None +130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +131 | field47: typing.Optional[int] | typing.Optional[dict] + | ^^^^^^^^^^^^^^^ PYI016 +132 | +133 | # avoid reporting twice + | + = help: Remove duplicate union member `None` + +ℹ Safe fix +128 128 | field45: typing.Optional[int] | typing.Optional[int] +129 129 | # equivalent to int | dict | None +130 130 | field46: typing.Union[typing.Optional[int], typing.Optional[dict]] +131 |-field47: typing.Optional[int] | typing.Optional[dict] + 131 |+field47: typing.Union[None, int, dict] +132 132 | +133 133 | # avoid reporting twice +134 134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + +PYI016.pyi:134:61: PYI016 [*] Duplicate union member `complex` + | +133 | # avoid reporting twice +134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + | ^^^^^^^ PYI016 +135 | field49: typing.Optional[complex | complex] | complex + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +131 131 | field47: typing.Optional[int] | typing.Optional[dict] +132 132 | +133 133 | # avoid reporting twice +134 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + 134 |+field48: typing.Union[None, complex] +135 135 | field49: typing.Optional[complex | complex] | complex + +PYI016.pyi:134:72: PYI016 [*] Duplicate union member `complex` + | +133 | # avoid reporting twice +134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + | ^^^^^^^ PYI016 +135 | field49: typing.Optional[complex | complex] | complex + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +131 131 | field47: typing.Optional[int] | typing.Optional[dict] +132 132 | +133 133 | # avoid reporting twice +134 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] + 134 |+field48: typing.Union[None, complex] +135 135 | field49: typing.Optional[complex | complex] | complex + +PYI016.pyi:135:36: PYI016 [*] Duplicate union member `complex` + | +133 | # avoid reporting twice +134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +135 | field49: typing.Optional[complex | complex] | complex + | ^^^^^^^ PYI016 + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +132 132 | +133 133 | # avoid reporting twice +134 134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +135 |-field49: typing.Optional[complex | complex] | complex + 135 |+field49: None | complex + +PYI016.pyi:135:47: PYI016 [*] Duplicate union member `complex` + | +133 | # avoid reporting twice +134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +135 | field49: typing.Optional[complex | complex] | complex + | ^^^^^^^ PYI016 + | + = help: Remove duplicate union member `complex` + +ℹ Safe fix +132 132 | +133 133 | # avoid reporting twice +134 134 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex] +135 |-field49: typing.Optional[complex | complex] | complex + 135 |+field49: None | complex diff --git a/crates/ruff_python_ast/src/node_index.rs b/crates/ruff_python_ast/src/node_index.rs index 794a332b4e..bf53ac386c 100644 --- a/crates/ruff_python_ast/src/node_index.rs +++ b/crates/ruff_python_ast/src/node_index.rs @@ -24,8 +24,8 @@ pub struct AtomicNodeIndex(AtomicU32); impl AtomicNodeIndex { /// Returns a placeholder `AtomicNodeIndex`. - pub fn dummy() -> AtomicNodeIndex { - AtomicNodeIndex(AtomicU32::from(u32::MAX)) + pub const fn dummy() -> AtomicNodeIndex { + AtomicNodeIndex(AtomicU32::new(u32::MAX)) } /// Load the current value of the `AtomicNodeIndex`. diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index 7038d267d4..01220fea0e 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -428,12 +428,52 @@ pub fn is_sys_version_block(stmt: &ast::StmtIf, semantic: &SemanticModel) -> boo pub fn traverse_union<'a, F>(func: &mut F, semantic: &SemanticModel, expr: &'a Expr) where F: FnMut(&'a Expr, &'a Expr), +{ + traverse_union_options(func, semantic, expr, UnionTraversalOptions::default()); +} + +/// Traverse a "union" type annotation, applying `func` to each union member. +/// +/// Supports traversal of `Union`, `|`, and `Optional` union expressions. +/// +/// The function is called with each expression in the union (excluding declarations of nested +/// unions) and the parent expression. +pub fn traverse_union_and_optional<'a, F>(func: &mut F, semantic: &SemanticModel, expr: &'a Expr) +where + F: FnMut(&'a Expr, &'a Expr), +{ + traverse_union_options( + func, + semantic, + expr, + UnionTraversalOptions { + traverse_optional: true, + }, + ); +} + +#[derive(Debug, Clone, Copy, Default)] +/// Options for traversing union types. +/// +/// See also [`traverse_union_options`]. +struct UnionTraversalOptions { + traverse_optional: bool, +} + +fn traverse_union_options<'a, F>( + func: &mut F, + semantic: &SemanticModel, + expr: &'a Expr, + options: UnionTraversalOptions, +) where + F: FnMut(&'a Expr, &'a Expr), { fn inner<'a, F>( func: &mut F, semantic: &SemanticModel, expr: &'a Expr, parent: Option<&'a Expr>, + options: UnionTraversalOptions, ) where F: FnMut(&'a Expr, &'a Expr), { @@ -456,25 +496,31 @@ where // in the order they appear in the source code. // Traverse the left then right arms - inner(func, semantic, left, Some(expr)); - inner(func, semantic, right, Some(expr)); + inner(func, semantic, left, Some(expr), options); + inner(func, semantic, right, Some(expr), options); return; } - // Ex) `Union[x, y]` if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { + // Ex) `Union[x, y]` if semantic.match_typing_expr(value, "Union") { if let Expr::Tuple(tuple) = &**slice { // Traverse each element of the tuple within the union recursively to handle cases // such as `Union[..., Union[...]]` tuple .iter() - .for_each(|elem| inner(func, semantic, elem, Some(expr))); + .for_each(|elem| inner(func, semantic, elem, Some(expr), options)); return; } // Ex) `Union[Union[a, b]]` and `Union[a | b | c]` - inner(func, semantic, slice, Some(expr)); + inner(func, semantic, slice, Some(expr), options); + return; + } + // Ex) `Optional[x]` + if options.traverse_optional && semantic.match_typing_expr(value, "Optional") { + inner(func, semantic, value, Some(expr), options); + inner(func, semantic, slice, Some(expr), options); return; } } @@ -485,7 +531,7 @@ where } } - inner(func, semantic, expr, None); + inner(func, semantic, expr, None, options); } /// Traverse a "literal" type annotation, applying `func` to each literal member. diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 6576b74437..856e043a89 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -1604,7 +1604,7 @@ impl<'a> SemanticModel<'a> { let mut parent_expressions = self.current_expressions().skip(1); match parent_expressions.next() { - // The parent expression is of the inner union is a single `typing.Union`. + // The parent expression of the inner union is a single `typing.Union`. // Ex) `Union[Union[a, b]]` Some(Expr::Subscript(parent)) => self.match_typing_expr(&parent.value, "Union"), // The parent expression is of the inner union is a tuple with two or more @@ -1624,6 +1624,18 @@ impl<'a> SemanticModel<'a> { } } + /// Return `true` if the model is directly inside an Optional (e.g., the inner `Union` in + /// `Optional[Union[int, str]]`). + pub fn inside_optional(&self) -> bool { + let mut parent_expressions = self.current_expressions().skip(1); + matches!( + parent_expressions.next(), + // The parent expression is a single `typing.Optional`. + // Ex) `Optional[EXPR]` + Some(Expr::Subscript(parent)) if self.match_typing_expr(&parent.value, "Optional") + ) + } + /// Return `true` if the model is in a nested literal expression (e.g., the inner `Literal` in /// `Literal[Literal[int, str], float]`). pub fn in_nested_literal(&self) -> bool {