From 9247a6530ba78d009b529664b5dea01b86a63dde Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 12 Aug 2018 23:49:52 +0100 Subject: [PATCH 1/9] Support callback protocols --- mypy/constraints.py | 12 +++++ mypy/subtypes.py | 13 +++++ test-data/unit/check-protocols.test | 81 +++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) diff --git a/mypy/constraints.py b/mypy/constraints.py index f479229ce0a4..e393241634c1 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -306,6 +306,18 @@ def visit_type_var(self, template: TypeVarType) -> List[Constraint]: def visit_instance(self, template: Instance) -> List[Constraint]: original_actual = actual = self.actual res = [] # type: List[Constraint] + if isinstance(actual, (CallableType, Overloaded)) and template.type.is_protocol: + if template.type.protocol_members == ['__call__']: + # Special case: a generic callback protocol + if not any(is_same_type(template, t) for t in template.type.inferring): + template.type.inferring.append(template) + call = mypy.subtypes.find_member('__call__', template, actual) + assert call is not None + if mypy.subtypes.is_subtype(actual, erase_typevars(call)): + subres = infer_constraints(call, actual, self.direction) + res.extend(subres) + template.type.inferring.pop() + return res if isinstance(actual, CallableType) and actual.fallback is not None: actual = actual.fallback if isinstance(actual, Overloaded) and actual.fallback is not None: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index b7a45c310eb7..b78fb245105c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -214,6 +214,13 @@ def visit_callable_type(self, left: CallableType) -> bool: ignore_pos_arg_names=self.ignore_pos_arg_names) for item in right.items()) elif isinstance(right, Instance): + if right.type.is_protocol and right.type.protocol_members == ['__call__']: + # OK, a callable can implement a protocol with a single `__call__` member. + # TODO: we should probably explicitly exclude self-types in this case. + call = find_member('__call__', right, left) + assert call is not None + if is_subtype(left, call): + return True return is_subtype(left.fallback, right, ignore_pos_arg_names=self.ignore_pos_arg_names) elif isinstance(right, TypeType): @@ -281,6 +288,12 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool: def visit_overloaded(self, left: Overloaded) -> bool: right = self.right if isinstance(right, Instance): + if right.type.is_protocol and right.type.protocol_members == ['__call__']: + # same as for CallableType + call = find_member('__call__', right, left) + assert call is not None + if is_subtype(left, call): + return True return is_subtype(left.fallback, right) elif isinstance(right, CallableType): for item in left.items(): diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 82e7582c5988..25a0e7557c75 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2187,3 +2187,84 @@ else: reveal_type(cls) # E: Revealed type is 'Type[__main__.E]' [builtins fixtures/isinstance.pyi] [out] + +[case testCallableImplementsProtocol] +from typing import Protocol + +class Caller(Protocol): + def __call__(self, x: str, *args: int) -> None: ... + +def call(x: str, *args: int) -> None: + pass +def bad(x: int, *args: str) -> None: + pass + +def func(caller: Caller) -> None: + pass + +func(call) +func(bad) # E: Argument 1 to "func" has incompatible type "Callable[[int, VarArg(str)], None]"; expected "Caller" +[out] + +[case testCallableImplementsProtocolGeneric] +from typing import Protocol, TypeVar, Tuple + +T = TypeVar('T') +S = TypeVar('S') + +class Caller(Protocol[T, S]): + def __call__(self, x: T, y: S) -> Tuple[T, S]: ... + +def call(x: int, y: str) -> Tuple[int, str]: ... + +def func(caller: Caller[T, S]) -> Tuple[T, S]: + pass + +reveal_type(func(call)) # E: Revealed type is 'Tuple[builtins.int*, builtins.str*]' +[builtins fixtures/tuple.pyi] +[out] + +[case testCallableImplementsProtocolGenericTight] +from typing import Protocol, TypeVar + +T = TypeVar('T') + +class Caller(Protocol): + def __call__(self, x: T) -> T: ... + +def call(x: T) -> T: ... +def bad(x: int) -> int: ... + +def func(caller: Caller) -> None: + pass + +func(call) +func(bad) # E: Argument 1 to "func" has incompatible type "Callable[[int], int]"; expected "Caller" +[builtins fixtures/tuple.pyi] +[out] + +[case testCallableImplementsProtocolOverload] +from typing import Protocol, overload, Union + +class Caller(Protocol): + @overload + def __call__(self, x: int) -> int: ... + @overload + def __call__(self, x: str) -> str: ... + +@overload +def call(x: int) -> int: ... +@overload +def call(x: str) -> str: ... +def call(x: Union[int, str]) -> Union[int, str]: + pass + +def bad(x: Union[int, str]) -> Union[int, str]: + pass + +def func(caller: Caller) -> None: + pass + +func(call) +func(bad) # E: Argument 1 to "func" has incompatible type "Callable[[Union[int, str]], Union[int, str]]"; expected "Caller" +[out] From c37e3338ef7fa0fc99cd9de1e4494b1d0645a8a4 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 13 Aug 2018 00:03:05 +0100 Subject: [PATCH 2/9] Add an extra note for bad assignments --- mypy/checker.py | 5 +++++ test-data/unit/check-protocols.test | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index ed3955bc6a3a..9b53166ba86a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3170,6 +3170,11 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context, call = find_member('__call__', subtype, subtype) if call: self.msg.note_call(subtype, call, context) + if isinstance(subtype, (CallableType, Overloaded)) and isinstance(supertype, Instance): + if supertype.type.is_protocol and supertype.type.protocol_members == ['__call__']: + call = find_member('__call__', supertype, subtype) + assert call is not None + self.msg.note_call(supertype, call, context) return False def contains_none(self, t: Type) -> bool: diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 25a0e7557c75..e4eb96afe3de 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2268,3 +2268,16 @@ def func(caller: Caller) -> None: func(call) func(bad) # E: Argument 1 to "func" has incompatible type "Callable[[Union[int, str]], Union[int, str]]"; expected "Caller" [out] + +[case testCallableImplementsProtocolExtraNote] +from typing import Protocol + +class Caller(Protocol): + def __call__(self, x: str, *args: int) -> None: ... + +def bad(x: int, *args: str) -> None: + pass + +cb: Caller = bad # E: Incompatible types in assignment (expression has type "Callable[[int, VarArg(str)], None]", variable has type "Caller") \ + # N: "Caller.__call__" has type "Callable[[Arg(str, 'x'), VarArg(int)], None]" +[out] From 29e71bf4d1a07d47d3844f00ce116f9b7069513e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 13 Aug 2018 10:07:54 +0100 Subject: [PATCH 3/9] Test arg names --- test-data/unit/check-protocols.test | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index e4eb96afe3de..28427279f8a6 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2281,3 +2281,29 @@ def bad(x: int, *args: str) -> None: cb: Caller = bad # E: Incompatible types in assignment (expression has type "Callable[[int, VarArg(str)], None]", variable has type "Caller") \ # N: "Caller.__call__" has type "Callable[[Arg(str, 'x'), VarArg(int)], None]" [out] + +[case testCallableImplementsProtocolArgName] +from typing import Protocol + +class Caller(Protocol): + def __call__(self, x: str) -> None: ... + +class CallerAnon(Protocol): + def __call__(self, __x: str) -> None: ... + +def call(x: str) -> None: + pass +def bad(y: str) -> None: + pass + +def func(caller: Caller) -> None: + pass + +def anon(caller: CallerAnon) -> None: + pass + + +func(call) +func(bad) # E: Argument 1 to "func" has incompatible type "Callable[[str], None]"; expected "Caller" +anon(bad) +[out] From 192cb312fd8418b6f2445b2f96cf4d99a017ccd1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 16 Aug 2018 11:35:53 +0100 Subject: [PATCH 4/9] Address CR --- mypy/checker.py | 5 +- mypy/join.py | 24 +++++++-- mypy/meet.py | 19 +++++-- mypy/subtypes.py | 8 +-- test-data/unit/check-classes.test | 7 +++ test-data/unit/check-protocols.test | 80 +++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 10 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 2632720b9894..fd312c64a58d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1308,7 +1308,10 @@ def check_override(self, override: FunctionLike, original: FunctionLike, """ # Use boolean variable to clarify code. fail = False - if not is_subtype(override, original, ignore_pos_arg_names=True): + # __call__ is special among dunders, because arguments can be passed + # as keyword args (unlike other dunders). + ignore_names = name != '__call__' + if not is_subtype(override, original, ignore_pos_arg_names=ignore_names): fail = True elif (not isinstance(original, Overloaded) and isinstance(override, Overloaded) and diff --git a/mypy/join.py b/mypy/join.py index fed988bba110..31d14d51aa04 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -11,7 +11,7 @@ from mypy.maptype import map_instance_to_supertype from mypy.subtypes import ( is_subtype, is_equivalent, is_subtype_ignoring_tvars, is_proper_subtype, - is_protocol_implementation + is_protocol_implementation, find_member ) from mypy.nodes import ARG_NAMED, ARG_NAMED_OPT @@ -154,6 +154,10 @@ def visit_instance(self, t: Instance) -> Type: return nominal return structural elif isinstance(self.s, FunctionLike): + if t.type.is_protocol: + call = unpack_callback_protocol(t) + if call: + return join_types(call, self.s) return join_types(t, self.s.fallback) elif isinstance(self.s, TypeType): return join_types(t, self.s) @@ -174,8 +178,11 @@ def visit_callable_type(self, t: CallableType) -> Type: elif isinstance(self.s, Overloaded): # Switch the order of arguments to that we'll get to visit_overloaded. return join_types(t, self.s) - else: - return join_types(t.fallback, self.s) + elif isinstance(self.s, Instance) and self.s.type.is_protocol: + call = unpack_callback_protocol(self.s) + if call: + return join_types(t, call) + return join_types(t.fallback, self.s) def visit_overloaded(self, t: Overloaded) -> Type: # This is more complex than most other cases. Here are some @@ -224,6 +231,10 @@ def visit_overloaded(self, t: Overloaded) -> Type: else: return Overloaded(result) return join_types(t.fallback, s.fallback) + elif isinstance(s, Instance) and s.type.is_protocol: + call = unpack_callback_protocol(s) + if call: + return join_types(t, call) return join_types(t.fallback, s) def visit_tuple_type(self, t: TupleType) -> Type: @@ -436,3 +447,10 @@ def join_type_list(types: List[Type]) -> Type: for t in types[1:]: joined = join_types(joined, t) return joined + + +def unpack_callback_protocol(t: Instance) -> Optional[Type]: + assert t.type.is_protocol + if t.type.protocol_members == ['__call__']: + return find_member('__call__', t, t) + return None diff --git a/mypy/meet.py b/mypy/meet.py index abd0a97a3c6b..22383f9aa0b4 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -1,7 +1,9 @@ from collections import OrderedDict from typing import List, Optional, Tuple -from mypy.join import is_similar_callables, combine_similar_callables, join_type_list +from mypy.join import ( + is_similar_callables, combine_similar_callables, join_type_list, unpack_callback_protocol +) from mypy.types import ( Type, AnyType, TypeVisitor, UnboundType, NoneTyp, TypeVarType, Instance, CallableType, TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType, @@ -297,6 +299,10 @@ def visit_instance(self, t: Instance) -> Type: return UninhabitedType() else: return NoneTyp() + elif isinstance(self.s, FunctionLike) and t.type.is_protocol: + call = unpack_callback_protocol(t) + if call: + return meet_types(call, self.s) elif isinstance(self.s, TypeType): return meet_types(t, self.s) elif isinstance(self.s, TupleType): @@ -313,8 +319,11 @@ def visit_callable_type(self, t: CallableType) -> Type: # Return a plain None or instead of a weird function. return self.default(self.s) return result - else: - return self.default(self.s) + elif isinstance(self.s, Instance) and self.s.type.is_protocol: + call = unpack_callback_protocol(self.s) + if call: + return meet_types(t, call) + return self.default(self.s) def visit_overloaded(self, t: Overloaded) -> Type: # TODO: Implement a better algorithm that covers at least the same cases @@ -329,6 +338,10 @@ def visit_overloaded(self, t: Overloaded) -> Type: return t else: return meet_types(t.fallback, s.fallback) + elif isinstance(self.s, Instance) and self.s.type.is_protocol: + call = unpack_callback_protocol(self.s) + if call: + return meet_types(t, call) return meet_types(t.fallback, s) def visit_tuple_type(self, t: TupleType) -> Type: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index b762aafb0301..3b62900530c2 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -452,6 +452,7 @@ def f(self) -> A: ... # nominal subtyping currently ignores '__init__' and '__new__' signatures if member in ('__init__', '__new__'): continue + ignore_names = member != '__call__' # __call__ can be passed kwargs # The third argument below indicates to what self type is bound. # We always bind self to the subtype. (Similarly to nominal types). supertype = find_member(member, right, left) @@ -466,7 +467,7 @@ def f(self) -> A: ... # Nominal check currently ignores arg names # NOTE: If we ever change this, be sure to also change the call to # SubtypeVisitor.build_subtype_kind(...) down below. - is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=True) + is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=ignore_names) else: is_compat = is_proper_subtype(subtype, supertype) if not is_compat: @@ -489,8 +490,9 @@ def f(self) -> A: ... return False if not proper_subtype: - # Nominal check currently ignores arg names - subtype_kind = SubtypeVisitor.build_subtype_kind(ignore_pos_arg_names=True) + # Nominal check currently ignores arg names, except for __call__ + ignore_names = right.type.protocol_members != ['__call__'] + subtype_kind = SubtypeVisitor.build_subtype_kind(ignore_pos_arg_names=ignore_names) else: subtype_kind = ProperSubtypeVisitor.build_subtype_kind() TypeState.record_subtype_cache_entry(subtype_kind, left, right) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 81b520324b18..ac4c1c4e227b 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4636,3 +4636,10 @@ class C: reveal_type(C.foo) # E: Revealed type is 'builtins.int*' reveal_type(C().foo) # E: Revealed type is 'builtins.int*' [out] + +[case tesBadCallOverride] +class Base: + def __call__(self, arg: int) -> None: ... +class Sub(Base): + def __call__(self, zzz: int) -> None: ... # E: Signature of "__call__" incompatible with supertype "Base" +[out] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 28427279f8a6..a93c0d199794 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2243,6 +2243,26 @@ func(bad) # E: Argument 1 to "func" has incompatible type "Callable[[int], int] [builtins fixtures/tuple.pyi] [out] +[case testCallableImplementsProtocolGenericNotGeneric] +from typing import Protocol, TypeVar, Tuple + +T = TypeVar('T') + +class Caller(Protocol): + def __call__(self, x: int) -> int: ... + +def call(x: T) -> T: ... + +def bad(x: T) -> Tuple[T, T]: ... + +def func(caller: Caller) -> None: + pass + +func(call) +func(bad) # E: Argument 1 to "func" has incompatible type "Callable[[T], Tuple[T, T]]"; expected "Caller" +[builtins fixtures/tuple.pyi] +[out] + [case testCallableImplementsProtocolOverload] from typing import Protocol, overload, Union @@ -2307,3 +2327,63 @@ func(call) func(bad) # E: Argument 1 to "func" has incompatible type "Callable[[str], None]"; expected "Caller" anon(bad) [out] + +[case testCallableProtocolVsProtocol] +from typing import Protocol + +class One(Protocol): + def __call__(self, x: str) -> None: ... + +class Other(Protocol): + def __call__(self, x: str) -> None: ... + +class Bad(Protocol): + def __call__(self, zzz: str) -> None: ... + +def func(caller: One) -> None: + pass + +a: Other +b: Bad + +func(a) +func(b) # E: Argument 1 to "func" has incompatible type "Bad"; expected "One" +[out] + +[case testJoinProtocolCallback] +from typing import Protocol, Callable + +class A: ... +class B(A): ... +class C(B): ... +class D(B): ... + +class Call(Protocol): + def __call__(self, x: B) -> C: ... +Normal = Callable[[A], D] + +a: Call +b: Normal + +reveal_type([a, b]) # E: Revealed type is 'builtins.list[def (__main__.B) -> __main__.B]' +[builtins fixtures/list.pyi] +[out] + +[case testMeetProtocolCallback] +from typing import Protocol, Callable + +class A: ... +class B(A): ... +class C(B): ... +class D(B): ... + +class Call(Protocol): + def __call__(self, __x: C) -> B: ... +Normal = Callable[[D], A] + +def a(x: Call) -> None: ... +def b(x: Normal) -> None: ... + +reveal_type([a, b]) # E: Revealed type is 'builtins.list[def (x: def (__main__.B) -> __main__.B)]' +[builtins fixtures/list.pyi] +[out] From 2822b48be028fbba9379e8647f49c8f317fba21a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 16 Aug 2018 11:46:24 +0100 Subject: [PATCH 5/9] Fix self-check --- mypy/meet.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/meet.py b/mypy/meet.py index 22383f9aa0b4..ccc70d60bddd 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -307,8 +307,7 @@ def visit_instance(self, t: Instance) -> Type: return meet_types(t, self.s) elif isinstance(self.s, TupleType): return meet_types(t, self.s) - else: - return self.default(self.s) + return self.default(self.s) def visit_callable_type(self, t: CallableType) -> Type: if isinstance(self.s, CallableType) and is_similar_callables(t, self.s): From 793961321aaffe5145e580212bd1da6246fafb28 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 16 Aug 2018 14:59:36 +0100 Subject: [PATCH 6/9] Add docs --- docs/source/kinds_of_types.rst | 2 ++ docs/source/protocols.rst | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 28fd9f19db22..ad6fefaad7e6 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -186,6 +186,8 @@ Any)`` function signature. Example: arbitrary_call(open) # Error: does not return an int arbitrary_call(1) # Error: 'int' is not callable +In situations where more precise or complex types of callbacks are +necessary one can use flexible :ref:`callback protocols `. Lambdas are also supported. The lambda argument and return value types cannot be given explicitly; they are always inferred based on context using bidirectional type inference: diff --git a/docs/source/protocols.rst b/docs/source/protocols.rst index 113f73538e6c..c4c097b8e32c 100644 --- a/docs/source/protocols.rst +++ b/docs/source/protocols.rst @@ -409,3 +409,53 @@ in ``typing`` such as ``Iterable``. ``isinstance()`` with protocols is not completely safe at runtime. For example, signatures of methods are not checked. The runtime implementation only checks that all protocol members are defined. + +.. _callback_protocols: + +Callback protocols +****************** + +Protocols can be used to define flexible callback types that are impossible +to express using the ``Callable[...]`` syntax, for example variadic, overloaded, +and complex generic callbacks. They are defined with a special ``__call__`` member: + +.. code-block:: python + + from typing import Optional, Iterable, List + from typing_extensions import Protocol + + class Combiner(Protocol): + def __call__(self, *vals: bytes, maxlen: Optional[int] = None) -> List[bytes]: ... + + def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes: + for item in data: + ... + + def good_cb(*vals: bytes, maxlen: Optional[int] = None) -> List[bytes]: + ... + def bad_cb(*vals: bytes, maxitems: Optional[int]) -> List[bytes]: + ... + + batch_proc([], good_cb) # OK + batch_proc([], bad_cb) # Error! Argument 2 has incompatible type because of + # different name and kind in the callback + +Callback protocols and ``Callable[...]`` types can be used interchangeably. +To indicate an "anonymous" positional-only argument use double underscore, +for example: + +.. code-block:: python + + from typing import Callable, TypeVar + from typing_extensions import Protocol + + T = TypeVar('T') + + class Copy(Protocol): + def __call__(self, __origin: T) -> T: ... + + copy_a: Callable[[T], T] + copy_b: Copy + + copy_a = copy_b # OK + copy_b = copy_a # Also OK From 8ebcf9090a95f958383443cd0ad700cd6defb5c1 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 16 Aug 2018 18:06:03 +0100 Subject: [PATCH 7/9] Address some comments --- docs/source/protocols.rst | 11 ++++++----- test-data/unit/check-protocols.test | 2 ++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/source/protocols.rst b/docs/source/protocols.rst index c4c097b8e32c..caeac7c81368 100644 --- a/docs/source/protocols.rst +++ b/docs/source/protocols.rst @@ -415,9 +415,10 @@ in ``typing`` such as ``Iterable``. Callback protocols ****************** -Protocols can be used to define flexible callback types that are impossible -to express using the ``Callable[...]`` syntax, for example variadic, overloaded, -and complex generic callbacks. They are defined with a special ``__call__`` member: +Protocols can be used to define flexible callback types that are hard +(or even impossible) to express using the ``Callable[...]`` syntax, such as variadic, +overloaded, and complex generic callbacks. They are defined with a special ``__call__`` +member: .. code-block:: python @@ -441,8 +442,8 @@ and complex generic callbacks. They are defined with a special ``__call__`` memb # different name and kind in the callback Callback protocols and ``Callable[...]`` types can be used interchangeably. -To indicate an "anonymous" positional-only argument use double underscore, -for example: +Keyword argument names in ``__call__`` methods must be identical, unless +a double underscore prefix is used. For example: .. code-block:: python diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index a93c0d199794..689ec46a83cb 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2366,6 +2366,7 @@ a: Call b: Normal reveal_type([a, b]) # E: Revealed type is 'builtins.list[def (__main__.B) -> __main__.B]' +reveal_type([b, a]) # E: Revealed type is 'builtins.list[def (__main__.B) -> __main__.B]' [builtins fixtures/list.pyi] [out] @@ -2385,5 +2386,6 @@ def a(x: Call) -> None: ... def b(x: Normal) -> None: ... reveal_type([a, b]) # E: Revealed type is 'builtins.list[def (x: def (__main__.B) -> __main__.B)]' +reveal_type([b, a]) # E: Revealed type is 'builtins.list[def (x: def (__main__.B) -> __main__.B)]' [builtins fixtures/list.pyi] [out] From 7ef0e19c65f8a35823b940174be75e8b2621e3ef Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 16 Aug 2018 18:54:19 +0100 Subject: [PATCH 8/9] Skip arg name check in nominal case; only keep it for structural --- mypy/checker.py | 5 +---- mypy/subtypes.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index fd312c64a58d..2632720b9894 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1308,10 +1308,7 @@ def check_override(self, override: FunctionLike, original: FunctionLike, """ # Use boolean variable to clarify code. fail = False - # __call__ is special among dunders, because arguments can be passed - # as keyword args (unlike other dunders). - ignore_names = name != '__call__' - if not is_subtype(override, original, ignore_pos_arg_names=ignore_names): + if not is_subtype(override, original, ignore_pos_arg_names=True): fail = True elif (not isinstance(original, Overloaded) and isinstance(override, Overloaded) and diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 3b62900530c2..735804280945 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -490,7 +490,7 @@ def f(self) -> A: ... return False if not proper_subtype: - # Nominal check currently ignores arg names, except for __call__ + # Nominal check currently ignores arg names, but __call__ is special for protocols ignore_names = right.type.protocol_members != ['__call__'] subtype_kind = SubtypeVisitor.build_subtype_kind(ignore_pos_arg_names=ignore_names) else: From 9219cc0c9568cd71accbb52ded04aad7827a68ea Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 16 Aug 2018 18:58:25 +0100 Subject: [PATCH 9/9] Remove test --- test-data/unit/check-classes.test | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index ac4c1c4e227b..81b520324b18 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4636,10 +4636,3 @@ class C: reveal_type(C.foo) # E: Revealed type is 'builtins.int*' reveal_type(C().foo) # E: Revealed type is 'builtins.int*' [out] - -[case tesBadCallOverride] -class Base: - def __call__(self, arg: int) -> None: ... -class Sub(Base): - def __call__(self, zzz: int) -> None: ... # E: Signature of "__call__" incompatible with supertype "Base" -[out]