Skip to content

Commit 125ef0d

Browse files
onlinedilevkivskyi
authored andcommitted
Fix crash related with __getattr__ and __call__ (python#7446)
Fixes python#7243.
1 parent 28423cd commit 125ef0d

File tree

6 files changed

+42
-13
lines changed

6 files changed

+42
-13
lines changed

mypy/checker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3808,12 +3808,12 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context,
38083808
isinstance(subtype, (Instance, TupleType, TypedDictType))):
38093809
self.msg.report_protocol_problems(subtype, supertype, context, code=code)
38103810
if isinstance(supertype, CallableType) and isinstance(subtype, Instance):
3811-
call = find_member('__call__', subtype, subtype)
3811+
call = find_member('__call__', subtype, subtype, is_operator=True)
38123812
if call:
38133813
self.msg.note_call(subtype, call, context, code=code)
38143814
if isinstance(subtype, (CallableType, Overloaded)) and isinstance(supertype, Instance):
38153815
if supertype.type.is_protocol and supertype.type.protocol_members == ['__call__']:
3816-
call = find_member('__call__', supertype, subtype)
3816+
call = find_member('__call__', supertype, subtype, is_operator=True)
38173817
assert call is not None
38183818
self.msg.note_call(supertype, call, context, code=code)
38193819
return False
@@ -4072,7 +4072,7 @@ def iterable_item_type(self, instance: Instance) -> Type:
40724072
# in case there is no explicit base class.
40734073
return item_type
40744074
# Try also structural typing.
4075-
iter_type = get_proper_type(find_member('__iter__', instance, instance))
4075+
iter_type = get_proper_type(find_member('__iter__', instance, instance, is_operator=True))
40764076
if iter_type and isinstance(iter_type, CallableType):
40774077
ret_type = get_proper_type(iter_type.ret_type)
40784078
if isinstance(ret_type, Instance):

mypy/constraints.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,8 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
285285
if not any(mypy.sametypes.is_same_type(template, t)
286286
for t in template.type.inferring):
287287
template.type.inferring.append(template)
288-
call = mypy.subtypes.find_member('__call__', template, actual)
288+
call = mypy.subtypes.find_member('__call__', template, actual,
289+
is_operator=True)
289290
assert call is not None
290291
if mypy.subtypes.is_subtype(actual, erase_typevars(call)):
291292
subres = infer_constraints(call, actual, self.direction)
@@ -430,7 +431,8 @@ def visit_callable_type(self, template: CallableType) -> List[Constraint]:
430431
elif isinstance(self.actual, Instance):
431432
# Instances with __call__ method defined are considered structural
432433
# subtypes of Callable with a compatible signature.
433-
call = mypy.subtypes.find_member('__call__', self.actual, self.actual)
434+
call = mypy.subtypes.find_member('__call__', self.actual, self.actual,
435+
is_operator=True)
434436
if call:
435437
return infer_constraints(template, call, self.direction)
436438
else:

mypy/join.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,5 +468,5 @@ def join_type_list(types: List[Type]) -> Type:
468468
def unpack_callback_protocol(t: Instance) -> Optional[Type]:
469469
assert t.type.is_protocol
470470
if t.type.protocol_members == ['__call__']:
471-
return find_member('__call__', t, t)
471+
return find_member('__call__', t, t, is_operator=True)
472472
return None

