Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,7 @@ def func2() -> str | str: # PYI016: Duplicate union member `str`
# avoid reporting twice
field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
field49: typing.Optional[complex | complex] | complex

# Regression test for https://github.com/astral-sh/ruff/issues/19403
# Should throw duplicate union member but not fix
isinstance(None, typing.Union[None, None])
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) {
let mut diagnostics = Vec::new();

let mut union_type = UnionKind::TypingUnion;
let mut optional_present = false;
// Adds a member to `literal_exprs` if it is a `Literal` annotation
let mut check_for_duplicate_members = |expr: &'a Expr, parent: &'a Expr| {
if matches!(parent, Expr::BinOp(_)) {
Expand All @@ -74,6 +75,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) {
&& is_optional_type(checker, expr)
{
// If the union member is an `Optional`, add a virtual `None` literal.
optional_present = true;
&VIRTUAL_NONE_LITERAL
} else {
expr
Expand All @@ -87,7 +89,7 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) {
DuplicateUnionMember {
duplicate_name: checker.generator().expr(virtual_expr),
},
// Use the real expression's range for diagnostics,
// Use the real expression's range for diagnostics.
expr.range(),
));
}
Expand All @@ -104,6 +106,13 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) {
return;
}

// Do not reduce `Union[None, ... None]` to avoid introducing a `TypeError` unintentionally
// e.g. `isinstance(None, Union[None, None])`, if reduced to `isinstance(None, None)`, causes
// `TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union` to throw.
if unique_nodes.iter().all(|expr| expr.is_none_literal_expr()) && !optional_present {
return;
}

// Mark [`Fix`] as unsafe when comments are in range.
let applicability = if checker.comment_ranges().intersects(expr.range()) {
Applicability::Unsafe
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -974,13 +974,17 @@ PYI016.py:143:61: PYI016 [*] Duplicate union member `complex`
143 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
143 |+field48: typing.Union[typing.Optional[complex], complex]
144 144 | field49: typing.Optional[complex | complex] | complex
145 145 |
146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403

PYI016.py:144:36: PYI016 [*] Duplicate union member `complex`
|
142 | # avoid reporting twice
143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
144 | field49: typing.Optional[complex | complex] | complex
| ^^^^^^^ PYI016
145 |
146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403
|
= help: Remove duplicate union member `complex`

Expand All @@ -990,3 +994,15 @@ PYI016.py:144:36: PYI016 [*] Duplicate union member `complex`
143 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
144 |-field49: typing.Optional[complex | complex] | complex
144 |+field49: typing.Optional[complex] | complex
145 145 |
146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403
147 147 | # Should throw duplicate union member but not fix

PYI016.py:148:37: PYI016 Duplicate union member `None`
|
146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403
147 | # Should throw duplicate union member but not fix
148 | isinstance(None, typing.Union[None, None])
| ^^^^ PYI016
|
= help: Remove duplicate union member `None`
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,8 @@ PYI016.py:143:61: PYI016 [*] Duplicate union member `complex`
143 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
143 |+field48: typing.Union[None, complex]
144 144 | field49: typing.Optional[complex | complex] | complex
145 145 |
146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403

PYI016.py:143:72: PYI016 [*] Duplicate union member `complex`
|
Expand All @@ -1179,13 +1181,17 @@ PYI016.py:143:72: PYI016 [*] Duplicate union member `complex`
143 |-field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
143 |+field48: typing.Union[None, complex]
144 144 | field49: typing.Optional[complex | complex] | complex
145 145 |
146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403

PYI016.py:144:36: PYI016 [*] Duplicate union member `complex`
|
142 | # avoid reporting twice
143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
144 | field49: typing.Optional[complex | complex] | complex
| ^^^^^^^ PYI016
145 |
146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403
|
= help: Remove duplicate union member `complex`

Expand All @@ -1195,13 +1201,18 @@ PYI016.py:144:36: PYI016 [*] Duplicate union member `complex`
143 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
144 |-field49: typing.Optional[complex | complex] | complex
144 |+field49: None | complex
145 145 |
146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403
147 147 | # Should throw duplicate union member but not fix

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
145 |
146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403
|
= help: Remove duplicate union member `complex`

Expand All @@ -1211,3 +1222,15 @@ PYI016.py:144:47: PYI016 [*] Duplicate union member `complex`
143 143 | field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
144 |-field49: typing.Optional[complex | complex] | complex
144 |+field49: None | complex
145 145 |
146 146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403
147 147 | # Should throw duplicate union member but not fix

PYI016.py:148:37: PYI016 Duplicate union member `None`
|
146 | # Regression test for https://github.com/astral-sh/ruff/issues/19403
147 | # Should throw duplicate union member but not fix
148 | isinstance(None, typing.Union[None, None])
| ^^^^ PYI016
|
= help: Remove duplicate union member `None`
Loading