| from __future__ import annotations | |
| import unittest | |
| from mypyc.common import ( | |
| ENV_ATTR_NAME, | |
| GENERATOR_ATTRIBUTE_PREFIX, | |
| NEXT_LABEL_ATTR_NAME, | |
| TEMP_ATTR_NAME, | |
| generator_frame_attribute_prefix, | |
| ) | |
| from mypyc.ir.rtypes import RInstance | |
| from mypyc.test.testutil import build_ir_for_single_file2 | |
| from mypyc.transform.spill import insert_spills | |
| class TestSpill(unittest.TestCase): | |
| def test_separate_generator_environment_keeps_spills_on_frame(self) -> None: | |
| # A nested generator needs a separate environment. Since make() is | |
| # evaluated before the yield, its result must be spilled across the | |
| # suspension point by the post-IRBuild spill pass. | |
| source = """\ | |
| def make() -> str: | |
| return "left" | |
| def outer(): | |
| def nested(value: str): | |
| result = "" | |
| for item in [value]: | |
| # The loop iterator is promoted from a register during IR building, | |
| # while the result of make() is spilled by the later transform. | |
| result += make() + (yield item) | |
| return result | |
| return nested("right") | |
| """ | |
| module, _, _, _ = build_ir_for_single_file2(source.splitlines()) | |
| frame = next(cl for cl in module.classes if cl.has_running_flag) | |
| assert frame.env_user_function is not None | |
| insert_spills(frame.env_user_function, frame) | |
| env_type = frame.attributes[ENV_ATTR_NAME] | |
| assert isinstance(env_type, RInstance) | |
| environment = env_type.class_ir | |
| # Private generator resume state lives on the generator frame, which | |
| # is protected by the running flag and can use plain attribute access. | |
| assert frame.attrs_are_thread_confined() | |
| assert NEXT_LABEL_ATTR_NAME in frame.attributes | |
| assert any(name.startswith(TEMP_ATTR_NAME + "2_") for name in frame.attributes) | |
| assert any(name.startswith(TEMP_ATTR_NAME + "3_") for name in frame.attributes) | |
| # Noncaptured source-level variables also stay on the private frame. | |
| frame_prefix = generator_frame_attribute_prefix( | |
| frame.fullname, is_final_class=frame.is_final_class | |
| ) | |
| assert frame_prefix + "value" in frame.attributes | |
| assert GENERATOR_ATTRIBUTE_PREFIX + "value" not in environment.attributes | |
| def test_generator_locals_use_private_frame_unless_captured(self) -> None: | |
| source = """\ | |
| def outer(): | |
| def nested(captured: str, private: str): | |
| def callback() -> str: | |
| return captured | |
| private_local = private | |
| yield callback() | |
| yield private_local | |
| return nested("captured", "private") | |
| """ | |
| module, _, _, _ = build_ir_for_single_file2(source.splitlines()) | |
| frame = next(cl for cl in module.classes if cl.has_running_flag) | |
| env_type = frame.attributes[ENV_ATTR_NAME] | |
| assert isinstance(env_type, RInstance) | |
| environment = env_type.class_ir | |
| frame_attrs = set(frame.attributes) | |
| environment_attrs = set(environment.attributes) | |
| assert frame.attrs_are_thread_confined() | |
| assert not environment.attrs_are_thread_confined() | |
| frame_prefix = generator_frame_attribute_prefix( | |
| frame.fullname, is_final_class=frame.is_final_class | |
| ) | |
| assert GENERATOR_ATTRIBUTE_PREFIX + "captured" in environment_attrs | |
| assert frame_prefix + "captured" not in frame_attrs | |
| for name in ("private", "private_local", "callback"): | |
| assert frame_prefix + name in frame_attrs | |
| assert GENERATOR_ATTRIBUTE_PREFIX + name not in environment_attrs |