Skip to content

Commit ab6185e

Browse files
authored
Further speed up union simplification (#12541)
This tweaks a change made in #12539 that may have slowed things down. The behavior introduced in the PR was more correct, but it's not worth a potential major performance regression, since union simplification is not something we have to get always right for correctness. Work on #12526.
1 parent cddd819 commit ab6185e

File tree

2 files changed

+41
-9
lines changed

2 files changed

+41
-9
lines changed

mypy/test/testtypes.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from mypy.types import (
1313
UnboundType, AnyType, CallableType, TupleType, TypeVarType, Type, Instance, NoneType,
1414
Overloaded, TypeType, UnionType, UninhabitedType, TypeVarId, TypeOfAny, ProperType,
15-
get_proper_type
15+
LiteralType, get_proper_type
1616
)
1717
from mypy.nodes import ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, CONTRAVARIANT, INVARIANT, COVARIANT
1818
from mypy.subtypes import is_subtype, is_more_precise, is_proper_subtype
@@ -496,6 +496,14 @@ def test_simplified_union_with_str_literals(self) -> None:
496496
self.assert_simplified_union([fx.lit_str1, fx.lit_str2, fx.uninhabited],
497497
UnionType([fx.lit_str1, fx.lit_str2]))
498498

499+
def test_simplify_very_large_union(self) -> None:
500+
fx = self.fx
501+
literals = []
502+
for i in range(5000):
503+
literals.append(LiteralType("v%d" % i, fx.str_type))
504+
# This shouldn't be very slow, even if the union is big.
505+
self.assert_simplified_union([*literals, fx.str_type], fx.str_type)
506+
499507
def test_simplified_union_with_str_instance_literals(self) -> None:
500508
fx = self.fx
501509

mypy/typeops.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,15 @@ def simple_literal_value_key(t: ProperType) -> Optional[Tuple[str, ...]]:
318318
return None
319319

320320

321+
def is_simple_literal(t: ProperType) -> bool:
322+
"""Fast way to check if simple_literal_value_key() would return a non-None value."""
323+
if isinstance(t, LiteralType):
324+
return t.fallback.type.is_enum or t.fallback.type.fullname == 'builtins.str'
325+
if isinstance(t, Instance):
326+
return t.last_known_value is not None and isinstance(t.last_known_value.value, str)
327+
return False
328+
329+
321330
def make_simplified_union(items: Sequence[Type],
322331
line: int = -1, column: int = -1,
323332
*, keep_erased: bool = False,
@@ -392,17 +401,26 @@ def _remove_redundant_union_items(items: List[ProperType], keep_erased: bool) ->
392401

393402
# Keep track of the truishness info for deleted subtypes which can be relevant
394403
cbt = cbf = False
404+
num_items = len(items)
395405
for j, tj in enumerate(items):
396-
# NB: we don't need to check literals as the fast path above takes care of that
397-
if (
398-
i != j
406+
if i != j:
407+
# NB: The first check below is an optimization to
408+
# avoid very expensive computations with large
409+
# unions involving literals. We approximate the
410+
# results for unions with many items. This is
411+
# "fine" since simplifying these union items is
412+
# (almost) always optional.
413+
if (
414+
(num_items < 5
415+
or is_likely_literal_supertype(item)
416+
or not is_simple_literal(tj))
399417
and is_proper_subtype(tj, item, keep_erased_types=keep_erased)
400418
and is_redundant_literal_instance(item, tj) # XXX?
401-
):
402-
# We found a redundant item in the union.
403-
removed.add(j)
404-
cbt = cbt or tj.can_be_true
405-
cbf = cbf or tj.can_be_false
419+
):
420+
# We found a redundant item in the union.
421+
removed.add(j)
422+
cbt = cbt or tj.can_be_true
423+
cbf = cbf or tj.can_be_false
406424
# if deleted subtypes had more general truthiness, use that
407425
if not item.can_be_true and cbt:
408426
items[i] = true_or_false(item)
@@ -412,6 +430,12 @@ def _remove_redundant_union_items(items: List[ProperType], keep_erased: bool) ->
412430
return [items[i] for i in range(len(items)) if i not in removed]
413431

414432

433+
def is_likely_literal_supertype(t: ProperType) -> bool:
434+
"""Is the type likely to cause simplification of literal types in unions?"""
435+
return isinstance(t, Instance) and t.type.fullname in ('builtins.object',
436+
'builtins.str')
437+
438+
415439
def _get_type_special_method_bool_ret_type(t: Type) -> Optional[Type]:
416440
t = get_proper_type(t)
417441

0 commit comments

Comments
 (0)