Skip to content

Commit efe9a31

Browse files
MaxShvetsjhance
authored andcommitted
Implement close method for generators
1 parent e0f16ed commit efe9a31

File tree

3 files changed

+111
-5
lines changed

3 files changed

+111
-5
lines changed

mypyc/irbuild/generator.py

+44-5
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@
1515
from mypyc.common import SELF_NAME, NEXT_LABEL_ATTR_NAME, ENV_ATTR_NAME
1616
from mypyc.ir.ops import (
1717
BasicBlock, Call, Return, Goto, Integer, SetAttr, Unreachable, RaiseStandardError,
18-
Value, Register
18+
Value, Register, MethodCall, TupleSet, Branch, NO_TRACEBACK_LINE_NO
1919
)
2020
from mypyc.ir.rtypes import RInstance, int_rprimitive, object_rprimitive
2121
from mypyc.ir.func_ir import FuncIR, FuncDecl, FuncSignature, RuntimeArg
2222
from mypyc.ir.class_ir import ClassIR
23-
from mypyc.primitives.exc_ops import raise_exception_with_tb_op
23+
from mypyc.irbuild.nonlocalcontrol import ExceptNonlocalControl
24+
from mypyc.primitives.exc_ops import (
25+
raise_exception_with_tb_op, error_catch_op, exc_matches_op, reraise_exception_op,
26+
restore_exc_info_op
27+
)
2428
from mypyc.irbuild.env_class import (
2529
add_args_to_env, load_outer_env, load_env_registers, finalize_env_class
2630
)
@@ -220,11 +224,46 @@ def add_throw_to_generator_class(builder: IRBuilder,
220224

221225
def add_close_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
222226
"""Generates the '__close__' method for a generator class."""
223-
# TODO: Currently this method just triggers a runtime error.
224-
# We should fill this out (https://github.com/mypyc/mypyc/issues/790).
225227
with builder.enter_method(fn_info.generator_class.ir, 'close', object_rprimitive, fn_info):
228+
except_block, else_block = BasicBlock(), BasicBlock()
229+
builder.builder.push_error_handler(except_block)
230+
builder.goto_and_activate(BasicBlock())
231+
generator_exit = builder.load_module_attr_by_fullname('builtins.GeneratorExit',
232+
fn_info.fitem.line)
233+
builder.add(MethodCall(
234+
builder.self(),
235+
'throw',
236+
[generator_exit, builder.none_object(), builder.none_object()]))
237+
builder.goto(else_block)
238+
builder.builder.pop_error_handler()
239+
240+
builder.activate_block(except_block)
241+
old_exc = builder.call_c(error_catch_op, [], fn_info.fitem.line)
242+
builder.nonlocal_control.append(
243+
ExceptNonlocalControl(builder.nonlocal_control[-1], old_exc))
244+
stop_iteration = builder.load_module_attr_by_fullname('builtins.StopIteration',
245+
fn_info.fitem.line)
246+
exceptions = builder.add(
247+
TupleSet([generator_exit, stop_iteration], fn_info.fitem.line))
248+
matches = builder.call_c(
249+
exc_matches_op, [exceptions], fn_info.fitem.line)
250+
251+
match_block, non_match_block = BasicBlock(), BasicBlock()
252+
builder.add(Branch(matches, match_block, non_match_block, Branch.BOOL))
253+
254+
builder.activate_block(match_block)
255+
builder.call_c(restore_exc_info_op, [builder.read(old_exc)], fn_info.fitem.line)
256+
builder.add(Return(builder.none_object()))
257+
258+
builder.activate_block(non_match_block)
259+
builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO)
260+
builder.add(Unreachable())
261+
262+
builder.nonlocal_control.pop()
263+
264+
builder.activate_block(else_block)
226265
builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR,
227-
'close method on generator classes unimplemented',
266+
'generator ignored GeneratorExit',
228267
fn_info.fitem.line))
229268
builder.add(Unreachable())
230269

mypyc/test-data/fixtures/ir.py

+2
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ class ArithmeticError(Exception): pass
276276

277277
class ZeroDivisionError(Exception): pass
278278

279+
class GeneratorExit(BaseException): pass
280+
279281
def any(i: Iterable[T]) -> bool: pass
280282
def all(i: Iterable[T]) -> bool: pass
281283
def sum(i: Iterable[T]) -> int: pass

mypyc/test-data/run-generators.test

+65
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,68 @@ class E:
516516

517517
[file driver.py]
518518
# really I only care it builds
519+
520+
[case testCloseStopIterationRaised]
521+
def g() -> object:
522+
try:
523+
yield 1
524+
except GeneratorExit:
525+
raise
526+
527+
[file driver.py]
528+
from native import g
529+
530+
gen = g()
531+
next(gen)
532+
gen.close()
533+
534+
[case testCloseGeneratorExitRaised]
535+
def g() -> object:
536+
yield 1
537+
538+
[file driver.py]
539+
from native import g
540+
541+
gen = g()
542+
next(gen)
543+
gen.close()
544+
545+
[case testCloseGeneratorExitIgnored]
546+
def g() -> object:
547+
try:
548+
yield 1
549+
except GeneratorExit:
550+
pass
551+
552+
yield 2
553+
554+
[file driver.py]
555+
from native import g
556+
557+
gen = g()
558+
next(gen)
559+
try:
560+
gen.close()
561+
except RuntimeError as e:
562+
assert str(e) == 'generator ignored GeneratorExit'
563+
else:
564+
assert False
565+
566+
[case testCloseGeneratorRaisesAnotherException]
567+
def g() -> object:
568+
try:
569+
yield 1
570+
except GeneratorExit:
571+
raise RuntimeError("error")
572+
573+
[file driver.py]
574+
from native import g
575+
576+
gen = g()
577+
next(gen)
578+
try:
579+
gen.close()
580+
except RuntimeError as e:
581+
assert str(e) == 'error'
582+
else:
583+
assert False

0 commit comments

Comments
 (0)