Skip to content

Commit 94a3b2d

Browse files
committed
scripts: generate_rust_analyzer.py: add type hints
Python type hints allow static analysis tools like mypy to detect type errors during development, improving the developer experience. Python type hints have been present in the kernel since 2019 at the latest; see commit 6ebf586 ("kunit: tool: add Python wrappers for running KUnit tests"). Add a subclass of `argparse.Namespace` to get type checking on the CLI arguments. Run `mypy --strict scripts/generate_rust_analyzer.py --python-version 3.9` to verify. Note that `mypy` no longer supports python < 3.9. Tested-by: Daniel Almeida <daniel.almeida@collabora.com> Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com> Reviewed-by: Trevor Gross <tmgross@umich.edu> Reviewed-by: Jesung Yang <y.j3ms.n@gmail.com> Tested-by: Jesung Yang <y.j3ms.n@gmail.com> Link: https://patch.msgid.link/20260122-rust-analyzer-types-v1-3-29cc2e91dcd5@kernel.org Signed-off-by: Tamir Duberstein <tamird@kernel.org>
1 parent 4079cf0 commit 94a3b2d

1 file changed

Lines changed: 89 additions & 39 deletions

File tree

scripts/generate_rust_analyzer.py

Lines changed: 89 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,53 @@
1010
import pathlib
1111
import subprocess
1212
import sys
13+
from typing import Dict, Iterable, List, Literal, Optional, TypedDict
1314

14-
def args_crates_cfgs(cfgs):
15+
def args_crates_cfgs(cfgs: List[str]) -> Dict[str, List[str]]:
1516
crates_cfgs = {}
1617
for cfg in cfgs:
1718
crate, vals = cfg.split("=", 1)
1819
crates_cfgs[crate] = vals.split()
1920

2021
return crates_cfgs
2122

22-
def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs, core_edition):
23+
class Dependency(TypedDict):
24+
crate: int
25+
name: str
26+
27+
28+
class Source(TypedDict):
29+
include_dirs: List[str]
30+
exclude_dirs: List[str]
31+
32+
33+
class Crate(TypedDict):
34+
display_name: str
35+
root_module: str
36+
is_workspace_member: bool
37+
deps: List[Dependency]
38+
cfg: List[str]
39+
edition: str
40+
env: Dict[str, str]
41+
42+
43+
class ProcMacroCrate(Crate):
44+
is_proc_macro: Literal[True]
45+
proc_macro_dylib_path: str # `pathlib.Path` is not JSON serializable.
46+
47+
48+
class CrateWithGenerated(Crate):
49+
source: Source
50+
51+
52+
def generate_crates(
53+
srctree: pathlib.Path,
54+
objtree: pathlib.Path,
55+
sysroot_src: pathlib.Path,
56+
external_src: Optional[pathlib.Path],
57+
cfgs: List[str],
58+
core_edition: str,
59+
) -> List[Crate]:
2360
# Generate the configuration list.
2461
cfg = []
2562
with open(objtree / "include" / "generated" / "rustc_cfg") as fd:
@@ -31,19 +68,19 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs, core_edit
3168
# Now fill the crates list -- dependencies need to come first.
3269
#
3370
# Avoid O(n^2) iterations by keeping a map of indexes.
34-
crates = []
35-
crates_indexes = {}
71+
crates: List[Crate] = []
72+
crates_indexes: Dict[str, int] = {}
3673
crates_cfgs = args_crates_cfgs(cfgs)
3774

