[flake8-pyi, ruff] Fix traversal of nested literals and unions (PYI016, PYI051, PYI055, PYI062, RUF041) (#14641)

This commit is contained in:
Simon Brugman 2024-11-28 19:07:12 +01:00 committed by GitHub
parent d9cbf2fe44
commit dc29f52750
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 318 additions and 169 deletions

View file

@ -108,3 +108,6 @@ field32: typing.Union[float, typing.Union[int | int | int]] # Error
# Test case for mixed union type fix # Test case for mixed union type fix
field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error 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

View file

@ -108,3 +108,6 @@ field32: typing.Union[float, typing.Union[int | int | int]] # Error
# Test case for mixed union type fix # Test case for mixed union type fix
field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error 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

View file

@ -330,6 +330,16 @@ PYI016.py:89:41: PYI016 Duplicate union member `int`
| |
= help: Remove duplicate union member `int` = help: Remove 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`
PYI016.py:95:29: PYI016 Duplicate union member `int` PYI016.py:95:29: PYI016 Duplicate union member `int`
| |
94 | # Should emit in cases with mixed `typing.Union` and `|` 94 | # Should emit in cases with mixed `typing.Union` and `|`
@ -340,6 +350,16 @@ PYI016.py:95:29: PYI016 Duplicate union member `int`
| |
= help: Remove duplicate union member `int` = help: Remove duplicate union member `int`
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`
PYI016.py:98:59: PYI016 Duplicate union member `int` PYI016.py:98:59: PYI016 Duplicate union member `int`
| |
97 | # Should emit twice in cases with multiple nested `typing.Union` 97 | # Should emit twice in cases with multiple nested `typing.Union`
@ -350,6 +370,16 @@ PYI016.py:98:59: PYI016 Duplicate union member `int`
| |
= help: Remove duplicate union member `int` = help: Remove duplicate union member `int`
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`
PYI016.py:104:49: PYI016 Duplicate union member `int` PYI016.py:104:49: PYI016 Duplicate union member `int`
| |
103 | # Should emit once, and fix to `typing.Union[float, int]` 103 | # Should emit once, and fix to `typing.Union[float, int]`
@ -385,21 +415,35 @@ PYI016.py:110:42: PYI016 Duplicate union member `int`
109 | # Test case for mixed union type fix 109 | # Test case for mixed union type fix
110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error
| ^^^ PYI016 | ^^^ PYI016
111 |
112 | # Test case for mixed union type
| |
= help: Remove duplicate union member `int` = help: Remove duplicate union member `int`
PYI016.py:110:49: PYI016 Duplicate union member `typing.Union[int | int]` PYI016.py:110:62: PYI016 Duplicate union member `int`
| |
109 | # Test case for mixed union type fix 109 | # Test case for mixed union type fix
110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI016 | ^^^ PYI016
111 |
112 | # Test case for mixed union type
| |
= help: Remove duplicate union member `typing.Union[int | int]` = help: Remove duplicate union member `int`
PYI016.py:110:68: PYI016 Duplicate union member `int` PYI016.py:110:68: PYI016 Duplicate union member `int`
| |
109 | # Test case for mixed union type fix 109 | # Test case for mixed union type fix
110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error
| ^^^ PYI016 | ^^^ PYI016
111 |
112 | # Test case for mixed union type
| |
= help: Remove duplicate union member `int` = help: Remove duplicate union member `int`
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
|
= help: Remove duplicate union member `list[int]`

View file

@ -330,6 +330,16 @@ PYI016.pyi:89:41: PYI016 Duplicate union member `int`
| |
= help: Remove duplicate union member `int` = help: Remove 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`
PYI016.pyi:95:29: PYI016 Duplicate union member `int` PYI016.pyi:95:29: PYI016 Duplicate union member `int`
| |
94 | # Should emit in cases with mixed `typing.Union` and `|` 94 | # Should emit in cases with mixed `typing.Union` and `|`
@ -340,6 +350,16 @@ PYI016.pyi:95:29: PYI016 Duplicate union member `int`
| |
= help: Remove duplicate union member `int` = help: Remove duplicate union member `int`
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`
PYI016.pyi:98:59: PYI016 Duplicate union member `int` PYI016.pyi:98:59: PYI016 Duplicate union member `int`
| |
97 | # Should emit twice in cases with multiple nested `typing.Union` 97 | # Should emit twice in cases with multiple nested `typing.Union`
@ -350,6 +370,16 @@ PYI016.pyi:98:59: PYI016 Duplicate union member `int`
| |
= help: Remove duplicate union member `int` = help: Remove duplicate union member `int`
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`
PYI016.pyi:104:49: PYI016 Duplicate union member `int` PYI016.pyi:104:49: PYI016 Duplicate union member `int`
| |
103 | # Should emit once, and fix to `typing.Union[float, int]` 103 | # Should emit once, and fix to `typing.Union[float, int]`
@ -385,21 +415,35 @@ PYI016.pyi:110:42: PYI016 Duplicate union member `int`
109 | # Test case for mixed union type fix 109 | # Test case for mixed union type fix
110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error
| ^^^ PYI016 | ^^^ PYI016
111 |
112 | # Test case for mixed union type
| |
= help: Remove duplicate union member `int` = help: Remove duplicate union member `int`
PYI016.pyi:110:49: PYI016 Duplicate union member `typing.Union[int | int]` PYI016.pyi:110:62: PYI016 Duplicate union member `int`
| |
109 | # Test case for mixed union type fix 109 | # Test case for mixed union type fix
110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI016 | ^^^ PYI016
111 |
112 | # Test case for mixed union type
| |
= help: Remove duplicate union member `typing.Union[int | int]` = help: Remove duplicate union member `int`
PYI016.pyi:110:68: PYI016 Duplicate union member `int` PYI016.pyi:110:68: PYI016 Duplicate union member `int`
| |
109 | # Test case for mixed union type fix 109 | # Test case for mixed union type fix
110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error 110 | field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error
| ^^^ PYI016 | ^^^ PYI016
111 |
112 | # Test case for mixed union type
| |
= help: Remove duplicate union member `int` = help: Remove duplicate union member `int`
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
|
= help: Remove duplicate union member `list[int]`

View file

@ -25,6 +25,14 @@ PYI041.py:30:28: PYI041 Use `float` instead of `int | float`
| |
= help: Remove redundant type = help: Remove redundant type
PYI041.py:34:26: PYI041 Use `float` instead of `int | float`
|
34 | def f3(arg1: int, *args: Union[int | int | float]) -> None:
| ^^^^^^^^^^^^^^^^^^^^^^^^ PYI041
35 | ...
|
= help: Remove redundant type
PYI041.py:38:24: PYI041 Use `float` instead of `int | float` PYI041.py:38:24: PYI041 Use `float` instead of `int | float`
| |
38 | async def f4(**kwargs: int | int | float) -> None: 38 | async def f4(**kwargs: int | int | float) -> None:
@ -41,6 +49,30 @@ PYI041.py:42:26: PYI041 Use `float` instead of `int | float`
| |
= help: Remove redundant type = help: Remove redundant type
PYI041.py:46:26: PYI041 Use `float` instead of `int | float`
|
46 | def f6(arg1: int, *args: Union[Union[int, int, float]]) -> None:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041
47 | ...
|
= help: Remove redundant type
PYI041.py:50:26: PYI041 Use `float` instead of `int | float`
|
50 | def f7(arg1: int, *args: Union[Union[Union[int, int, float]]]) -> None:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041
51 | ...
|
= help: Remove redundant type
PYI041.py:54:26: PYI041 Use `float` instead of `int | float`
|
54 | def f8(arg1: int, *args: Union[Union[Union[int | int | float]]]) -> None:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041
55 | ...
|
= help: Remove redundant type
PYI041.py:59:10: PYI041 Use `complex` instead of `int | float | complex` PYI041.py:59:10: PYI041 Use `complex` instead of `int | float | complex`
| |
58 | def f9( 58 | def f9(

View file

@ -22,6 +22,13 @@ PYI041.pyi:27:28: PYI041 Use `float` instead of `int | float`
| |
= help: Remove redundant type = help: Remove redundant type
PYI041.pyi:30:26: PYI041 Use `float` instead of `int | float`
|
30 | def f3(arg1: int, *args: Union[int | int | float]) -> None: ... # PYI041
| ^^^^^^^^^^^^^^^^^^^^^^^^ PYI041
|
= help: Remove redundant type
PYI041.pyi:33:24: PYI041 Use `float` instead of `int | float` PYI041.pyi:33:24: PYI041 Use `float` instead of `int | float`
| |
33 | async def f4(**kwargs: int | int | float) -> None: ... # PYI041 33 | async def f4(**kwargs: int | int | float) -> None: ... # PYI041
@ -66,6 +73,27 @@ PYI041.pyi:49:26: PYI041 Use `float` instead of `int | float`
| |
= help: Remove redundant type = help: Remove redundant type
PYI041.pyi:52:26: PYI041 Use `float` instead of `int | float`
|
52 | def f6(arg1: int, *args: Union[Union[int, int, float]]) -> None: ... # PYI041
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041
|
= help: Remove redundant type
PYI041.pyi:55:26: PYI041 Use `float` instead of `int | float`
|
55 | def f7(arg1: int, *args: Union[Union[Union[int, int, float]]]) -> None: ... # PYI041
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041
|
= help: Remove redundant type
PYI041.pyi:58:26: PYI041 Use `float` instead of `int | float`
|
58 | def f8(arg1: int, *args: Union[Union[Union[int | int | float]]]) -> None: ... # PYI041
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI041
|
= help: Remove redundant type
PYI041.pyi:64:24: PYI041 Use `complex` instead of `int | float | complex` PYI041.pyi:64:24: PYI041 Use `complex` instead of `int | float | complex`
| |
62 | def good(self, arg: int) -> None: ... 62 | def good(self, arg: int) -> None: ...

View file

@ -70,6 +70,35 @@ PYI051.py:7:51: PYI051 `Literal[42]` is redundant in a union with `int`
9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
| |
PYI051.py:8:76: PYI051 `Literal["foo"]` is redundant in a union with `str`
|
6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]]
| ^^^^^ PYI051
9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
|
PYI051.py:9:81: PYI051 `Literal["foo"]` is redundant in a union with `str`
|
7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]]
9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
| ^^^^^ PYI051
10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
|
PYI051.py:10:69: PYI051 `Literal["foo"]` is redundant in a union with `str`
|
8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]]
9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
| ^^^^^ PYI051
11 |
12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...
|
PYI051.py:12:31: PYI051 `Literal[1J]` is redundant in a union with `complex` PYI051.py:12:31: PYI051 `Literal[1J]` is redundant in a union with `complex`
| |
10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]

