diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ea8aff82d209..93cd1e0541ab 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -20,6 +20,8 @@ from mypy.plugin import Plugin, AttributeContext from mypy import messages from mypy import subtypes +from mypy import meet + MYPY = False if MYPY: # import for forward declaration only import mypy.checker @@ -286,6 +288,7 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont This is conceptually part of analyze_member_access and the arguments are similar. + itype is the class object in which var is dedined original_type is the type of E in the expression E.var """ # Found a member variable. @@ -310,15 +313,21 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont msg.cant_assign_to_method(node) if not var.is_staticmethod: - # Class-level function objects and classmethods become bound - # methods: the former to the instance, the latter to the - # class. + # Class-level function objects and classmethods become bound methods: + # the former to the instance, the latter to the class. functype = t - check_method_type(functype, itype, var.is_classmethod, node, msg) + # Use meet to narrow original_type to the dispatched type. + # For example, assume + # * A.f: Callable[[A1], None] where A1 <: A (maybe A1 == A) + # * B.f: Callable[[B1], None] where B1 <: B (maybe B1 == B) + # * x: Union[A1, B1] + # In `x.f`, when checking `x` against A1 we assume x is compatible with A + # and similarly for B1 when checking agains B + dispatched_type = meet.meet_types(original_type, itype) + check_self_arg(functype, dispatched_type, var.is_classmethod, node, name, msg) signature = bind_self(functype, original_type, var.is_classmethod) if var.is_property: - # A property cannot have an overloaded type => the cast - # is fine. + # A property cannot have an overloaded type => the cast is fine. assert isinstance(signature, CallableType) result = signature.ret_type else: @@ -370,33 +379,28 @@ def lookup_member_var_or_accessor(info: TypeInfo, name: str, return None -def check_method_type(functype: FunctionLike, itype: Instance, is_classmethod: bool, - context: Context, msg: MessageBuilder) -> None: +def check_self_arg(functype: FunctionLike, dispatched_arg_type: Type, is_classmethod: bool, + context: Context, name: str, msg: MessageBuilder) -> None: + """For x.f where A.f: A1 -> T, check that meet(type(x), A) <: A1 for each overload. + + dispatched_arg_type is meet(B, A) in the following example + + def g(x: B): x.f + class A: + f: Callable[[A1], None] + """ + # TODO: this is too strict. We can return filtered overloads for matching definitions for item in functype.items(): if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR): # No positional first (self) argument (*args is okay). - msg.invalid_method_type(item, context) - elif not is_classmethod: - # Check that self argument has type 'Any' or valid instance type. - selfarg = item.arg_types[0] - # If this is a method of a tuple class, correct for the fact that - # we passed to typ.fallback in analyze_member_access. See #1432. - if isinstance(selfarg, TupleType): - selfarg = selfarg.fallback - if not subtypes.is_subtype(selfarg, itype): - msg.invalid_method_type(item, context) + msg.no_formal_self(name, item, context) else: - # Check that cls argument has type 'Any' or valid class type. - # (This is sufficient for the current treatment of @classmethod, - # but probably needs to be revisited when we implement Type[C] - # or advanced variants of it like Type[, C].) - clsarg = item.arg_types[0] - if isinstance(clsarg, CallableType) and clsarg.is_type_obj(): - if not subtypes.is_equivalent(clsarg.ret_type, itype): - msg.invalid_class_method_type(item, context) - else: - if not subtypes.is_equivalent(clsarg, AnyType(TypeOfAny.special_form)): - msg.invalid_class_method_type(item, context) + selfarg = item.arg_types[0] + if is_classmethod: + dispatched_arg_type = TypeType.make_normalized(dispatched_arg_type) + if not subtypes.is_subtype(dispatched_arg_type, erase_to_bound(selfarg)): + msg.incompatible_self_argument(name, dispatched_arg_type, item, + is_classmethod, context) def analyze_class_attribute_access(itype: Instance, diff --git a/mypy/messages.py b/mypy/messages.py index 094244920410..b49292e735f3 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -863,11 +863,15 @@ def cannot_determine_type(self, name: str, context: Context) -> None: def cannot_determine_type_in_base(self, name: str, base: str, context: Context) -> None: self.fail("Cannot determine type of '%s' in base class '%s'" % (name, base), context) - def invalid_method_type(self, sig: CallableType, context: Context) -> None: - self.fail('Invalid method type', context) - - def invalid_class_method_type(self, sig: CallableType, context: Context) -> None: - self.fail('Invalid class method type', context) + def no_formal_self(self, name: str, item: CallableType, context: Context) -> None: + self.fail('Attribute function "%s" with type %s does not accept self argument' + % (name, self.format(item)), context) + + def incompatible_self_argument(self, name: str, arg: Type, sig: CallableType, + is_classmethod: bool, context: Context) -> None: + kind = 'class attribute function' if is_classmethod else 'attribute function' + self.fail('Invalid self argument %s to %s "%s" with type %s' + % (self.format(arg), kind, name, self.format(sig)), context) def incompatible_conditional_function_def(self, defn: FuncDef) -> None: self.fail('All conditional function variants must have identical ' diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 13545b3f0b92..7f72491983a0 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2105,7 +2105,7 @@ class B: a = A bad = lambda: 42 -B().bad() # E: Invalid method type +B().bad() # E: Attribute function "bad" with type "Callable[[], int]" does not accept self argument reveal_type(B.a) # E: Revealed type is 'def () -> __main__.A' reveal_type(B().a) # E: Revealed type is 'def () -> __main__.A' reveal_type(B().a()) # E: Revealed type is '__main__.A' diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index e39606251f9d..5e99613eeba2 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -507,8 +507,8 @@ class A: f = x # type: Callable[[], None] g = x # type: Callable[[B], None] a = None # type: A -a.f() # E: Invalid method type -a.g() # E: Invalid method type +a.f() # E: Attribute function "f" with type "Callable[[], None]" does not accept self argument +a.g() # E: Invalid self argument "A" to attribute function "g" with type "Callable[[B], None]" [case testMethodWithDynamicallyTypedMethodAsDataAttribute] from typing import Any, Callable @@ -568,7 +568,7 @@ class A(Generic[t]): ab = None # type: A[B] ac = None # type: A[C] ab.f() -ac.f() # E: Invalid method type +ac.f() # E: Invalid self argument "A[C]" to attribute function "f" with type "Callable[[A[B]], None]" [case testPartiallyTypedSelfInMethodDataAttribute] from typing import Any, TypeVar, Generic, Callable diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index d0c4a56f2038..4d79b9272026 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -351,21 +351,130 @@ class E: def __init_subclass__(cls) -> None: reveal_type(cls) # E: Revealed type is 'def () -> __main__.E' -[case testSelfTypeProperty] -from typing import TypeVar +[case testSelfTypePropertyUnion] +from typing import Union +class A: + @property + def f(self: A) -> int: pass -T = TypeVar('T', bound='A') +class B: + @property + def f(self: B) -> int: pass +x: Union[A, B] +reveal_type(x.f) # E: Revealed type is 'builtins.int' -class A: +[builtins fixtures/property.pyi] + +[case testSelfTypeProperSupertypeAttribute] +from typing import Callable, TypeVar +class K: pass +T = TypeVar('T', bound=K) +class A(K): @property - def member(self: T) -> T: - pass + def g(self: K) -> int: return 0 + @property + def gt(self: T) -> T: return self + f: Callable[[object], int] + ft: Callable[[T], T] + +class B(A): + pass + +reveal_type(A().g) # E: Revealed type is 'builtins.int' +reveal_type(A().gt) # E: Revealed type is '__main__.A*' +reveal_type(A().f()) # E: Revealed type is 'builtins.int' +reveal_type(A().ft()) # E: Revealed type is '__main__.A*' +reveal_type(B().g) # E: Revealed type is 'builtins.int' +reveal_type(B().gt) # E: Revealed type is '__main__.B*' +reveal_type(B().f()) # E: Revealed type is 'builtins.int' +reveal_type(B().ft()) # E: Revealed type is '__main__.B*' + +[builtins fixtures/property.pyi] + +[case testSelfTypeProperSupertypeAttributeTuple] +from typing import Callable, TypeVar, Tuple +T = TypeVar('T') +class A(Tuple[int, int]): + @property + def g(self: object) -> int: return 0 + @property + def gt(self: T) -> T: return self + f: Callable[[object], int] + ft: Callable[[T], T] class B(A): pass -reveal_type(A().member) # E: Revealed type is '__main__.A*' -reveal_type(B().member) # E: Revealed type is '__main__.B*' +reveal_type(A().g) # E: Revealed type is 'builtins.int' +reveal_type(A().gt) # E: Revealed type is 'Tuple[builtins.int, builtins.int, fallback=__main__.A]' +reveal_type(A().f()) # E: Revealed type is 'builtins.int' +reveal_type(A().ft()) # E: Revealed type is 'Tuple[builtins.int, builtins.int, fallback=__main__.A]' +reveal_type(B().g) # E: Revealed type is 'builtins.int' +reveal_type(B().gt) # E: Revealed type is 'Tuple[builtins.int, builtins.int, fallback=__main__.B]' +reveal_type(B().f()) # E: Revealed type is 'builtins.int' +reveal_type(B().ft()) # E: Revealed type is 'Tuple[builtins.int, builtins.int, fallback=__main__.B]' + +[builtins fixtures/property.pyi] + +[case testSelfTypeProperSupertypeAttributeMeta] +from typing import Callable, TypeVar, Type +T = TypeVar('T') +class A(type): + @property + def g(cls: object) -> int: return 0 + @property + def gt(cls: T) -> T: return cls + f: Callable[[object], int] + ft: Callable[[T], T] + +class B(A): + pass + +class X(metaclass=B): + def __init__(self, x: int) -> None: pass +class Y(X): pass +X1: Type[X] +reveal_type(X.g) # E: Revealed type is 'builtins.int' +reveal_type(X.gt) # E: Revealed type is 'def (x: builtins.int) -> __main__.X' +reveal_type(X.f()) # E: Revealed type is 'builtins.int' +reveal_type(X.ft()) # E: Revealed type is 'def (x: builtins.int) -> __main__.X' +reveal_type(Y.g) # E: Revealed type is 'builtins.int' +reveal_type(Y.gt) # E: Revealed type is 'def (x: builtins.int) -> __main__.Y' +reveal_type(Y.f()) # E: Revealed type is 'builtins.int' +reveal_type(Y.ft()) # E: Revealed type is 'def (x: builtins.int) -> __main__.Y' +reveal_type(X1.g) # E: Revealed type is 'builtins.int' +reveal_type(X1.gt) # E: Revealed type is 'Type[__main__.X]' +reveal_type(X1.f()) # E: Revealed type is 'builtins.int' +reveal_type(X1.ft()) # E: Revealed type is 'Type[__main__.X]' + +[builtins fixtures/property.pyi] + +[case testSelfTypeProperSupertypeAttributeGeneric] +from typing import Callable, TypeVar, Generic +Q = TypeVar('Q', covariant=True) +class K(Generic[Q]): + q: Q +T = TypeVar('T') +class A(K[Q]): + @property + def g(self: K[object]) -> int: return 0 + @property + def gt(self: K[T]) -> T: return self.q + f: Callable[[object], int] + ft: Callable[[T], T] + +class B(A[Q]): + pass +a: A[int] +b: B[str] +reveal_type(a.g) # E: Revealed type is 'builtins.int' +--reveal_type(a.gt) # E: Revealed type is 'builtins.int' +reveal_type(a.f()) # E: Revealed type is 'builtins.int' +reveal_type(a.ft()) # E: Revealed type is '__main__.A*[builtins.int]' +reveal_type(b.g) # E: Revealed type is 'builtins.int' +--reveal_type(b.gt) # E: Revealed type is '__main__.B*[builtins.str]' +reveal_type(b.f()) # E: Revealed type is 'builtins.int' +reveal_type(b.ft()) # E: Revealed type is '__main__.B*[builtins.str]' [builtins fixtures/property.pyi] @@ -376,3 +485,21 @@ class A: # def g(self: None) -> None: ... see in check-python2.test [out] main:3: error: Self argument missing for a non-static method (or an invalid type for self) + +[case testUnionPropertyField] +from typing import Union + +class A: + x: int + +class B: + @property + def x(self) -> int: return 1 + +class C: + @property + def x(self) -> int: return 1 + +ab: Union[A, B, C] +reveal_type(ab.x) # E: Revealed type is 'builtins.int' +[builtins fixtures/property.pyi]