@@ -185,6 +185,8 @@ def is_subtype(
185185 # steps we come back to initial call is_subtype(A, B) and immediately return True.
186186 with pop_on_exit (type_state .get_assumptions (is_proper = False ), left , right ):
187187 return _is_subtype (left , right , subtype_context , proper_subtype = False )
188+ left = get_proper_type (left )
189+ right = get_proper_type (right )
188190 return _is_subtype (left , right , subtype_context , proper_subtype = False )
189191
190192
@@ -282,6 +284,21 @@ def is_same_type(
282284 )
283285
284286
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+
285302# This is a common entry point for subtyping checks (both proper and non-proper).
286303# Never call this private function directly, use the public versions.
287304def _is_subtype (
@@ -303,7 +320,30 @@ def _is_subtype(
303320 # TODO: should we consider all types proper subtypes of UnboundType and/or
304321 # ErasedType as we do for non-proper subtyping.
305322 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 )
307347 if isinstance (right , UnionType ) and not isinstance (left , UnionType ):
308348 # Normally, when 'left' is not itself a union, the only way
309349 # 'left' can be a subtype of the union 'right' is if it is a
0 commit comments