Skip to content

Commit 85ae39f

Browse files
Merge pull request RustPython#1534 from RustPython/coolreader18/yieldfrom-fix
Fix the `yield from` expression
2 parents 8b31ee5 + 672f146 commit 85ae39f

File tree

6 files changed

+162
-52
lines changed

6 files changed

+162
-52
lines changed

tests/snippets/generators.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,46 @@ def g5():
8888
# print(l)
8989
assert l == [99]
9090
assert r == ['a', 66, None]
91+
92+
def binary(n):
93+
if n <= 1:
94+
return 1
95+
l = yield from binary(n - 1)
96+
r = yield from binary(n - 1)
97+
return l + 1 + r
98+
99+
with assert_raises(StopIteration):
100+
try:
101+
next(binary(5))
102+
except StopIteration as stopiter:
103+
# TODO: StopIteration.value
104+
assert stopiter.args[0] == 31
105+
raise
106+
107+
class SpamException(Exception):
108+
pass
109+
110+
l = []
111+
112+
def writer():
113+
while True:
114+
try:
115+
w = (yield)
116+
except SpamException:
117+
l.append('***')
118+
else:
119+
l.append(f'>> {w}')
120+
121+
def wrapper(coro):
122+
yield from coro
123+
124+
w = writer()
125+
wrap = wrapper(w)
126+
wrap.send(None) # "prime" the coroutine
127+
for i in [0, 1, 2, 'spam', 4]:
128+
if i == 'spam':
129+
wrap.throw(SpamException)
130+
else:
131+
wrap.send(i)
132+
133+
assert l == ['>> 0', '>> 1', '>> 2', '***', '>> 4']

vm/src/frame.rs

Lines changed: 89 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::cell::RefCell;
1+
use std::cell::{Cell, RefCell};
22
use std::fmt;
33

44
use indexmap::IndexMap;
@@ -90,7 +90,7 @@ pub struct Frame {
9090
/// Variables
9191
pub scope: Scope,
9292
/// index of last instruction ran
93-
pub lasti: RefCell<usize>,
93+
pub lasti: Cell<usize>,
9494
}
9595

9696
impl PyValue for Frame {
@@ -105,6 +105,38 @@ pub enum ExecutionResult {
105105
Yield(PyObjectRef),
106106
}
107107

108+
impl ExecutionResult {
109+
/// Extract an ExecutionResult from a PyResult returned from e.g. gen.__next__() or gen.send()
110+
pub fn from_result(vm: &VirtualMachine, res: PyResult) -> PyResult<Self> {
111+
match res {
112+
Ok(val) => Ok(ExecutionResult::Yield(val)),
113+
Err(err) => {
114+
if objtype::isinstance(&err, &vm.ctx.exceptions.stop_iteration) {
115+
objiter::stop_iter_value(vm, &err).map(ExecutionResult::Return)
116+
} else {
117+
Err(err)
118+
}
119+
}
120+
}
121+
}
122+
123+
/// Turn an ExecutionResult into a PyResult that would be returned from a generator or coroutine
124+
pub fn into_result(self, vm: &VirtualMachine) -> PyResult {
125+
match self {
126+
ExecutionResult::Yield(value) => Ok(value),
127+
ExecutionResult::Return(value) => {
128+
let stop_iteration = vm.ctx.exceptions.stop_iteration.clone();
129+
let args = if vm.is_none(&value) {
130+
vec![]
131+
} else {
132+
vec![value]
133+
};
134+
Err(vm.new_exception_obj(stop_iteration, args).unwrap())
135+
}
136+
}
137+
}
138+
}
139+
108140
/// A valid execution result, or an exception
109141
pub type FrameResult = PyResult<Option<ExecutionResult>>;
110142

@@ -128,7 +160,7 @@ impl Frame {
128160
// save the callargs as locals
129161
// globals: locals.clone(),
130162
scope,
131-
lasti: RefCell::new(0),
163+
lasti: Cell::new(0),
132164
}
133165
}
134166

@@ -185,17 +217,40 @@ impl Frame {
185217
}
186218
}
187219

