From 448a7b1a5b8426e42c42a78375cb01034c6e810d Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 4 Aug 2022 13:46:32 +0300 Subject: [PATCH 1/6] Allow stubtest to raise errors on abstract state mismatch --- mypy/stubtest.py | 7 +++++++ mypy/test/teststubtest.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index ebc7fa12857d..499ba4a01887 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -825,6 +825,13 @@ def verify_funcitem( if not callable(runtime): return + if isinstance(stub, nodes.FuncDef): + stub_abstract = stub.abstract_status == nodes.IS_ABSTRACT + runtime_abstract = getattr(runtime, "__isabstractmethod__", False) + # The opposite can exist: some implementations omit `@abstractmethod` decorators + if runtime_abstract and not stub_abstract: + yield Error(object_path, "runtime method is abstract, but stub is not", stub, runtime) + for message in _verify_static_class_methods(stub, runtime, object_path): yield Error(object_path, "is inconsistent, " + message, stub, runtime) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 3de0e3fd5fc6..d2b2ee11e118 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -1264,6 +1264,40 @@ def test_type_var(self) -> Iterator[Case]: ) yield Case(stub="C = ParamSpec('C')", runtime="C = ParamSpec('C')", error=None) + @collect_cases + def test_abstract_methods(self) -> Iterator[Case]: + yield Case( + stub=""" + from abc import abstractmethod + + class A1: + def some(self) -> None: ... + + class A2: + @abstractmethod + def some(self) -> None: ... + + class A3: + @abstractmethod + def some(self) -> None: ... + """, + runtime=""" + from abc import abstractmethod + + class A1: + @abstractmethod + def some(self) -> None: ... + + class A2: + @abstractmethod + def some(self) -> None: ... + + class A3: + def some(self) -> None: ... + """, + error="A1.some", + ) + def remove_color_code(s: str) -> str: return re.sub("\\x1b.*?m", "", s) # this works! From 1b4158f3f65523478329588c2f162cc83f07a924 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 4 Aug 2022 17:05:37 +0300 Subject: [PATCH 2/6] Improve test case --- mypy/test/teststubtest.py | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index d2b2ee11e118..cc64637d579c 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -1266,36 +1266,48 @@ def test_type_var(self) -> Iterator[Case]: @collect_cases def test_abstract_methods(self) -> Iterator[Case]: + yield Case( + stub="from abc import abstractmethod", + runtime="from abc import abstractmethod", + error=None, + ) yield Case( stub=""" - from abc import abstractmethod - class A1: def some(self) -> None: ... - - class A2: + """, + runtime=""" + class A1: @abstractmethod def some(self) -> None: ... - - class A3: + """, + error="A1.some", + ) + yield Case( + stub=""" + class A2: @abstractmethod def some(self) -> None: ... """, runtime=""" - from abc import abstractmethod - - class A1: + class A2: @abstractmethod def some(self) -> None: ... - - class A2: + """, + error=None, + ) + # Runtime can miss `@abstractmethod`: + yield Case( + stub=""" + class A3: @abstractmethod def some(self) -> None: ... - + """, + runtime=""" class A3: def some(self) -> None: ... """, - error="A1.some", + error=None, ) From 0dc74708d3052cf0944d2aec1f906c6cbcd57377 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 4 Aug 2022 17:41:43 +0300 Subject: [PATCH 3/6] Add abstract `@property` tests --- mypy/test/teststubtest.py | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index cc64637d579c..be953ae17615 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -1310,6 +1310,58 @@ def some(self) -> None: ... error=None, ) + @collect_cases + def test_abstract_properties(self) -> Iterator[Case]: + yield Case( + stub="from abc import abstractmethod", + runtime="from abc import abstractmethod", + error=None, + ) + # Ensure that `@property` also can be abstract: + yield Case( + stub=""" + class AP1: + def some(self) -> int: ... + """, + runtime=""" + class AP1: + @property + @abstractmethod + def some(self) -> int: ... + """, + error="AP1.some", + ) + yield Case( + stub=""" + class AP2: + @property + @abstractmethod + def some(self) -> int: ... + """, + runtime=""" + class AP2: + @property + @abstractmethod + def some(self) -> int: ... + """, + error=None, + ) + # Runtime can miss `@abstractmethod`: + yield Case( + stub=""" + class AP3: + @property + @abstractmethod + def some(self) -> int: ... + """, + runtime=""" + class AP3: + @property + def some(self) -> int: ... + """, + error=None, + ) + def remove_color_code(s: str) -> str: return re.sub("\\x1b.*?m", "", s) # this works! From 790afb22d2f4de3e40b2c914b7febdd60b932a20 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Thu, 4 Aug 2022 18:51:19 +0300 Subject: [PATCH 4/6] Update mypy/stubtest.py Co-authored-by: Alex Waygood --- mypy/stubtest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 499ba4a01887..368674fc32f5 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -830,7 +830,7 @@ def verify_funcitem( runtime_abstract = getattr(runtime, "__isabstractmethod__", False) # The opposite can exist: some implementations omit `@abstractmethod` decorators if runtime_abstract and not stub_abstract: - yield Error(object_path, "runtime method is abstract, but stub is not", stub, runtime) + yield Error(object_path, "runtime method is abstract, but stub is not", stub, runtime, stub_desc="A concrete method", runtime_desc="An abstract method") for message in _verify_static_class_methods(stub, runtime, object_path): yield Error(object_path, "is inconsistent, " + message, stub, runtime) From 631958e015cdab20bef1487b75ac4fc677fbf8b2 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Thu, 4 Aug 2022 18:55:06 +0300 Subject: [PATCH 5/6] Update stubtest.py --- mypy/stubtest.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 368674fc32f5..28d650cb8bf1 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -830,7 +830,14 @@ def verify_funcitem( runtime_abstract = getattr(runtime, "__isabstractmethod__", False) # The opposite can exist: some implementations omit `@abstractmethod` decorators if runtime_abstract and not stub_abstract: - yield Error(object_path, "runtime method is abstract, but stub is not", stub, runtime, stub_desc="A concrete method", runtime_desc="An abstract method") + yield Error( + object_path, + "runtime method is abstract, but stub is not", + stub, + runtime, + stub_desc="A concrete method", + runtime_desc="An abstract method", + ) for message in _verify_static_class_methods(stub, runtime, object_path): yield Error(object_path, "is inconsistent, " + message, stub, runtime) From ce2d3cbd17072bfddc03b25c18069e2ac0a249ad Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Tue, 9 Aug 2022 18:39:39 -0700 Subject: [PATCH 6/6] Update mypy/stubtest.py --- mypy/stubtest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 28d650cb8bf1..33de28f993f2 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -832,11 +832,9 @@ def verify_funcitem( if runtime_abstract and not stub_abstract: yield Error( object_path, - "runtime method is abstract, but stub is not", + "is inconsistent, runtime method is abstract but stub is not", stub, runtime, - stub_desc="A concrete method", - runtime_desc="An abstract method", ) for message in _verify_static_class_methods(stub, runtime, object_path):