3875
def build_crate(
39-
display_name,
40-
root_module,
41-
deps,
76+
display_name: str,
77+
root_module: pathlib.Path,
78+
deps: List[str],
4279
*,
43-
cfg,
44-
is_workspace_member,
45-
edition,
46-
):
80+
cfg: Optional[List[str]],
81+
is_workspace_member: Optional[bool],
82+
edition: Optional[str],
83+
) -> Crate:
4784
cfg = cfg if cfg is not None else []
4885
is_workspace_member = (
4986
is_workspace_member if is_workspace_member is not None else True
@@ -62,14 +99,14 @@ def build_crate(
6299
}
63100

64101
def append_proc_macro_crate(
65-
display_name,
66-
root_module,
67-
deps,
102+
display_name: str,
103+
root_module: pathlib.Path,
104+
deps: List[str],
68105
*,
69-
cfg=None,
70-
is_workspace_member=None,
71-
edition=None,
72-
):
106+
cfg: Optional[List[str]] = None,
107+
is_workspace_member: Optional[bool] = None,
108+
edition: Optional[str] = None,
109+
) -> None:
73110
crate = build_crate(
74111
display_name,
75112
root_module,
@@ -95,26 +132,26 @@ def append_proc_macro_crate(
95132
.decode("utf-8")
96133
.strip()
97134
)
98-
proc_macro_crate = {
135+
proc_macro_crate: ProcMacroCrate = {
99136
**crate,
100137
"is_proc_macro": True,
101138
"proc_macro_dylib_path": str(objtree / "rust" / proc_macro_dylib_name),
102139
}
103140
return register_crate(proc_macro_crate)
104141

105-
def register_crate(crate):
142+
def register_crate(crate: Crate) -> None:
106143
crates_indexes[crate["display_name"]] = len(crates)
107144
crates.append(crate)
108145

109146
def append_crate(
110-
display_name,
111-
root_module,
112-
deps,
147+
display_name: str,
148+
root_module: pathlib.Path,
149+
deps: List[str],
113150
*,
114-
cfg=None,
115-
is_workspace_member=None,
116-
edition=None,
117-
):
151+
cfg: Optional[List[str]] = None,
152+
is_workspace_member: Optional[bool] = None,
153+
edition: Optional[str] = None,
154+
) -> None:
118155
return register_crate(
119156
build_crate(
120157
display_name,
@@ -127,11 +164,11 @@ def append_crate(
127164
)
128165

129166
def append_sysroot_crate(
130-
display_name,
131-
deps,
167+
display_name: str,
168+
deps: List[str],
132169
*,
133-
cfg=None,
134-
):
170+
cfg: Optional[List[str]] = None,
171+
) -> None:
135172
return append_crate(
136173
display_name,
137174
sysroot_src / display_name / "src" / "lib.rs",
@@ -234,9 +271,9 @@ def append_sysroot_crate(
234271
)
235272

236273
def append_crate_with_generated(
237-
display_name,
238-
deps,
239-
):
274+
display_name: str,
275+
deps: List[str],
276+
) -> None:
240277
crate = build_crate(
241278
display_name,
242279
srctree / "rust"/ display_name / "lib.rs",
@@ -246,7 +283,7 @@ def append_crate_with_generated(
246283
edition=None,
247284
)
248285
crate["env"]["OBJTREE"] = str(objtree.resolve(True))
249-
crate_with_generated = {
286+
crate_with_generated: CrateWithGenerated = {
250287
**crate,
251288
"source": {
252289
"include_dirs": [
@@ -262,7 +299,7 @@ def append_crate_with_generated(
262299
append_crate_with_generated("uapi", ["core", "ffi", "pin_init"])
263300
append_crate_with_generated("kernel", ["core", "macros", "build_error", "pin_init", "ffi", "bindings", "uapi"])
264301

265-
def is_root_crate(build_file, target):
302+
def is_root_crate(build_file: pathlib.Path, target: str) -> bool:
266303
try:
267304
return f"{target}.o" in open(build_file).read()
268305
except FileNotFoundError:
@@ -271,7 +308,9 @@ def is_root_crate(build_file, target):
271308
# Then, the rest outside of `rust/`.
272309
#
273310
# We explicitly mention the top-level folders we want to cover.
274-
extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers"))
311+
extra_dirs: Iterable[pathlib.Path] = (
312+
srctree / dir for dir in ("samples", "drivers")
313+
)
275314
if external_src is not None:
276315
extra_dirs = [external_src]
277316
for folder in extra_dirs:
@@ -294,7 +333,7 @@ def is_root_crate(build_file, target):
294333

295334
return crates
296335

297-
def main():
336+
def main() -> None:
298337
parser = argparse.ArgumentParser()
299338
parser.add_argument('--verbose', '-v', action='store_true')
300339
parser.add_argument('--cfgs', action='append', default=[])
@@ -304,7 +343,18 @@ def main():
304343
parser.add_argument("sysroot", type=pathlib.Path)
305344
parser.add_argument("sysroot_src", type=pathlib.Path)
306345
parser.add_argument("exttree", type=pathlib.Path, nargs="?")
307-
args = parser.parse_args()
346+
347+
class Args(argparse.Namespace):
348+
verbose: bool
349+
cfgs: List[str]
350+
srctree: pathlib.Path
351+
objtree: pathlib.Path
352+
sysroot: pathlib.Path
353+
sysroot_src: pathlib.Path
354+
exttree: Optional[pathlib.Path]
355+
core_edition: str
356+
357+
args = parser.parse_args(namespace=Args())
308358

309359
logging.basicConfig(
310360
format="[%(asctime)s] [%(levelname)s] %(message)s",

0 commit comments

Comments
 (0)