From 1c3dac540b53d534d45d7d29e328c0e084a3cbc7 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Wed, 17 May 2023 22:18:23 -0400 Subject: [PATCH 1/5] signature_incompatible_with_supertype: show non-callables --- mypy/checker.py | 2 +- mypy/messages.py | 32 ++++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 957da2cd33bc..8229ef9ce11d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1988,7 +1988,7 @@ def check_method_override_for_base_with_name( # If the attribute is read-only, allow covariance pass else: - self.msg.signature_incompatible_with_supertype(defn.name, name, base.name, context) + self.msg.signature_incompatible_with_supertype(defn.name, name, base.name, context, original_type, typ) return False def bind_and_map_method( diff --git a/mypy/messages.py b/mypy/messages.py index 981849df663d..5ffabae478db 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1136,13 +1136,15 @@ def signature_incompatible_with_supertype( name_in_super: str, supertype: str, context: Context, - original: FunctionLike | None = None, - override: FunctionLike | None = None, + original: ProperType, + override: ProperType, ) -> None: code = codes.OVERRIDE target = self.override_target(name, name_in_super, supertype) self.fail(f'Signature of "{name}" incompatible with {target}', context, code=code) + original_str, override_str = format_type_distinctly(original, override, options=self.options, bare=True) + INCLUDE_DECORATOR = True # Include @classmethod and @staticmethod decorators, if any ALLOW_DUPS = True # Allow duplicate notes, needed when signatures are duplicates ALIGN_OFFSET = 1 # One space, to account for the difference between error and note @@ -1152,13 +1154,8 @@ def signature_incompatible_with_supertype( # note: def f(self) -> str # note: Subclass: # note: def f(self, x: str) -> None - if ( - original is not None - and isinstance(original, (CallableType, Overloaded)) - and override is not None - and isinstance(override, (CallableType, Overloaded)) - ): - self.note("Superclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code) + self.note("Superclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code) + if isinstance(original, (CallableType, Overloaded)): self.pretty_callable_or_overload( original, context, @@ -1167,8 +1164,16 @@ def signature_incompatible_with_supertype( allow_dups=ALLOW_DUPS, code=code, ) + else: + self.note( + original_str, + context, + offset=ALIGN_OFFSET + 2 * OFFSET, + code=code, + ) - self.note("Subclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code) + self.note("Subclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code) + if isinstance(override, (CallableType, Overloaded)): self.pretty_callable_or_overload( override, context, @@ -1177,6 +1182,13 @@ def signature_incompatible_with_supertype( allow_dups=ALLOW_DUPS, code=code, ) + else: + self.note( + override_str, + context, + offset=ALIGN_OFFSET + 2 * OFFSET, + code=code, + ) def pretty_callable_or_overload( self, From 6448e24499b30b15b71666754432d3cd46e71bdb Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Thu, 18 May 2023 00:45:54 -0400 Subject: [PATCH 2/5] update tests --- test-data/unit/check-abstract.test | 6 +- test-data/unit/check-classes.test | 89 ++++++++++++++++++++------ test-data/unit/check-dataclasses.test | 6 +- test-data/unit/check-functions.test | 12 +++- test-data/unit/check-plugin-attrs.test | 6 +- 5 files changed, 96 insertions(+), 23 deletions(-) diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 566bb92d6e18..8a13e5cb5760 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -790,7 +790,11 @@ class A(metaclass=ABCMeta): def x(self) -> int: pass class B(A): @property - def x(self) -> str: return "no" # E: Signature of "x" incompatible with supertype "A" + def x(self) -> str: return "no" # E: Signature of "x" incompatible with supertype "A" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: str b = B() b.x() # E: "str" not callable [builtins fixtures/property.pyi] diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 6476ad1566dc..f51e53c0a067 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -115,7 +115,11 @@ class Base: __hash__ = None class Derived(Base): - def __hash__(self) -> int: # E: Signature of "__hash__" incompatible with supertype "Base" + def __hash__(self) -> int: # E: Signature of "__hash__" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: None \ + # N: Subclass: \ + # N: def __hash__(self) -> int pass # Correct: @@ -157,7 +161,11 @@ class Base: class Derived(Base): - def partial_type(self) -> int: # E: Signature of "partial_type" incompatible with supertype "Base" + def partial_type(self) -> int: # E: Signature of "partial_type" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: List[Any] \ + # N: Subclass: \ + # N: def partial_type(self) -> int ... @@ -567,8 +575,16 @@ class A: class B(A): @dec - def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A" - def g(self) -> int: pass # E: Signature of "g" incompatible with supertype "A" + def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: def f(self) -> str \ + # N: Subclass: \ + # N: str + def g(self) -> int: pass # E: Signature of "g" incompatible with supertype "A" \ + # N: Superclass: \ + # N: str \ + # N: Subclass: \ + # N: def g(self) -> int @dec def h(self) -> str: pass @@ -4223,11 +4239,12 @@ class A: def a(self) -> None: pass b = 1 class B(A): - a = 1 - def b(self) -> None: pass -[out] -main:5: error: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "Callable[[A], None]") -main:6: error: Signature of "b" incompatible with supertype "A" + a = 1 # E: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "Callable[[A], None]") + def b(self) -> None: pass # E: Signature of "b" incompatible with supertype "A" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def b(self) -> None [case testVariableProperty] class A: @@ -6166,7 +6183,11 @@ import a [file b.py] import a class Sub(a.Base): - def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" + def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def x(self) -> int [file a.py] import b @@ -6182,7 +6203,11 @@ import a import c class Sub(a.Base): @c.deco - def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" + def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def x(*Any, **Any) -> Tuple[int, int] [file a.py] import b @@ -6204,7 +6229,11 @@ import a import c class Sub(a.Base): @c.deco - def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" + def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def x(*Any, **Any) -> Tuple[int, int] [file a.py] import b @@ -7687,13 +7716,29 @@ class Parent: foobar = TypeVar("foobar") class Child(Parent): - def foo(self, val: int) -> int: # E: Signature of "foo" incompatible with supertype "Parent" + def foo(self, val: int) -> int: # E: Signature of "foo" incompatible with supertype "Parent" \ + # N: Superclass: \ + # N: None \ + # N: Subclass: \ + # N: def foo(self, val: int) -> int return val - def bar(self, val: str) -> str: # E: Signature of "bar" incompatible with supertype "Parent" + def bar(self, val: str) -> str: # E: Signature of "bar" incompatible with supertype "Parent" \ + # N: Superclass: \ + # N: None \ + # N: Subclass: \ + # N: def bar(self, val: str) -> str return val - def baz(self, val: float) -> float: # E: Signature of "baz" incompatible with supertype "Parent" + def baz(self, val: float) -> float: # E: Signature of "baz" incompatible with supertype "Parent" \ + # N: Superclass: \ + # N: None \ + # N: Subclass: \ + # N: def baz(self, val: float) -> float return val - def foobar(self) -> bool: # E: Signature of "foobar" incompatible with supertype "Parent" + def foobar(self) -> bool: # E: Signature of "foobar" incompatible with supertype "Parent" \ + # N: Superclass: \ + # N: None \ + # N: Subclass: \ + # N: def foobar(self) -> bool return False x: Parent.foo = lambda: 5 @@ -7761,7 +7806,11 @@ class B: ... class C(B): @property - def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B" + def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B" \ + # N: Superclass: \ + # N: def foo(self) -> int \ + # N: Subclass: \ + # N: int ... [builtins fixtures/property.pyi] @@ -7771,7 +7820,11 @@ class B: def foo(self) -> int: ... class C(B): - def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B" + def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def foo(self) -> int ... [builtins fixtures/property.pyi] diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 9a68651ed5f6..914e1c2e0602 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -198,7 +198,11 @@ class Base: @dataclass class BadDerived1(Base): def foo(self) -> int: # E: Dataclass attribute may only be overridden by another attribute \ - # E: Signature of "foo" incompatible with supertype "Base" + # E: Signature of "foo" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: int \ + # N: Subclass: \ + # N: def foo(self) -> int return 1 @dataclass diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 4bad19af539c..b5d540b105e3 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2848,7 +2848,11 @@ class C(A): # inverted order of decorators class D(A): @property @override - def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A" + def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: str \ + # N: Subclass: \ + # N: int [builtins fixtures/property.pyi] [typing fixtures/typing-full.pyi] @@ -2877,7 +2881,11 @@ class C(A): def f(self, value: str) -> None: pass class D(A): - @override # E: Signature of "f" incompatible with supertype "A" + @override # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: str \ + # N: Subclass: \ + # N: int @property def f(self) -> int: pass diff --git a/test-data/unit/check-plugin-attrs.test b/test-data/unit/check-plugin-attrs.test index ce1d670431c7..182837107bd6 100644 --- a/test-data/unit/check-plugin-attrs.test +++ b/test-data/unit/check-plugin-attrs.test @@ -1877,7 +1877,11 @@ class Sub(Base): last_name: str @property - def name(self) -> int: ... # E: Signature of "name" incompatible with supertype "Base" + def name(self) -> int: ... # E: Signature of "name" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: str \ + # N: Subclass: \ + # N: int # This matches runtime semantics reveal_type(Sub) # N: Revealed type is "def (*, name: builtins.str, first_name: builtins.str, last_name: builtins.str) -> __main__.Sub" From 6775bef6b8a430f2c388a10d832165bdc8f3358c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 04:49:37 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checker.py | 4 +++- mypy/messages.py | 18 +++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8229ef9ce11d..e11d841f7153 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1988,7 +1988,9 @@ def check_method_override_for_base_with_name( # If the attribute is read-only, allow covariance pass else: - self.msg.signature_incompatible_with_supertype(defn.name, name, base.name, context, original_type, typ) + self.msg.signature_incompatible_with_supertype( + defn.name, name, base.name, context, original_type, typ + ) return False def bind_and_map_method( diff --git a/mypy/messages.py b/mypy/messages.py index 5ffabae478db..fb8070dbca38 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1143,7 +1143,9 @@ def signature_incompatible_with_supertype( target = self.override_target(name, name_in_super, supertype) self.fail(f'Signature of "{name}" incompatible with {target}', context, code=code) - original_str, override_str = format_type_distinctly(original, override, options=self.options, bare=True) + original_str, override_str = format_type_distinctly( + original, override, options=self.options, bare=True + ) INCLUDE_DECORATOR = True # Include @classmethod and @staticmethod decorators, if any ALLOW_DUPS = True # Allow duplicate notes, needed when signatures are duplicates @@ -1165,12 +1167,7 @@ def signature_incompatible_with_supertype( code=code, ) else: - self.note( - original_str, - context, - offset=ALIGN_OFFSET + 2 * OFFSET, - code=code, - ) + self.note(original_str, context, offset=ALIGN_OFFSET + 2 * OFFSET, code=code) self.note("Subclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code) if isinstance(override, (CallableType, Overloaded)): @@ -1183,12 +1180,7 @@ def signature_incompatible_with_supertype( code=code, ) else: - self.note( - override_str, - context, - offset=ALIGN_OFFSET + 2 * OFFSET, - code=code, - ) + self.note(override_str, context, offset=ALIGN_OFFSET + 2 * OFFSET, code=code) def pretty_callable_or_overload( self, From 3135c0f12c80815c302b69c0e387d0ae3645de63 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Thu, 18 May 2023 15:21:55 -0400 Subject: [PATCH 4/5] pass original and override by kw --- mypy/checker.py | 2 +- mypy/messages.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index e11d841f7153..b7bd656ca87e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1989,7 +1989,7 @@ def check_method_override_for_base_with_name( pass else: self.msg.signature_incompatible_with_supertype( - defn.name, name, base.name, context, original_type, typ + defn.name, name, base.name, context, original=original_type, override=typ ) return False diff --git a/mypy/messages.py b/mypy/messages.py index fb8070dbca38..3aeaa1c4da3d 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1136,6 +1136,7 @@ def signature_incompatible_with_supertype( name_in_super: str, supertype: str, context: Context, + *, original: ProperType, override: ProperType, ) -> None: From a00844842290a2c2f1a6ceb53374accf0124b432 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Fri, 19 May 2023 23:11:56 -0400 Subject: [PATCH 5/5] allow more dups --- mypy/messages.py | 24 ++++++++++++++++++++---- test-data/unit/check-classes.test | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 3aeaa1c4da3d..a732d612123c 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1157,7 +1157,9 @@ def signature_incompatible_with_supertype( # note: def f(self) -> str # note: Subclass: # note: def f(self, x: str) -> None - self.note("Superclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code) + self.note( + "Superclass:", context, offset=ALIGN_OFFSET + OFFSET, allow_dups=ALLOW_DUPS, code=code + ) if isinstance(original, (CallableType, Overloaded)): self.pretty_callable_or_overload( original, @@ -1168,9 +1170,17 @@ def signature_incompatible_with_supertype( code=code, ) else: - self.note(original_str, context, offset=ALIGN_OFFSET + 2 * OFFSET, code=code) + self.note( + original_str, + context, + offset=ALIGN_OFFSET + 2 * OFFSET, + allow_dups=ALLOW_DUPS, + code=code, + ) - self.note("Subclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code) + self.note( + "Subclass:", context, offset=ALIGN_OFFSET + OFFSET, allow_dups=ALLOW_DUPS, code=code + ) if isinstance(override, (CallableType, Overloaded)): self.pretty_callable_or_overload( override, @@ -1181,7 +1191,13 @@ def signature_incompatible_with_supertype( code=code, ) else: - self.note(override_str, context, offset=ALIGN_OFFSET + 2 * OFFSET, code=code) + self.note( + override_str, + context, + offset=ALIGN_OFFSET + 2 * OFFSET, + allow_dups=ALLOW_DUPS, + code=code, + ) def pretty_callable_or_overload( self, diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index f51e53c0a067..3af20bd1e7ea 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -588,6 +588,32 @@ class B(A): @dec def h(self) -> str: pass +[case testOverrideIncompatibleWithMultipleSupertypes] +class A: + def f(self, *, a: int) -> None: + return + +class B(A): + def f(self, *, b: int) -> None: # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: def f(self, *, a: int) -> None \ + # N: Subclass: \ + # N: def f(self, *, b: int) -> None + return + +class C(B): + def f(self, *, c: int) -> None: # E: Signature of "f" incompatible with supertype "B" \ + # N: Superclass: \ + # N: def f(self, *, b: int) -> None \ + # N: Subclass: \ + # N: def f(self, *, c: int) -> None \ + # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: def f(self, *, a: int) -> None \ + # N: Subclass: \ + # N: def f(self, *, c: int) -> None + return + [case testOverrideStaticMethodWithStaticMethod] class A: @staticmethod