Skip to content

Commit

Permalink
Revert "mypy: remove has_member (#8438)"
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
msullivan committed Mar 6, 2020
1 parent a6a53e5 commit 52af459
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 2 deletions.
48 changes: 48 additions & 0 deletions mypy/checkexpr.py
Expand Up @@ -2414,7 +2414,16 @@ def make_local_errors() -> MessageBuilder:
def lookup_operator(op_name: str, base_type: Type) -> Optional[Type]:
"""Looks up the given operator and returns the corresponding type,
if it exists."""

# This check is an important performance optimization,
# even though it is mostly a subset of
# analyze_member_access.
# TODO: Find a way to remove this call without performance implications.
if not self.has_member(base_type, op_name):
return None

local_errors = make_local_errors()

member = analyze_member_access(
name=op_name,
typ=base_type,
Expand Down Expand Up @@ -3795,6 +3804,45 @@ def is_valid_keyword_var_arg(self, typ: Type) -> bool:
[self.named_type('builtins.unicode'),
AnyType(TypeOfAny.special_form)])))

def has_member(self, typ: Type, member: str) -> bool:
"""Does type have member with the given name?"""
# TODO: refactor this to use checkmember.analyze_member_access, otherwise
# these two should be carefully kept in sync.
# This is much faster than analyze_member_access, though, and so using
# it first as a filter is important for performance.
typ = get_proper_type(typ)

if isinstance(typ, TypeVarType):
typ = get_proper_type(typ.upper_bound)
if isinstance(typ, TupleType):
typ = tuple_fallback(typ)
if isinstance(typ, LiteralType):
typ = typ.fallback
if isinstance(typ, Instance):
return typ.type.has_readable_member(member)
if isinstance(typ, CallableType) and typ.is_type_obj():
return typ.fallback.type.has_readable_member(member)
elif isinstance(typ, AnyType):
return True
elif isinstance(typ, UnionType):
result = all(self.has_member(x, member) for x in typ.relevant_items())
return result
elif isinstance(typ, TypeType):
# Type[Union[X, ...]] is always normalized to Union[Type[X], ...],
# so we don't need to care about unions here.
item = typ.item
if isinstance(item, TypeVarType):
item = get_proper_type(item.upper_bound)
if isinstance(item, TupleType):
item = tuple_fallback(item)
if isinstance(item, Instance) and item.type.metaclass_type is not None:
return self.has_member(item.type.metaclass_type, member)
if isinstance(item, AnyType):
return True
return False
else:
return False

def not_ready_callback(self, name: str, context: Context) -> None:
"""Called when we can't infer the type of a variable because it's not ready yet.
Expand Down
2 changes: 1 addition & 1 deletion mypy/typeops.py
Expand Up @@ -719,5 +719,5 @@ def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool
if isinstance(typ, AnyType):
# Avoid false positives in uncertain cases.
return True
# TODO: support other types (see analyze_member_access)?
# TODO: support other types (see ExpressionChecker.has_member())?
return False
2 changes: 1 addition & 1 deletion test-data/unit/check-classes.test
Expand Up @@ -6650,7 +6650,7 @@ reveal_type(0.5 + C) # N: Revealed type is 'Any'

reveal_type(0.5 + D()) # N: Revealed type is 'Any'
reveal_type(D() + 0.5) # N: Revealed type is 'Any'
reveal_type("str" + D()) # N: Revealed type is 'Any'
reveal_type("str" + D()) # N: Revealed type is 'builtins.str'
reveal_type(D() + "str") # N: Revealed type is 'Any'


Expand Down

0 comments on commit 52af459

Please sign in to comment.