def transform_with(builder: IRBuilder, expr: Expression, target: Optional[Lvalue], body: GenFunc, line: int) -> None: # This is basically a straight transcription of the Python code in PEP 343. # I don't actually understand why a bunch of it is the way it is. # We could probably optimize the case where the manager is compiled by us, # but that is not our common case at all, so. mgr_v = builder.accept(expr) typ = builder.primitive_op(type_op, [mgr_v], line) exit_ = builder.maybe_spill(builder.py_get_attr(typ, '__exit__', line)) value = builder.py_call( builder.py_get_attr(typ, '__enter__', line), [mgr_v], line ) mgr = builder.maybe_spill(mgr_v) exc = builder.maybe_spill_assignable(builder.primitive_op(true_op, [], -1)) def try_body() -> None: if target: builder.assign(builder.get_assignment_target(target), value, line) body() def except_body() -> None: builder.assign(exc, builder.primitive_op(false_op, [], -1), line) out_block, reraise_block = BasicBlock(), BasicBlock() builder.add_bool_branch( builder.py_call(builder.read(exit_), [builder.read(mgr)] + get_sys_exc_info(builder), line), out_block, reraise_block ) builder.activate_block(reraise_block) builder.primitive_op(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.activate_block(out_block) def finally_body() -> None: out_block, exit_block = BasicBlock(), BasicBlock() builder.add( Branch(builder.read(exc), exit_block, out_block, Branch.BOOL_EXPR) ) builder.activate_block(exit_block) none = builder.none_object() builder.py_call( builder.read(exit_), [builder.read(mgr), none, none, none], line ) builder.goto_and_activate(out_block) transform_try_finally_stmt( builder, lambda: transform_try_except(builder, try_body, [(None, None, except_body)], None, line), finally_body )
def transform_try_except(builder: IRBuilder, body: GenFunc, handlers: Sequence[ Tuple[Optional[Expression], Optional[Expression], GenFunc]], else_body: Optional[GenFunc], line: int) -> None: """Generalized try/except/else handling that takes functions to gen the bodies. The point of this is to also be able to support with.""" assert handlers, "try needs except" except_entry, exit_block, cleanup_block = BasicBlock(), BasicBlock(), BasicBlock() double_except_block = BasicBlock() # If there is an else block, jump there after the try, otherwise just leave else_block = BasicBlock() if else_body else exit_block # Compile the try block with an error handler builder.builder.push_error_handler(except_entry) builder.goto_and_activate(BasicBlock()) body() builder.goto(else_block) builder.builder.pop_error_handler() # The error handler catches the error and then checks it # against the except clauses. We compile the error handler # itself with an error handler so that it can properly restore # the *old* exc_info if an exception occurs. # The exception chaining will be done automatically when the # exception is raised, based on the exception in exc_info. builder.builder.push_error_handler(double_except_block) builder.activate_block(except_entry) old_exc = builder.maybe_spill(builder.primitive_op(error_catch_op, [], line)) # Compile the except blocks with the nonlocal control flow overridden to clear exc_info builder.nonlocal_control.append( ExceptNonlocalControl(builder.nonlocal_control[-1], old_exc)) # Process the bodies for type, var, handler_body in handlers: next_block = None if type: next_block, body_block = BasicBlock(), BasicBlock() matches = builder.primitive_op( exc_matches_op, [builder.accept(type)], type.line ) builder.add(Branch(matches, body_block, next_block, Branch.BOOL_EXPR)) builder.activate_block(body_block) if var: target = builder.get_assignment_target(var) builder.assign( target, builder.primitive_op(get_exc_value_op, [], var.line), var.line ) handler_body() builder.goto(cleanup_block) if next_block: builder.activate_block(next_block) # Reraise the exception if needed if next_block: builder.primitive_op(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.nonlocal_control.pop() builder.builder.pop_error_handler() # Cleanup for if we leave except through normal control flow: # restore the saved exc_info information and continue propagating # the exception if it exists. builder.activate_block(cleanup_block) builder.primitive_op(restore_exc_info_op, [builder.read(old_exc)], line) builder.goto(exit_block) # Cleanup for if we leave except through a raised exception: # restore the saved exc_info information and continue propagating # the exception. builder.activate_block(double_except_block) builder.primitive_op(restore_exc_info_op, [builder.read(old_exc)], line) builder.primitive_op(keep_propagating_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) # If present, compile the else body in the obvious way if else_body: builder.activate_block(else_block) else_body() builder.goto(exit_block) builder.activate_block(exit_block)