Skip to content

Commit 2c6e43e

Browse files
authored
Stubtest: check that the stub is abstract if the runtime is, even when the stub is an overloaded method (#14955)
#13323 means that stubtest will currently emit an error if the runtime is decorated with `@abstractmethod` but the stub is not, but _only_ if the stub is not an overloaded function, as overloaded functions have a different internal representation in mypy. This PR fixes that discrepancy.
1 parent 764ff6f commit 2c6e43e

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

mypy/stubtest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,17 @@ def verify_overloadedfuncdef(
10521052
if not callable(runtime):
10531053
return
10541054

1055+
# mypy doesn't allow overloads where one overload is abstract but another isn't,
1056+
# so it should be okay to just check whether the first overload is abstract or not.
1057+
#
1058+
# TODO: Mypy *does* allow properties where e.g. the getter is abstract but the setter is not;
1059+
# and any property with a setter is represented as an OverloadedFuncDef internally;
1060+
# not sure exactly what (if anything) we should do about that.
1061+
first_part = stub.items[0]
1062+
if isinstance(first_part, nodes.Decorator) and first_part.is_overload:
1063+
for msg in _verify_abstract_status(first_part.func, runtime):
1064+
yield Error(object_path, msg, stub, runtime)
1065+
10551066
for message in _verify_static_class_methods(stub, runtime, object_path):
10561067
yield Error(object_path, "is inconsistent, " + message, stub, runtime)
10571068

mypy/test/teststubtest.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,7 +1475,10 @@ def test_metaclass_abcmeta(self) -> Iterator[Case]:
14751475
@collect_cases
14761476
def test_abstract_methods(self) -> Iterator[Case]:
14771477
yield Case(
1478-
stub="from abc import abstractmethod",
1478+
stub="""
1479+
from abc import abstractmethod
1480+
from typing import overload
1481+
""",
14791482
runtime="from abc import abstractmethod",
14801483
error=None,
14811484
)
@@ -1504,15 +1507,64 @@ def some(self) -> None: ...
15041507
""",
15051508
error=None,
15061509
)
1507-
# Runtime can miss `@abstractmethod`:
15081510
yield Case(
15091511
stub="""
15101512
class A3:
1513+
@overload
1514+
def some(self, other: int) -> str: ...
1515+
@overload
1516+
def some(self, other: str) -> int: ...
1517+
""",
1518+
runtime="""
1519+
class A3:
1520+
@abstractmethod
1521+
def some(self, other) -> None: ...
1522+
""",
1523+
error="A3.some",
1524+
)
1525+
yield Case(
1526+
stub="""
1527+
class A4:
1528+
@overload
1529+
@abstractmethod
1530+
def some(self, other: int) -> str: ...
1531+
@overload
1532+
@abstractmethod
1533+
def some(self, other: str) -> int: ...
1534+
""",
1535+
runtime="""
1536+
class A4:
1537+
@abstractmethod
1538+
def some(self, other) -> None: ...
1539+
""",
1540+
error=None,
1541+
)
1542+
yield Case(
1543+
stub="""
1544+
class A5:
1545+
@abstractmethod
1546+
@overload
1547+
def some(self, other: int) -> str: ...
1548+
@abstractmethod
1549+
@overload
1550+
def some(self, other: str) -> int: ...
1551+
""",
1552+
runtime="""
1553+
class A5:
1554+
@abstractmethod
1555+
def some(self, other) -> None: ...
1556+
""",
1557+
error=None,
1558+
)
1559+
# Runtime can miss `@abstractmethod`:
1560+
yield Case(
1561+
stub="""
1562+
class A6:
15111563
@abstractmethod
15121564
def some(self) -> None: ...
15131565
""",
15141566
runtime="""
1515-
class A3:
1567+
class A6:
15161568
def some(self) -> None: ...
15171569
""",
15181570
error=None,

0 commit comments

Comments
 (0)