Skip to content

Commit 13fa6c3

Browse files
authored
stubtest: get better signatures for __init__ of C classes (#18259)
When an `__init__` method has the generic C-class signature, check the underlying class for a better signature. I was looking at `asyncio.futures.Future.__init__` and wanted to take advantage of the better `__text_signature__` on `asyncio.futures.Future`. The upside is that this clears several allowlist entries and surfaced several other cases where typeshed is currently incorrect, with no false positives.
1 parent 30a5263 commit 13fa6c3

File tree

1 file changed

+68
-0
lines changed

1 file changed

+68
-0
lines changed

mypy/stubtest.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,7 @@ def from_overloadedfuncdef(stub: nodes.OverloadedFuncDef) -> Signature[nodes.Arg
938938
or arg.pos_only
939939
or assume_positional_only
940940
or arg.variable.name.strip("_") == "self"
941+
or (index == 0 and arg.variable.name.strip("_") == "cls")
941942
else arg.variable.name
942943
)
943944
all_args.setdefault(name, []).append((arg, index))
@@ -1008,6 +1009,7 @@ def _verify_signature(
10081009
and not stub_arg.pos_only
10091010
and not stub_arg.variable.name.startswith("__")
10101011
and stub_arg.variable.name.strip("_") != "self"
1012+
and stub_arg.variable.name.strip("_") != "cls"
10111013
and not is_dunder(function_name, exclude_special=True) # noisy for dunder methods
10121014
):
10131015
yield (
@@ -1019,6 +1021,7 @@ def _verify_signature(
10191021
and (stub_arg.pos_only or stub_arg.variable.name.startswith("__"))
10201022
and not runtime_arg.name.startswith("__")
10211023
and stub_arg.variable.name.strip("_") != "self"
1024+
and stub_arg.variable.name.strip("_") != "cls"
10221025
and not is_dunder(function_name, exclude_special=True) # noisy for dunder methods
10231026
):
10241027
yield (
@@ -1662,6 +1665,71 @@ def is_read_only_property(runtime: object) -> bool:
16621665

16631666

16641667
def safe_inspect_signature(runtime: Any) -> inspect.Signature | None:
1668+
if (
1669+
hasattr(runtime, "__name__")
1670+
and runtime.__name__ == "__init__"
1671+
and hasattr(runtime, "__text_signature__")
1672+
and runtime.__text_signature__ == "($self, /, *args, **kwargs)"
1673+
and hasattr(runtime, "__objclass__")
1674+
and hasattr(runtime.__objclass__, "__text_signature__")
1675+
and runtime.__objclass__.__text_signature__ is not None
1676+
):
1677+
# This is an __init__ method with the generic C-class signature.
1678+
# In this case, the underlying class often has a better signature,
1679+
# which we can convert into an __init__ signature by adding in the
1680+
# self parameter.
1681+
try:
1682+
s = inspect.signature(runtime.__objclass__)
1683+
1684+
parameter_kind: inspect._ParameterKind = inspect.Parameter.POSITIONAL_OR_KEYWORD
1685+
if s.parameters:
1686+
first_parameter = next(iter(s.parameters.values()))
1687+
if first_parameter.kind == inspect.Parameter.POSITIONAL_ONLY:
1688+
parameter_kind = inspect.Parameter.POSITIONAL_ONLY
1689+
return s.replace(
1690+
parameters=[inspect.Parameter("self", parameter_kind), *s.parameters.values()]
1691+
)
1692+
except Exception:
1693+
pass
1694+
1695+
if (
1696+
hasattr(runtime, "__name__")
1697+
and runtime.__name__ == "__new__"
1698+
and hasattr(runtime, "__text_signature__")
1699+
and runtime.__text_signature__ == "($type, *args, **kwargs)"
1700+
and hasattr(runtime, "__self__")
1701+
and hasattr(runtime.__self__, "__text_signature__")
1702+
and runtime.__self__.__text_signature__ is not None
1703+
):
1704+
# This is a __new__ method with the generic C-class signature.
1705+
# In this case, the underlying class often has a better signature,
1706+
# which we can convert into a __new__ signature by adding in the
1707+
# cls parameter.
1708+
1709+
# If the attached class has a valid __init__, skip recovering a
1710+
# signature for this __new__ method.
1711+
has_init = False
1712+
if (
1713+
hasattr(runtime.__self__, "__init__")
1714+
and hasattr(runtime.__self__.__init__, "__objclass__")
1715+
and runtime.__self__.__init__.__objclass__ is runtime.__self__
1716+
):
1717+
has_init = True
1718+
1719+
if not has_init:
1720+
try:
1721+
s = inspect.signature(runtime.__self__)
1722+
parameter_kind = inspect.Parameter.POSITIONAL_OR_KEYWORD
1723+
if s.parameters:
1724+
first_parameter = next(iter(s.parameters.values()))
1725+
if first_parameter.kind == inspect.Parameter.POSITIONAL_ONLY:
1726+
parameter_kind = inspect.Parameter.POSITIONAL_ONLY
1727+
return s.replace(
1728+
parameters=[inspect.Parameter("cls", parameter_kind), *s.parameters.values()]
1729+
)
1730+
except Exception:
1731+
pass
1732+
16651733
try:
16661734
try:
16671735
return inspect.signature(runtime)

0 commit comments

Comments
 (0)