mypy/messages.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,8 @@ def incompatible_argument_note(self,
507507
self.report_protocol_problems(original_caller_type, callee_type, context, code=code)
508508
if (isinstance(callee_type, CallableType) and
509509
isinstance(original_caller_type, Instance)):
510-
call = find_member('__call__', original_caller_type, original_caller_type)
510+
call = find_member('__call__', original_caller_type, original_caller_type,
511+
is_operator=True)
511512
if call:
512513
self.note_call(original_caller_type, call, context, code=code)
513514

mypy/subtypes.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ def visit_instance(self, left: Instance) -> bool:
229229
return is_named_instance(item, 'builtins.object')
230230
if isinstance(right, CallableType):
231231
# Special case: Instance can be a subtype of Callable.
232-
call = find_member('__call__', left, left)
232+
call = find_member('__call__', left, left, is_operator=True)
233233
if call:
234234
return self._is_subtype(call, right)
235235
return False
@@ -258,7 +258,7 @@ def visit_callable_type(self, left: CallableType) -> bool:
258258
if right.type.is_protocol and right.type.protocol_members == ['__call__']:
259259
# OK, a callable can implement a protocol with a single `__call__` member.
260260
# TODO: we should probably explicitly exclude self-types in this case.
261-
call = find_member('__call__', right, left)
261+
call = find_member('__call__', right, left, is_operator=True)
262262
assert call is not None
263263
if self._is_subtype(left, call):
264264
return True
@@ -345,7 +345,7 @@ def visit_overloaded(self, left: Overloaded) -> bool:
345345
if isinstance(right, Instance):
346346
if right.type.is_protocol and right.type.protocol_members == ['__call__']:
347347
# same as for CallableType
348-
call = find_member('__call__', right, left)
348+
call = find_member('__call__', right, left, is_operator=True)
349349
assert call is not None
350350
if self._is_subtype(left, call):
351351
return True
@@ -521,7 +521,10 @@ def f(self) -> A: ...
521521
return True
522522

523523

524-
def find_member(name: str, itype: Instance, subtype: Type) -> Optional[Type]:
524+
def find_member(name: str,
525+
itype: Instance,
526+
subtype: Type,
527+
is_operator: bool = False) -> Optional[Type]:
525528
"""Find the type of member by 'name' in 'itype's TypeInfo.
526529
527530
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]:
549552
v = v.var
550553
if isinstance(v, Var):
551554
return find_node_type(v, itype, subtype)
552-
if not v and name not in ['__getattr__', '__setattr__', '__getattribute__']:
555+
if (not v and name not in ['__getattr__', '__setattr__', '__getattribute__'] and
556+
not is_operator):
553557
for method_name in ('__getattribute__', '__getattr__'):
554558
# Normally, mypy assumes that instances that define __getattr__ have all
555559
# attributes with the corresponding return type. If this will produce
@@ -1167,7 +1171,7 @@ def check_argument(leftarg: Type, rightarg: Type, variance: int) -> bool:
11671171
return True
11681172
return False
11691173
if isinstance(right, CallableType):
1170-
call = find_member('__call__', left, left)
1174+
call = find_member('__call__', left, left, is_operator=True)
11711175
if call:
11721176
return self._is_proper_subtype(call, right)
11731177
return False

test-data/unit/check-classes.test

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2511,6 +2511,28 @@ class A:
25112511
a = A()
25122512
a.y() # E: "A" not callable
25132513

2514+
[case testGetattrWithCallable]
2515+
from typing import Callable, Any
2516+
2517+
class C:
2518+
def __getattr__(self, attr: str) -> C: ...
2519+
2520+
def do(cd: Callable[..., Any]) -> None: ...
2521+
2522+
do(C()) # E: Argument 1 to "do" has incompatible type "C"; expected "Callable[..., Any]"
2523+
2524+
[case testGetattrWithCallableTypeVar]
2525+
from typing import Callable, Any, TypeVar
2526+
2527+
class C:
2528+
def __getattr__(self, attr: str) -> C: ...
2529+
2530+
T = TypeVar('T', bound=Callable[..., Any])
2531+
2532+
def do(cd: T) -> T: ...
2533+
2534+
do(C()) # E: Value of type variable "T" of "do" cannot be "C"
2535+
25142536
[case testNestedGetattr]
25152537
def foo() -> object:
25162538
def __getattr__() -> None: # no error because not in a class

0 commit comments

Comments
 (0)
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