View file

@ -70,6 +70,35 @@ PYI051.pyi:7:51: PYI051 `Literal[42]` is redundant in a union with `int`
9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
| |
PYI051.pyi:8:76: PYI051 `Literal["foo"]` is redundant in a union with `str`
|
6 | C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]]
| ^^^^^ PYI051
9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
|
PYI051.pyi:9:81: PYI051 `Literal["foo"]` is redundant in a union with `str`
|
7 | D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]]
9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
| ^^^^^ PYI051
10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
|
PYI051.pyi:10:69: PYI051 `Literal["foo"]` is redundant in a union with `str`
|
8 | E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]]
9 | F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
| ^^^^^ PYI051
11 |
12 | def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...
|
PYI051.pyi:12:31: PYI051 `Literal[1J]` is redundant in a union with `complex` PYI051.pyi:12:31: PYI051 `Literal[1J]` is redundant in a union with `complex`
| |
10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]] 10 | G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]

View file

@ -106,12 +106,12 @@ PYI055.py:50:9: PYI055 [*] Multiple `type` members in a union. Combine them into
52 52 | ... 52 52 | ...
53 53 | 53 53 |
PYI055.py:56:15: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[_T | Converter[_T]]`. PYI055.py:56:9: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[_T | Converter[_T]]`.
| |
54 | def convert_union(union: UnionType) -> _T | None: 54 | def convert_union(union: UnionType) -> _T | None:
55 | converters: tuple[ 55 | converters: tuple[
56 | Union[type[_T] | type[Converter[_T]] | Converter[_T] | Callable[[str], _T]], ... # PYI055 56 | Union[type[_T] | type[Converter[_T]] | Converter[_T] | Callable[[str], _T]], ... # PYI055
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
57 | ] = union.__args__ 57 | ] = union.__args__
58 | ... 58 | ...
| |
@ -122,17 +122,17 @@ PYI055.py:56:15: PYI055 [*] Multiple `type` members in a union. Combine them int
54 54 | def convert_union(union: UnionType) -> _T | None: 54 54 | def convert_union(union: UnionType) -> _T | None:
55 55 | converters: tuple[ 55 55 | converters: tuple[
56 |- Union[type[_T] | type[Converter[_T]] | Converter[_T] | Callable[[str], _T]], ... # PYI055 56 |- Union[type[_T] | type[Converter[_T]] | Converter[_T] | Callable[[str], _T]], ... # PYI055
56 |+ Union[type[_T | Converter[_T]] | Converter[_T] | Callable[[str], _T]], ... # PYI055 56 |+ type[_T | Converter[_T]] | Converter[_T] | Callable[[str], _T], ... # PYI055
57 57 | ] = union.__args__ 57 57 | ] = union.__args__
58 58 | ... 58 58 | ...
59 59 | 59 59 |
PYI055.py:62:15: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[_T | Converter[_T]]`. PYI055.py:62:9: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[_T | Converter[_T]]`.
| |
60 | def convert_union(union: UnionType) -> _T | None: 60 | def convert_union(union: UnionType) -> _T | None:
61 | converters: tuple[ 61 | converters: tuple[
62 | Union[type[_T] | type[Converter[_T]]] | Converter[_T] | Callable[[str], _T], ... # PYI055 62 | Union[type[_T] | type[Converter[_T]]] | Converter[_T] | Callable[[str], _T], ... # PYI055
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
63 | ] = union.__args__ 63 | ] = union.__args__
64 | ... 64 | ...
| |
@ -143,17 +143,17 @@ PYI055.py:62:15: PYI055 [*] Multiple `type` members in a union. Combine them int
60 60 | def convert_union(union: UnionType) -> _T | None: 60 60 | def convert_union(union: UnionType) -> _T | None:
61 61 | converters: tuple[ 61 61 | converters: tuple[
62 |- Union[type[_T] | type[Converter[_T]]] | Converter[_T] | Callable[[str], _T], ... # PYI055 62 |- Union[type[_T] | type[Converter[_T]]] | Converter[_T] | Callable[[str], _T], ... # PYI055
62 |+ Union[type[_T | Converter[_T]]] | Converter[_T] | Callable[[str], _T], ... # PYI055 62 |+ type[_T | Converter[_T]] | Converter[_T] | Callable[[str], _T], ... # PYI055
63 63 | ] = union.__args__ 63 63 | ] = union.__args__
64 64 | ... 64 64 | ...
65 65 | 65 65 |
PYI055.py:68:15: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[_T | Converter[_T]]`. PYI055.py:68:9: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[_T | Converter[_T]]`.
| |
66 | def convert_union(union: UnionType) -> _T | None: 66 | def convert_union(union: UnionType) -> _T | None:
67 | converters: tuple[ 67 | converters: tuple[
68 | Union[type[_T] | type[Converter[_T]] | str] | Converter[_T] | Callable[[str], _T], ... # PYI055 68 | Union[type[_T] | type[Converter[_T]] | str] | Converter[_T] | Callable[[str], _T], ... # PYI055
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
69 | ] = union.__args__ 69 | ] = union.__args__
70 | ... 70 | ...
| |
@ -164,6 +164,6 @@ PYI055.py:68:15: PYI055 [*] Multiple `type` members in a union. Combine them int
66 66 | def convert_union(union: UnionType) -> _T | None: 66 66 | def convert_union(union: UnionType) -> _T | None:
67 67 | converters: tuple[ 67 67 | converters: tuple[
68 |- Union[type[_T] | type[Converter[_T]] | str] | Converter[_T] | Callable[[str], _T], ... # PYI055 68 |- Union[type[_T] | type[Converter[_T]] | str] | Converter[_T] | Callable[[str], _T], ... # PYI055
68 |+ Union[type[_T | Converter[_T]] | str] | Converter[_T] | Callable[[str], _T], ... # PYI055 68 |+ type[_T | Converter[_T]] | str | Converter[_T] | Callable[[str], _T], ... # PYI055
69 69 | ] = union.__args__ 69 69 | ] = union.__args__
70 70 | ... 70 70 | ...

