Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-104504: cases generator: Add --warn-unreachable to the mypy config #108112

Merged
merged 9 commits into from
Aug 20, 2023
15 changes: 15 additions & 0 deletions Tools/cases_generator/_typing_backports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Backports from newer versions of the typing module.

We backport these features here so that Python can still build
while using an older Python version for PYTHON_FOR_REGEN.
"""

from typing import NoReturn


def assert_never(obj: NoReturn) -> NoReturn:
"""Statically assert that a line of code is unreachable.

Backport of typing.assert_never (introduced in Python 3.11).
"""
raise AssertionError(f"Expected code to be unreachable, but got: {obj}")
7 changes: 4 additions & 3 deletions Tools/cases_generator/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sys
import typing

from _typing_backports import assert_never
from flags import InstructionFlags, variable_used
from formatting import prettify_filename, UNUSED
from instructions import (
Expand Down Expand Up @@ -172,7 +173,7 @@ def parse_file(self, filename: str, instrs_idx: dict[str, int]) -> None:
self.pseudos[name] = thing
self.everything.append(thing)
case _:
typing.assert_never(thing)
assert_never(thing)
if not psr.eof():
raise psr.make_syntax_error(f"Extra stuff at the end of {filename}")

Expand Down Expand Up @@ -368,7 +369,7 @@ def analyze_macro(self, macro: parsing.Macro) -> MacroInstruction:
# SAVE_IP in a macro is a no-op in Tier 1
flags.add(instr.instr_flags)
case _:
typing.assert_never(component)
assert_never(component)
format = "IB" if flags.HAS_ARG_FLAG else "IX"
if offset:
format += "C" + "0" * (offset - 1)
Expand Down Expand Up @@ -409,5 +410,5 @@ def check_macro_components(
case parsing.CacheEffect():
components.append(uop)
case _:
typing.assert_never(uop)
assert_never(uop)
return components
23 changes: 11 additions & 12 deletions Tools/cases_generator/generate_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from collections.abc import Iterator

import stacking # Early import to avoid circular import
from _typing_backports import assert_never
from analysis import Analyzer
from formatting import Formatter, list_effect_size
from flags import InstructionFlags, variable_used
Expand Down Expand Up @@ -154,8 +155,8 @@ def effect_str(effects: list[StackEffect]) -> str:
return str(n_effect)

instr: AnyInstruction | None
popped: str | None
pushed: str | None
popped: str | None = None
pushed: str | None = None
match thing:
case parsing.InstDef():
if thing.kind != "op" or self.instrs[thing.name].is_viable_uop():
Expand All @@ -171,7 +172,6 @@ def effect_str(effects: list[StackEffect]) -> str:
popped, pushed = stacking.get_stack_effect_info_for_macro(instr)
case parsing.Pseudo():
instr = self.pseudo_instrs[thing.name]
popped = pushed = None
AlexWaygood marked this conversation as resolved.
Show resolved Hide resolved
# Calculate stack effect, and check that it's the the same
# for all targets.
for target in self.pseudos[thing.name].targets:
Expand All @@ -189,7 +189,7 @@ def effect_str(effects: list[StackEffect]) -> str:
assert popped == target_popped
assert pushed == target_pushed
case _:
typing.assert_never(thing)
assert_never(thing)
return instr, popped, pushed

@contextlib.contextmanager
Expand Down Expand Up @@ -379,7 +379,7 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No
# Compute the set of all instruction formats.
all_formats: set[str] = set()
for thing in self.everything:
format: str | None
format: str | None = None
match thing:
case OverriddenInstructionPlaceHolder():
continue
Expand All @@ -388,7 +388,6 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No
case parsing.Macro():
format = self.macro_instrs[thing.name].instr_fmt
case parsing.Pseudo():
format = None
Copy link
Member Author

Choose a reason for hiding this comment

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

Similarly, here mypy was eagerly narrowing the type of format to None, meaning that it was inferring the else branch on line 396 to be unreachable.

Copy link
Member

Choose a reason for hiding this comment

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

Still feels like a mypy bug to me, but your fix is fine.

for target in self.pseudos[thing.name].targets:
target_instr = self.instrs.get(target)
assert target_instr
Expand All @@ -398,7 +397,7 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No
assert format == target_instr.instr_fmt
assert format is not None
case _:
typing.assert_never(thing)
assert_never(thing)
all_formats.add(format)

# Turn it into a sorted list of enum values.
Expand Down Expand Up @@ -488,7 +487,7 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No
self.pseudo_instrs[thing.name]
)
case _:
typing.assert_never(thing)
assert_never(thing)

with self.metadata_item(
"const struct opcode_macro_expansion "
Expand Down Expand Up @@ -525,7 +524,7 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No
case parsing.Pseudo():
pass
case _:
typing.assert_never(thing)
assert_never(thing)

with self.metadata_item(
"const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE]", "=", ";"
Expand Down Expand Up @@ -774,7 +773,7 @@ def write_instructions(
case parsing.Pseudo():
pass
case _:
typing.assert_never(thing)
assert_never(thing)

print(
f"Wrote {n_instrs} instructions and {n_macros} macros "
Expand Down Expand Up @@ -818,7 +817,7 @@ def write_executor_instructions(
case parsing.Pseudo():
pass
case _:
typing.assert_never(thing)
assert_never(thing)
print(
f"Wrote {n_instrs} instructions and {n_uops} ops to {executor_filename}",
file=sys.stderr,
Expand Down Expand Up @@ -850,7 +849,7 @@ def write_abstract_interpreter_instructions(
case parsing.Pseudo():
pass
case _:
typing.assert_never(thing)
assert_never(thing)
print(
f"Wrote some stuff to {abstract_interpreter_filename}",
file=sys.stderr,
Expand Down
9 changes: 4 additions & 5 deletions Tools/cases_generator/mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
files = Tools/cases_generator/
pretty = True

# Make sure Python can still be built
# using Python 3.10 for `PYTHON_FOR_REGEN`...
python_version = 3.10

# Be strict:
# ...And be strict:
strict = True
strict_concatenate = True
enable_error_code = ignore-without-code,redundant-expr,truthy-bool

# Don't enable this one yet;
# it has a lot of false positives on `cases_generator`
warn_unreachable = False
warn_unreachable = True