nonlocalcontrol.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. """Helpers for dealing with nonlocal control such as 'break' and 'return'.
  2. Model how these behave differently in different contexts.
  3. """
  4. from __future__ import annotations
  5. from abc import abstractmethod
  6. from typing import TYPE_CHECKING
  7. from mypyc.ir.ops import (
  8. NO_TRACEBACK_LINE_NO,
  9. BasicBlock,
  10. Branch,
  11. Goto,
  12. Integer,
  13. Register,
  14. Return,
  15. Unreachable,
  16. Value,
  17. )
  18. from mypyc.irbuild.targets import AssignmentTarget
  19. from mypyc.primitives.exc_ops import restore_exc_info_op, set_stop_iteration_value
  20. if TYPE_CHECKING:
  21. from mypyc.irbuild.builder import IRBuilder
  22. class NonlocalControl:
  23. """ABC representing a stack frame of constructs that modify nonlocal control flow.
  24. The nonlocal control flow constructs are break, continue, and
  25. return, and their behavior is modified by a number of other
  26. constructs. The most obvious is loop, which override where break
  27. and continue jump to, but also `except` (which needs to clear
  28. exc_info when left) and (eventually) finally blocks (which need to
  29. ensure that the finally block is always executed when leaving the
  30. try/except blocks).
  31. """
  32. @abstractmethod
  33. def gen_break(self, builder: IRBuilder, line: int) -> None:
  34. pass
  35. @abstractmethod
  36. def gen_continue(self, builder: IRBuilder, line: int) -> None:
  37. pass
  38. @abstractmethod
  39. def gen_return(self, builder: IRBuilder, value: Value, line: int) -> None:
  40. pass
  41. class BaseNonlocalControl(NonlocalControl):
  42. """Default nonlocal control outside any statements that affect it."""
  43. def gen_break(self, builder: IRBuilder, line: int) -> None:
  44. assert False, "break outside of loop"
  45. def gen_continue(self, builder: IRBuilder, line: int) -> None:
  46. assert False, "continue outside of loop"
  47. def gen_return(self, builder: IRBuilder, value: Value, line: int) -> None:
  48. builder.add(Return(value))
  49. class LoopNonlocalControl(NonlocalControl):
  50. """Nonlocal control within a loop."""
  51. def __init__(
  52. self, outer: NonlocalControl, continue_block: BasicBlock, break_block: BasicBlock
  53. ) -> None:
  54. self.outer = outer
  55. self.continue_block = continue_block
  56. self.break_block = break_block
  57. def gen_break(self, builder: IRBuilder, line: int) -> None:
  58. builder.add(Goto(self.break_block))
  59. def gen_continue(self, builder: IRBuilder, line: int) -> None:
  60. builder.add(Goto(self.continue_block))
  61. def gen_return(self, builder: IRBuilder, value: Value, line: int) -> None:
  62. self.outer.gen_return(builder, value, line)
  63. class GeneratorNonlocalControl(BaseNonlocalControl):
  64. """Default nonlocal control in a generator function outside statements."""
  65. def gen_return(self, builder: IRBuilder, value: Value, line: int) -> None:
  66. # Assign an invalid next label number so that the next time
  67. # __next__ is called, we jump to the case in which
  68. # StopIteration is raised.
  69. builder.assign(builder.fn_info.generator_class.next_label_target, Integer(-1), line)
  70. # Raise a StopIteration containing a field for the value that
  71. # should be returned. Before doing so, create a new block
  72. # without an error handler set so that the implicitly thrown
  73. # StopIteration isn't caught by except blocks inside of the
  74. # generator function.
  75. builder.builder.push_error_handler(None)
  76. builder.goto_and_activate(BasicBlock())
  77. # Skip creating a traceback frame when we raise here, because
  78. # we don't care about the traceback frame and it is kind of
  79. # expensive since raising StopIteration is an extremely common
  80. # case. Also we call a special internal function to set
  81. # StopIteration instead of using RaiseStandardError because
  82. # the obvious thing doesn't work if the value is a tuple
  83. # (???).
  84. builder.call_c(set_stop_iteration_value, [value], NO_TRACEBACK_LINE_NO)
  85. builder.add(Unreachable())
  86. builder.builder.pop_error_handler()
  87. class CleanupNonlocalControl(NonlocalControl):
  88. """Abstract nonlocal control that runs some cleanup code."""
  89. def __init__(self, outer: NonlocalControl) -> None:
  90. self.outer = outer
  91. @abstractmethod
  92. def gen_cleanup(self, builder: IRBuilder, line: int) -> None:
  93. ...
  94. def gen_break(self, builder: IRBuilder, line: int) -> None:
  95. self.gen_cleanup(builder, line)
  96. self.outer.gen_break(builder, line)
  97. def gen_continue(self, builder: IRBuilder, line: int) -> None:
  98. self.gen_cleanup(builder, line)
  99. self.outer.gen_continue(builder, line)
  100. def gen_return(self, builder: IRBuilder, value: Value, line: int) -> None:
  101. self.gen_cleanup(builder, line)
  102. self.outer.gen_return(builder, value, line)
  103. class TryFinallyNonlocalControl(NonlocalControl):
  104. """Nonlocal control within try/finally."""
  105. def __init__(self, target: BasicBlock) -> None:
  106. self.target = target
  107. self.ret_reg: None | Register | AssignmentTarget = None
  108. def gen_break(self, builder: IRBuilder, line: int) -> None:
  109. builder.error("break inside try/finally block is unimplemented", line)
  110. def gen_continue(self, builder: IRBuilder, line: int) -> None:
  111. builder.error("continue inside try/finally block is unimplemented", line)
  112. def gen_return(self, builder: IRBuilder, value: Value, line: int) -> None:
  113. if self.ret_reg is None:
  114. if builder.fn_info.is_generator:
  115. self.ret_reg = builder.make_spill_target(builder.ret_types[-1])
  116. else:
  117. self.ret_reg = Register(builder.ret_types[-1])
  118. # assert needed because of apparent mypy bug... it loses track of the union
  119. # and infers the type as object
  120. assert isinstance(self.ret_reg, (Register, AssignmentTarget))
  121. builder.assign(self.ret_reg, value, line)
  122. builder.add(Goto(self.target))
  123. class ExceptNonlocalControl(CleanupNonlocalControl):
  124. """Nonlocal control for except blocks.
  125. Just makes sure that sys.exc_info always gets restored when we leave.
  126. This is super annoying.
  127. """
  128. def __init__(self, outer: NonlocalControl, saved: Value | AssignmentTarget) -> None:
  129. super().__init__(outer)
  130. self.saved = saved
  131. def gen_cleanup(self, builder: IRBuilder, line: int) -> None:
  132. builder.call_c(restore_exc_info_op, [builder.read(self.saved)], line)
  133. class FinallyNonlocalControl(CleanupNonlocalControl):
  134. """Nonlocal control for finally blocks.
  135. Just makes sure that sys.exc_info always gets restored when we
  136. leave and the return register is decrefed if it isn't null.
  137. """
  138. def __init__(self, outer: NonlocalControl, saved: Value) -> None:
  139. super().__init__(outer)
  140. self.saved = saved
  141. def gen_cleanup(self, builder: IRBuilder, line: int) -> None:
  142. # Restore the old exc_info
  143. target, cleanup = BasicBlock(), BasicBlock()
  144. builder.add(Branch(self.saved, target, cleanup, Branch.IS_ERROR))
  145. builder.activate_block(cleanup)
  146. builder.call_c(restore_exc_info_op, [self.saved], line)
  147. builder.goto_and_activate(target)