Skip to content

Commit dc0d35f

Browse files
committed
Revert "mypy: remove has_member (#8438)" (#8500)
It turns out that the has_member check is an important (accidental?) performance optimization. Removing this caused a major (30+%?) slowdown at dropbox. There might be a better way to optimize this but I'm just going to revert it for now at least. This reverts commit 09cdab4.
1 parent 30c46ab commit dc0d35f

File tree

3 files changed

+50
-2
lines changed

3 files changed

+50
-2
lines changed

mypy/checkexpr.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2409,7 +2409,16 @@ def make_local_errors() -> MessageBuilder:
24092409
def lookup_operator(op_name: str, base_type: Type) -> Optional[Type]:
24102410
"""Looks up the given operator and returns the corresponding type,
24112411
if it exists."""
2412+
2413+
# This check is an important performance optimization,
2414+
# even though it is mostly a subset of
2415+
# analyze_member_access.
2416+
# TODO: Find a way to remove this call without performance implications.
2417+
if not self.has_member(base_type, op_name):
2418+
return None
2419+
24122420
local_errors = make_local_errors()
2421+
24132422
member = analyze_member_access(
24142423
name=op_name,
24152424
typ=base_type,
@@ -3790,6 +3799,45 @@ def is_valid_keyword_var_arg(self, typ: Type) -> bool:
37903799
[self.named_type('builtins.unicode'),
37913800
AnyType(TypeOfAny.special_form)])))
37923801

3802+
def has_member(self, typ: Type, member: str) -> bool:
3803+
"""Does type have member with the given name?"""
3804+
# TODO: refactor this to use checkmember.analyze_member_access, otherwise
3805+
# these two should be carefully kept in sync.
3806+
# This is much faster than analyze_member_access, though, and so using
3807+
# it first as a filter is important for performance.
3808+
typ = get_proper_type(typ)
3809+
3810+
if isinstance(typ, TypeVarType):
3811+
typ = get_proper_type(typ.upper_bound)
3812+
if isinstance(typ, TupleType):
3813+
typ = tuple_fallback(typ)
3814+
if isinstance(typ, LiteralType):
3815+
typ = typ.fallback
3816+
if isinstance(typ, Instance):
3817+
return typ.type.has_readable_member(member)
3818+
if isinstance(typ, CallableType) and typ.is_type_obj():
3819+
return typ.fallback.type.has_readable_member(member)
3820+
elif isinstance(typ, AnyType):
3821+
return True
3822+
elif isinstance(typ, UnionType):
3823+
result = all(self.has_member(x, member) for x in typ.relevant_items())
3824+
return result
3825+
elif isinstance(typ, TypeType):
3826+
# Type[Union[X, ...]] is always normalized to Union[Type[X], ...],
3827+
# so we don't need to care about unions here.
3828+
item = typ.item
3829+
if isinstance(item, TypeVarType):
3830+
item = get_proper_type(item.upper_bound)
3831+
if isinstance(item, TupleType):
3832+
item = tuple_fallback(item)
3833+
if isinstance(item, Instance) and item.type.metaclass_type is not None:
3834+
return self.has_member(item.type.metaclass_type, member)
3835+
if isinstance(item, AnyType):
3836+
return True
3837+
return False
3838+
else:
3839+
return False
3840+
37933841
def not_ready_callback(self, name: str, context: Context) -> None:
37943842
"""Called when we can't infer the type of a variable because it's not ready yet.
37953843

mypy/typeops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,5 +719,5 @@ def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool
719719
if isinstance(typ, AnyType):
720720
# Avoid false positives in uncertain cases.
721721
return True
722-
# TODO: support other types (see analyze_member_access)?
722+
# TODO: support other types (see ExpressionChecker.has_member())?
723723
return False

test-data/unit/check-classes.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6650,7 +6650,7 @@ reveal_type(0.5 + C) # N: Revealed type is 'Any'
66506650

66516651
reveal_type(0.5 + D()) # N: Revealed type is 'Any'
66526652
reveal_type(D() + 0.5) # N: Revealed type is 'Any'
6653-
reveal_type("str" + D()) # N: Revealed type is 'Any'
6653+
reveal_type("str" + D()) # N: Revealed type is 'builtins.str'
66546654
reveal_type(D() + "str") # N: Revealed type is 'Any'
66556655

66566656

0 commit comments

Comments
 (0)