-
-
Notifications
You must be signed in to change notification settings - Fork 519
Added support for Yield from statement #821
Changes from 3 commits
e0daee5
3a64b7b
105e291
271e476
b3b013d
2071e50
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,24 +72,48 @@ def fizz_buzz(self): | |
print(i) | ||
""") | ||
|
||
def test_yield_from_not_used(self): | ||
"""Yield from is currently not implemented. | ||
Unused yield from statements must not break | ||
the program compilation, but only ensue a warning.""" | ||
def test_simplest_yieldfrom(self): | ||
self.assertCodeExecution(""" | ||
|
||
def unused(): | ||
yield from range(5) | ||
def gen1(): | ||
yield 1 | ||
yield 2 | ||
yield 3 | ||
|
||
def gen2(): | ||
yield from gen1() | ||
|
||
for i in gen2(): | ||
print(i) | ||
""") | ||
|
||
print('Hello, world!') | ||
""") | ||
def test_yieldfrom_list(self): | ||
self.assertCodeExecution(""" | ||
def gen(): | ||
yield from [1, 2, 3] | ||
|
||
for i in gen(): | ||
print(i) | ||
""") | ||
|
||
def test_chainning_yieldfrom(self): | ||
self.assertCodeExecution(""" | ||
def gen1(): | ||
yield 'a' | ||
yield 'b' | ||
|
||
def gen2(): | ||
yield from gen1() | ||
yield 'gen2' | ||
|
||
def gen3(): | ||
yield from gen2() | ||
|
||
for i in gen3(): | ||
print(i) | ||
""") | ||
|
||
@expectedFailure | ||
def test_yield_from_used(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is one of those rare situations where a test can be removed. The test itself is a much simpler version of what you've tested thoroughly with your other |
||
"""Yield from is currently not implemented. | ||
Yield from statements become NotImplementedErrors at runtime.""" | ||
self.assertCodeExecution(""" | ||
|
||
def using_yieldfrom(): | ||
yield from range(5) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1712,7 +1712,8 @@ def visit_GeneratorExp(self, node): | |
|
||
@node_visitor | ||
def visit_Yield(self, node): | ||
self.visit(node.value) | ||
if hasattr(node, "lineno"): | ||
self.visit(node.value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It isn't clear to me why At the very least, a comment explaining the need for this branch is required; better still would be a test case that exercises this case (assuming there isn't one already) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is sort of a 'hackish' way to differentiate a programmatically defined yield node from those generated normally by ast.parse. Combined with #823, there will be 3 types of yield node passed into visit_Yield method:
Edit: I just remembered it is possible to add custom attribute to a class so I'm thinking of using a flag (i.e There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok - that reasoning makes sense; however, it isn't immediately obvious from the code. Adding a comment explaining what is going on would be helpful. A custom attribute isn't necessary; as long as the code explains what it's doing, that will be sufficient. |
||
yield_point = len(self.context.yield_points) + 1 | ||
self.context.add_opcodes( | ||
# Convert to a new value for return purposes | ||
|
@@ -1747,18 +1748,39 @@ def visit_Yield(self, node): | |
|
||
@node_visitor | ||
def visit_YieldFrom(self, node): | ||
# expr value): | ||
print( | ||
'"yield from" statement not yet implemented. ' | ||
'Will throw a NotImplementedError at runtime' | ||
self.visit(node.value) # pushes expression on stack | ||
self.context.add_opcodes( | ||
python.Object.iter() # pops expression from stack then gets and pushes its iterator on stack | ||
) | ||
self.context.store_name('#yield-iter-%x' % id(node)) | ||
|
||
loop = START_LOOP() | ||
self.context.add_opcodes( | ||
java.THROW( | ||
'org/python/exceptions/NotImplementedError', | ||
['Ljava/lang/String;', | ||
JavaOpcodes.LDC_W('"yield from" statement not yet implemented')] | ||
) | ||
loop, | ||
) | ||
self.context.add_opcodes( | ||
TRY(), | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a style thing, it's preferable to include as many opcodes as possible in a single |
||
self.context.load_name('#yield-iter-%x' % id(node)) | ||
self.context.add_opcodes( | ||
python.Iterable.next(), | ||
) | ||
self.context.add_opcodes( | ||
CATCH('org/python/exceptions/StopIteration'), | ||
) | ||
self.context.add_opcodes( | ||
JavaOpcodes.POP(), | ||
jump(JavaOpcodes.GOTO(0), self.context, loop, OpcodePosition.NEXT), | ||
) | ||
self.context.add_opcodes( | ||
END_TRY(), | ||
) | ||
self.visit(ast.Yield(None)) | ||
self.context.add_opcodes( | ||
END_LOOP(), | ||
) | ||
|
||
self.context.delete_name('#yield-iter-%x' % id(node)) | ||
|
||
@node_visitor | ||
def visit_Compare(self, node): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spelling:
chaining