Skip to content

Commit 20d59fc

Browse files
committed
Fix handling of non-method callable attribute (python#3227)
1. Reverse subtyping check 2. For classmethod, fallback on the argument instead of on the parameter 3. Mimic dispatch better: meet original_type with static class 4. Better error message
1 parent c332ea0 commit 20d59fc

File tree

5 files changed

+53
-37
lines changed

5 files changed

+53
-37
lines changed

mypy/checkmember.py

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from mypy.plugin import Plugin, AttributeContext
2121
from mypy import messages
2222
from mypy import subtypes
23+
from mypy import meet
24+
2325
MYPY = False
2426
if MYPY: # import for forward declaration only
2527
import mypy.checker
@@ -314,7 +316,9 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont
314316
# methods: the former to the instance, the latter to the
315317
# class.
316318
functype = t
317-
check_method_type(functype, itype, var.is_classmethod, node, msg)
319+
# Use meet to simulate dispatch - e.g. reduce Union[A, B] to A on dispatch to A
320+
dispatched_type = meet.meet_types(original_type, itype)
321+
check_self_arg(functype, dispatched_type, var.is_classmethod, node, name, msg)
318322
signature = bind_self(functype, original_type, var.is_classmethod)
319323
if var.is_property:
320324
# A property cannot have an overloaded type => the cast
@@ -370,33 +374,22 @@ def lookup_member_var_or_accessor(info: TypeInfo, name: str,
370374
return None
371375

372376

373-
def check_method_type(functype: FunctionLike, itype: Instance, is_classmethod: bool,
374-
context: Context, msg: MessageBuilder) -> None:
377+
def check_self_arg(functype: FunctionLike, original_type: Type, is_classmethod: bool,
378+
context: Context, name: str, msg: MessageBuilder) -> None:
379+
"""Check that the the most precise type of the self argument is compatible
380+
with the declared type of each of the overloads.
381+
"""
375382
for item in functype.items():
376383
if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR):
377384
# No positional first (self) argument (*args is okay).
378-
msg.invalid_method_type(item, context)
379-
elif not is_classmethod:
380-
# Check that self argument has type 'Any' or valid instance type.
381-
selfarg = item.arg_types[0]
382-
# If this is a method of a tuple class, correct for the fact that
383-
# we passed to typ.fallback in analyze_member_access. See #1432.
384-
if isinstance(selfarg, TupleType):
385-
selfarg = selfarg.fallback
386-
if not subtypes.is_subtype(selfarg, itype):
387-
msg.invalid_method_type(item, context)
385+
msg.fail('Attribute function with type %s does not accept self argument'
386+
% msg.format(item), context)
388387
else:
389-
# Check that cls argument has type 'Any' or valid class type.
390-
# (This is sufficient for the current treatment of @classmethod,
391-
# but probably needs to be revisited when we implement Type[C]
392-
# or advanced variants of it like Type[<args>, C].)
393-
clsarg = item.arg_types[0]
394-
if isinstance(clsarg, CallableType) and clsarg.is_type_obj():
395-
if not subtypes.is_equivalent(clsarg.ret_type, itype):
396-
msg.invalid_class_method_type(item, context)
397-
else:
398-
if not subtypes.is_equivalent(clsarg, AnyType(TypeOfAny.special_form)):
399-
msg.invalid_class_method_type(item, context)
388+
selfarg = item.arg_types[0]
389+
if is_classmethod:
390+
original_type = TypeType.make_normalized(original_type)
391+
if not subtypes.is_subtype(original_type, erase_to_bound(selfarg)):
392+
msg.invalid_method_type(name, original_type, item, is_classmethod, context)
400393

401394

402395
def analyze_class_attribute_access(itype: Instance,

mypy/messages.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -856,11 +856,11 @@ def cannot_determine_type(self, name: str, context: Context) -> None:
856856
def cannot_determine_type_in_base(self, name: str, base: str, context: Context) -> None:
857857
self.fail("Cannot determine type of '%s' in base class '%s'" % (name, base), context)
858858

859-
def invalid_method_type(self, sig: CallableType, context: Context) -> None:
860-
self.fail('Invalid method type', context)
861-
862-
def invalid_class_method_type(self, sig: CallableType, context: Context) -> None:
863-
self.fail('Invalid class method type', context)
859+
def invalid_method_type(self, name: str, arg: Type, sig: CallableType, is_classmethod: bool,
860+
context: Context) -> None:
861+
kind = 'class attribute function' if is_classmethod else 'attribute function'
862+
self.fail('Invalid self argument %s to %s "%s" with type %s'
863+
% (self.format(arg), kind, name, self.format(sig)), context)
864864

865865
def incompatible_conditional_function_def(self, defn: FuncDef) -> None:
866866
self.fail('All conditional function variants must have identical '

test-data/unit/check-classes.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2105,7 +2105,7 @@ class B:
21052105
a = A
21062106
bad = lambda: 42
21072107

2108-
B().bad() # E: Invalid method type
2108+
B().bad() # E: Attribute function with type "Callable[[], int]" does not accept self argument
21092109
reveal_type(B.a) # E: Revealed type is 'def () -> __main__.A'
21102110
reveal_type(B().a) # E: Revealed type is 'def () -> __main__.A'
21112111
reveal_type(B().a()) # E: Revealed type is '__main__.A'

test-data/unit/check-functions.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,8 +507,8 @@ class A:
507507
f = x # type: Callable[[], None]
508508
g = x # type: Callable[[B], None]
509509
a = None # type: A
510-
a.f() # E: Invalid method type
511-
a.g() # E: Invalid method type
510+
a.f() # E: Attribute function with type "Callable[[], None]" does not accept self argument
511+
a.g() # E: Invalid self argument "A" to attribute function "g" with type "Callable[[B], None]"
512512

513513
[case testMethodWithDynamicallyTypedMethodAsDataAttribute]
514514
from typing import Any, Callable
@@ -568,7 +568,7 @@ class A(Generic[t]):
568568
ab = None # type: A[B]
569569
ac = None # type: A[C]
570570
ab.f()
571-
ac.f() # E: Invalid method type
571+
ac.f() # E: Invalid self argument "A[C]" to attribute function "f" with type "Callable[[A[B]], None]"
572572

573573
[case testPartiallyTypedSelfInMethodDataAttribute]
574574
from typing import Any, TypeVar, Generic, Callable

test-data/unit/check-selftype.test

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,16 +354,21 @@ class E:
354354
[case testSelfTypeProperty]
355355
from typing import TypeVar
356356

357-
T = TypeVar('T', bound='A')
357+
Q = TypeVar('Q')
358+
T = TypeVar('T', bound='X')
358359

359-
class A:
360+
class X:
360361
@property
361-
def member(self: T) -> T:
362-
pass
362+
def __members__(self: Q) -> Q: return self
363+
364+
class A(X):
365+
@property
366+
def member(self: T) -> T: return self
363367

364368
class B(A):
365369
pass
366370

371+
reveal_type(X().__members__) # E: Revealed type is '__main__.X*'
367372
reveal_type(A().member) # E: Revealed type is '__main__.A*'
368373
reveal_type(B().member) # E: Revealed type is '__main__.B*'
369374

@@ -376,3 +381,21 @@ class A:
376381
# def g(self: None) -> None: ... see in check-python2.test
377382
[out]
378383
main:3: error: Self argument missing for a non-static method (or an invalid type for self)
384+
385+
[case testUnionPropertyField]
386+
from typing import Union
387+
388+
class A:
389+
x: int
390+
391+
class B:
392+
@property
393+
def x(self) -> int: return 1
394+
395+
class C:
396+
@property
397+
def x(self) -> int: return 1
398+
399+
ab: Union[A, B, C]
400+
reveal_type(ab.x) # E: Revealed type is 'builtins.int'
401+
[builtins fixtures/property.pyi]

0 commit comments

Comments
 (0)