55 since these may assume that MROs are ready.
66"""
77
8- from typing import cast , Optional , List , Sequence , Set , Iterable , TypeVar , Dict , Tuple , Any , Union
8+ from typing import (
9+ cast , Optional , List , Sequence , Set , Iterable , TypeVar , Dict , Tuple , Any , Union , Callable
10+ )
911from typing_extensions import Type as TypingType
1012import itertools
1113import sys
@@ -336,6 +338,47 @@ def is_simple_literal(t: ProperType) -> bool:
336338 return False
337339
338340
341+ def _get_flattened_proper_types (items : Sequence [Type ]) -> Sequence [ProperType ]:
342+ """Similar to types.get_proper_types, with flattening of UnionType
343+
344+ Optimized to avoid allocating a new list whenever possible"""
345+ i : int = 0
346+ base : int = 0
347+ n : int = len (items )
348+
349+ # optimistic fast path
350+ while i < n :
351+ t = items [i ]
352+ pt = get_proper_type (t )
353+ if id (t ) != id (pt ) or isinstance (pt , UnionType ):
354+ # we need to allocate, switch to slow path
355+ break
356+ # simplify away any number of bottom type at the start of the input
357+ if i == base and i + 1 < n and isinstance (pt , UninhabitedType ):
358+ base += 1
359+ i += 1
360+
361+ # optimistic fast path reached end of input, no need to allocate
362+ if i == n :
363+ return cast (Sequence [ProperType ], items [base :] if base > 0 else items )
364+
365+ all_items = list (cast (Sequence [ProperType ], items [base :i ]))
366+
367+ while i < n :
368+ pt = get_proper_type (items [i ])
369+ if isinstance (pt , UnionType ):
370+ all_items .extend (_get_flattened_proper_types (pt .items ))
371+ else :
372+ all_items .append (pt )
373+ i += 1
374+ return all_items
375+
376+
377+ _simplified_union_cache : List [Dict [Tuple [ProperType , ...], ProperType ]] = [
378+ {} for _ in range (2 ** 3 )
379+ ]
380+
381+
339382def make_simplified_union (items : Sequence [Type ],
340383 line : int = - 1 , column : int = - 1 ,
341384 * , keep_erased : bool = False ,
@@ -362,17 +405,35 @@ def make_simplified_union(items: Sequence[Type],
362405 back into a sum type. Set it to False when called by try_expanding_sum_type_
363406 to_union().
364407 """
365- items = get_proper_types (items )
366-
367408 # Step 1: expand all nested unions
368- while any (isinstance (typ , UnionType ) for typ in items ):
369- all_items : List [ProperType ] = []
370- for typ in items :
371- if isinstance (typ , UnionType ):
372- all_items .extend (get_proper_types (typ .items ))
373- else :
374- all_items .append (typ )
375- items = all_items
409+ items = _get_flattened_proper_types (items )
410+
411+ cache_fn : Optional [Callable [[ProperType ], None ]] = None
412+
413+ # 1 or 2 elements account for the vast majority of inputs and are not worth caching:
414+ # - they're two small for the quadratic worst-case cost of simplification to really
415+ # manifest
416+ # - they majority of those inputs are only triggered once
417+ # - avoiding the extra allocations is a bigger win
418+ if len (items ) == 1 :
419+ return items [0 ]
420+ elif len (items ) > 2 :
421+ # NB: ideally we would use a frozenset, but that would require normalizing the
422+ # order of entries in the simplified union, or updating the test harness to
423+ # treat Unions as equivalent regardless of item ordering (which is particularly
424+ # tricky when it comes to all tests using string matching on reveal_type output)
425+ cache_key = tuple (items )
426+ # NB: we need to maintain separate caches depending on flags that might impact
427+ # the results of simplification
428+ cache = _simplified_union_cache [
429+ int (keep_erased )
430+ | int (contract_literals ) << 1
431+ | int (state .strict_optional ) << 2
432+ ]
433+ ret = cache .get (cache_key , None )
434+ if ret is not None :
435+ return ret
436+ cache_fn = lambda v : cache .__setitem__ (cache_key , v ) # noqa: E731
376437
377438 # Step 2: remove redundant unions
378439 simplified_set = _remove_redundant_union_items (items , keep_erased )
@@ -381,13 +442,20 @@ def make_simplified_union(items: Sequence[Type],
381442 if contract_literals and sum (isinstance (item , LiteralType ) for item in simplified_set ) > 1 :
382443 simplified_set = try_contracting_literals_in_union (simplified_set )
383444
384- return UnionType .make_union (simplified_set , line , column )
445+ ret = UnionType .make_union (simplified_set , line , column )
446+
447+ if cache_fn :
448+ cache_fn (ret )
449+
450+ return ret
385451
386452
387- def _remove_redundant_union_items (items : List [ProperType ], keep_erased : bool ) -> List [ProperType ]:
453+ def _remove_redundant_union_items (items : Sequence [ProperType ],
454+ keep_erased : bool ) -> Sequence [ProperType ]:
388455 from mypy .subtypes import is_proper_subtype
389456
390457 removed : Set [int ] = set ()
458+ truthed : Set [int ] = set ()
391459 seen : Set [Tuple [str , ...]] = set ()
392460
393461 # NB: having a separate fast path for Union of Literal and slow path for other things
@@ -397,6 +465,7 @@ def _remove_redundant_union_items(items: List[ProperType], keep_erased: bool) ->
397465 for i , item in enumerate (items ):
398466 if i in removed :
399467 continue
468+
400469 # Avoid slow nested for loop for Union of Literal of strings/enums (issue #9169)
401470 k = simple_literal_value_key (item )
402471 if k is not None :
@@ -434,20 +503,34 @@ def _remove_redundant_union_items(items: List[ProperType], keep_erased: bool) ->
434503 continue
435504 # actual redundancy checks
436505 if (
437- is_redundant_literal_instance (item , tj ) # XXX?
438- and is_proper_subtype (tj , item , keep_erased_types = keep_erased )
506+ isinstance (tj , UninhabitedType )
507+ or (
508+ (
509+ not isinstance (item , Instance )
510+ or item .last_known_value is None
511+ or (
512+ isinstance (tj , Instance )
513+ and tj .last_known_value == item .last_known_value
514+ )
515+ )
516+ and is_proper_subtype (tj , item , keep_erased_types = keep_erased )
517+ )
439518 ):
440519 # We found a redundant item in the union.
441520 removed .add (j )
442521 cbt = cbt or tj .can_be_true
443522 cbf = cbf or tj .can_be_false
523+
444524 # if deleted subtypes had more general truthiness, use that
445525 if not item .can_be_true and cbt :
446- items [ i ] = true_or_false ( item )
526+ truthed . add ( i )
447527 elif not item .can_be_false and cbf :
448- items [ i ] = true_or_false ( item )
528+ truthed . add ( i )
449529
450- return [items [i ] for i in range (len (items )) if i not in removed ]
530+ if not removed and not truthed :
531+ return items
532+ return [true_or_false (items [i ]) if i in truthed else items [i ]
533+ for i in range (len (items )) if i not in removed ]
451534
452535
453536def _get_type_special_method_bool_ret_type (t : Type ) -> Optional [Type ]:
@@ -889,17 +972,6 @@ def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool
889972 return False
890973
891974
892- def is_redundant_literal_instance (general : ProperType , specific : ProperType ) -> bool :
893- if not isinstance (general , Instance ) or general .last_known_value is None :
894- return True
895- if isinstance (specific , Instance ) and specific .last_known_value == general .last_known_value :
896- return True
897- if isinstance (specific , UninhabitedType ):
898- return True
899-
900- return False
901-
902-
903975def separate_union_literals (t : UnionType ) -> Tuple [Sequence [LiteralType ], Sequence [Type ]]:
904976 """Separate literals from other members in a union type."""
905977 literal_items = []
0 commit comments