From 51f334fbab461f58c0a67db75affd6e0cc839036 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 21 Dec 2022 13:13:39 +0000 Subject: [PATCH 1/2] Optimize meet --- mypy/meet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/meet.py b/mypy/meet.py index 5c187eeb37d4..8760b8c6d4fe 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -76,7 +76,7 @@ def meet_types(s: Type, t: Type) -> ProperType: # Code in checker.py should merge any extra_items where possible, so we # should have only compatible extra_items here. We check this before # the below subtype check, so that extra_attrs will not get erased. - if is_same_type(s, t) and (s.extra_attrs or t.extra_attrs): + if (s.extra_attrs or t.extra_attrs) and is_same_type(s, t): if s.extra_attrs and t.extra_attrs: if len(s.extra_attrs.attrs) > len(t.extra_attrs.attrs): # Return the one that has more precise information. From f69a55e48809e0ba8781285e3a41abace46ff1e8 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 21 Dec 2022 13:33:27 +0000 Subject: [PATCH 2/2] Micro-optimize extra attrs handling in union simplification This reduces a performance regression. The code path is very common. --- mypy/typeops.py | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 9f224e02c088..baf5b8552eff 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -33,6 +33,7 @@ ENUM_REMOVED_PROPS, AnyType, CallableType, + ExtraAttrs, FormalArgument, FunctionLike, Instance, @@ -466,16 +467,27 @@ def make_simplified_union( result = get_proper_type(UnionType.make_union(simplified_set, line, column)) - # Step 4: At last, we erase any (inconsistent) extra attributes on instances. - extra_attrs_set = set() - for item in items: - instance = try_getting_instance_fallback(item) - if instance and instance.extra_attrs: - extra_attrs_set.add(instance.extra_attrs) - - fallback = try_getting_instance_fallback(result) - if len(extra_attrs_set) > 1 and fallback: - fallback.extra_attrs = None + nitems = len(items) + if nitems > 1 and ( + nitems > 2 or not (type(items[0]) is NoneType or type(items[1]) is NoneType) + ): + # Step 4: At last, we erase any (inconsistent) extra attributes on instances. + + # Initialize with None instead of an empty set as a micro-optimization. The set + # is needed very rarely, so we try to avoid constructing it. + extra_attrs_set: set[ExtraAttrs] | None = None + for item in items: + instance = try_getting_instance_fallback(item) + if instance and instance.extra_attrs: + if extra_attrs_set is None: + extra_attrs_set = {instance.extra_attrs} + else: + extra_attrs_set.add(instance.extra_attrs) + + if extra_attrs_set is not None and len(extra_attrs_set) > 1: + fallback = try_getting_instance_fallback(result) + if fallback: + fallback.extra_attrs = None return result @@ -1006,13 +1018,15 @@ def try_getting_instance_fallback(typ: Type) -> Instance | None: typ = get_proper_type(typ) if isinstance(typ, Instance): return typ - elif isinstance(typ, TupleType): - return typ.partial_fallback - elif isinstance(typ, TypedDictType): + elif isinstance(typ, LiteralType): return typ.fallback + elif isinstance(typ, NoneType): + return None # Fast path for None, which is common elif isinstance(typ, FunctionLike): return typ.fallback - elif isinstance(typ, LiteralType): + elif isinstance(typ, TupleType): + return typ.partial_fallback + elif isinstance(typ, TypedDictType): return typ.fallback elif isinstance(typ, TypeVarType): return try_getting_instance_fallback(typ.upper_bound)