From a82a8837abe4e9a4e78b9a4f71226270c84f4894 Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Tue, 3 Sep 2019 00:26:47 +0300 Subject: [PATCH 1/5] Add check for type of __call__ --- mypy/subtypes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 7f995b4fca91..79fbfb3f34dc 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -229,8 +229,8 @@ 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) - if call: + call = get_proper_type(find_member('__call__', left, left)) + if isinstance(call, CallableType): return self._is_subtype(call, right) return False else: @@ -1167,8 +1167,8 @@ def check_argument(leftarg: Type, rightarg: Type, variance: int) -> bool: return True return False if isinstance(right, CallableType): - call = find_member('__call__', left, left) - if call: + call = get_proper_type(find_member('__call__', left, left)) + if isinstance(call, CallableType): return self._is_proper_subtype(call, right) return False return False From 5426ca1715a8e1cce43d9ea45b5b3d5ad4f07e0e Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Tue, 3 Sep 2019 07:56:09 +0300 Subject: [PATCH 2/5] Change the way of fix --- mypy/subtypes.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 79fbfb3f34dc..35a540be270b 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -229,8 +229,8 @@ 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 = get_proper_type(find_member('__call__', left, left)) - if isinstance(call, CallableType): + call = find_member('__call__', left, left, is_operator=True) + if call: return self._is_subtype(call, right) return False else: @@ -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 @@ -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,8 +1171,8 @@ def check_argument(leftarg: Type, rightarg: Type, variance: int) -> bool: return True return False if isinstance(right, CallableType): - call = get_proper_type(find_member('__call__', left, left)) - if isinstance(call, CallableType): + call = find_member('__call__', left, left, is_operator=True) + if call: return self._is_proper_subtype(call, right) return False return False From a64852cc87dcfad8fa9cd1ca1c2caa631c1ffb7d Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Tue, 3 Sep 2019 08:05:22 +0300 Subject: [PATCH 3/5] Add forgotten changes --- mypy/checker.py | 6 +++--- mypy/constraints.py | 4 ++-- mypy/join.py | 2 +- mypy/messages.py | 3 ++- mypy/subtypes.py | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) 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..7fd3cb506381 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -285,7 +285,7 @@ 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 +430,7 @@ 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 35a540be270b..a1ae2ec5946d 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -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 From aed207e902e96c0992ae7d50f4a9b4d947361311 Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Tue, 10 Sep 2019 22:31:28 +0300 Subject: [PATCH 4/5] Fix code style --- mypy/constraints.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/constraints.py b/mypy/constraints.py index 7fd3cb506381..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, is_operator=True) + 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, is_operator=True) + call = mypy.subtypes.find_member('__call__', self.actual, self.actual, + is_operator=True) if call: return infer_constraints(template, call, self.direction) else: From 02ae5f79fceac69dd0950fb61e84f250ba1d936e Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Sun, 15 Sep 2019 16:02:14 +0300 Subject: [PATCH 5/5] Add tests --- test-data/unit/check-classes.test | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) 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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy