diff --git a/mypy/checker.py b/mypy/checker.py index 3fcc3c665e12..8495a3a7722c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -807,51 +807,30 @@ def is_trivial_body(self, block: Block) -> bool: (isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, EllipsisExpr))) - def check_reverse_op_method(self, defn: FuncItem, typ: CallableType, - method: str) -> None: + def check_reverse_op_method(self, defn: FuncItem, typ: CallableType, method: str) -> None: """Check a reverse operator method such as __radd__.""" - - # This used to check for some very obscure scenario. It now - # just decides whether it's worth calling - # check_overlapping_op_methods(). + # Decides whether it's worth calling check_overlapping_op_methods(). if method in ('__eq__', '__ne__'): # These are defined for all objects => can't cause trouble. return + if len(typ.arg_types) != 2: + # Plausibly the method could have too few arguments, which would result + # in an error elsewhere. + return - # With 'Any' or 'object' return type we are happy, since any possible - # return value is valid. - ret_type = typ.ret_type - if isinstance(ret_type, AnyType): + other_method = nodes.normal_from_reverse_op[method] + arg_type = typ.arg_types[1] + # TODO: arg_type = arg_type.fallback, e.g. for TupleType + if not (isinstance(arg_type, (Instance, UnionType)) + and arg_type.has_readable_member(other_method)): return - if isinstance(ret_type, Instance): - if ret_type.type.fullname() == 'builtins.object': - return - # Plausibly the method could have too few arguments, which would result - # in an error elsewhere. - if len(typ.arg_types) <= 2: - # TODO check self argument kind - - # Check for the issue described above. - arg_type = typ.arg_types[1] - other_method = nodes.normal_from_reverse_op[method] - if isinstance(arg_type, Instance): - if not arg_type.type.has_readable_member(other_method): - return - elif isinstance(arg_type, AnyType): - return - elif isinstance(arg_type, UnionType): - if not arg_type.has_readable_member(other_method): - return - else: - return - typ2 = self.expr_checker.analyze_external_member_access( - other_method, arg_type, defn) - self.check_overlapping_op_methods( - typ, method, defn.info, - typ2, other_method, cast(Instance, arg_type), - defn) + typ2 = self.expr_checker.analyze_external_member_access(other_method, arg_type, defn) + for t in union_items(arg_type): + assert isinstance(t, Instance) + self.check_overlapping_op_methods(typ, method, defn.info, + typ2, other_method, t, defn) def check_overlapping_op_methods(self, reverse_type: CallableType, diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ea8aff82d209..798a08995c45 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -20,6 +20,7 @@ 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 @@ -314,7 +315,9 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont # 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 simulate dispatch - e.g. reduce Union[A, B] to A on dispatch to A + 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 @@ -370,33 +373,22 @@ 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, original_type: Type, is_classmethod: bool, + context: Context, name: str, msg: MessageBuilder) -> None: + """Check that the the most precise type of the self argument is compatible + with the declared type of each of the overloads. + """ 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.fail('Attribute function with type %s does not accept self argument' + % msg.format(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: + original_type = TypeType.make_normalized(original_type) + if not subtypes.is_subtype(original_type, erase_to_bound(selfarg)): + msg.invalid_method_type(name, original_type, item, is_classmethod, context) def analyze_class_attribute_access(itype: Instance, diff --git a/mypy/messages.py b/mypy/messages.py index 2f3473b3e59d..1f25f1990d2a 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -856,11 +856,11 @@ 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 invalid_method_type(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/mypy/types.py b/mypy/types.py index a53085a66a36..c102b3d30583 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -515,6 +515,9 @@ def deserialize(cls, data: Union[JsonDict, str]) -> 'Instance': def copy_modified(self, *, args: List[Type]) -> 'Instance': return Instance(self.type, args, self.line, self.column, self.erased) + def has_readable_member(self, name: str) -> bool: + return self.type.has_readable_member(name) + class TypeVarType(Type): """A type variable type. diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 2ff0b8d84d78..6f299d5c5647 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 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..f6f3d7a971b6 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 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..d32b74261371 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -354,16 +354,21 @@ class E: [case testSelfTypeProperty] from typing import TypeVar -T = TypeVar('T', bound='A') +Q = TypeVar('Q') +T = TypeVar('T', bound='X') -class A: +class X: @property - def member(self: T) -> T: - pass + def __members__(self: Q) -> Q: return self + +class A(X): + @property + def member(self: T) -> T: return self class B(A): pass +reveal_type(X().__members__) # E: Revealed type is '__main__.X*' reveal_type(A().member) # E: Revealed type is '__main__.A*' reveal_type(B().member) # E: Revealed type is '__main__.B*' @@ -376,3 +381,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] diff --git a/typeshed b/typeshed index 55dbb967ad00..b2df503cde54 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 55dbb967ad006d5835590c53ab023774dc5a1341 +Subproject commit b2df503cde54891bdce4ac5fb6f6dc03626ba858