Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-133403: Check Tools/build/deepfreeze.py with mypy
  • Loading branch information
sobolevn committed May 10, 2025
commit 3e5890f4618708a5aeee453f50a5afb218bfcd28
2 changes: 2 additions & 0 deletions .github/workflows/mypy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ on:
- "Lib/tomllib/**"
- "Misc/mypy/**"
- "Tools/build/compute-changes.py"
- "Tools/build/deepfreeze.py"
- "Tools/build/generate_sbom.py"
- "Tools/build/generate-build-details.py"
- "Tools/build/verify_ensurepip_wheels.py"
- "Tools/build/update_file.py"
- "Tools/build/umarshal.py"
- "Tools/cases_generator/**"
- "Tools/clinic/**"
- "Tools/jit/**"
Expand Down
2 changes: 1 addition & 1 deletion Tools/build/.ruff.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
extend = "../../.ruff.toml" # Inherit the project-wide settings

[per-file-target-version]
"deepfreeze.py" = "py310"
"deepfreeze.py" = "py311" # requires `code.co_exceptiontable`
"stable_abi.py" = "py311" # requires 'tomllib'

[format]
Expand Down
51 changes: 33 additions & 18 deletions Tools/build/deepfreeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

The script may be executed by _bootstrap_python interpreter.
Shared library extension modules are not available in that case.
On Windows, and in cross-compilation cases, it is executed
by Python 3.10, and 3.11 features are not available.
Requires 3.11+ to be executed,
because relies on `code.co_qualname` and `code.co_exceptiontable`.
"""

from __future__ import annotations

import argparse
import builtins
import collections
Expand All @@ -13,9 +16,13 @@
import re
import time
import types
from typing import TextIO

TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Iterator
from typing import Any, TextIO

import umarshal

Check failure on line 25 in Tools/build/deepfreeze.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (E402)

Tools/build/deepfreeze.py:25:1: E402 Module level import not at top of file

ROOT = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))

Expand Down Expand Up @@ -45,8 +52,8 @@

next_code_version = 1

def get_localsplus(code: types.CodeType):
a = collections.defaultdict(int)
def get_localsplus(code: types.CodeType) -> tuple[tuple[str, ...], bytes]:
a: collections.defaultdict[str, int] = collections.defaultdict(int)
for name in code.co_varnames:
a[name] |= CO_FAST_LOCAL
for name in code.co_cellvars:
Expand Down Expand Up @@ -136,7 +143,7 @@
return identifiers, strings

@contextlib.contextmanager
def indent(self) -> None:
def indent(self) -> Iterator[None]:
save_level = self.level
try:
self.level += 1
Expand All @@ -148,7 +155,7 @@
self.file.writelines((" "*self.level, arg, "\n"))

@contextlib.contextmanager
def block(self, prefix: str, suffix: str = "") -> None:
def block(self, prefix: str, suffix: str = "") -> Iterator[None]:
self.write(prefix + " {")
with self.indent():
yield
Expand Down Expand Up @@ -250,9 +257,17 @@
co_names = self.generate(name + "_names", code.co_names)
co_filename = self.generate(name + "_filename", code.co_filename)
co_name = self.generate(name + "_name", code.co_name)
co_qualname = self.generate(name + "_qualname", code.co_qualname)
co_linetable = self.generate(name + "_linetable", code.co_linetable)
co_exceptiontable = self.generate(name + "_exceptiontable", code.co_exceptiontable)
# We use 3.10 for type checking, but this module requires 3.11
# TODO: bump python version for this script.
co_qualname = self.generate(
name + "_qualname",
code.co_qualname, # type: ignore[attr-defined]
)
co_exceptiontable = self.generate(
name + "_exceptiontable",
code.co_exceptiontable, # type: ignore[attr-defined]
)
# These fields are not directly accessible
localsplusnames, localspluskinds = get_localsplus(code)
co_localsplusnames = self.generate(name + "_localsplusnames", localsplusnames)
Expand Down Expand Up @@ -379,13 +394,13 @@
self.write(f".cval = {{ {z.real}, {z.imag} }},")
return f"&{name}.ob_base"

def generate_frozenset(self, name: str, fs: frozenset[object]) -> str:
def generate_frozenset(self, name: str, fs: frozenset[Any]) -> str:
try:
fs = sorted(fs)
fs_sorted = sorted(fs)
except TypeError:
# frozen set with incompatible types, fallback to repr()
fs = sorted(fs, key=repr)
ret = self.generate_tuple(name, tuple(fs))
fs_sorted = sorted(fs, key=repr)
ret = self.generate_tuple(name, tuple(fs_sorted))
self.write("// TODO: The above tuple should be a frozenset")
return ret

Expand All @@ -402,7 +417,7 @@
# print(f"Cache hit {key!r:.40}: {self.cache[key]!r:.40}")
return self.cache[key]
self.misses += 1
if isinstance(obj, (types.CodeType, umarshal.Code)) :
if isinstance(obj, types.CodeType) :
Copy link
Copy Markdown
Member Author

@sobolevn sobolevn May 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here umarshal.Code won't work, because generate_code requires a lot more attrs than umarshal.Code has

val = self.generate_code(name, obj)
elif isinstance(obj, tuple):
val = self.generate_tuple(name, obj)
Expand Down Expand Up @@ -458,7 +473,7 @@
if re.match(FROZEN_DATA_LINE, line):
values.extend([int(x) for x in line.split(",") if x.strip()])
data = bytes(values)
return umarshal.loads(data)
return umarshal.loads(data) # type: ignore[no-any-return]


def generate(args: list[str], output: TextIO) -> None:
Expand Down Expand Up @@ -494,12 +509,12 @@
help="Input file and module name (required) in file:modname format")

@contextlib.contextmanager
def report_time(label: str):
t0 = time.time()
def report_time(label: str) -> Iterator[None]:
t0 = time.perf_counter()
try:
yield
finally:
t1 = time.time()
t1 = time.perf_counter()
if verbose:
print(f"{label}: {t1-t0:.3f} sec")

Expand Down
4 changes: 3 additions & 1 deletion Tools/build/mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
# .github/workflows/mypy.yml
files =
Tools/build/compute-changes.py,
Tools/build/deepfreeze.py,
Tools/build/generate-build-details.py,
Tools/build/generate_sbom.py,
Tools/build/verify_ensurepip_wheels.py,
Tools/build/update_file.py
Tools/build/update_file.py,
Tools/build/umarshal.py

pretty = True

Expand Down
11 changes: 6 additions & 5 deletions Tools/build/umarshal.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ def r_PyLong(self) -> int:
def r_float_bin(self) -> float:
buf = self.r_string(8)
import struct # Lazy import to avoid breaking UNIX build
return struct.unpack("d", buf)[0]
return struct.unpack("d", buf)[0] # type: ignore[no-any-return]

def r_float_str(self) -> float:
n = self.r_byte()
buf = self.r_string(n)
return ast.literal_eval(buf.decode("ascii"))
return ast.literal_eval(buf.decode("ascii")) # type: ignore[no-any-return]

def r_ref_reserve(self, flag: int) -> int:
if flag:
Expand Down Expand Up @@ -306,16 +306,17 @@ def loads(data: bytes) -> Any:
return r.r_object()


def main():
def main() -> None:
# Test
import marshal
import pprint
sample = {'foo': {(42, "bar", 3.14)}}
data = marshal.dumps(sample)
retval = loads(data)
assert retval == sample, retval
sample = main.__code__
data = marshal.dumps(sample)

sample2 = main.__code__
data = marshal.dumps(sample2)
retval = loads(data)
assert isinstance(retval, Code), retval
pprint.pprint(retval.__dict__)
Expand Down
Loading