Skip to content

Commit 125ef0d

Browse files
onlinedilevkivskyi
authored andcommitted
Fix crash related with __getattr__ and __call__ (#7446)
Fixes #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)