@@ -185,6 +185,8 @@ def is_subtype(
185
185
# steps we come back to initial call is_subtype(A, B) and immediately return True.
186
186
with pop_on_exit (type_state .get_assumptions (is_proper = False ), left , right ):
187
187
return _is_subtype (left , right , subtype_context , proper_subtype = False )
188
+ left = get_proper_type (left )
189
+ right = get_proper_type (right )
188
190
return _is_subtype (left , right , subtype_context , proper_subtype = False )
189
191
190
192
@@ -282,6 +284,21 @@ def is_same_type(
282
284
)
283
285
284
286
287
+ # This is a helper function used to check for recursive type of distributed tuple
288
+ def is_structurally_recursive (typ : Type , seen : set [Type ] | None = None ) -> bool :
289
+ if seen is None :
290
+ seen = set ()
291
+ typ = get_proper_type (typ )
292
+ if typ in seen :
293
+ return True
294
+ seen .add (typ )
295
+ if isinstance (typ , UnionType ):
296
+ return any (is_structurally_recursive (item , seen .copy ()) for item in typ .items )
297
+ if isinstance (typ , TupleType ):
298
+ return any (is_structurally_recursive (item , seen .copy ()) for item in typ .items )
299
+ return False
300
+
301
+
285
302
# This is a common entry point for subtyping checks (both proper and non-proper).
286
303
# Never call this private function directly, use the public versions.
287
304
def _is_subtype (
@@ -303,7 +320,30 @@ def _is_subtype(
303
320
# TODO: should we consider all types proper subtypes of UnboundType and/or
304
321
# ErasedType as we do for non-proper subtyping.
305
322
return True
306
-
323
+ if isinstance (left , TupleType ) and isinstance (right , UnionType ):
324
+ # check only if not recursive type because if recursive type,
325
+ # test run into maximum recursive depth reached
326
+ if not is_structurally_recursive (left ) and not is_structurally_recursive (right ):
327
+ fallback = left .partial_fallback
328
+ tuple_items = left .items
329
+ if hasattr (left , "fallback" ) and left .fallback is not None :
330
+ fallback = left .fallback
331
+ for i in range (len (tuple_items )):
332
+ uitems = tuple_items [i ]
333
+ uitems_type = get_proper_type (uitems )
334
+ if isinstance (uitems_type , UnionType ):
335
+ new_tuples = [
336
+ TupleType (
337
+ tuple_items [:i ] + [uitem ] + tuple_items [i + 1 :], fallback = fallback
338
+ )
339
+ for uitem in uitems_type .items
340
+ ]
341
+ result = [
342
+ _is_subtype (t , right , subtype_context , proper_subtype = False )
343
+ for t in new_tuples
344
+ ]
345
+ inverted_list = [not item for item in result ]
346
+ return not any (inverted_list )
307
347
if isinstance (right , UnionType ) and not isinstance (left , UnionType ):
308
348
# Normally, when 'left' is not itself a union, the only way
309
349
# 'left' can be a subtype of the union 'right' is if it is a
0 commit comments