Skip to content

Commit 893d428

Browse files
committed
[stubgen] Introduce an object-oriented design for C extension stub generation
1 parent 54bc37c commit 893d428

File tree

12 files changed

+1974
-1398
lines changed

12 files changed

+1974
-1398
lines changed

docs/source/stubgen.rst

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,22 @@ alter the default behavior:
127127
unwanted side effects, such as the running of tests. Stubgen tries to skip test
128128
modules even without this option, but this does not always work.
129129

130-
.. option:: --parse-only
130+
.. option:: --no-analysis
131131

132132
Don't perform semantic analysis of source files. This may generate
133133
worse stubs -- in particular, some module, class, and function aliases may
134134
be represented as variables with the ``Any`` type. This is generally only
135-
useful if semantic analysis causes a critical mypy error.
135+
useful if semantic analysis causes a critical mypy error. Does not apply to
136+
C extension modules. Incompatible with :option:`--inspect-mode`.
137+
138+
.. option:: --inspect-mode
139+
140+
Import and inspect modules instead of parsing source code. This is the default
141+
behavior for c modules and pyc-only packages. The flag is useful to force
142+
inspection for pure python modules that make use of dynamically generated
143+
members that would otherwiswe be omitted when using the default behavior of
144+
code parsing. Implies :option:`--no-analysis` as analysis requires source
145+
code.
136146

137147
.. option:: --doc-dir PATH
138148

mypy/moduleinspect.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ def is_c_module(module: ModuleType) -> bool:
3939
return os.path.splitext(module.__dict__["__file__"])[-1] in [".so", ".pyd", ".dll"]
4040

4141

42+
def is_pyc_only(file: str | None) -> bool:
43+
return bool(file and file.endswith(".pyc") and not os.path.exists(file[:-1]))
44+
45+
4246
class InspectError(Exception):
4347
pass
4448

mypy/stubdoc.py

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import contextlib
1010
import io
11+
import keyword
1112
import re
1213
import tokenize
1314
from typing import Any, Final, MutableMapping, MutableSequence, NamedTuple, Sequence, Tuple
@@ -35,12 +36,16 @@ class ArgSig:
3536

3637
def __init__(self, name: str, type: str | None = None, default: bool = False):
3738
self.name = name
38-
if type and not is_valid_type(type):
39-
raise ValueError("Invalid type: " + type)
4039
self.type = type
4140
# Does this argument have a default value?
4241
self.default = default
4342

43+
def is_star_arg(self) -> bool:
44+
return self.name.startswith("*") and not self.name.startswith("**")
45+
46+
def is_star_kwarg(self) -> bool:
47+
return self.name.startswith("**")
48+
4449
def __repr__(self) -> str:
4550
return "ArgSig(name={}, type={}, default={})".format(
4651
repr(self.name), repr(self.type), repr(self.default)
@@ -59,7 +64,68 @@ def __eq__(self, other: Any) -> bool:
5964
class FunctionSig(NamedTuple):
6065
name: str
6166
args: list[ArgSig]
62-
ret_type: str
67+
ret_type: str | None
68+
69+
def is_special_method(self) -> bool:
70+
return bool(
71+
self.name.startswith("__")
72+
and self.name.endswith("__")
73+
and self.args
74+
and self.args[0].name in ("self", "cls")
75+
)
76+
77+
def has_catchall_args(self) -> bool:
78+
"""Return if this signature has catchall args: (*args, **kwargs)"""
79+
if self.args and self.args[0].name in ("self", "cls"):
80+
args = self.args[1:]
81+
else:
82+
args = self.args
83+
return (
84+
len(args) == 2
85+
and all(a.type in (None, "Any", "typing.Any") for a in args)
86+
and args[0].is_star_arg()
87+
and args[1].is_star_kwarg()
88+
)
89+
90+
def is_identity(self) -> bool:
91+
"""Return if this signature is the catchall identity: (*args, **kwargs) -> Any"""
92+
return self.has_catchall_args() and self.ret_type in (None, "Any", "typing.Any")
93+
94+
def format_sig(self, any_val: str | None = None, suffix: str = ": ...") -> str:
95+
args: list[str] = []
96+
for arg in self.args:
97+
arg_def = arg.name
98+
99+
if arg_def in keyword.kwlist:
100+
arg_def = "_" + arg_def
101+
102+
if (
103+
arg.type is None
104+
and any_val is not None
105+
and arg.name not in ("self", "cls")
106+
and not arg.name.startswith("*")
107+
):
108+
arg_type: str | None = any_val
109+
else:
110+
arg_type = arg.type
111+
if arg_type:
112+
arg_def += ": " + arg_type
113+
if arg.default:
114+
arg_def += " = ..."
115+
116+
elif arg.default:
117+
arg_def += "=..."
118+
119+
args.append(arg_def)
120+
121+
retfield = ""
122+
ret_type = self.ret_type if self.ret_type else any_val
123+
if ret_type is not None:
124+
retfield = " -> " + ret_type
125+
126+
return "def {name}({args}){ret}{suffix}".format(
127+
name=self.name, args=", ".join(args), ret=retfield, suffix=suffix
128+
)
63129

64130

65131
# States of the docstring parser.
@@ -176,17 +242,17 @@ def add_token(self, token: tokenize.TokenInfo) -> None:
176242

177243
# arg_name is empty when there are no args. e.g. func()
178244
if self.arg_name:
179-
try:
245+
if self.arg_type and not is_valid_type(self.arg_type):
246+
# wrong type, use Any
247+
self.args.append(
248+
ArgSig(name=self.arg_name, type=None, default=bool(self.arg_default))
249+
)
250+
else:
180251
self.args.append(
181252
ArgSig(
182253
name=self.arg_name, type=self.arg_type, default=bool(self.arg_default)
183254
)
184255
)
185-
except ValueError:
186-
# wrong type, use Any
187-
self.args.append(
188-
ArgSig(name=self.arg_name, type=None, default=bool(self.arg_default))
189-
)
190256
self.arg_name = ""
191257
self.arg_type = None
192258
self.arg_default = None
@@ -240,7 +306,7 @@ def args_kwargs(signature: FunctionSig) -> bool:
240306

241307

242308
def infer_sig_from_docstring(docstr: str | None, name: str) -> list[FunctionSig] | None:
243-
"""Convert function signature to list of TypedFunctionSig
309+
"""Convert function signature to list of FunctionSig
244310
245311
Look for function signatures of function in docstring. Signature is a string of
246312
the format <function_name>(<signature>) -> <return type> or perhaps without

0 commit comments

Comments
 (0)