Skip to content

Commit

Permalink
Allow stubtest to raise errors on abstract state mismatch (#13323)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
  • Loading branch information
3 people committed Aug 10, 2022
1 parent 0ce8793 commit e77ee3b
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 0 deletions.
12 changes: 12 additions & 0 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,18 @@ 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,
"is inconsistent, 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)

Expand Down
98 changes: 98 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,104 @@ 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",
runtime="from abc import abstractmethod",
error=None,
)
yield Case(
stub="""
class A1:
def some(self) -> None: ...
""",
runtime="""
class A1:
@abstractmethod
def some(self) -> None: ...
""",
error="A1.some",
)
yield Case(
stub="""
class A2:
@abstractmethod
def some(self) -> None: ...
""",
runtime="""
class A2:
@abstractmethod
def some(self) -> None: ...
""",
error=None,
)
# Runtime can miss `@abstractmethod`:
yield Case(
stub="""
class A3:
@abstractmethod
def some(self) -> None: ...
""",
runtime="""
class A3:
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!
Expand Down

0 comments on commit e77ee3b

Please sign in to comment.