From 7424f561d61bf00d802e9020baf1ccdbea30f72a Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Thu, 13 Jul 2023 18:40:07 +0530 Subject: [PATCH] Consider single element subscript expr for implicit optional (#5717) ## Summary Consider single element subscript expr for implicit optional. On `main`, the cases where there is only a single element in the subscript list was giving false positives such as for the following: ```python typing.Union[None] typing.Literal[None] ``` ## Test Plan `cargo test` --------- Co-authored-by: Charlie Marsh --- .../resources/test/fixtures/ruff/RUF013_0.py | 16 + ..._ruff__tests__PY39_RUF013_RUF013_0.py.snap | 438 ++++++++++-------- ...ules__ruff__tests__RUF013_RUF013_0.py.snap | 438 ++++++++++-------- crates/ruff/src/rules/ruff/typing.rs | 62 ++- crates/ruff/src/test.rs | 2 +- 5 files changed, 568 insertions(+), 388 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF013_0.py b/crates/ruff/resources/test/fixtures/ruff/RUF013_0.py index 21c59089798958..e4807c9f5ac611 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF013_0.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF013_0.py @@ -48,6 +48,10 @@ def f(arg: typing.Optional[int] = None): # Union +def f(arg: Union[None] = None): + pass + + def f(arg: Union[None, int] = None): pass @@ -68,6 +72,10 @@ def f(arg: Union = None): # RUF013 pass +def f(arg: Union[int] = None): # RUF013 + pass + + def f(arg: Union[int, str] = None): # RUF013 pass @@ -106,10 +114,18 @@ def f(arg: None = None): pass +def f(arg: Literal[None] = None): + pass + + def f(arg: Literal[1, 2, None, 3] = None): pass +def f(arg: Literal[1] = None): # RUF013 + pass + + def f(arg: Literal[1, "foo"] = None): # RUF013 pass diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__PY39_RUF013_RUF013_0.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__PY39_RUF013_RUF013_0.py.snap index 7d4b9a9f004034..1e25245a2a42aa 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__PY39_RUF013_RUF013_0.py.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__PY39_RUF013_RUF013_0.py.snap @@ -37,28 +37,46 @@ RUF013_0.py:25:12: RUF013 [*] PEP 484 prohibits implicit `Optional` 27 27 | 28 28 | -RUF013_0.py:67:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +RUF013_0.py:29:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -67 | def f(arg: Union = None): # RUF013 - | ^^^^^ RUF013 -68 | pass +29 | def f(arg: typing.List[str] = None): # RUF013 + | ^^^^^^^^^^^^^^^^ RUF013 +30 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -64 64 | pass -65 65 | -66 66 | -67 |-def f(arg: Union = None): # RUF013 - 67 |+def f(arg: Optional[Union] = None): # RUF013 -68 68 | pass -69 69 | -70 70 | +26 26 | pass +27 27 | +28 28 | +29 |-def f(arg: typing.List[str] = None): # RUF013 + 29 |+def f(arg: Optional[typing.List[str]] = None): # RUF013 +30 30 | pass +31 31 | +32 32 | + +RUF013_0.py:33:12: RUF013 [*] PEP 484 prohibits implicit `Optional` + | +33 | def f(arg: Tuple[str] = None): # RUF013 + | ^^^^^^^^^^ RUF013 +34 | pass + | + = help: Convert to `Optional[T]` + +ℹ Suggested fix +30 30 | pass +31 31 | +32 32 | +33 |-def f(arg: Tuple[str] = None): # RUF013 + 33 |+def f(arg: Optional[Tuple[str]] = None): # RUF013 +34 34 | pass +35 35 | +36 36 | RUF013_0.py:71:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -71 | def f(arg: Union[int, str] = None): # RUF013 - | ^^^^^^^^^^^^^^^ RUF013 +71 | def f(arg: Union = None): # RUF013 + | ^^^^^ RUF013 72 | pass | = help: Convert to `Optional[T]` @@ -67,16 +85,16 @@ RUF013_0.py:71:12: RUF013 [*] PEP 484 prohibits implicit `Optional` 68 68 | pass 69 69 | 70 70 | -71 |-def f(arg: Union[int, str] = None): # RUF013 - 71 |+def f(arg: Optional[Union[int, str]] = None): # RUF013 +71 |-def f(arg: Union = None): # RUF013 + 71 |+def f(arg: Optional[Union] = None): # RUF013 72 72 | pass 73 73 | 74 74 | RUF013_0.py:75:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -75 | def f(arg: typing.Union[int, str] = None): # RUF013 - | ^^^^^^^^^^^^^^^^^^^^^^ RUF013 +75 | def f(arg: Union[int] = None): # RUF013 + | ^^^^^^^^^^ RUF013 76 | pass | = help: Convert to `Optional[T]` @@ -85,260 +103,314 @@ RUF013_0.py:75:12: RUF013 [*] PEP 484 prohibits implicit `Optional` 72 72 | pass 73 73 | 74 74 | -75 |-def f(arg: typing.Union[int, str] = None): # RUF013 - 75 |+def f(arg: Optional[typing.Union[int, str]] = None): # RUF013 +75 |-def f(arg: Union[int] = None): # RUF013 + 75 |+def f(arg: Optional[Union[int]] = None): # RUF013 76 76 | pass 77 77 | 78 78 | -RUF013_0.py:94:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +RUF013_0.py:79:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -94 | def f(arg: int | float = None): # RUF013 - | ^^^^^^^^^^^ RUF013 -95 | pass +79 | def f(arg: Union[int, str] = None): # RUF013 + | ^^^^^^^^^^^^^^^ RUF013 +80 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -91 91 | pass -92 92 | -93 93 | -94 |-def f(arg: int | float = None): # RUF013 - 94 |+def f(arg: Optional[int | float] = None): # RUF013 -95 95 | pass -96 96 | -97 97 | - -RUF013_0.py:98:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +76 76 | pass +77 77 | +78 78 | +79 |-def f(arg: Union[int, str] = None): # RUF013 + 79 |+def f(arg: Optional[Union[int, str]] = None): # RUF013 +80 80 | pass +81 81 | +82 82 | + +RUF013_0.py:83:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -98 | def f(arg: int | float | str | bytes = None): # RUF013 - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013 -99 | pass +83 | def f(arg: typing.Union[int, str] = None): # RUF013 + | ^^^^^^^^^^^^^^^^^^^^^^ RUF013 +84 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -95 95 | pass -96 96 | -97 97 | -98 |-def f(arg: int | float | str | bytes = None): # RUF013 - 98 |+def f(arg: Optional[int | float | str | bytes] = None): # RUF013 -99 99 | pass +80 80 | pass +81 81 | +82 82 | +83 |-def f(arg: typing.Union[int, str] = None): # RUF013 + 83 |+def f(arg: Optional[typing.Union[int, str]] = None): # RUF013 +84 84 | pass +85 85 | +86 86 | + +RUF013_0.py:102:12: RUF013 [*] PEP 484 prohibits implicit `Optional` + | +102 | def f(arg: int | float = None): # RUF013 + | ^^^^^^^^^^^ RUF013 +103 | pass + | + = help: Convert to `Optional[T]` + +ℹ Suggested fix +99 99 | pass 100 100 | 101 101 | +102 |-def f(arg: int | float = None): # RUF013 + 102 |+def f(arg: Optional[int | float] = None): # RUF013 +103 103 | pass +104 104 | +105 105 | + +RUF013_0.py:106:12: RUF013 [*] PEP 484 prohibits implicit `Optional` + | +106 | def f(arg: int | float | str | bytes = None): # RUF013 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013 +107 | pass + | + = help: Convert to `Optional[T]` -RUF013_0.py:113:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +ℹ Suggested fix +103 103 | pass +104 104 | +105 105 | +106 |-def f(arg: int | float | str | bytes = None): # RUF013 + 106 |+def f(arg: Optional[int | float | str | bytes] = None): # RUF013 +107 107 | pass +108 108 | +109 109 | + +RUF013_0.py:125:12: RUF013 [*] PEP 484 prohibits implicit `Optional` + | +125 | def f(arg: Literal[1] = None): # RUF013 + | ^^^^^^^^^^ RUF013 +126 | pass + | + = help: Convert to `Optional[T]` + +ℹ Suggested fix +122 122 | pass +123 123 | +124 124 | +125 |-def f(arg: Literal[1] = None): # RUF013 + 125 |+def f(arg: Optional[Literal[1]] = None): # RUF013 +126 126 | pass +127 127 | +128 128 | + +RUF013_0.py:129:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -113 | def f(arg: Literal[1, "foo"] = None): # RUF013 +129 | def f(arg: Literal[1, "foo"] = None): # RUF013 | ^^^^^^^^^^^^^^^^^ RUF013 -114 | pass +130 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -110 110 | pass -111 111 | -112 112 | -113 |-def f(arg: Literal[1, "foo"] = None): # RUF013 - 113 |+def f(arg: Optional[Literal[1, "foo"]] = None): # RUF013 -114 114 | pass -115 115 | -116 116 | - -RUF013_0.py:117:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +126 126 | pass +127 127 | +128 128 | +129 |-def f(arg: Literal[1, "foo"] = None): # RUF013 + 129 |+def f(arg: Optional[Literal[1, "foo"]] = None): # RUF013 +130 130 | pass +131 131 | +132 132 | + +RUF013_0.py:133:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -117 | def f(arg: typing.Literal[1, "foo", True] = None): # RUF013 +133 | def f(arg: typing.Literal[1, "foo", True] = None): # RUF013 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013 -118 | pass +134 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -114 114 | pass -115 115 | -116 116 | -117 |-def f(arg: typing.Literal[1, "foo", True] = None): # RUF013 - 117 |+def f(arg: Optional[typing.Literal[1, "foo", True]] = None): # RUF013 -118 118 | pass -119 119 | -120 120 | - -RUF013_0.py:136:22: RUF013 [*] PEP 484 prohibits implicit `Optional` +130 130 | pass +131 131 | +132 132 | +133 |-def f(arg: typing.Literal[1, "foo", True] = None): # RUF013 + 133 |+def f(arg: Optional[typing.Literal[1, "foo", True]] = None): # RUF013 +134 134 | pass +135 135 | +136 136 | + +RUF013_0.py:152:22: RUF013 [*] PEP 484 prohibits implicit `Optional` | -136 | def f(arg: Annotated[int, ...] = None): # RUF013 +152 | def f(arg: Annotated[int, ...] = None): # RUF013 | ^^^ RUF013 -137 | pass +153 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -133 133 | pass -134 134 | -135 135 | -136 |-def f(arg: Annotated[int, ...] = None): # RUF013 - 136 |+def f(arg: Annotated[Optional[int], ...] = None): # RUF013 -137 137 | pass -138 138 | -139 139 | +149 149 | pass +150 150 | +151 151 | +152 |-def f(arg: Annotated[int, ...] = None): # RUF013 + 152 |+def f(arg: Annotated[Optional[int], ...] = None): # RUF013 +153 153 | pass +154 154 | +155 155 | -RUF013_0.py:140:32: RUF013 [*] PEP 484 prohibits implicit `Optional` +RUF013_0.py:156:32: RUF013 [*] PEP 484 prohibits implicit `Optional` | -140 | def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013 +156 | def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013 | ^^^^^^^^^ RUF013 -141 | pass +157 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -137 137 | pass -138 138 | -139 139 | -140 |-def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013 - 140 |+def f(arg: Annotated[Annotated[Optional[int | str], ...], ...] = None): # RUF013 -141 141 | pass -142 142 | -143 143 | - -RUF013_0.py:156:11: RUF013 [*] PEP 484 prohibits implicit `Optional` +153 153 | pass +154 154 | +155 155 | +156 |-def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013 + 156 |+def f(arg: Annotated[Annotated[Optional[int | str], ...], ...] = None): # RUF013 +157 157 | pass +158 158 | +159 159 | + +RUF013_0.py:172:11: RUF013 [*] PEP 484 prohibits implicit `Optional` | -155 | def f( -156 | arg1: int = None, # RUF013 +171 | def f( +172 | arg1: int = None, # RUF013 | ^^^ RUF013 -157 | arg2: Union[int, float] = None, # RUF013 -158 | arg3: Literal[1, 2, 3] = None, # RUF013 +173 | arg2: Union[int, float] = None, # RUF013 +174 | arg3: Literal[1, 2, 3] = None, # RUF013 | = help: Convert to `Optional[T]` ℹ Suggested fix -153 153 | -154 154 | -155 155 | def f( -156 |- arg1: int = None, # RUF013 - 156 |+ arg1: Optional[int] = None, # RUF013 -157 157 | arg2: Union[int, float] = None, # RUF013 -158 158 | arg3: Literal[1, 2, 3] = None, # RUF013 -159 159 | ): - -RUF013_0.py:157:11: RUF013 [*] PEP 484 prohibits implicit `Optional` +169 169 | +170 170 | +171 171 | def f( +172 |- arg1: int = None, # RUF013 + 172 |+ arg1: Optional[int] = None, # RUF013 +173 173 | arg2: Union[int, float] = None, # RUF013 +174 174 | arg3: Literal[1, 2, 3] = None, # RUF013 +175 175 | ): + +RUF013_0.py:173:11: RUF013 [*] PEP 484 prohibits implicit `Optional` | -155 | def f( -156 | arg1: int = None, # RUF013 -157 | arg2: Union[int, float] = None, # RUF013 +171 | def f( +172 | arg1: int = None, # RUF013 +173 | arg2: Union[int, float] = None, # RUF013 | ^^^^^^^^^^^^^^^^^ RUF013 -158 | arg3: Literal[1, 2, 3] = None, # RUF013 -159 | ): +174 | arg3: Literal[1, 2, 3] = None, # RUF013 +175 | ): | = help: Convert to `Optional[T]` ℹ Suggested fix -154 154 | -155 155 | def f( -156 156 | arg1: int = None, # RUF013 -157 |- arg2: Union[int, float] = None, # RUF013 - 157 |+ arg2: Optional[Union[int, float]] = None, # RUF013 -158 158 | arg3: Literal[1, 2, 3] = None, # RUF013 -159 159 | ): -160 160 | pass - -RUF013_0.py:158:11: RUF013 [*] PEP 484 prohibits implicit `Optional` +170 170 | +171 171 | def f( +172 172 | arg1: int = None, # RUF013 +173 |- arg2: Union[int, float] = None, # RUF013 + 173 |+ arg2: Optional[Union[int, float]] = None, # RUF013 +174 174 | arg3: Literal[1, 2, 3] = None, # RUF013 +175 175 | ): +176 176 | pass + +RUF013_0.py:174:11: RUF013 [*] PEP 484 prohibits implicit `Optional` | -156 | arg1: int = None, # RUF013 -157 | arg2: Union[int, float] = None, # RUF013 -158 | arg3: Literal[1, 2, 3] = None, # RUF013 +172 | arg1: int = None, # RUF013 +173 | arg2: Union[int, float] = None, # RUF013 +174 | arg3: Literal[1, 2, 3] = None, # RUF013 | ^^^^^^^^^^^^^^^^ RUF013 -159 | ): -160 | pass +175 | ): +176 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -155 155 | def f( -156 156 | arg1: int = None, # RUF013 -157 157 | arg2: Union[int, float] = None, # RUF013 -158 |- arg3: Literal[1, 2, 3] = None, # RUF013 - 158 |+ arg3: Optional[Literal[1, 2, 3]] = None, # RUF013 -159 159 | ): -160 160 | pass -161 161 | - -RUF013_0.py:186:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +171 171 | def f( +172 172 | arg1: int = None, # RUF013 +173 173 | arg2: Union[int, float] = None, # RUF013 +174 |- arg3: Literal[1, 2, 3] = None, # RUF013 + 174 |+ arg3: Optional[Literal[1, 2, 3]] = None, # RUF013 +175 175 | ): +176 176 | pass +177 177 | + +RUF013_0.py:202:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -186 | def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013 +202 | def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013 -187 | pass +203 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -183 183 | pass -184 184 | -185 185 | -186 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013 - 186 |+def f(arg: Optional[Union[Annotated[int, ...], Union[str, bytes]]] = None): # RUF013 -187 187 | pass -188 188 | -189 189 | - -RUF013_0.py:193:13: RUF013 [*] PEP 484 prohibits implicit `Optional` +199 199 | pass +200 200 | +201 201 | +202 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013 + 202 |+def f(arg: Optional[Union[Annotated[int, ...], Union[str, bytes]]] = None): # RUF013 +203 203 | pass +204 204 | +205 205 | + +RUF013_0.py:209:13: RUF013 [*] PEP 484 prohibits implicit `Optional` | -193 | def f(arg: "int" = None): # RUF013 +209 | def f(arg: "int" = None): # RUF013 | ^^^ RUF013 -194 | pass +210 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -190 190 | # Quoted -191 191 | -192 192 | -193 |-def f(arg: "int" = None): # RUF013 - 193 |+def f(arg: "Optional[int]" = None): # RUF013 -194 194 | pass -195 195 | -196 196 | - -RUF013_0.py:197:13: RUF013 [*] PEP 484 prohibits implicit `Optional` +206 206 | # Quoted +207 207 | +208 208 | +209 |-def f(arg: "int" = None): # RUF013 + 209 |+def f(arg: "Optional[int]" = None): # RUF013 +210 210 | pass +211 211 | +212 212 | + +RUF013_0.py:213:13: RUF013 [*] PEP 484 prohibits implicit `Optional` | -197 | def f(arg: "str" = None): # RUF013 +213 | def f(arg: "str" = None): # RUF013 | ^^^ RUF013 -198 | pass +214 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -194 194 | pass -195 195 | -196 196 | -197 |-def f(arg: "str" = None): # RUF013 - 197 |+def f(arg: "Optional[str]" = None): # RUF013 -198 198 | pass -199 199 | -200 200 | +210 210 | pass +211 211 | +212 212 | +213 |-def f(arg: "str" = None): # RUF013 + 213 |+def f(arg: "Optional[str]" = None): # RUF013 +214 214 | pass +215 215 | +216 216 | -RUF013_0.py:201:12: RUF013 PEP 484 prohibits implicit `Optional` +RUF013_0.py:217:12: RUF013 PEP 484 prohibits implicit `Optional` | -201 | def f(arg: "st" "r" = None): # RUF013 +217 | def f(arg: "st" "r" = None): # RUF013 | ^^^^^^^^ RUF013 -202 | pass +218 | pass | = help: Convert to `Optional[T]` -RUF013_0.py:209:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +RUF013_0.py:225:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -209 | def f(arg: Union["int", "str"] = None): # RUF013 +225 | def f(arg: Union["int", "str"] = None): # RUF013 | ^^^^^^^^^^^^^^^^^^^ RUF013 -210 | pass +226 | pass | = help: Convert to `Optional[T]` ℹ Suggested fix -206 206 | pass -207 207 | -208 208 | -209 |-def f(arg: Union["int", "str"] = None): # RUF013 - 209 |+def f(arg: Optional[Union["int", "str"]] = None): # RUF013 -210 210 | pass -211 211 | -212 212 | +222 222 | pass +223 223 | +224 224 | +225 |-def f(arg: Union["int", "str"] = None): # RUF013 + 225 |+def f(arg: Optional[Union["int", "str"]] = None): # RUF013 +226 226 | pass +227 227 | +228 228 | diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF013_RUF013_0.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF013_RUF013_0.py.snap index 341aecce5e0d2e..3ff56b6bc20d90 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF013_RUF013_0.py.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF013_RUF013_0.py.snap @@ -37,28 +37,46 @@ RUF013_0.py:25:12: RUF013 [*] PEP 484 prohibits implicit `Optional` 27 27 | 28 28 | -RUF013_0.py:67:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +RUF013_0.py:29:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -67 | def f(arg: Union = None): # RUF013 - | ^^^^^ RUF013 -68 | pass +29 | def f(arg: typing.List[str] = None): # RUF013 + | ^^^^^^^^^^^^^^^^ RUF013 +30 | pass | = help: Convert to `T | None` ℹ Suggested fix -64 64 | pass -65 65 | -66 66 | -67 |-def f(arg: Union = None): # RUF013 - 67 |+def f(arg: Union | None = None): # RUF013 -68 68 | pass -69 69 | -70 70 | +26 26 | pass +27 27 | +28 28 | +29 |-def f(arg: typing.List[str] = None): # RUF013 + 29 |+def f(arg: typing.List[str] | None = None): # RUF013 +30 30 | pass +31 31 | +32 32 | + +RUF013_0.py:33:12: RUF013 [*] PEP 484 prohibits implicit `Optional` + | +33 | def f(arg: Tuple[str] = None): # RUF013 + | ^^^^^^^^^^ RUF013 +34 | pass + | + = help: Convert to `T | None` + +ℹ Suggested fix +30 30 | pass +31 31 | +32 32 | +33 |-def f(arg: Tuple[str] = None): # RUF013 + 33 |+def f(arg: Tuple[str] | None = None): # RUF013 +34 34 | pass +35 35 | +36 36 | RUF013_0.py:71:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -71 | def f(arg: Union[int, str] = None): # RUF013 - | ^^^^^^^^^^^^^^^ RUF013 +71 | def f(arg: Union = None): # RUF013 + | ^^^^^ RUF013 72 | pass | = help: Convert to `T | None` @@ -67,16 +85,16 @@ RUF013_0.py:71:12: RUF013 [*] PEP 484 prohibits implicit `Optional` 68 68 | pass 69 69 | 70 70 | -71 |-def f(arg: Union[int, str] = None): # RUF013 - 71 |+def f(arg: Union[int, str] | None = None): # RUF013 +71 |-def f(arg: Union = None): # RUF013 + 71 |+def f(arg: Union | None = None): # RUF013 72 72 | pass 73 73 | 74 74 | RUF013_0.py:75:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -75 | def f(arg: typing.Union[int, str] = None): # RUF013 - | ^^^^^^^^^^^^^^^^^^^^^^ RUF013 +75 | def f(arg: Union[int] = None): # RUF013 + | ^^^^^^^^^^ RUF013 76 | pass | = help: Convert to `T | None` @@ -85,260 +103,314 @@ RUF013_0.py:75:12: RUF013 [*] PEP 484 prohibits implicit `Optional` 72 72 | pass 73 73 | 74 74 | -75 |-def f(arg: typing.Union[int, str] = None): # RUF013 - 75 |+def f(arg: typing.Union[int, str] | None = None): # RUF013 +75 |-def f(arg: Union[int] = None): # RUF013 + 75 |+def f(arg: Union[int] | None = None): # RUF013 76 76 | pass 77 77 | 78 78 | -RUF013_0.py:94:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +RUF013_0.py:79:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -94 | def f(arg: int | float = None): # RUF013 - | ^^^^^^^^^^^ RUF013 -95 | pass +79 | def f(arg: Union[int, str] = None): # RUF013 + | ^^^^^^^^^^^^^^^ RUF013 +80 | pass | = help: Convert to `T | None` ℹ Suggested fix -91 91 | pass -92 92 | -93 93 | -94 |-def f(arg: int | float = None): # RUF013 - 94 |+def f(arg: int | float | None = None): # RUF013 -95 95 | pass -96 96 | -97 97 | - -RUF013_0.py:98:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +76 76 | pass +77 77 | +78 78 | +79 |-def f(arg: Union[int, str] = None): # RUF013 + 79 |+def f(arg: Union[int, str] | None = None): # RUF013 +80 80 | pass +81 81 | +82 82 | + +RUF013_0.py:83:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -98 | def f(arg: int | float | str | bytes = None): # RUF013 - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013 -99 | pass +83 | def f(arg: typing.Union[int, str] = None): # RUF013 + | ^^^^^^^^^^^^^^^^^^^^^^ RUF013 +84 | pass | = help: Convert to `T | None` ℹ Suggested fix -95 95 | pass -96 96 | -97 97 | -98 |-def f(arg: int | float | str | bytes = None): # RUF013 - 98 |+def f(arg: int | float | str | bytes | None = None): # RUF013 -99 99 | pass +80 80 | pass +81 81 | +82 82 | +83 |-def f(arg: typing.Union[int, str] = None): # RUF013 + 83 |+def f(arg: typing.Union[int, str] | None = None): # RUF013 +84 84 | pass +85 85 | +86 86 | + +RUF013_0.py:102:12: RUF013 [*] PEP 484 prohibits implicit `Optional` + | +102 | def f(arg: int | float = None): # RUF013 + | ^^^^^^^^^^^ RUF013 +103 | pass + | + = help: Convert to `T | None` + +ℹ Suggested fix +99 99 | pass 100 100 | 101 101 | +102 |-def f(arg: int | float = None): # RUF013 + 102 |+def f(arg: int | float | None = None): # RUF013 +103 103 | pass +104 104 | +105 105 | + +RUF013_0.py:106:12: RUF013 [*] PEP 484 prohibits implicit `Optional` + | +106 | def f(arg: int | float | str | bytes = None): # RUF013 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013 +107 | pass + | + = help: Convert to `T | None` -RUF013_0.py:113:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +ℹ Suggested fix +103 103 | pass +104 104 | +105 105 | +106 |-def f(arg: int | float | str | bytes = None): # RUF013 + 106 |+def f(arg: int | float | str | bytes | None = None): # RUF013 +107 107 | pass +108 108 | +109 109 | + +RUF013_0.py:125:12: RUF013 [*] PEP 484 prohibits implicit `Optional` + | +125 | def f(arg: Literal[1] = None): # RUF013 + | ^^^^^^^^^^ RUF013 +126 | pass + | + = help: Convert to `T | None` + +ℹ Suggested fix +122 122 | pass +123 123 | +124 124 | +125 |-def f(arg: Literal[1] = None): # RUF013 + 125 |+def f(arg: Literal[1] | None = None): # RUF013 +126 126 | pass +127 127 | +128 128 | + +RUF013_0.py:129:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -113 | def f(arg: Literal[1, "foo"] = None): # RUF013 +129 | def f(arg: Literal[1, "foo"] = None): # RUF013 | ^^^^^^^^^^^^^^^^^ RUF013 -114 | pass +130 | pass | = help: Convert to `T | None` ℹ Suggested fix -110 110 | pass -111 111 | -112 112 | -113 |-def f(arg: Literal[1, "foo"] = None): # RUF013 - 113 |+def f(arg: Literal[1, "foo"] | None = None): # RUF013 -114 114 | pass -115 115 | -116 116 | - -RUF013_0.py:117:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +126 126 | pass +127 127 | +128 128 | +129 |-def f(arg: Literal[1, "foo"] = None): # RUF013 + 129 |+def f(arg: Literal[1, "foo"] | None = None): # RUF013 +130 130 | pass +131 131 | +132 132 | + +RUF013_0.py:133:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -117 | def f(arg: typing.Literal[1, "foo", True] = None): # RUF013 +133 | def f(arg: typing.Literal[1, "foo", True] = None): # RUF013 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013 -118 | pass +134 | pass | = help: Convert to `T | None` ℹ Suggested fix -114 114 | pass -115 115 | -116 116 | -117 |-def f(arg: typing.Literal[1, "foo", True] = None): # RUF013 - 117 |+def f(arg: typing.Literal[1, "foo", True] | None = None): # RUF013 -118 118 | pass -119 119 | -120 120 | - -RUF013_0.py:136:22: RUF013 [*] PEP 484 prohibits implicit `Optional` +130 130 | pass +131 131 | +132 132 | +133 |-def f(arg: typing.Literal[1, "foo", True] = None): # RUF013 + 133 |+def f(arg: typing.Literal[1, "foo", True] | None = None): # RUF013 +134 134 | pass +135 135 | +136 136 | + +RUF013_0.py:152:22: RUF013 [*] PEP 484 prohibits implicit `Optional` | -136 | def f(arg: Annotated[int, ...] = None): # RUF013 +152 | def f(arg: Annotated[int, ...] = None): # RUF013 | ^^^ RUF013 -137 | pass +153 | pass | = help: Convert to `T | None` ℹ Suggested fix -133 133 | pass -134 134 | -135 135 | -136 |-def f(arg: Annotated[int, ...] = None): # RUF013 - 136 |+def f(arg: Annotated[int | None, ...] = None): # RUF013 -137 137 | pass -138 138 | -139 139 | +149 149 | pass +150 150 | +151 151 | +152 |-def f(arg: Annotated[int, ...] = None): # RUF013 + 152 |+def f(arg: Annotated[int | None, ...] = None): # RUF013 +153 153 | pass +154 154 | +155 155 | -RUF013_0.py:140:32: RUF013 [*] PEP 484 prohibits implicit `Optional` +RUF013_0.py:156:32: RUF013 [*] PEP 484 prohibits implicit `Optional` | -140 | def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013 +156 | def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013 | ^^^^^^^^^ RUF013 -141 | pass +157 | pass | = help: Convert to `T | None` ℹ Suggested fix -137 137 | pass -138 138 | -139 139 | -140 |-def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013 - 140 |+def f(arg: Annotated[Annotated[int | str | None, ...], ...] = None): # RUF013 -141 141 | pass -142 142 | -143 143 | - -RUF013_0.py:156:11: RUF013 [*] PEP 484 prohibits implicit `Optional` +153 153 | pass +154 154 | +155 155 | +156 |-def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013 + 156 |+def f(arg: Annotated[Annotated[int | str | None, ...], ...] = None): # RUF013 +157 157 | pass +158 158 | +159 159 | + +RUF013_0.py:172:11: RUF013 [*] PEP 484 prohibits implicit `Optional` | -155 | def f( -156 | arg1: int = None, # RUF013 +171 | def f( +172 | arg1: int = None, # RUF013 | ^^^ RUF013 -157 | arg2: Union[int, float] = None, # RUF013 -158 | arg3: Literal[1, 2, 3] = None, # RUF013 +173 | arg2: Union[int, float] = None, # RUF013 +174 | arg3: Literal[1, 2, 3] = None, # RUF013 | = help: Convert to `T | None` ℹ Suggested fix -153 153 | -154 154 | -155 155 | def f( -156 |- arg1: int = None, # RUF013 - 156 |+ arg1: int | None = None, # RUF013 -157 157 | arg2: Union[int, float] = None, # RUF013 -158 158 | arg3: Literal[1, 2, 3] = None, # RUF013 -159 159 | ): - -RUF013_0.py:157:11: RUF013 [*] PEP 484 prohibits implicit `Optional` +169 169 | +170 170 | +171 171 | def f( +172 |- arg1: int = None, # RUF013 + 172 |+ arg1: int | None = None, # RUF013 +173 173 | arg2: Union[int, float] = None, # RUF013 +174 174 | arg3: Literal[1, 2, 3] = None, # RUF013 +175 175 | ): + +RUF013_0.py:173:11: RUF013 [*] PEP 484 prohibits implicit `Optional` | -155 | def f( -156 | arg1: int = None, # RUF013 -157 | arg2: Union[int, float] = None, # RUF013 +171 | def f( +172 | arg1: int = None, # RUF013 +173 | arg2: Union[int, float] = None, # RUF013 | ^^^^^^^^^^^^^^^^^ RUF013 -158 | arg3: Literal[1, 2, 3] = None, # RUF013 -159 | ): +174 | arg3: Literal[1, 2, 3] = None, # RUF013 +175 | ): | = help: Convert to `T | None` ℹ Suggested fix -154 154 | -155 155 | def f( -156 156 | arg1: int = None, # RUF013 -157 |- arg2: Union[int, float] = None, # RUF013 - 157 |+ arg2: Union[int, float] | None = None, # RUF013 -158 158 | arg3: Literal[1, 2, 3] = None, # RUF013 -159 159 | ): -160 160 | pass - -RUF013_0.py:158:11: RUF013 [*] PEP 484 prohibits implicit `Optional` +170 170 | +171 171 | def f( +172 172 | arg1: int = None, # RUF013 +173 |- arg2: Union[int, float] = None, # RUF013 + 173 |+ arg2: Union[int, float] | None = None, # RUF013 +174 174 | arg3: Literal[1, 2, 3] = None, # RUF013 +175 175 | ): +176 176 | pass + +RUF013_0.py:174:11: RUF013 [*] PEP 484 prohibits implicit `Optional` | -156 | arg1: int = None, # RUF013 -157 | arg2: Union[int, float] = None, # RUF013 -158 | arg3: Literal[1, 2, 3] = None, # RUF013 +172 | arg1: int = None, # RUF013 +173 | arg2: Union[int, float] = None, # RUF013 +174 | arg3: Literal[1, 2, 3] = None, # RUF013 | ^^^^^^^^^^^^^^^^ RUF013 -159 | ): -160 | pass +175 | ): +176 | pass | = help: Convert to `T | None` ℹ Suggested fix -155 155 | def f( -156 156 | arg1: int = None, # RUF013 -157 157 | arg2: Union[int, float] = None, # RUF013 -158 |- arg3: Literal[1, 2, 3] = None, # RUF013 - 158 |+ arg3: Literal[1, 2, 3] | None = None, # RUF013 -159 159 | ): -160 160 | pass -161 161 | - -RUF013_0.py:186:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +171 171 | def f( +172 172 | arg1: int = None, # RUF013 +173 173 | arg2: Union[int, float] = None, # RUF013 +174 |- arg3: Literal[1, 2, 3] = None, # RUF013 + 174 |+ arg3: Literal[1, 2, 3] | None = None, # RUF013 +175 175 | ): +176 176 | pass +177 177 | + +RUF013_0.py:202:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -186 | def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013 +202 | def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013 -187 | pass +203 | pass | = help: Convert to `T | None` ℹ Suggested fix -183 183 | pass -184 184 | -185 185 | -186 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013 - 186 |+def f(arg: Union[Annotated[int, ...], Union[str, bytes]] | None = None): # RUF013 -187 187 | pass -188 188 | -189 189 | - -RUF013_0.py:193:13: RUF013 [*] PEP 484 prohibits implicit `Optional` +199 199 | pass +200 200 | +201 201 | +202 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013 + 202 |+def f(arg: Union[Annotated[int, ...], Union[str, bytes]] | None = None): # RUF013 +203 203 | pass +204 204 | +205 205 | + +RUF013_0.py:209:13: RUF013 [*] PEP 484 prohibits implicit `Optional` | -193 | def f(arg: "int" = None): # RUF013 +209 | def f(arg: "int" = None): # RUF013 | ^^^ RUF013 -194 | pass +210 | pass | = help: Convert to `T | None` ℹ Suggested fix -190 190 | # Quoted -191 191 | -192 192 | -193 |-def f(arg: "int" = None): # RUF013 - 193 |+def f(arg: "int | None" = None): # RUF013 -194 194 | pass -195 195 | -196 196 | - -RUF013_0.py:197:13: RUF013 [*] PEP 484 prohibits implicit `Optional` +206 206 | # Quoted +207 207 | +208 208 | +209 |-def f(arg: "int" = None): # RUF013 + 209 |+def f(arg: "int | None" = None): # RUF013 +210 210 | pass +211 211 | +212 212 | + +RUF013_0.py:213:13: RUF013 [*] PEP 484 prohibits implicit `Optional` | -197 | def f(arg: "str" = None): # RUF013 +213 | def f(arg: "str" = None): # RUF013 | ^^^ RUF013 -198 | pass +214 | pass | = help: Convert to `T | None` ℹ Suggested fix -194 194 | pass -195 195 | -196 196 | -197 |-def f(arg: "str" = None): # RUF013 - 197 |+def f(arg: "str | None" = None): # RUF013 -198 198 | pass -199 199 | -200 200 | +210 210 | pass +211 211 | +212 212 | +213 |-def f(arg: "str" = None): # RUF013 + 213 |+def f(arg: "str | None" = None): # RUF013 +214 214 | pass +215 215 | +216 216 | -RUF013_0.py:201:12: RUF013 PEP 484 prohibits implicit `Optional` +RUF013_0.py:217:12: RUF013 PEP 484 prohibits implicit `Optional` | -201 | def f(arg: "st" "r" = None): # RUF013 +217 | def f(arg: "st" "r" = None): # RUF013 | ^^^^^^^^ RUF013 -202 | pass +218 | pass | = help: Convert to `T | None` -RUF013_0.py:209:12: RUF013 [*] PEP 484 prohibits implicit `Optional` +RUF013_0.py:225:12: RUF013 [*] PEP 484 prohibits implicit `Optional` | -209 | def f(arg: Union["int", "str"] = None): # RUF013 +225 | def f(arg: Union["int", "str"] = None): # RUF013 | ^^^^^^^^^^^^^^^^^^^ RUF013 -210 | pass +226 | pass | = help: Convert to `T | None` ℹ Suggested fix -206 206 | pass -207 207 | -208 208 | -209 |-def f(arg: Union["int", "str"] = None): # RUF013 - 209 |+def f(arg: Union["int", "str"] | None = None): # RUF013 -210 210 | pass -211 211 | -212 212 | +222 222 | pass +223 223 | +224 224 | +225 |-def f(arg: Union["int", "str"] = None): # RUF013 + 225 |+def f(arg: Union["int", "str"] | None = None): # RUF013 +226 226 | pass +227 227 | +228 228 | diff --git a/crates/ruff/src/rules/ruff/typing.rs b/crates/ruff/src/rules/ruff/typing.rs index 127c213fef3919..35baa3f9ce8812 100644 --- a/crates/ruff/src/rules/ruff/typing.rs +++ b/crates/ruff/src/rules/ruff/typing.rs @@ -1,3 +1,4 @@ +use itertools::Either::{Left, Right}; use rustpython_parser::ast::{self, Constant, Expr, Operator}; use ruff_python_ast::call_path::CallPath; @@ -52,6 +53,15 @@ fn is_known_type(call_path: &CallPath, minor_version: u32) -> bool { } } +/// Returns an iterator over the expressions in a slice. If the slice is not a +/// tuple, the iterator will only yield the slice. +fn resolve_slice_value(slice: &Expr) -> impl Iterator { + match slice { + Expr::Tuple(ast::ExprTuple { elts: elements, .. }) => Left(elements.iter()), + _ => Right(std::iter::once(slice)), + } +} + #[derive(Debug)] enum TypingTarget<'a> { /// Literal `None` type. @@ -66,12 +76,14 @@ enum TypingTarget<'a> { /// Forward reference to a type e.g., `"List[str]"`. ForwardReference(Expr), - /// A `typing.Union` type or `|` separated types e.g., `Union[int, str]` - /// or `int | str`. - Union(Vec<&'a Expr>), + /// A `typing.Union` type e.g., `Union[int, str]`. + Union(&'a Expr), + + /// A PEP 604 union type e.g., `int | str`. + PEP604Union(&'a Expr), /// A `typing.Literal` type e.g., `Literal[1, 2, 3]`. - Literal(Vec<&'a Expr>), + Literal(&'a Expr), /// A `typing.Optional` type e.g., `Optional[int]`. Optional(&'a Expr), @@ -99,17 +111,15 @@ impl<'a> TypingTarget<'a> { match expr { Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => { if semantic.match_typing_expr(value, "Optional") { - return Some(TypingTarget::Optional(slice.as_ref())); - } - let Expr::Tuple(ast::ExprTuple { elts: elements, .. }) = slice.as_ref() else { - return None; - }; - if semantic.match_typing_expr(value, "Literal") { - Some(TypingTarget::Literal(elements.iter().collect())) + Some(TypingTarget::Optional(slice.as_ref())) + } else if semantic.match_typing_expr(value, "Literal") { + Some(TypingTarget::Literal(slice)) } else if semantic.match_typing_expr(value, "Union") { - Some(TypingTarget::Union(elements.iter().collect())) + Some(TypingTarget::Union(slice)) } else if semantic.match_typing_expr(value, "Annotated") { - elements.first().map(TypingTarget::Annotated) + resolve_slice_value(slice.as_ref()) + .next() + .map(TypingTarget::Annotated) } else { semantic.resolve_call_path(value).map_or( // If we can't resolve the call path, it must be defined @@ -125,9 +135,7 @@ impl<'a> TypingTarget<'a> { ) } } - Expr::BinOp(..) => Some(TypingTarget::Union( - PEP604UnionIterator::new(expr).collect(), - )), + Expr::BinOp(..) => Some(TypingTarget::PEP604Union(expr)), Expr::Constant(ast::ExprConstant { value: Constant::None, .. @@ -172,7 +180,7 @@ impl<'a> TypingTarget<'a> { | TypingTarget::Object | TypingTarget::Unknown => true, TypingTarget::Known => false, - TypingTarget::Literal(elements) => elements.iter().any(|element| { + TypingTarget::Literal(slice) => resolve_slice_value(slice).any(|element| { // Literal can only contain `None`, a literal value, other `Literal` // or an enum value. match TypingTarget::try_from_expr(element, semantic, locator, minor_version) { @@ -183,17 +191,23 @@ impl<'a> TypingTarget<'a> { _ => false, } }), - TypingTarget::Union(elements) => elements.iter().any(|element| { + TypingTarget::Union(slice) => resolve_slice_value(slice).any(|element| { TypingTarget::try_from_expr(element, semantic, locator, minor_version) .map_or(true, |new_target| { new_target.contains_none(semantic, locator, minor_version) }) }), - TypingTarget::Annotated(element) => { + TypingTarget::PEP604Union(expr) => PEP604UnionIterator::new(expr).any(|element| { TypingTarget::try_from_expr(element, semantic, locator, minor_version) .map_or(true, |new_target| { new_target.contains_none(semantic, locator, minor_version) }) + }), + TypingTarget::Annotated(expr) => { + TypingTarget::try_from_expr(expr, semantic, locator, minor_version) + .map_or(true, |new_target| { + new_target.contains_none(semantic, locator, minor_version) + }) } TypingTarget::ForwardReference(expr) => { TypingTarget::try_from_expr(expr, semantic, locator, minor_version) @@ -219,17 +233,23 @@ impl<'a> TypingTarget<'a> { | TypingTarget::Object | TypingTarget::Known | TypingTarget::Unknown => false, - TypingTarget::Union(elements) => elements.iter().any(|element| { + TypingTarget::Union(slice) => resolve_slice_value(slice).any(|element| { TypingTarget::try_from_expr(element, semantic, locator, minor_version) .map_or(true, |new_target| { new_target.contains_any(semantic, locator, minor_version) }) }), - TypingTarget::Annotated(element) | TypingTarget::Optional(element) => { + TypingTarget::PEP604Union(expr) => PEP604UnionIterator::new(expr).any(|element| { TypingTarget::try_from_expr(element, semantic, locator, minor_version) .map_or(true, |new_target| { new_target.contains_any(semantic, locator, minor_version) }) + }), + TypingTarget::Annotated(expr) | TypingTarget::Optional(expr) => { + TypingTarget::try_from_expr(expr, semantic, locator, minor_version) + .map_or(true, |new_target| { + new_target.contains_any(semantic, locator, minor_version) + }) } TypingTarget::ForwardReference(expr) => { TypingTarget::try_from_expr(expr, semantic, locator, minor_version) diff --git a/crates/ruff/src/test.rs b/crates/ruff/src/test.rs index 1c2eb7aefb46aa..82814908db0d9a 100644 --- a/crates/ruff/src/test.rs +++ b/crates/ruff/src/test.rs @@ -83,7 +83,7 @@ pub fn test_snippet(contents: &str, settings: &Settings) -> Vec { } thread_local! { - static MAX_ITERATIONS: std::cell::Cell = std::cell::Cell::new(20); + static MAX_ITERATIONS: std::cell::Cell = std::cell::Cell::new(30); } pub fn set_max_iterations(max: usize) {