188-
pub fn throw(&self, vm: &VirtualMachine, exception: PyObjectRef) -> PyResult<ExecutionResult> {
189-
match self.unwind_blocks(vm, UnwindReason::Raising { exception }) {
190-
Ok(None) => self.run(vm),
191-
Ok(Some(result)) => Ok(result),
192-
Err(exception) => Err(exception),
220+
pub(crate) fn gen_throw(
221+
&self,
222+
vm: &VirtualMachine,
223+
exc_type: PyClassRef,
224+
exc_val: PyObjectRef,
225+
exc_tb: PyObjectRef,
226+
) -> PyResult {
227+
if let bytecode::Instruction::YieldFrom = self.code.instructions[self.lasti.get()] {
228+
let coro = self.last_value();
229+
vm.call_method(
230+
&coro,
231+
"throw",
232+
vec![exc_type.into_object(), exc_val, exc_tb],
233+
)
234+
.or_else(|err| {
235+
self.pop_value();
236+
self.lasti.set(self.lasti.get() + 1);
237+
let val = objiter::stop_iter_value(vm, &err)?;
238+
self._send(coro, val, vm)
239+
})
240+
} else {
241+
let exception = vm.new_exception_obj(exc_type, vec![exc_val])?;
242+
match self.unwind_blocks(vm, UnwindReason::Raising { exception }) {
243+
Ok(None) => self.run(vm),
244+
Ok(Some(result)) => Ok(result),
245+
Err(exception) => Err(exception),
246+
}
247+
.and_then(|res| res.into_result(vm))
193248
}
194249
}
195250

196251
pub fn fetch_instruction(&self) -> &bytecode::Instruction {
197-
let ins2 = &self.code.instructions[*self.lasti.borrow()];
198-
*self.lasti.borrow_mut() += 1;
252+
let ins2 = &self.code.instructions[self.lasti.get()];
253+
self.lasti.set(self.lasti.get() + 1);
199254
ins2
200255
}
201256

@@ -954,20 +1009,35 @@ impl Frame {
9541009
Err(exception)
9551010
}
9561011

1012+
fn _send(&self, coro: PyObjectRef, val: PyObjectRef, vm: &VirtualMachine) -> PyResult {
1013+
if vm.is_none(&val) {
1014+
objiter::call_next(vm, &coro)
1015+
} else {
1016+
vm.call_method(&coro, "send", vec![val])
1017+
}
1018+
}
1019+
9571020
fn execute_yield_from(&self, vm: &VirtualMachine) -> FrameResult {
9581021
// Value send into iterator:
959-
self.pop_value();
1022+
let val = self.pop_value();
9601023

961-
let top_of_stack = self.last_value();
962-
let next_obj = objiter::get_next_object(vm, &top_of_stack)?;
1024+
let coro = self.last_value();
9631025

964-
match next_obj {
965-
Some(value) => {
1026+
let result = self._send(coro, val, vm);
1027+
1028+
let result = ExecutionResult::from_result(vm, result)?;
1029+
1030+
match result {
1031+
ExecutionResult::Yield(value) => {
9661032
// Set back program counter:
967-
*self.lasti.borrow_mut() -= 1;
1033+
self.lasti.set(self.lasti.get() - 1);
9681034
Ok(Some(ExecutionResult::Yield(value)))
9691035
}
970-
None => Ok(None),
1036+
ExecutionResult::Return(value) => {
1037+
self.pop_value();
1038+
self.push_value(value);
1039+
Ok(None)
1040+
}
9711041
}
9721042
}
9731043

@@ -1006,7 +1076,7 @@ impl Frame {
10061076
let target_pc = self.code.label_map[&label];
10071077
#[cfg(feature = "vm-tracing-logging")]
10081078
trace!("jump from {:?} to {:?}", self.lasti, target_pc);
1009-
self.lasti.replace(target_pc);
1079+
self.lasti.set(target_pc);
10101080
}
10111081

10121082
/// The top of stack contains the iterator, lets push it forward
@@ -1238,7 +1308,7 @@ impl Frame {
12381308
}
12391309

12401310
pub fn get_lineno(&self) -> bytecode::Location {
1241-
self.code.locations[*self.lasti.borrow()].clone()
1311+
self.code.locations[self.lasti.get()].clone()
12421312
}
12431313

12441314
fn push_block(&self, typ: BlockType) {

vm/src/obj/objframe.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ impl FrameRef {
4747

4848
#[pyproperty]
4949
fn f_lasti(self, vm: &VirtualMachine) -> PyObjectRef {
50-
vm.ctx.new_int(*self.lasti.borrow())
50+
vm.ctx.new_int(self.lasti.get())
5151
}
5252
}

vm/src/obj/objgenerator.rs

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
* The mythical generator.
33
*/
44

5-
use super::objtype::{isinstance, PyClassRef};
6-
use crate::frame::{ExecutionResult, FrameRef};
5+
use super::objtype::{issubclass, PyClassRef};
6+
use crate::frame::FrameRef;
7+
use crate::function::OptionalArg;
78
use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue};
89
use crate::vm::VirtualMachine;
910

@@ -41,36 +42,31 @@ impl PyGenerator {
4142
fn send(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
4243
self.frame.push_value(value.clone());
4344

44-
let result = vm.run_frame(self.frame.clone())?;
45-
handle_execution_result(result, vm)
45+
vm.run_frame(self.frame.clone())?.into_result(vm)
4646
}
4747

4848
#[pymethod]
4949
fn throw(
5050
&self,
51-
_exc_type: PyObjectRef,
52-
exc_val: PyObjectRef,
53-
_exc_tb: PyObjectRef,
51+
exc_type: PyClassRef,
52+
exc_val: OptionalArg,
53+
exc_tb: OptionalArg,
5454
vm: &VirtualMachine,
5555
) -> PyResult {
5656
// TODO what should we do with the other parameters? CPython normalises them with
5757
// PyErr_NormalizeException, do we want to do the same.
58-
if !isinstance(&exc_val, &vm.ctx.exceptions.base_exception_type) {
58+
if !issubclass(&exc_type, &vm.ctx.exceptions.base_exception_type) {
5959
return Err(vm.new_type_error("Can't throw non exception".to_string()));
6060
}
61-
let result = vm.frame_throw(self.frame.clone(), exc_val)?;
62-
handle_execution_result(result, vm)
63-
}
64-
}
65-
66-
fn handle_execution_result(result: ExecutionResult, vm: &VirtualMachine) -> PyResult {
67-
match result {
68-
ExecutionResult::Yield(value) => Ok(value),
69-
ExecutionResult::Return(_value) => {
70-
// Stop iteration!
71-
let stop_iteration = vm.ctx.exceptions.stop_iteration.clone();
72-
Err(vm.new_exception(stop_iteration, "End of generator".to_string()))
73-
}
61+
vm.frames.borrow_mut().push(self.frame.clone());
62+
let result = self.frame.gen_throw(
63+
vm,
64+
exc_type,
65+
exc_val.unwrap_or(vm.get_none()),
66+
exc_tb.unwrap_or(vm.get_none()),
67+
);
68+
vm.frames.borrow_mut().pop();
69+
result
7470
}
7571
}
7672

vm/src/obj/objiter.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use std::cell::Cell;
66

7+
use super::objtuple::PyTuple;
78
use super::objtype::{self, PyClassRef};
89
use crate::pyobject::{
910
PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol,
@@ -76,6 +77,17 @@ pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef {
7677
vm.new_exception(stop_iteration_type, "End of iterator".to_string())
7778
}
7879

80+
pub fn stop_iter_value(vm: &VirtualMachine, exc: &PyObjectRef) -> PyResult {
81+
let args = vm.get_attribute(exc.clone(), "args")?;
82+
let args: &PyTuple = args.payload().unwrap();
83+
let val = args
84+
.elements
85+
.first()
86+
.cloned()
87+
.unwrap_or_else(|| vm.get_none());
88+
Ok(val)
89+
}
90+
7991
#[pyclass]
8092
#[derive(Debug)]
8193
pub struct PySequenceIterator {

vm/src/vm.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -234,17 +234,6 @@ impl VirtualMachine {
234234
}
235235
}
236236

237-
pub fn frame_throw(
238-
&self,
239-
frame: FrameRef,
240-
exception: PyObjectRef,
241-
) -> PyResult<ExecutionResult> {
242-
self.frames.borrow_mut().push(frame.clone());
243-
let result = frame.throw(self, exception);
244-
self.frames.borrow_mut().pop();
245-
result
246-
}
247-
248237
pub fn current_frame(&self) -> Option<Ref<FrameRef>> {
249238
let frames = self.frames.borrow();
250239
if frames.is_empty() {

0 commit comments

Comments
 (0)