View file

@ -105,12 +105,12 @@ PYI055.pyi:8:4: PYI055 [*] Multiple `type` members in a union. Combine them into
10 10 | y: Union[Union[Union[type[float, int], type[complex]]]] 10 10 | y: Union[Union[Union[type[float, int], type[complex]]]]
11 11 | z: Union[type[complex], Union[Union[type[float, int]]]] 11 11 | z: Union[type[complex], Union[Union[type[float, int]]]]
PYI055.pyi:9:10: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`. PYI055.pyi:9:4: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`.
| |
7 | v: Union[type[float], type[complex]] 7 | v: Union[type[float], type[complex]]
8 | w: Union[type[float, int], type[complex]] 8 | w: Union[type[float, int], type[complex]]
9 | x: Union[Union[type[float, int], type[complex]]] 9 | x: Union[Union[type[float, int], type[complex]]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
10 | y: Union[Union[Union[type[float, int], type[complex]]]] 10 | y: Union[Union[Union[type[float, int], type[complex]]]]
11 | z: Union[type[complex], Union[Union[type[float, int]]]] 11 | z: Union[type[complex], Union[Union[type[float, int]]]]
| |
@ -121,11 +121,52 @@ PYI055.pyi:9:10: PYI055 [*] Multiple `type` members in a union. Combine them int
7 7 | v: Union[type[float], type[complex]] 7 7 | v: Union[type[float], type[complex]]
8 8 | w: Union[type[float, int], type[complex]] 8 8 | w: Union[type[float, int], type[complex]]
9 |-x: Union[Union[type[float, int], type[complex]]] 9 |-x: Union[Union[type[float, int], type[complex]]]
9 |+x: Union[type[Union[float, int, complex]]] 9 |+x: type[Union[float, int, complex]]
10 10 | y: Union[Union[Union[type[float, int], type[complex]]]] 10 10 | y: Union[Union[Union[type[float, int], type[complex]]]]
11 11 | z: Union[type[complex], Union[Union[type[float, int]]]] 11 11 | z: Union[type[complex], Union[Union[type[float, int]]]]
12 12 | 12 12 |
PYI055.pyi:10:4: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`.
|
8 | w: Union[type[float, int], type[complex]]
9 | x: Union[Union[type[float, int], type[complex]]]
10 | y: Union[Union[Union[type[float, int], type[complex]]]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
11 | z: Union[type[complex], Union[Union[type[float, int]]]]
|
= help: Combine multiple `type` members
Safe fix
7 7 | v: Union[type[float], type[complex]]
8 8 | w: Union[type[float, int], type[complex]]
9 9 | x: Union[Union[type[float, int], type[complex]]]
10 |-y: Union[Union[Union[type[float, int], type[complex]]]]
10 |+y: type[Union[float, int, complex]]
11 11 | z: Union[type[complex], Union[Union[type[float, int]]]]
12 12 |
13 13 | def func(arg: type[int] | str | type[float]) -> None: ...
PYI055.pyi:11:4: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[complex, float, int]]`.
|
9 | x: Union[Union[type[float, int], type[complex]]]
10 | y: Union[Union[Union[type[float, int], type[complex]]]]
11 | z: Union[type[complex], Union[Union[type[float, int]]]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
12 |
13 | def func(arg: type[int] | str | type[float]) -> None: ...
|
= help: Combine multiple `type` members
Safe fix
8 8 | w: Union[type[float, int], type[complex]]
9 9 | x: Union[Union[type[float, int], type[complex]]]
10 10 | y: Union[Union[Union[type[float, int], type[complex]]]]
11 |-z: Union[type[complex], Union[Union[type[float, int]]]]
11 |+z: type[Union[complex, float, int]]
12 12 |
13 13 | def func(arg: type[int] | str | type[float]) -> None: ...
14 14 |
PYI055.pyi:13:15: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[int | float]`. PYI055.pyi:13:15: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[int | float]`.
| |
11 | z: Union[type[complex], Union[Union[type[float, int]]]] 11 | z: Union[type[complex], Union[Union[type[float, int]]]]

View file

@ -318,24 +318,6 @@ PYI062.py:25:46: PYI062 [*] Duplicate literal member `True`
27 27 | n: Literal["No", "duplicates", "here", 1, "1"] 27 27 | n: Literal["No", "duplicates", "here", 1, "1"]
28 28 | 28 28 |
PYI062.py:32:37: PYI062 [*] Duplicate literal member `1`
|
30 | # nested literals, all equivalent to `Literal[1]`
31 | Literal[Literal[1]] # no duplicate
32 | Literal[Literal[Literal[1], Literal[1]]] # once
| ^ PYI062
33 | Literal[Literal[1], Literal[Literal[Literal[1]]]] # once
|
= help: Remove duplicates
Safe fix
29 29 |
30 30 | # nested literals, all equivalent to `Literal[1]`
31 31 | Literal[Literal[1]] # no duplicate
32 |-Literal[Literal[Literal[1], Literal[1]]] # once
32 |+Literal[Literal[1]] # once
33 33 | Literal[Literal[1], Literal[Literal[Literal[1]]]] # once
PYI062.py:32:37: PYI062 [*] Duplicate literal member `1` PYI062.py:32:37: PYI062 [*] Duplicate literal member `1`
| |
30 | # nested literals, all equivalent to `Literal[1]` 30 | # nested literals, all equivalent to `Literal[1]`

View file

@ -318,24 +318,6 @@ PYI062.pyi:25:46: PYI062 [*] Duplicate literal member `True`
27 27 | n: Literal["No", "duplicates", "here", 1, "1"] 27 27 | n: Literal["No", "duplicates", "here", 1, "1"]
28 28 | 28 28 |
PYI062.pyi:32:37: PYI062 [*] Duplicate literal member `1`
|
30 | # nested literals, all equivalent to `Literal[1]`
31 | Literal[Literal[1]] # no duplicate
32 | Literal[Literal[Literal[1], Literal[1]]] # once
| ^ PYI062
33 | Literal[Literal[1], Literal[Literal[Literal[1]]]] # once
|
= help: Remove duplicates
Safe fix
29 29 |
30 30 | # nested literals, all equivalent to `Literal[1]`
31 31 | Literal[Literal[1]] # no duplicate
32 |-Literal[Literal[Literal[1], Literal[1]]] # once
32 |+Literal[Literal[1]] # once
33 33 | Literal[Literal[1], Literal[Literal[Literal[1]]]] # once
PYI062.pyi:32:37: PYI062 [*] Duplicate literal member `1` PYI062.pyi:32:37: PYI062 [*] Duplicate literal member `1`
| |
30 | # nested literals, all equivalent to `Literal[1]` 30 | # nested literals, all equivalent to `Literal[1]`

View file

@ -216,26 +216,6 @@ RUF041.py:24:1: RUF041 [*] Unnecessary nested `Literal`
26 26 | 26 26 |
27 27 | # OK 27 27 | # OK
RUF041.py:24:9: RUF041 [*] Unnecessary nested `Literal`
|
22 | # nested literals, all equivalent to `Literal[1]`
23 | Literal[Literal[1]]
24 | Literal[Literal[Literal[1], Literal[1]]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041
25 | Literal[Literal[1], Literal[Literal[Literal[1]]]]
|
= help: Replace with flattened `Literal`
Safe fix
21 21 |
22 22 | # nested literals, all equivalent to `Literal[1]`
23 23 | Literal[Literal[1]]
24 |-Literal[Literal[Literal[1], Literal[1]]]
24 |+Literal[Literal[1, 1]]
25 25 | Literal[Literal[1], Literal[Literal[Literal[1]]]]
26 26 |
27 27 | # OK
RUF041.py:25:1: RUF041 [*] Unnecessary nested `Literal` RUF041.py:25:1: RUF041 [*] Unnecessary nested `Literal`
| |
23 | Literal[Literal[1]] 23 | Literal[Literal[1]]
@ -256,24 +236,3 @@ RUF041.py:25:1: RUF041 [*] Unnecessary nested `Literal`
26 26 | 26 26 |
27 27 | # OK 27 27 | # OK
28 28 | x: Literal[True, False, True, False] 28 28 | x: Literal[True, False, True, False]
RUF041.py:25:29: RUF041 [*] Unnecessary nested `Literal`
|
23 | Literal[Literal[1]]
24 | Literal[Literal[Literal[1], Literal[1]]]
25 | Literal[Literal[1], Literal[Literal[Literal[1]]]]
| ^^^^^^^^^^^^^^^^^^^ RUF041
26 |
27 | # OK
|
= help: Replace with flattened `Literal`
Safe fix
22 22 | # nested literals, all equivalent to `Literal[1]`
23 23 | Literal[Literal[1]]
24 24 | Literal[Literal[Literal[1], Literal[1]]]
25 |-Literal[Literal[1], Literal[Literal[Literal[1]]]]
25 |+Literal[Literal[1], Literal[Literal[1]]]
26 26 |
27 27 | # OK
28 28 | x: Literal[True, False, True, False]

View file

@ -1,6 +1,5 @@
--- ---
source: crates/ruff_linter/src/rules/ruff/mod.rs source: crates/ruff_linter/src/rules/ruff/mod.rs
snapshot_kind: text
--- ---
RUF041.pyi:6:4: RUF041 [*] Unnecessary nested `Literal` RUF041.pyi:6:4: RUF041 [*] Unnecessary nested `Literal`
| |
@ -216,26 +215,6 @@ RUF041.pyi:24:1: RUF041 [*] Unnecessary nested `Literal`
26 26 | 26 26 |
27 27 | # OK 27 27 | # OK
RUF041.pyi:24:9: RUF041 [*] Unnecessary nested `Literal`
|
22 | # nested literals, all equivalent to `Literal[1]`
23 | Literal[Literal[1]]
24 | Literal[Literal[Literal[1], Literal[1]]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041
25 | Literal[Literal[1], Literal[Literal[Literal[1]]]]
|
= help: Replace with flattened `Literal`
Safe fix
21 21 |
22 22 | # nested literals, all equivalent to `Literal[1]`
23 23 | Literal[Literal[1]]
24 |-Literal[Literal[Literal[1], Literal[1]]]
24 |+Literal[Literal[1, 1]]
25 25 | Literal[Literal[1], Literal[Literal[Literal[1]]]]
26 26 |
27 27 | # OK
RUF041.pyi:25:1: RUF041 [*] Unnecessary nested `Literal` RUF041.pyi:25:1: RUF041 [*] Unnecessary nested `Literal`
| |
23 | Literal[Literal[1]] 23 | Literal[Literal[1]]
@ -256,24 +235,3 @@ RUF041.pyi:25:1: RUF041 [*] Unnecessary nested `Literal`
26 26 | 26 26 |
27 27 | # OK 27 27 | # OK
28 28 | x: Literal[True, False, True, False] 28 28 | x: Literal[True, False, True, False]
RUF041.pyi:25:29: RUF041 [*] Unnecessary nested `Literal`
|
23 | Literal[Literal[1]]
24 | Literal[Literal[Literal[1], Literal[1]]]
25 | Literal[Literal[1], Literal[Literal[Literal[1]]]]
| ^^^^^^^^^^^^^^^^^^^ RUF041
26 |
27 | # OK
|
= help: Replace with flattened `Literal`
Safe fix
22 22 | # nested literals, all equivalent to `Literal[1]`
23 23 | Literal[Literal[1]]
24 24 | Literal[Literal[Literal[1], Literal[1]]]
25 |-Literal[Literal[1], Literal[Literal[Literal[1]]]]
25 |+Literal[Literal[1], Literal[Literal[1]]]
26 26 |
27 27 | # OK
28 28 | x: Literal[True, False, True, False]

View file

@ -373,7 +373,7 @@ where
) where ) where
F: FnMut(&'a Expr, &'a Expr), F: FnMut(&'a Expr, &'a Expr),
{ {
// Ex) x | y // Ex) `x | y`
if let Expr::BinOp(ast::ExprBinOp { if let Expr::BinOp(ast::ExprBinOp {
op: Operator::BitOr, op: Operator::BitOr,
left, left,
@ -396,17 +396,21 @@ where
return; return;
} }
// Ex) Union[x, y] // Ex) `Union[x, y]`
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
if semantic.match_typing_expr(value, "Union") { if semantic.match_typing_expr(value, "Union") {
if let Expr::Tuple(tuple) = &**slice { if let Expr::Tuple(tuple) = &**slice {
// Traverse each element of the tuple within the union recursively to handle cases // Traverse each element of the tuple within the union recursively to handle cases
// such as `Union[..., Union[...]] // such as `Union[..., Union[...]]`
tuple tuple
.iter() .iter()
.for_each(|elem| inner(func, semantic, elem, Some(expr))); .for_each(|elem| inner(func, semantic, elem, Some(expr)));
return; return;
} }
// Ex) `Union[Union[a, b]]` and `Union[a | b | c]`
inner(func, semantic, slice, Some(expr));
return;
} }
} }
@ -435,18 +439,19 @@ where
) where ) where
F: FnMut(&'a Expr, &'a Expr), F: FnMut(&'a Expr, &'a Expr),
{ {
// Ex) Literal[x, y] // Ex) `Literal[x, y]`
if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr { if let Expr::Subscript(ast::ExprSubscript { value, slice, .. }) = expr {
if semantic.match_typing_expr(value, "Literal") { if semantic.match_typing_expr(value, "Literal") {
match &**slice { match &**slice {
Expr::Tuple(tuple) => { Expr::Tuple(tuple) => {
// Traverse each element of the tuple within the literal recursively to handle cases // Traverse each element of the tuple within the literal recursively to handle cases
// such as `Literal[..., Literal[...]] // such as `Literal[..., Literal[...]]`
for element in tuple { for element in tuple {
inner(func, semantic, element, Some(expr)); inner(func, semantic, element, Some(expr));
} }
} }
other => { other => {
// Ex) `Literal[Literal[...]]`
inner(func, semantic, other, Some(expr)); inner(func, semantic, other, Some(expr));
} }
} }

View file

@ -5,7 +5,7 @@ use rustc_hash::FxHashMap;
use ruff_python_ast::helpers::from_relative_import; use ruff_python_ast::helpers::from_relative_import;
use ruff_python_ast::name::{QualifiedName, UnqualifiedName}; use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
use ruff_python_ast::{self as ast, Expr, ExprContext, Operator, PySourceType, Stmt}; use ruff_python_ast::{self as ast, Expr, ExprContext, PySourceType, Stmt};
use ruff_text_size::{Ranged, TextRange, TextSize}; use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::binding::{ use crate::binding::{
@ -1506,38 +1506,48 @@ impl<'a> SemanticModel<'a> {
/// Return `true` if the model is in a nested union expression (e.g., the inner `Union` in /// Return `true` if the model is in a nested union expression (e.g., the inner `Union` in
/// `Union[Union[int, str], float]`). /// `Union[Union[int, str], float]`).
pub fn in_nested_union(&self) -> bool { pub fn in_nested_union(&self) -> bool {
// Ex) `Union[Union[int, str], float]` let mut parent_expressions = self.current_expressions().skip(1);
if self
.current_expression_grandparent() match parent_expressions.next() {
// The parent expression is 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
// comma-separated elements and the parent of that tuple is a `typing.Union`.
// Ex) `Union[Union[a, b], Union[c, d]]`
Some(Expr::Tuple(_)) => parent_expressions
.next()
.and_then(Expr::as_subscript_expr) .and_then(Expr::as_subscript_expr)
.is_some_and(|parent| self.match_typing_expr(&parent.value, "Union")) .is_some_and(|grandparent| self.match_typing_expr(&grandparent.value, "Union")),
{ // The parent expression of the inner union is a PEP604-style union.
return true; // Ex) `a | b | c` or `Union[a, b] | c`
// In contrast to `typing.Union`, PEP604-style unions are always binary operations, e.g.
// the expression `a | b | c` is represented by two binary unions: `(a | b) | c`.
Some(Expr::BinOp(bin_op)) => bin_op.op.is_bit_or(),
// Not a nested union otherwise.
_ => false,
} }
// Ex) `int | Union[str, float]`
if self.current_expression_parent().is_some_and(|parent| {
matches!(
parent,
Expr::BinOp(ast::ExprBinOp {
op: Operator::BitOr,
..
})
)
}) {
return true;
}
false
} }
/// Return `true` if the model is in a nested literal expression (e.g., the inner `Literal` in /// Return `true` if the model is in a nested literal expression (e.g., the inner `Literal` in
/// `Literal[Literal[int, str], float]`). /// `Literal[Literal[int, str], float]`).
pub fn in_nested_literal(&self) -> bool { pub fn in_nested_literal(&self) -> bool {
// Ex) `Literal[Literal[int, str], float]` let mut parent_expressions = self.current_expressions().skip(1);
self.current_expression_grandparent()
match parent_expressions.next() {
// The parent expression of the current `Literal` is a tuple, and the
// grandparent is a `Literal`.
// Ex) `Literal[Literal[str], Literal[int]]`
Some(Expr::Tuple(_)) => parent_expressions
.next()
.and_then(Expr::as_subscript_expr) .and_then(Expr::as_subscript_expr)
.is_some_and(|parent| self.match_typing_expr(&parent.value, "Literal")) .is_some_and(|grandparent| self.match_typing_expr(&grandparent.value, "Literal")),
// The parent expression of the current `Literal` is also a `Literal`.
// Ex) `Literal[Literal[str]]`
Some(Expr::Subscript(parent)) => self.match_typing_expr(&parent.value, "Literal"),
// Not a nested literal otherwise
_ => false,
}
} }
/// Returns `true` if `left` and `right` are in the same branches of an `if`, `match`, or /// Returns `true` if `left` and `right` are in the same branches of an `if`, `match`, or