From 2f37e863d208db8cd76a9d2d27ccb978a121e682 Mon Sep 17 00:00:00 2001 From: chetan22 Date: Wed, 20 May 2020 22:47:45 +0530 Subject: [PATCH] Fixes #5705 Added notes as suggested in the issue description after error from incompatible arguments in subtype is raised. --- mypy/messages.py | 8 +++++ test-data/unit/check-abstract.test | 18 ++++++++--- test-data/unit/check-classes.test | 24 ++++++++++++--- test-data/unit/check-columns.test | 4 ++- test-data/unit/check-dynamic-typing.test | 4 ++- test-data/unit/check-errorcodes.test | 4 ++- test-data/unit/check-functions.test | 5 +++- test-data/unit/check-generic-subtyping.test | 30 ++++++++++++++----- .../unit/check-multiple-inheritance.test | 2 ++ 9 files changed, 80 insertions(+), 19 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index dff51475e552..2dd95e878572 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -772,6 +772,14 @@ def argument_incompatible_with_supertype( .format(arg_num, name, target, arg_type_in_supertype_f), context, code=codes.OVERRIDE) + self.note( + 'This violates the Liskov substitution principle', + context, + code=codes.OVERRIDE) + self.note( + 'See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides', + context, + code=codes.OVERRIDE) if name == "__eq__" and type_name: multiline_msg = self.comparison_method_example_msg(class_name=type_name) diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 1a06a8c41d56..47889f3cbe0e 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -311,7 +311,9 @@ class A(metaclass=ABCMeta): def g(self, x: int) -> int: pass class B(A): def f(self, x: str) -> int: \ - # E: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" + # E: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides pass def g(self, x: int) -> int: pass [out] @@ -327,7 +329,9 @@ class J(metaclass=ABCMeta): def g(self, x: str) -> str: pass class A(I, J): def f(self, x: str) -> int: pass \ - # E: Argument 1 of "f" is incompatible with supertype "I"; supertype defines the argument type as "int" + # E: Argument 1 of "f" is incompatible with supertype "I"; supertype defines the argument type as "int" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides def g(self, x: str) -> int: pass \ # E: Return type "int" of "g" incompatible with return type "str" in supertype "J" def h(self) -> int: pass # Not related to any base class @@ -342,7 +346,9 @@ class J(metaclass=ABCMeta): class I(J): pass class A(I): def f(self, x: str) -> int: pass \ - # E: Argument 1 of "f" is incompatible with supertype "J"; supertype defines the argument type as "int" + # E: Argument 1 of "f" is incompatible with supertype "J"; supertype defines the argument type as "int" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides [out] [case testInvalidOverridingAbstractMethod] @@ -353,7 +359,9 @@ class J(metaclass=ABCMeta): def f(self, x: 'J') -> None: pass class I(J): @abstractmethod - def f(self, x: 'I') -> None: pass # E: Argument 1 of "f" is incompatible with supertype "J"; supertype defines the argument type as "J" + def f(self, x: 'I') -> None: pass # E: Argument 1 of "f" is incompatible with supertype "J"; supertype defines the argument type as "J" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides [out] [case testAbstractClassCoAndContraVariance] @@ -375,6 +383,8 @@ class A(I): pass [out] main:11: error: Argument 1 of "h" is incompatible with supertype "I"; supertype defines the argument type as "I" +main:11: note: This violates the Liskov substitution principle +main:11: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides main:11: error: Return type "I" of "h" incompatible with return type "A" in supertype "I" diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index cec429adcb8f..0e94b0c443c3 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -282,6 +282,8 @@ class B(A): def h(self, x: A, y: 'B') -> object: pass # Fail [out] main:7: error: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "A" +main:7: note: This violates the Liskov substitution principle +main:7: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides main:9: error: Return type "object" of "h" incompatible with return type "A" in supertype "A" [case testEqMethodsOverridingWithNonObjects] @@ -290,6 +292,8 @@ class A: [builtins fixtures/attr.pyi] [out] main:2: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" +main:2: note: This violates the Liskov substitution principle +main:2: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides main:2: note: It is recommended for "__eq__" to work with arbitrary objects, for example: main:2: note: def __eq__(self, other: object) -> bool: main:2: note: if not isinstance(other, A): @@ -318,6 +322,8 @@ class C(B): # with gap in implementations pass [out] main:6: error: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "B" +main:6: note: This violates the Liskov substitution principle +main:6: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides [case testMethodOverridingAcrossDeepInheritanceHierarchy2] import typing @@ -459,7 +465,9 @@ class B(A): @staticmethod def f(x: int, y: str) -> None: pass @staticmethod - def g(x: str, y: str) -> None: pass # E: Argument 1 of "g" is incompatible with supertype "A"; supertype defines the argument type as "int" + def g(x: str, y: str) -> None: pass # E: Argument 1 of "g" is incompatible with supertype "A"; supertype defines the argument type as "int" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides [builtins fixtures/classmethod.pyi] [case testOverrideClassMethodWithClassMethod] @@ -473,7 +481,9 @@ class B(A): @classmethod def f(cls, x: int, y: str) -> None: pass @classmethod - def g(cls, x: str, y: str) -> None: pass # E: Argument 1 of "g" is incompatible with supertype "A"; supertype defines the argument type as "int" + def g(cls, x: str, y: str) -> None: pass # E: Argument 1 of "g" is incompatible with supertype "A"; supertype defines the argument type as "int" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides [builtins fixtures/classmethod.pyi] [case testOverrideClassMethodWithStaticMethod] @@ -489,7 +499,9 @@ class B(A): @staticmethod def f(x: int) -> None: pass @staticmethod - def g(x: str) -> int: pass # E: Argument 1 of "g" is incompatible with supertype "A"; supertype defines the argument type as "int" + def g(x: str) -> int: pass # E: Argument 1 of "g" is incompatible with supertype "A"; supertype defines the argument type as "int" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides @staticmethod def h() -> int: pass [builtins fixtures/classmethod.pyi] @@ -507,7 +519,9 @@ class B(A): @classmethod def f(cls, x: int) -> None: pass @classmethod - def g(cls, x: int) -> int: pass # E: Argument 1 of "g" is incompatible with supertype "A"; supertype defines the argument type as "str" + def g(cls, x: int) -> int: pass # E: Argument 1 of "g" is incompatible with supertype "A"; 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 @classmethod def h(cls) -> int: pass [builtins fixtures/classmethod.pyi] @@ -2478,6 +2492,8 @@ class D(A): [out] main:6: error: Return type "A" of "__iadd__" incompatible with return type "B" in "__add__" of supertype "A" main:8: error: Argument 1 of "__iadd__" is incompatible with "__add__" of supertype "A"; supertype defines the argument type as "A" +main:8: note: This violates the Liskov substitution principle +main:8: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides main:8: error: Signatures of "__iadd__" and "__add__" are incompatible [case testGetattribute] diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index 73562ccc1c5d..339d0ce863a7 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -262,7 +262,9 @@ if int(): class A: def f(self, x: int) -> None: pass class B(A): - def f(self, x: str) -> None: pass # E:5: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" + def f(self, x: str) -> None: pass # E:5: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" \ + # N:5: This violates the Liskov substitution principle \ + # N:5: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides class C(A): def f(self, x: int) -> int: pass # E:5: Return type "int" of "f" incompatible with return type "None" in supertype "A" class D(A): diff --git a/test-data/unit/check-dynamic-typing.test b/test-data/unit/check-dynamic-typing.test index 98895ba3b302..615aafd4849a 100644 --- a/test-data/unit/check-dynamic-typing.test +++ b/test-data/unit/check-dynamic-typing.test @@ -704,7 +704,9 @@ class C: class B(C): def f(self, a): pass class A(B): - def f(self, a: 'D') -> None: # E: Argument 1 of "f" is incompatible with supertype "C"; supertype defines the argument type as "A" + def f(self, a: 'D') -> None: # E: Argument 1 of "f" is incompatible with supertype "C"; supertype defines the argument type as "A" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides pass class D: pass [out] diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index cf6429bd3e8f..2c1e52b04ac7 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -279,7 +279,9 @@ class D: def f(self, x: int) -> int: return 0 class E(D): - def f(self, x: str) -> int: # E: Argument 1 of "f" is incompatible with supertype "D"; supertype defines the argument type as "int" [override] + def f(self, x: str) -> int: # E: Argument 1 of "f" is incompatible with supertype "D"; supertype defines the argument type as "int" [override] \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides return 0 class O: diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index c37ca101de76..cff1818fd8f5 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -46,7 +46,10 @@ class A(object): def f(self, a: int, b: str) -> None: pass class B(A): - def f(self, b: str, a: int) -> None: pass # E: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" # E: Argument 2 of "f" is incompatible with supertype "A"; supertype defines the argument type as "str" + def f(self, b: str, a: int) -> None: pass # E: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides \ + # E: Argument 2 of "f" is incompatible with supertype "A"; supertype defines the argument type as "str" class C(A): def f(self, foo: int, bar: str) -> None: pass diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test index aca56a061e8c..f1fbed9fe654 100644 --- a/test-data/unit/check-generic-subtyping.test +++ b/test-data/unit/check-generic-subtyping.test @@ -189,7 +189,9 @@ class C: pass class D: pass class A(B[C]): def f(self, a: D) -> None: pass \ - # E: Argument 1 of "f" is incompatible with supertype "B"; supertype defines the argument type as "C" + # E: Argument 1 of "f" is incompatible with supertype "B"; supertype defines the argument type as "C" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides def g(self, a: C) -> None: pass [out] @@ -202,7 +204,9 @@ class B: def g(self, a: C) -> None: pass class A(B, Generic[T]): def f(self, a: T) -> None: pass \ - # E: Argument 1 of "f" is incompatible with supertype "B"; supertype defines the argument type as "C" + # E: Argument 1 of "f" is incompatible with supertype "B"; supertype defines the argument type as "C" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides def g(self, a: 'C') -> None: pass [out] @@ -215,7 +219,9 @@ class B(Generic[T]): def g(self, a: T) -> None: pass class A(B[S], Generic[T, S]): def f(self, a: T) -> None: pass \ - # E: Argument 1 of "f" is incompatible with supertype "B"; supertype defines the argument type as "S" + # E: Argument 1 of "f" is incompatible with supertype "B"; supertype defines the argument type as "S" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides def g(self, a: S) -> None: pass [out] @@ -233,7 +239,9 @@ class C(Generic[T, U, V]): class B(C[D, D, T], Generic[T]): pass class A(B[S], Generic[T, S]): def f(self, a: T) -> None: pass \ - # E: Argument 1 of "f" is incompatible with supertype "C"; supertype defines the argument type as "S" + # E: Argument 1 of "f" is incompatible with supertype "C"; supertype defines the argument type as "S" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides def g(self, a: S) -> None: pass [out] @@ -283,6 +291,8 @@ class D(A): def f(self, x: T1, y: S) -> None: pass # TODO: This error could be more specific. [out] main:12: error: Argument 2 of "f" is incompatible with supertype "A"; supertype defines the argument type as "S" +main:12: note: This violates the Liskov substitution principle +main:12: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides main:14: error: Signature of "f" incompatible with supertype "A" @@ -538,7 +548,9 @@ class I(Generic[T]): def g(self, a: T) -> None: pass class A(I[C]): def f(self, a: 'D') -> None: pass \ - # E: Argument 1 of "f" is incompatible with supertype "I"; supertype defines the argument type as "C" + # E: Argument 1 of "f" is incompatible with supertype "I"; supertype defines the argument type as "C" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides def g(self, a: 'C') -> None: pass class C: pass class D: pass @@ -570,7 +582,9 @@ class B(I[C]): def g(self, a: 'C', b: Any) -> None: pass class A(B): def g(self, a: 'C', b: 'C') -> None: pass \ - # E: Argument 2 of "g" is incompatible with supertype "I"; supertype defines the argument type as "D" + # E: Argument 2 of "g" is incompatible with supertype "I"; supertype defines the argument type as "D" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides def f(self, a: 'C', b: 'C') -> None: pass class C: pass class D: pass @@ -587,7 +601,9 @@ class B(I[C]): def f(self, a: 'C', b: Any) -> None: pass class A(B): def f(self, a: 'C', b: 'D') -> None: pass \ - # E: Argument 2 of "f" is incompatible with supertype "I"; supertype defines the argument type as "C" + # E: Argument 2 of "f" is incompatible with supertype "I"; supertype defines the argument type as "C" \ + # N: This violates the Liskov substitution principle \ + # N: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides class C: pass class D: pass [out] diff --git a/test-data/unit/check-multiple-inheritance.test b/test-data/unit/check-multiple-inheritance.test index 2605b8a1d340..a71ccd631753 100644 --- a/test-data/unit/check-multiple-inheritance.test +++ b/test-data/unit/check-multiple-inheritance.test @@ -539,6 +539,8 @@ class IntFlag(int, Flag): def __or__(self: _T, other: str) -> _T: ... [out] main:8: error: Argument 1 of "__or__" is incompatible with supertype "Flag"; supertype defines the argument type as "IntFlag" +main:8: note: This violates the Liskov substitution principle +main:8: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides [case testMultipleInheritance_MethodDefinitionsCompatibleNoOverride] from typing import TypeVar, Union