Skip to content

Commit

Permalink
Speed up the implementation of hasattr() checks (#14333)
Browse files Browse the repository at this point in the history
This makes the implementation of hasattr() checks faster (introduced in
#13544).

In particular, since the `extra_attrs` attribute used for hasattr()
checks is usually None, I micro-optimized the codepaths to avoid
expensive operations whenever there are no hasattr() checks.

Also avoid expensive operations on simple unions and order `isinstance`
checks so that common types are checked first.

I measured a 2% performance uplift in self-check.
  • Loading branch information
JukkaL authored Dec 26, 2022
1 parent c246a52 commit 01a1bf6
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 15 deletions.
2 changes: 1 addition & 1 deletion mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
42 changes: 28 additions & 14 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
ENUM_REMOVED_PROPS,
AnyType,
CallableType,
ExtraAttrs,
FormalArgument,
FunctionLike,
Instance,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 01a1bf6

Please sign in to comment.