diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 2f5749e7cd6a..0cb6e5094970 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -786,11 +786,16 @@ def push_loop_stack(self, continue_block: BasicBlock, break_block: BasicBlock) - def pop_loop_stack(self) -> None: self.nonlocal_control.pop() - def spill(self, value: Value) -> AssignmentTarget: + def make_spill_target(self, type: RType) -> AssignmentTarget: """Moves a given Value instance into the generator class' environment class.""" name = f"{TEMP_ATTR_NAME}{self.temp_counter}" self.temp_counter += 1 - target = self.add_var_to_env_class(Var(name), value.type, self.fn_info.generator_class) + target = self.add_var_to_env_class(Var(name), type, self.fn_info.generator_class) + return target + + def spill(self, value: Value) -> AssignmentTarget: + """Moves a given Value instance into the generator class' environment class.""" + target = self.make_spill_target(value.type) # Shouldn't be able to fail, so -1 for line self.assign(target, value, -1) return target diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index b0a3bb18b0aa..e45b04fc6ea5 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -17,7 +17,6 @@ from mypy.nodes import ( ArgKind, - AwaitExpr, ClassDef, Decorator, FuncDef, @@ -27,8 +26,6 @@ SymbolNode, TypeInfo, Var, - YieldExpr, - YieldFromExpr, ) from mypy.types import CallableType, get_proper_type from mypyc.common import LAMBDA_NAME, SELF_NAME @@ -44,7 +41,6 @@ ) from mypyc.ir.ops import ( BasicBlock, - Branch, GetAttr, InitStatic, Integer, @@ -62,7 +58,6 @@ bool_rprimitive, dict_rprimitive, int_rprimitive, - object_pointer_rprimitive, object_rprimitive, ) from mypyc.irbuild.builder import IRBuilder, SymbolTarget, gen_arg_defaults @@ -88,18 +83,11 @@ populate_switch_for_generator_class, setup_env_for_generator_class, ) -from mypyc.irbuild.statement import transform_try_except from mypyc.irbuild.targets import AssignmentTarget from mypyc.irbuild.util import is_constant from mypyc.primitives.dict_ops import dict_get_method_with_none, dict_new_op, dict_set_item_op -from mypyc.primitives.generic_ops import iter_op, next_raw_op, py_setattr_op -from mypyc.primitives.misc_ops import ( - check_stop_op, - coro_op, - register_function, - send_op, - yield_from_except_op, -) +from mypyc.primitives.generic_ops import py_setattr_op +from mypyc.primitives.misc_ops import register_function from mypyc.primitives.registry import builtin_names from mypyc.sametype import is_same_method_signature @@ -178,25 +166,6 @@ def transform_lambda_expr(builder: IRBuilder, expr: LambdaExpr) -> Value: return func_reg -def transform_yield_expr(builder: IRBuilder, expr: YieldExpr) -> Value: - if builder.fn_info.is_coroutine: - builder.error("async generators are unimplemented", expr.line) - - if expr.expr: - retval = builder.accept(expr.expr) - else: - retval = builder.builder.none() - return emit_yield(builder, retval, expr.line) - - -def transform_yield_from_expr(builder: IRBuilder, o: YieldFromExpr) -> Value: - return handle_yield_from_and_await(builder, o) - - -def transform_await_expr(builder: IRBuilder, o: AwaitExpr) -> Value: - return handle_yield_from_and_await(builder, o) - - # Internal functions @@ -551,112 +520,6 @@ def gen_func_ns(builder: IRBuilder) -> str: ) -def emit_yield(builder: IRBuilder, val: Value, line: int) -> Value: - retval = builder.coerce(val, builder.ret_types[-1], line) - - cls = builder.fn_info.generator_class - # Create a new block for the instructions immediately following the yield expression, and - # set the next label so that the next time '__next__' is called on the generator object, - # the function continues at the new block. - next_block = BasicBlock() - next_label = len(cls.continuation_blocks) - cls.continuation_blocks.append(next_block) - builder.assign(cls.next_label_target, Integer(next_label), line) - builder.add(Return(retval)) - builder.activate_block(next_block) - - add_raise_exception_blocks_to_generator_class(builder, line) - - assert cls.send_arg_reg is not None - return cls.send_arg_reg - - -def handle_yield_from_and_await(builder: IRBuilder, o: Union[YieldFromExpr, AwaitExpr]) -> Value: - # This is basically an implementation of the code in PEP 380. - - # TODO: do we want to use the right types here? - result = Register(object_rprimitive) - to_yield_reg = Register(object_rprimitive) - received_reg = Register(object_rprimitive) - - if isinstance(o, YieldFromExpr): - iter_val = builder.call_c(iter_op, [builder.accept(o.expr)], o.line) - else: - iter_val = builder.call_c(coro_op, [builder.accept(o.expr)], o.line) - - iter_reg = builder.maybe_spill_assignable(iter_val) - - stop_block, main_block, done_block = BasicBlock(), BasicBlock(), BasicBlock() - _y_init = builder.call_c(next_raw_op, [builder.read(iter_reg)], o.line) - builder.add(Branch(_y_init, stop_block, main_block, Branch.IS_ERROR)) - - # Try extracting a return value from a StopIteration and return it. - # If it wasn't, this reraises the exception. - builder.activate_block(stop_block) - builder.assign(result, builder.call_c(check_stop_op, [], o.line), o.line) - builder.goto(done_block) - - builder.activate_block(main_block) - builder.assign(to_yield_reg, _y_init, o.line) - - # OK Now the main loop! - loop_block = BasicBlock() - builder.goto_and_activate(loop_block) - - def try_body() -> None: - builder.assign( - received_reg, emit_yield(builder, builder.read(to_yield_reg), o.line), o.line - ) - - def except_body() -> None: - # The body of the except is all implemented in a C function to - # reduce how much code we need to generate. It returns a value - # indicating whether to break or yield (or raise an exception). - val = Register(object_rprimitive) - val_address = builder.add(LoadAddress(object_pointer_rprimitive, val)) - to_stop = builder.call_c( - yield_from_except_op, [builder.read(iter_reg), val_address], o.line - ) - - ok, stop = BasicBlock(), BasicBlock() - builder.add(Branch(to_stop, stop, ok, Branch.BOOL)) - - # The exception got swallowed. Continue, yielding the returned value - builder.activate_block(ok) - builder.assign(to_yield_reg, val, o.line) - builder.nonlocal_control[-1].gen_continue(builder, o.line) - - # The exception was a StopIteration. Stop iterating. - builder.activate_block(stop) - builder.assign(result, val, o.line) - builder.nonlocal_control[-1].gen_break(builder, o.line) - - def else_body() -> None: - # Do a next() or a .send(). It will return NULL on exception - # but it won't automatically propagate. - _y = builder.call_c(send_op, [builder.read(iter_reg), builder.read(received_reg)], o.line) - ok, stop = BasicBlock(), BasicBlock() - builder.add(Branch(_y, stop, ok, Branch.IS_ERROR)) - - # Everything's fine. Yield it. - builder.activate_block(ok) - builder.assign(to_yield_reg, _y, o.line) - builder.nonlocal_control[-1].gen_continue(builder, o.line) - - # Try extracting a return value from a StopIteration and return it. - # If it wasn't, this rereaises the exception. - builder.activate_block(stop) - builder.assign(result, builder.call_c(check_stop_op, [], o.line), o.line) - builder.nonlocal_control[-1].gen_break(builder, o.line) - - builder.push_loop_stack(loop_block, done_block) - transform_try_except(builder, try_body, [(None, None, except_body)], else_body, o.line) - builder.pop_loop_stack() - - builder.goto_and_activate(done_block) - return builder.read(result) - - def load_decorated_func(builder: IRBuilder, fdef: FuncDef, orig_func_reg: Value) -> Value: """Apply decorators to a function. diff --git a/mypyc/irbuild/nonlocalcontrol.py b/mypyc/irbuild/nonlocalcontrol.py index 769d927e2b84..4cc4131db5da 100644 --- a/mypyc/irbuild/nonlocalcontrol.py +++ b/mypyc/irbuild/nonlocalcontrol.py @@ -6,11 +6,10 @@ from __future__ import annotations from abc import abstractmethod -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING, Union from mypyc.ir.ops import ( NO_TRACEBACK_LINE_NO, - Assign, BasicBlock, Branch, Goto, @@ -142,7 +141,7 @@ class TryFinallyNonlocalControl(NonlocalControl): def __init__(self, target: BasicBlock) -> None: self.target = target - self.ret_reg: Optional[Register] = None + self.ret_reg: Union[None, Register, AssignmentTarget] = None def gen_break(self, builder: IRBuilder, line: int) -> None: builder.error("break inside try/finally block is unimplemented", line) @@ -152,9 +151,15 @@ def gen_continue(self, builder: IRBuilder, line: int) -> None: def gen_return(self, builder: IRBuilder, value: Value, line: int) -> None: if self.ret_reg is None: - self.ret_reg = Register(builder.ret_types[-1]) + if builder.fn_info.is_generator: + self.ret_reg = builder.make_spill_target(builder.ret_types[-1]) + else: + self.ret_reg = Register(builder.ret_types[-1]) + # assert needed because of apparent mypy bug... it loses track of the union + # and infers the type as object + assert isinstance(self.ret_reg, (Register, AssignmentTarget)) + builder.assign(self.ret_reg, value, line) - builder.add(Assign(self.ret_reg, value)) builder.add(Goto(self.target)) @@ -180,9 +185,8 @@ class FinallyNonlocalControl(CleanupNonlocalControl): leave and the return register is decrefed if it isn't null. """ - def __init__(self, outer: NonlocalControl, ret_reg: Optional[Value], saved: Value) -> None: + def __init__(self, outer: NonlocalControl, saved: Value) -> None: super().__init__(outer) - self.ret_reg = ret_reg self.saved = saved def gen_cleanup(self, builder: IRBuilder, line: int) -> None: diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index 09da12688d14..9282b7e2d3f5 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -9,11 +9,12 @@ from __future__ import annotations import importlib.util -from typing import Callable, List, Optional, Sequence, Tuple +from typing import Callable, List, Optional, Sequence, Tuple, Union from mypy.nodes import ( AssertStmt, AssignmentStmt, + AwaitExpr, Block, BreakStmt, ContinueStmt, @@ -37,23 +38,35 @@ TupleExpr, WhileStmt, WithStmt, + YieldExpr, + YieldFromExpr, ) from mypyc.ir.ops import ( NO_TRACEBACK_LINE_NO, Assign, BasicBlock, Branch, + Integer, + LoadAddress, LoadErrorValue, RaiseStandardError, Register, + Return, TupleGet, Unreachable, Value, ) -from mypyc.ir.rtypes import RInstance, exc_rtuple, is_tagged +from mypyc.ir.rtypes import ( + RInstance, + exc_rtuple, + is_tagged, + object_pointer_rprimitive, + object_rprimitive, +) from mypyc.irbuild.ast_helpers import is_borrow_friendly_expr, process_conditional from mypyc.irbuild.builder import IRBuilder, int_borrow_friendly_op from mypyc.irbuild.for_helpers import for_loop_helper +from mypyc.irbuild.generator import add_raise_exception_blocks_to_generator_class from mypyc.irbuild.nonlocalcontrol import ( ExceptNonlocalControl, FinallyNonlocalControl, @@ -76,8 +89,15 @@ reraise_exception_op, restore_exc_info_op, ) -from mypyc.primitives.generic_ops import py_delattr_op -from mypyc.primitives.misc_ops import import_from_op, type_op +from mypyc.primitives.generic_ops import iter_op, next_raw_op, py_delattr_op +from mypyc.primitives.misc_ops import ( + check_stop_op, + coro_op, + import_from_op, + send_op, + type_op, + yield_from_except_op, +) GenFunc = Callable[[], None] @@ -444,7 +464,7 @@ def try_finally_try( return_entry: BasicBlock, main_entry: BasicBlock, try_body: GenFunc, -) -> Optional[Register]: +) -> Union[Register, AssignmentTarget, None]: # Compile the try block with an error handler control = TryFinallyNonlocalControl(return_entry) builder.builder.push_error_handler(err_handler) @@ -465,14 +485,14 @@ def try_finally_entry_blocks( return_entry: BasicBlock, main_entry: BasicBlock, finally_block: BasicBlock, - ret_reg: Optional[Register], + ret_reg: Union[Register, AssignmentTarget, None], ) -> Value: old_exc = Register(exc_rtuple) # Entry block for non-exceptional flow builder.activate_block(main_entry) if ret_reg: - builder.add(Assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1])))) + builder.assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1])), -1) builder.goto(return_entry) builder.activate_block(return_entry) @@ -482,7 +502,7 @@ def try_finally_entry_blocks( # Entry block for errors builder.activate_block(err_handler) if ret_reg: - builder.add(Assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1])))) + builder.assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1])), -1) builder.add(Assign(old_exc, builder.call_c(error_catch_op, [], -1))) builder.goto(finally_block) @@ -490,16 +510,12 @@ def try_finally_entry_blocks( def try_finally_body( - builder: IRBuilder, - finally_block: BasicBlock, - finally_body: GenFunc, - ret_reg: Optional[Value], - old_exc: Value, + builder: IRBuilder, finally_block: BasicBlock, finally_body: GenFunc, old_exc: Value ) -> Tuple[BasicBlock, FinallyNonlocalControl]: cleanup_block = BasicBlock() # Compile the finally block with the nonlocal control flow overridden to restore exc_info builder.builder.push_error_handler(cleanup_block) - finally_control = FinallyNonlocalControl(builder.nonlocal_control[-1], ret_reg, old_exc) + finally_control = FinallyNonlocalControl(builder.nonlocal_control[-1], old_exc) builder.nonlocal_control.append(finally_control) builder.activate_block(finally_block) finally_body() @@ -513,7 +529,7 @@ def try_finally_resolve_control( cleanup_block: BasicBlock, finally_control: FinallyNonlocalControl, old_exc: Value, - ret_reg: Optional[Value], + ret_reg: Union[Register, AssignmentTarget, None], ) -> BasicBlock: """Resolve the control flow out of a finally block. @@ -533,10 +549,10 @@ def try_finally_resolve_control( if ret_reg: builder.activate_block(rest) return_block, rest = BasicBlock(), BasicBlock() - builder.add(Branch(ret_reg, rest, return_block, Branch.IS_ERROR)) + builder.add(Branch(builder.read(ret_reg), rest, return_block, Branch.IS_ERROR)) builder.activate_block(return_block) - builder.nonlocal_control[-1].gen_return(builder, ret_reg, -1) + builder.nonlocal_control[-1].gen_return(builder, builder.read(ret_reg), -1) # TODO: handle break/continue builder.activate_block(rest) @@ -578,7 +594,7 @@ def transform_try_finally_stmt( # Compile the body of the finally cleanup_block, finally_control = try_finally_body( - builder, finally_block, finally_body, ret_reg, old_exc + builder, finally_block, finally_body, old_exc ) # Resolve the control flow out of the finally block @@ -616,18 +632,28 @@ def get_sys_exc_info(builder: IRBuilder) -> List[Value]: def transform_with( - builder: IRBuilder, expr: Expression, target: Optional[Lvalue], body: GenFunc, line: int + builder: IRBuilder, + expr: Expression, + target: Optional[Lvalue], + body: GenFunc, + is_async: bool, + 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. + + al = "a" if is_async else "" + mgr_v = builder.accept(expr) typ = builder.call_c(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) + exit_ = builder.maybe_spill(builder.py_get_attr(typ, f"__{al}exit__", line)) + value = builder.py_call(builder.py_get_attr(typ, f"__{al}enter__", line), [mgr_v], line) mgr = builder.maybe_spill(mgr_v) exc = builder.maybe_spill_assignable(builder.true()) + if is_async: + value = emit_await(builder, value, line) def try_body() -> None: if target: @@ -637,13 +663,13 @@ def try_body() -> None: def except_body() -> None: builder.assign(exc, builder.false(), 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, + exit_val = builder.py_call( + builder.read(exit_), [builder.read(mgr)] + get_sys_exc_info(builder), line ) + if is_async: + exit_val = emit_await(builder, exit_val, line) + + builder.add_bool_branch(exit_val, out_block, reraise_block) builder.activate_block(reraise_block) builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) @@ -654,7 +680,12 @@ def finally_body() -> None: builder.add(Branch(builder.read(exc), exit_block, out_block, Branch.BOOL)) builder.activate_block(exit_block) none = builder.none_object() - builder.py_call(builder.read(exit_), [builder.read(mgr), none, none, none], line) + exit_val = builder.py_call( + builder.read(exit_), [builder.read(mgr), none, none, none], line + ) + if is_async: + emit_await(builder, exit_val, line) + builder.goto_and_activate(out_block) transform_try_finally_stmt( @@ -665,15 +696,14 @@ def finally_body() -> None: def transform_with_stmt(builder: IRBuilder, o: WithStmt) -> None: - if o.is_async: - builder.error("async with is unimplemented", o.line) - # Generate separate logic for each expr in it, left to right def generate(i: int) -> None: if i >= len(o.expr): builder.accept(o.body) else: - transform_with(builder, o.expr[i], o.target[i], lambda: generate(i + 1), o.line) + transform_with( + builder, o.expr[i], o.target[i], lambda: generate(i + 1), o.is_async, o.line + ) generate(0) @@ -731,3 +761,133 @@ def transform_del_item(builder: IRBuilder, target: AssignmentTarget, line: int) elif isinstance(target, AssignmentTargetTuple): for subtarget in target.items: transform_del_item(builder, subtarget, line) + + +# yield/yield from/await + +# These are really expressions, not statements... but they depend on try/except/finally + + +def emit_yield(builder: IRBuilder, val: Value, line: int) -> Value: + retval = builder.coerce(val, builder.ret_types[-1], line) + + cls = builder.fn_info.generator_class + # Create a new block for the instructions immediately following the yield expression, and + # set the next label so that the next time '__next__' is called on the generator object, + # the function continues at the new block. + next_block = BasicBlock() + next_label = len(cls.continuation_blocks) + cls.continuation_blocks.append(next_block) + builder.assign(cls.next_label_target, Integer(next_label), line) + builder.add(Return(retval)) + builder.activate_block(next_block) + + add_raise_exception_blocks_to_generator_class(builder, line) + + assert cls.send_arg_reg is not None + return cls.send_arg_reg + + +def emit_yield_from_or_await( + builder: IRBuilder, val: Value, line: int, *, is_await: bool +) -> Value: + # This is basically an implementation of the code in PEP 380. + + # TODO: do we want to use the right types here? + result = Register(object_rprimitive) + to_yield_reg = Register(object_rprimitive) + received_reg = Register(object_rprimitive) + + get_op = coro_op if is_await else iter_op + iter_val = builder.call_c(get_op, [val], line) + + iter_reg = builder.maybe_spill_assignable(iter_val) + + stop_block, main_block, done_block = BasicBlock(), BasicBlock(), BasicBlock() + _y_init = builder.call_c(next_raw_op, [builder.read(iter_reg)], line) + builder.add(Branch(_y_init, stop_block, main_block, Branch.IS_ERROR)) + + # Try extracting a return value from a StopIteration and return it. + # If it wasn't, this reraises the exception. + builder.activate_block(stop_block) + builder.assign(result, builder.call_c(check_stop_op, [], line), line) + builder.goto(done_block) + + builder.activate_block(main_block) + builder.assign(to_yield_reg, _y_init, line) + + # OK Now the main loop! + loop_block = BasicBlock() + builder.goto_and_activate(loop_block) + + def try_body() -> None: + builder.assign(received_reg, emit_yield(builder, builder.read(to_yield_reg), line), line) + + def except_body() -> None: + # The body of the except is all implemented in a C function to + # reduce how much code we need to generate. It returns a value + # indicating whether to break or yield (or raise an exception). + val = Register(object_rprimitive) + val_address = builder.add(LoadAddress(object_pointer_rprimitive, val)) + to_stop = builder.call_c(yield_from_except_op, [builder.read(iter_reg), val_address], line) + + ok, stop = BasicBlock(), BasicBlock() + builder.add(Branch(to_stop, stop, ok, Branch.BOOL)) + + # The exception got swallowed. Continue, yielding the returned value + builder.activate_block(ok) + builder.assign(to_yield_reg, val, line) + builder.nonlocal_control[-1].gen_continue(builder, line) + + # The exception was a StopIteration. Stop iterating. + builder.activate_block(stop) + builder.assign(result, val, line) + builder.nonlocal_control[-1].gen_break(builder, line) + + def else_body() -> None: + # Do a next() or a .send(). It will return NULL on exception + # but it won't automatically propagate. + _y = builder.call_c(send_op, [builder.read(iter_reg), builder.read(received_reg)], line) + ok, stop = BasicBlock(), BasicBlock() + builder.add(Branch(_y, stop, ok, Branch.IS_ERROR)) + + # Everything's fine. Yield it. + builder.activate_block(ok) + builder.assign(to_yield_reg, _y, line) + builder.nonlocal_control[-1].gen_continue(builder, line) + + # Try extracting a return value from a StopIteration and return it. + # If it wasn't, this rereaises the exception. + builder.activate_block(stop) + builder.assign(result, builder.call_c(check_stop_op, [], line), line) + builder.nonlocal_control[-1].gen_break(builder, line) + + builder.push_loop_stack(loop_block, done_block) + transform_try_except(builder, try_body, [(None, None, except_body)], else_body, line) + builder.pop_loop_stack() + + builder.goto_and_activate(done_block) + return builder.read(result) + + +def emit_await(builder: IRBuilder, val: Value, line: int) -> Value: + return emit_yield_from_or_await(builder, val, line, is_await=True) + + +def transform_yield_expr(builder: IRBuilder, expr: YieldExpr) -> Value: + if builder.fn_info.is_coroutine: + builder.error("async generators are unimplemented", expr.line) + + if expr.expr: + retval = builder.accept(expr.expr) + else: + retval = builder.builder.none() + return emit_yield(builder, retval, expr.line) + + +def transform_yield_from_expr(builder: IRBuilder, o: YieldFromExpr) -> Value: + return emit_yield_from_or_await(builder, builder.accept(o.expr), o.line, is_await=False) + + +def transform_await_expr(builder: IRBuilder, o: AwaitExpr) -> Value: + return emit_yield_from_or_await(builder, builder.accept(o.expr), o.line, is_await=True) diff --git a/mypyc/irbuild/visitor.py b/mypyc/irbuild/visitor.py index 94d037c93841..dc126d410409 100644 --- a/mypyc/irbuild/visitor.py +++ b/mypyc/irbuild/visitor.py @@ -112,17 +112,15 @@ transform_unary_expr, ) from mypyc.irbuild.function import ( - transform_await_expr, transform_decorator, transform_func_def, transform_lambda_expr, transform_overloaded_func_def, - transform_yield_expr, - transform_yield_from_expr, ) from mypyc.irbuild.statement import ( transform_assert_stmt, transform_assignment_stmt, + transform_await_expr, transform_block, transform_break_stmt, transform_continue_stmt, @@ -139,6 +137,8 @@ transform_try_stmt, transform_while_stmt, transform_with_stmt, + transform_yield_expr, + transform_yield_from_expr, ) diff --git a/mypyc/test-data/commandline.test b/mypyc/test-data/commandline.test index 971a4c4a254c..b5223adbfb64 100644 --- a/mypyc/test-data/commandline.test +++ b/mypyc/test-data/commandline.test @@ -210,14 +210,6 @@ async def async_for(xs: AsyncIterable[int]) -> None: {x async for x in xs} # E: async comprehensions are unimplemented {x: x async for x in xs} # E: async comprehensions are unimplemented -class async_ctx: - async def __aenter__(self) -> int: pass - async def __aexit__(self, x, y, z) -> None: pass - -async def async_with() -> None: - async with async_ctx() as x: # E: async with is unimplemented - print(x) - async def async_generators() -> AsyncIterable[int]: yield 1 # E: async generators are unimplemented diff --git a/mypyc/test-data/fixtures/testutil.py b/mypyc/test-data/fixtures/testutil.py index f6c20835a287..d74ac1af1952 100644 --- a/mypyc/test-data/fixtures/testutil.py +++ b/mypyc/test-data/fixtures/testutil.py @@ -1,9 +1,10 @@ # Simple support library for our run tests. from contextlib import contextmanager +from collections.abc import Iterator from typing import ( Any, Iterator, TypeVar, Generator, Optional, List, Tuple, Sequence, - Union, Callable, + Union, Callable, Generic, Awaitable, ) @contextmanager @@ -30,6 +31,8 @@ def run_generator(gen: Generator[T, V, U], if i >= 0 and inputs: # ... fixtures don't have send val = gen.send(inputs[i]) # type: ignore + elif not hasattr(gen, '__next__'): # type: ignore + val = gen.send(None) # type: ignore else: val = next(gen) except StopIteration as e: @@ -44,6 +47,15 @@ def run_generator(gen: Generator[T, V, U], F = TypeVar('F', bound=Callable) +class async_val(Awaitable[V]): + def __init__(self, val: T) -> None: + self.val = val + + def __await__(self) -> Generator[T, V, V]: + z = yield self.val + return z + + # Wrap a mypyc-generated function in a real python function, to allow it to be # stuck into classes and the like. def make_python_function(f: F) -> F: diff --git a/mypyc/test-data/run-async.test b/mypyc/test-data/run-async.test new file mode 100644 index 000000000000..d1958d09fe61 --- /dev/null +++ b/mypyc/test-data/run-async.test @@ -0,0 +1,66 @@ +# async test cases (compile and run) + +[case testAsync] +import asyncio + +async def h() -> int: + return 1 + +async def g() -> int: + await asyncio.sleep(0) + return await h() + +async def f() -> int: + return await g() + +[typing fixtures/typing-full.pyi] + +[file driver.py] +from native import f +import asyncio + +result = asyncio.run(f()) +assert result == 1 + +[case testAsyncWith] +from testutil import async_val + +class async_ctx: + async def __aenter__(self) -> str: + await async_val("enter") + return "test" + + async def __aexit__(self, x, y, z) -> None: + await async_val("exit") + + +async def async_with() -> str: + async with async_ctx() as x: + return await async_val("body") + + +[file driver.py] +from native import async_with +from testutil import run_generator + +yields, val = run_generator(async_with(), [None, 'x', None]) +assert yields == ('enter', 'body', 'exit'), yields +assert val == 'x', val + + +[case testAsyncReturn] +from testutil import async_val + +async def async_return() -> str: + try: + return 'test' + finally: + await async_val('foo') + +[file driver.py] +from native import async_return +from testutil import run_generator + +yields, val = run_generator(async_return()) +assert yields == ('foo',) +assert val == 'test', val diff --git a/mypyc/test-data/run-generators.test b/mypyc/test-data/run-generators.test index 93c4efbd52d2..db658eea6504 100644 --- a/mypyc/test-data/run-generators.test +++ b/mypyc/test-data/run-generators.test @@ -631,3 +631,22 @@ def test_generator() -> None: assert c.foo.flag1 == c.foo.flag2 == True assert list(gen) == [] assert c.foo.flag1 == c.foo.flag2 == False + + +[case testYieldInFinally] +from typing import Generator + +def finally_yield() -> Generator[str, None, str]: + try: + return 'test' + finally: + yield 'x' + + +[file driver.py] +from native import finally_yield +from testutil import run_generator + +yields, val = run_generator(finally_yield()) +assert yields == ('x',) +assert val == 'test', val diff --git a/mypyc/test-data/run-misc.test b/mypyc/test-data/run-misc.test index 66b00b25089d..001e0aa41b25 100644 --- a/mypyc/test-data/run-misc.test +++ b/mypyc/test-data/run-misc.test @@ -1,42 +1,3 @@ -# Misc test cases (compile and run) - -[case testAsync] -import asyncio -import sys - -async def h() -> int: - return 1 - -async def g() -> int: - await asyncio.sleep(0.01) - return await h() - -async def f() -> int: - return await g() - -# sys.version_info >= (3, 7) fails with -# error: Unsupported left operand type for >= ("Tuple[int, int, int, str, int]") -if sys.version_info[0] >= 3 and sys.version_info[1] >= 7: - result = asyncio.run(f()) -else: - loop = asyncio.get_event_loop() - result = loop.run_until_complete(f()) -assert result == 1 - -[typing fixtures/typing-full.pyi] - -[file driver.py] -from native import f -import asyncio -import sys - -if sys.version_info >= (3, 7): - result = asyncio.run(f()) -else: - loop = asyncio.get_event_loop() - result = loop.run_until_complete(f()) -assert result == 1 - [case testMaybeUninitVar] class C: def __init__(self, x: int) -> None: diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index a879265bf7d0..c896a09005e0 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -34,6 +34,7 @@ ) files = [ + "run-async.test", "run-misc.test", "run-functions.test", "run-integers.test",