@@ -99,13 +99,138 @@ static_assert(is_singleton(None))
99
99
static_assert(not is_singleton(tuple[None ]))
100
100
```
101
101
102
+ ## Tuples containing ` Never `
103
+
104
+ ``` toml
105
+ [environment ]
106
+ python-version = " 3.11"
107
+ ```
108
+
109
+ The ` Never ` type contains no inhabitants, so a tuple type that contains ` Never ` as a mandatory
110
+ element also contains no inhabitants.
111
+
112
+ ``` py
113
+ from typing import Never
114
+ from ty_extensions import static_assert, is_equivalent_to
115
+
116
+ static_assert(is_equivalent_to(tuple[Never], Never))
117
+ static_assert(is_equivalent_to(tuple[int , Never], Never))
118
+ static_assert(is_equivalent_to(tuple[Never, * tuple[int , ... ]], Never))
119
+ ```
120
+
121
+ If the variable-length portion of a tuple is ` Never ` , then that portion of the tuple must always be
122
+ empty. This means that the tuple is not actually variable-length!
123
+
124
+ ``` py
125
+ from typing import Never
126
+ from ty_extensions import static_assert, is_equivalent_to
127
+
128
+ static_assert(is_equivalent_to(tuple[Never, ... ], tuple[()]))
129
+ static_assert(is_equivalent_to(tuple[int , * tuple[Never, ... ]], tuple[int ]))
130
+ static_assert(is_equivalent_to(tuple[int , * tuple[Never, ... ], int ], tuple[int , int ]))
131
+ static_assert(is_equivalent_to(tuple[* tuple[Never, ... ], int ], tuple[int ]))
132
+ ```
133
+
134
+ ## Homogeneous non-empty tuples
135
+
136
+ ``` toml
137
+ [environment ]
138
+ python-version = " 3.11"
139
+ ```
140
+
141
+ A homogeneous tuple can contain zero or more elements of a particular type. You can represent a
142
+ tuple that can contain _ one_ or more elements of that type (or any other number of minimum elements)
143
+ using a mixed tuple.
144
+
145
+ ``` py
146
+ def takes_zero_or_more (t : tuple[int , ... ]) -> None : ...
147
+ def takes_one_or_more (t : tuple[int , * tuple[int , ... ]]) -> None : ...
148
+ def takes_two_or_more (t : tuple[int , int , * tuple[int , ... ]]) -> None : ...
149
+
150
+ takes_zero_or_more(())
151
+ takes_zero_or_more((1 ,))
152
+ takes_zero_or_more((1 , 2 ))
153
+
154
+ takes_one_or_more(()) # error: [invalid-argument-type]
155
+ takes_one_or_more((1 ,))
156
+ takes_one_or_more((1 , 2 ))
157
+
158
+ takes_two_or_more(()) # error: [invalid-argument-type]
159
+ takes_two_or_more((1 ,)) # error: [invalid-argument-type]
160
+ takes_two_or_more((1 , 2 ))
161
+ ```
162
+
163
+ The required elements can also appear in the suffix of the mixed tuple type.
164
+
165
+ ``` py
166
+ def takes_one_or_more_suffix (t : tuple[* tuple[int , ... ], int ]) -> None : ...
167
+ def takes_two_or_more_suffix (t : tuple[* tuple[int , ... ], int , int ]) -> None : ...
168
+ def takes_two_or_more_mixed (t : tuple[int , * tuple[int , ... ], int ]) -> None : ...
169
+
170
+ takes_one_or_more_suffix(()) # error: [invalid-argument-type]
171
+ takes_one_or_more_suffix((1 ,))
172
+ takes_one_or_more_suffix((1 , 2 ))
173
+
174
+ takes_two_or_more_suffix(()) # error: [invalid-argument-type]
175
+ takes_two_or_more_suffix((1 ,)) # error: [invalid-argument-type]
176
+ takes_two_or_more_suffix((1 , 2 ))
177
+
178
+ takes_two_or_more_mixed(()) # error: [invalid-argument-type]
179
+ takes_two_or_more_mixed((1 ,)) # error: [invalid-argument-type]
180
+ takes_two_or_more_mixed((1 , 2 ))
181
+ ```
182
+
183
+ The tuple types are equivalent regardless of whether the required elements appear in the prefix or
184
+ suffix.
185
+
186
+ ``` py
187
+ from ty_extensions import static_assert, is_subtype_of, is_equivalent_to
188
+
189
+ static_assert(is_equivalent_to(tuple[int , * tuple[int , ... ]], tuple[* tuple[int , ... ], int ]))
190
+
191
+ static_assert(is_equivalent_to(tuple[int , int , * tuple[int , ... ]], tuple[* tuple[int , ... ], int , int ]))
192
+ static_assert(is_equivalent_to(tuple[int , int , * tuple[int , ... ]], tuple[int , * tuple[int , ... ], int ]))
193
+ ```
194
+
195
+ This is true when the prefix/suffix and variable-length types are equivalent, not just identical.
196
+
197
+ ``` py
198
+ from ty_extensions import static_assert, is_subtype_of, is_equivalent_to
199
+
200
+ static_assert(is_equivalent_to(tuple[int | str , * tuple[str | int , ... ]], tuple[* tuple[str | int , ... ], int | str ]))
201
+
202
+ static_assert(
203
+ is_equivalent_to(tuple[int | str , str | int , * tuple[str | int , ... ]], tuple[* tuple[int | str , ... ], str | int , int | str ])
204
+ )
205
+ static_assert(
206
+ is_equivalent_to(tuple[int | str , str | int , * tuple[str | int , ... ]], tuple[str | int , * tuple[int | str , ... ], int | str ])
207
+ )
208
+ ```
209
+
102
210
## Disjointness
103
211
104
- A tuple ` tuple[P1, P2] ` is disjoint from a tuple ` tuple[Q1, Q2] ` if either ` P1 ` is disjoint from
105
- ` Q1 ` or if ` P2 ` is disjoint from ` Q2 ` :
212
+ ``` toml
213
+ [environment ]
214
+ python-version = " 3.11"
215
+ ```
216
+
217
+ Two tuples with incompatible minimum lengths are always disjoint, regardless of their element types.
218
+ (The lengths are incompatible if the minimum length of one tuple is larger than the maximum length
219
+ of the other.)
106
220
107
221
``` py
108
222
from ty_extensions import static_assert, is_disjoint_from
223
+
224
+ static_assert(is_disjoint_from(tuple[()], tuple[int ]))
225
+ static_assert(not is_disjoint_from(tuple[()], tuple[int , ... ]))
226
+ static_assert(not is_disjoint_from(tuple[int ], tuple[int , ... ]))
227
+ static_assert(not is_disjoint_from(tuple[str , ... ], tuple[int , ... ]))
228
+ ```
229
+
230
+ A tuple that is required to contain elements ` P1, P2 ` is disjoint from a tuple that is required to
231
+ contain elements ` Q1, Q2 ` if either ` P1 ` is disjoint from ` Q1 ` or if ` P2 ` is disjoint from ` Q2 ` .
232
+
233
+ ``` py
109
234
from typing import final
110
235
111
236
@final
@@ -124,9 +249,28 @@ static_assert(is_disjoint_from(tuple[F1, F2], tuple[F2, F1]))
124
249
static_assert(is_disjoint_from(tuple[F1, N1], tuple[F2, N2]))
125
250
static_assert(is_disjoint_from(tuple[N1, F1], tuple[N2, F2]))
126
251
static_assert(not is_disjoint_from(tuple[N1, N2], tuple[N2, N1]))
252
+
253
+ static_assert(is_disjoint_from(tuple[F1, * tuple[int , ... ], F2], tuple[F2, * tuple[int , ... ], F1]))
254
+ static_assert(is_disjoint_from(tuple[F1, * tuple[int , ... ], N1], tuple[F2, * tuple[int , ... ], N2]))
255
+ static_assert(is_disjoint_from(tuple[N1, * tuple[int , ... ], F1], tuple[N2, * tuple[int , ... ], F2]))
256
+ static_assert(not is_disjoint_from(tuple[N1, * tuple[int , ... ], N2], tuple[N2, * tuple[int , ... ], N1]))
257
+
258
+ static_assert(not is_disjoint_from(tuple[F1, F2, * tuple[object , ... ]], tuple[* tuple[object , ... ], F2, F1]))
259
+ static_assert(not is_disjoint_from(tuple[F1, N1, * tuple[object , ... ]], tuple[* tuple[object , ... ], F2, N2]))
260
+ static_assert(not is_disjoint_from(tuple[N1, F1, * tuple[object , ... ]], tuple[* tuple[object , ... ], N2, F2]))
261
+ static_assert(not is_disjoint_from(tuple[N1, N2, * tuple[object , ... ]], tuple[* tuple[object , ... ], N2, N1]))
262
+ ```
263
+
264
+ The variable-length portion of a tuple can never cause the tuples to be disjoint, since all
265
+ variable-length tuple types contain the empty tuple. (Note that per above, the variable-length
266
+ portion of a tuple cannot be ` Never ` ; internally we simplify this to a fixed-length tuple.)
267
+
268
+ ``` py
269
+ static_assert(not is_disjoint_from(tuple[F1, ... ], tuple[F2, ... ]))
270
+ static_assert(not is_disjoint_from(tuple[N1, ... ], tuple[N2, ... ]))
127
271
```
128
272
129
- We currently model tuple types to * not * be disjoint from arbitrary instance types, because we allow
273
+ We currently model tuple types to _ not _ be disjoint from arbitrary instance types, because we allow
130
274
for the possibility of ` tuple ` to be subclassed
131
275
132
276
``` py
@@ -152,21 +296,71 @@ class CommonSubtypeOfTuples(I1, I2): ...
152
296
153
297
## Truthiness
154
298
155
- The truthiness of the empty tuple is ` False ` :
299
+ ``` toml
300
+ [environment ]
301
+ python-version = " 3.11"
302
+ ```
303
+
304
+ The truthiness of the empty tuple is ` False ` .
156
305
157
306
``` py
158
307
from typing_extensions import assert_type, Literal
308
+ from ty_extensions import static_assert, is_assignable_to, AlwaysFalsy
159
309
160
310
assert_type(bool (()), Literal[False ])
311
+
312
+ static_assert(is_assignable_to(tuple[()], AlwaysFalsy))
161
313
```
162
314
163
- The truthiness of non-empty tuples is always ` True ` , even if all elements are falsy:
315
+ The truthiness of non-empty tuples is always ` True ` . This is true even if all elements are falsy,
316
+ and even if any element is gradual, since the truthiness of a tuple depends only on its length, not
317
+ its content.
164
318
165
319
``` py
166
- from typing_extensions import assert_type, Literal
320
+ from typing_extensions import assert_type, Any, Literal
321
+ from ty_extensions import static_assert, is_assignable_to, AlwaysTruthy
167
322
168
323
assert_type(bool ((False ,)), Literal[True ])
169
324
assert_type(bool ((False , False )), Literal[True ])
325
+
326
+ static_assert(is_assignable_to(tuple[Any], AlwaysTruthy))
327
+ static_assert(is_assignable_to(tuple[Any, Any], AlwaysTruthy))
328
+ static_assert(is_assignable_to(tuple[bool ], AlwaysTruthy))
329
+ static_assert(is_assignable_to(tuple[bool , bool ], AlwaysTruthy))
330
+ static_assert(is_assignable_to(tuple[Literal[False ]], AlwaysTruthy))
331
+ static_assert(is_assignable_to(tuple[Literal[False ], Literal[False ]], AlwaysTruthy))
332
+ ```
333
+
334
+ The truthiness of variable-length tuples is ambiguous, since that type contains both empty and
335
+ non-empty tuples.
336
+
337
+ ``` py
338
+ from typing_extensions import Any, Literal
339
+ from ty_extensions import static_assert, is_assignable_to, AlwaysFalsy, AlwaysTruthy
340
+
341
+ static_assert(not is_assignable_to(tuple[Any, ... ], AlwaysFalsy))
342
+ static_assert(not is_assignable_to(tuple[Any, ... ], AlwaysTruthy))
343
+ static_assert(not is_assignable_to(tuple[bool , ... ], AlwaysFalsy))
344
+ static_assert(not is_assignable_to(tuple[bool , ... ], AlwaysTruthy))
345
+ static_assert(not is_assignable_to(tuple[Literal[False ], ... ], AlwaysFalsy))
346
+ static_assert(not is_assignable_to(tuple[Literal[False ], ... ], AlwaysTruthy))
347
+ static_assert(not is_assignable_to(tuple[Literal[True ], ... ], AlwaysFalsy))
348
+ static_assert(not is_assignable_to(tuple[Literal[True ], ... ], AlwaysTruthy))
349
+
350
+ static_assert(is_assignable_to(tuple[int , * tuple[Any, ... ]], AlwaysTruthy))
351
+ static_assert(is_assignable_to(tuple[int , * tuple[bool , ... ]], AlwaysTruthy))
352
+ static_assert(is_assignable_to(tuple[int , * tuple[Literal[False ], ... ]], AlwaysTruthy))
353
+ static_assert(is_assignable_to(tuple[int , * tuple[Literal[True ], ... ]], AlwaysTruthy))
354
+
355
+ static_assert(is_assignable_to(tuple[* tuple[Any, ... ], int ], AlwaysTruthy))
356
+ static_assert(is_assignable_to(tuple[* tuple[bool , ... ], int ], AlwaysTruthy))
357
+ static_assert(is_assignable_to(tuple[* tuple[Literal[False ], ... ], int ], AlwaysTruthy))
358
+ static_assert(is_assignable_to(tuple[* tuple[Literal[True ], ... ], int ], AlwaysTruthy))
359
+
360
+ static_assert(is_assignable_to(tuple[int , * tuple[Any, ... ], int ], AlwaysTruthy))
361
+ static_assert(is_assignable_to(tuple[int , * tuple[bool , ... ], int ], AlwaysTruthy))
362
+ static_assert(is_assignable_to(tuple[int , * tuple[Literal[False ], ... ], int ], AlwaysTruthy))
363
+ static_assert(is_assignable_to(tuple[int , * tuple[Literal[True ], ... ], int ], AlwaysTruthy))
170
364
```
171
365
172
366
Both of these results are conflicting with the fact that tuples can be subclassed, and that we
0 commit comments