diff --git a/mypy/checker.py b/mypy/checker.py index 04e90c3e94cd..70db31c9a94f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2255,6 +2255,7 @@ def check_override( if fail: emitted_msg = False + offset_arguments = isinstance(override, CallableType) and override.unpack_kwargs # Normalize signatures, so we get better diagnostics. if isinstance(override, (CallableType, Overloaded)): override = override.with_unpacked_kwargs() @@ -2285,12 +2286,23 @@ def check_override( def erase_override(t: Type) -> Type: return erase_typevars(t, ids_to_erase=override_ids) - for i in range(len(override.arg_types)): - if not is_subtype( - original.arg_types[i], erase_override(override.arg_types[i]) - ): - arg_type_in_super = original.arg_types[i] - + for i, (sub_kind, super_kind) in enumerate( + zip(override.arg_kinds, original.arg_kinds) + ): + if sub_kind.is_positional() and super_kind.is_positional(): + override_arg_type = override.arg_types[i] + original_arg_type = original.arg_types[i] + elif sub_kind.is_named() and super_kind.is_named() and not offset_arguments: + arg_name = override.arg_names[i] + if arg_name in original.arg_names: + override_arg_type = override.arg_types[i] + original_i = original.arg_names.index(arg_name) + original_arg_type = original.arg_types[original_i] + else: + continue + else: + continue + if not is_subtype(original_arg_type, erase_override(override_arg_type)): if isinstance(node, FuncDef) and not node.is_property: context: Context = node.arguments[i + len(override.bound_args)] else: @@ -2300,7 +2312,7 @@ def erase_override(t: Type) -> Type: name, type_name, name_in_super, - arg_type_in_super, + original_arg_type, supertype, context, secondary_context=node, diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 4b04a3b96ae4..ef6ca9f3b285 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -40,11 +40,10 @@ class B(A): class C(A): def f(self, *, b: int, a: str) -> None: pass # Fail [out] -main:10: error: Signature of "f" incompatible with supertype "A" -main:10: note: Superclass: -main:10: note: def f(self, *, a: int, b: str) -> None -main:10: note: Subclass: -main:10: note: def f(self, *, b: int, a: str) -> None +main:10: error: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "str" +main:10: note: This violates the Liskov substitution principle +main:10: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides +main:10: error: Argument 2 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" [case testPositionalOverridingArgumentNameInsensitivity] import typing @@ -3324,3 +3323,47 @@ class Bar(Foo): # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides ... [builtins fixtures/property.pyi] + +[case testNoCrashOnUnpackOverride] +from typing import Unpack +from typing_extensions import TypedDict + +class Params(TypedDict): + x: int + y: str + +class Other(TypedDict): + x: int + y: int + +class B: + def meth(self, **kwargs: Unpack[Params]) -> None: + ... +class C(B): + def meth(self, **kwargs: Unpack[Other]) -> None: # E: Signature of "meth" incompatible with supertype "B" \ + # N: Superclass: \ + # N: def meth(*, x: int, y: str) -> None \ + # N: Subclass: \ + # N: def meth(*, x: int, y: int) -> None + + ... +[builtins fixtures/tuple.pyi] + +[case testOverrideErrorLocationNamed] +class B: + def meth( + self, *, + x: int, + y: str, + ) -> None: + ... +class C(B): + def meth( + self, *, + y: int, # E: Argument 1 of "meth" is incompatible with supertype "B"; supertype defines the argument type as "str" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides + x: int, + ) -> None: + ... +[builtins fixtures/tuple.pyi]