diff --git a/mypy/checker.py b/mypy/checker.py index 04d1ea9d0dfa..f67633611be1 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3801,12 +3801,12 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context, isinstance(subtype, (Instance, TupleType, TypedDictType))): self.msg.report_protocol_problems(subtype, supertype, context, code=code) if isinstance(supertype, CallableType) and isinstance(subtype, Instance): - call = find_member('__call__', subtype, subtype) + call = find_member('__call__', subtype, subtype, is_operator=True) if call: self.msg.note_call(subtype, call, context, code=code) if isinstance(subtype, (CallableType, Overloaded)) and isinstance(supertype, Instance): if supertype.type.is_protocol and supertype.type.protocol_members == ['__call__']: - call = find_member('__call__', supertype, subtype) + call = find_member('__call__', supertype, subtype, is_operator=True) assert call is not None self.msg.note_call(supertype, call, context, code=code) return False @@ -4068,7 +4068,7 @@ def iterable_item_type(self, instance: Instance) -> Type: # in case there is no explicit base class. return item_type # Try also structural typing. - iter_type = get_proper_type(find_member('__iter__', instance, instance)) + iter_type = get_proper_type(find_member('__iter__', instance, instance, is_operator=True)) if iter_type and isinstance(iter_type, CallableType): ret_type = get_proper_type(iter_type.ret_type) if isinstance(ret_type, Instance): diff --git a/mypy/constraints.py b/mypy/constraints.py index aebd1a76680b..1258065d0c09 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -285,7 +285,8 @@ def visit_instance(self, template: Instance) -> List[Constraint]: if not any(mypy.sametypes.is_same_type(template, t) for t in template.type.inferring): template.type.inferring.append(template) - call = mypy.subtypes.find_member('__call__', template, actual) + call = mypy.subtypes.find_member('__call__', template, actual, + is_operator=True) assert call is not None if mypy.subtypes.is_subtype(actual, erase_typevars(call)): subres = infer_constraints(call, actual, self.direction) @@ -430,7 +431,8 @@ def visit_callable_type(self, template: CallableType) -> List[Constraint]: elif isinstance(self.actual, Instance): # Instances with __call__ method defined are considered structural # subtypes of Callable with a compatible signature. - call = mypy.subtypes.find_member('__call__', self.actual, self.actual) + call = mypy.subtypes.find_member('__call__', self.actual, self.actual, + is_operator=True) if call: return infer_constraints(template, call, self.direction) else: diff --git a/mypy/join.py b/mypy/join.py index 20d18d96d16b..b8f1f2d66dc8 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -468,5 +468,5 @@ def join_type_list(types: List[Type]) -> Type: def unpack_callback_protocol(t: Instance) -> Optional[Type]: assert t.type.is_protocol if t.type.protocol_members == ['__call__']: - return find_member('__call__', t, t) + return find_member('__call__', t, t, is_operator=True) return None diff --git a/mypy/messages.py b/mypy/messages.py index 887735328c9b..0641fd42dd03 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -501,7 +501,8 @@ def incompatible_argument_note(self, self.report_protocol_problems(original_caller_type, callee_type, context, code=code) if (isinstance(callee_type, CallableType) and isinstance(original_caller_type, Instance)): - call = find_member('__call__', original_caller_type, original_caller_type) + call = find_member('__call__', original_caller_type, original_caller_type, + is_operator=True) if call: self.note_call(original_caller_type, call, context, code=code) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 7f995b4fca91..a1ae2ec5946d 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -229,7 +229,7 @@ def visit_instance(self, left: Instance) -> bool: return is_named_instance(item, 'builtins.object') if isinstance(right, CallableType): # Special case: Instance can be a subtype of Callable. - call = find_member('__call__', left, left) + call = find_member('__call__', left, left, is_operator=True) if call: return self._is_subtype(call, right) return False @@ -258,7 +258,7 @@ def visit_callable_type(self, left: CallableType) -> bool: if right.type.is_protocol and right.type.protocol_members == ['__call__']: # OK, a callable can implement a protocol with a single `__call__` member. # TODO: we should probably explicitly exclude self-types in this case. - call = find_member('__call__', right, left) + call = find_member('__call__', right, left, is_operator=True) assert call is not None if self._is_subtype(left, call): return True @@ -345,7 +345,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: if isinstance(right, Instance): if right.type.is_protocol and right.type.protocol_members == ['__call__']: # same as for CallableType - call = find_member('__call__', right, left) + call = find_member('__call__', right, left, is_operator=True) assert call is not None if self._is_subtype(left, call): return True @@ -521,7 +521,10 @@ def f(self) -> A: ... return True -def find_member(name: str, itype: Instance, subtype: Type) -> Optional[Type]: +def find_member(name: str, + itype: Instance, + subtype: Type, + is_operator: bool = False) -> Optional[Type]: """Find the type of member by 'name' in 'itype's TypeInfo. Fin the member type after applying type arguments from 'itype', and binding @@ -549,7 +552,8 @@ def find_member(name: str, itype: Instance, subtype: Type) -> Optional[Type]: v = v.var if isinstance(v, Var): return find_node_type(v, itype, subtype) - if not v and name not in ['__getattr__', '__setattr__', '__getattribute__']: + if (not v and name not in ['__getattr__', '__setattr__', '__getattribute__'] and + not is_operator): for method_name in ('__getattribute__', '__getattr__'): # Normally, mypy assumes that instances that define __getattr__ have all # attributes with the corresponding return type. If this will produce @@ -1167,7 +1171,7 @@ def check_argument(leftarg: Type, rightarg: Type, variance: int) -> bool: return True return False if isinstance(right, CallableType): - call = find_member('__call__', left, left) + call = find_member('__call__', left, left, is_operator=True) if call: return self._is_proper_subtype(call, right) return False diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index c00ffd076a56..f1fd57c69a61 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2513,6 +2513,28 @@ class A: a = A() a.y() # E: "A" not callable +[case testGetattrWithCallable] +from typing import Callable, Any + +class C: + def __getattr__(self, attr: str) -> C: ... + +def do(cd: Callable[..., Any]) -> None: ... + +do(C()) # E: Argument 1 to "do" has incompatible type "C"; expected "Callable[..., Any]" + +[case testGetattrWithCallableTypeVar] +from typing import Callable, Any, TypeVar + +class C: + def __getattr__(self, attr: str) -> C: ... + +T = TypeVar('T', bound=Callable[..., Any]) + +def do(cd: T) -> T: ... + +do(C()) # E: Value of type variable "T" of "do" cannot be "C" + [case testNestedGetattr] def foo() -> object: def __getattr__() -> None: # no error because not in a class
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: