use std::sync::Arc;
use indexmap::IndexMap;
use rustpython_parser::ast::Stmt;
use crate::{
error::{ControlFlow, EvalError, EvalResult, InterpreterError},
eval::{eval_stmt, statements::assign_target},
state::{GeneratorForState, GeneratorFrame, InterpreterState},
value::{ExceptionValue, FunctionDef, Value},
};
#[must_use]
pub(crate) fn is_generator_method(method: &str) -> bool {
matches!(method, "send" | "throw" | "close" | "__next__" | "__iter__")
}
#[must_use]
pub(crate) fn generator_suspendable(stmts: &[Stmt]) -> bool {
stmts.iter().all(top_level_suspendable)
}
fn top_level_suspendable(stmt: &Stmt) -> bool {
use rustpython_parser::ast::ExceptHandler;
if !super::definitions::contains_yield_stmts(std::slice::from_ref(stmt)) {
return true;
}
match stmt {
Stmt::For(f) => {
for_name_target(f).is_some() && f.orelse.is_empty() && loop_nest_suspendable(&f.body)
}
Stmt::While(w) => w.orelse.is_empty() && loop_nest_suspendable(&w.body),
Stmt::If(n) => if_branch_suspendable(&n.body) && if_branch_suspendable(&n.orelse),
Stmt::Try(t) => {
if_branch_suspendable(&t.body)
&& if_branch_suspendable(&t.orelse)
&& if_branch_suspendable(&t.finalbody)
&& t.handlers.iter().all(|h| {
let ExceptHandler::ExceptHandler(eh) = h;
if_branch_suspendable(&eh.body)
})
}
Stmt::With(w) => if_branch_suspendable(&w.body),
Stmt::Expr(_)
| Stmt::Assign(_)
| Stmt::AugAssign(_)
| Stmt::AnnAssign(_)
| Stmt::Return(_) => true,
_ => false,
}
}
fn loop_nest_suspendable(stmts: &[Stmt]) -> bool {
stmts.iter().all(|stmt| {
let yields = super::definitions::contains_yield_stmts(std::slice::from_ref(stmt));
match stmt {
Stmt::If(node) => {
loop_nest_suspendable(&node.body) && loop_nest_suspendable(&node.orelse)
}
Stmt::For(f) => !yields || (for_delegatable(f) && loop_nest_suspendable(&f.body)),
Stmt::While(w) => !yields || (while_delegatable(w) && loop_nest_suspendable(&w.body)),
Stmt::With(_) | Stmt::Try(_) | Stmt::TryStar(_) | Stmt::Match(_) => !yields,
_ => true,
}
})
}
fn for_name_target(f: &rustpython_parser::ast::StmtFor) -> Option<&str> {
match f.target.as_ref() {
rustpython_parser::ast::Expr::Name(t) => Some(t.id.as_str()),
_ => None,
}
}
fn for_delegatable(f: &rustpython_parser::ast::StmtFor) -> bool {
for_name_target(f).is_some() && f.orelse.is_empty()
}
fn while_delegatable(w: &rustpython_parser::ast::StmtWhile) -> bool {
w.orelse.is_empty()
}
fn if_branch_suspendable(stmts: &[Stmt]) -> bool {
use rustpython_parser::ast::ExceptHandler;
stmts.iter().all(|stmt| match stmt {
Stmt::If(node) => if_branch_suspendable(&node.body) && if_branch_suspendable(&node.orelse),
Stmt::Try(t) => {
if_branch_suspendable(&t.body)
&& if_branch_suspendable(&t.orelse)
&& if_branch_suspendable(&t.finalbody)
&& t.handlers.iter().all(|h| {
let ExceptHandler::ExceptHandler(eh) = h;
if_branch_suspendable(&eh.body)
})
}
Stmt::While(w) => {
!super::definitions::contains_yield_stmts(std::slice::from_ref(stmt))
|| (while_delegatable(w) && loop_nest_suspendable(&w.body))
}
Stmt::For(f) => {
!super::definitions::contains_yield_stmts(std::slice::from_ref(stmt))
|| (for_delegatable(f) && loop_nest_suspendable(&f.body))
}
Stmt::With(_) | Stmt::TryStar(_) | Stmt::Match(_) => {
!super::definitions::contains_yield_stmts(std::slice::from_ref(stmt))
}
_ => true,
})
}
pub(crate) fn create_synthetic_generator(
state: &mut InterpreterState,
name: &str,
body: Arc<Vec<Stmt>>,
locals: rustc_hash::FxHashMap<String, Value>,
touched: Vec<String>,
) -> Option<Value> {
if !generator_suspendable(&body) {
return None;
}
let func_def = FunctionDef {
name: name.to_string(),
body_key: format!("{name}#{}", state.next_cursor_id),
wraps_name: None,
params: crate::value::FunctionParams {
args: Vec::new(),
defaults: Vec::new(),
default_values: Vec::new(),
vararg: None,
kwonlyargs: Vec::new(),
kw_defaults: Vec::new(),
kw_default_values: Vec::new(),
kwarg: None,
posonly_count: 0,
},
closure: std::collections::BTreeMap::new(),
source: String::new(),
nonlocal_names: Vec::new(),
is_generator: true,
nonlocal_cell_id: None,
assigned_names: Vec::new(),
global_names: Vec::new(),
is_module_level: state.call_depth == 0,
docstring: None,
cell_refreshes: Vec::new(),
qualname: String::new(),
annotations: Vec::new(),
is_async: false,
};
Some(create_generator(state, &func_def, body, locals, touched))
}
pub(crate) fn create_generator(
state: &mut InterpreterState,
func_def: &FunctionDef,
body: Arc<Vec<Stmt>>,
locals: rustc_hash::FxHashMap<String, Value>,
touched: Vec<String>,
) -> Value {
let id = state.next_cursor_id;
state.next_cursor_id = state.next_cursor_id.wrapping_add(1);
state.generators.insert(
id,
GeneratorFrame {
func_name: func_def.name.clone(),
source: func_def.source.clone(),
body,
touched,
locals,
started: false,
finished: false,
closed: false,
send_value: Value::None,
resume_at_yield: false,
pending_throw: None,
stmt_index: 0,
for_stack: Vec::new(),
while_resume: Vec::new(),
try_stack: Vec::new(),
if_stack: Vec::new(),
with_stack: Vec::new(),
yield_from_return: None,
delegating_to: None,
},
);
Value::Generator { id }
}
pub(crate) async fn dispatch_generator_method(
state: &mut InterpreterState,
receiver: &Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
tools: &crate::tools::Tools,
) -> EvalResult {
if let Some((name, _)) = kwargs.first() {
return Err(InterpreterError::TypeError(format!(
"{method}() got an unexpected keyword argument '{name}'"
))
.into());
}
if let Value::Generator { id } = receiver {
return dispatch_suspended(state, *id, method, args, tools).await;
}
if let Value::BuiltinIter { id, .. } = receiver {
return dispatch_builtin_iter(state, receiver, *id, method, args);
}
dispatch_lazy(state, receiver, method, args)
}
async fn forward_to_delegate(
state: &mut InterpreterState,
id: u64,
sub_id: u64,
method: &str,
args: &[Value],
tools: &crate::tools::Tools,
) -> EvalResult {
let sub_method = if method == "throw" { "throw" } else { method };
let sub_result = Box::pin(dispatch_generator_method(
state,
&Value::Generator { id: sub_id },
sub_method,
args,
&indexmap::IndexMap::new(),
tools,
))
.await;
match sub_result {
Ok(v) => Ok(v),
Err(EvalError::Exception(e)) if e.type_name == "StopIteration" => {
let ret = e.args.first().cloned().unwrap_or(Value::None);
if let Some(frame) = state.generators.get_mut(&id) {
frame.delegating_to = None;
frame.yield_from_return = Some(ret);
frame.resume_at_yield = true;
}
step_generator(state, id, Value::None, tools).await
}
Err(EvalError::Exception(e)) => {
if let Some(frame) = state.generators.get_mut(&id) {
frame.delegating_to = None;
frame.pending_throw = Some(Box::new(e));
frame.resume_at_yield = true;
}
step_generator(state, id, Value::None, tools).await
}
Err(other) => Err(other),
}
}
async fn dispatch_suspended(
state: &mut InterpreterState,
id: u64,
method: &str,
args: &[Value],
tools: &crate::tools::Tools,
) -> EvalResult {
if matches!(method, "__next__" | "send" | "throw") {
let delegating_to = state.generators.get(&id).and_then(|f| f.delegating_to);
if let Some(sub_id) = delegating_to {
return forward_to_delegate(state, id, sub_id, method, args, tools).await;
}
}
match method {
"__next__" => {
if !args.is_empty() {
return Err(
InterpreterError::TypeError("__next__() takes no arguments".into()).into()
);
}
step_generator(state, id, Value::None, tools).await
}
"send" => {
let value = args.first().cloned().unwrap_or(Value::None);
let frame = state
.generators
.get(&id)
.ok_or_else(|| InterpreterError::Runtime("generator frame missing".into()))?;
if !frame.started && !matches!(value, Value::None) {
return Err(InterpreterError::TypeError(
"can't send non-None value to a just-started generator".into(),
)
.into());
}
step_generator(state, id, value, tools).await
}
"throw" => throw_into_generator(state, id, args, tools).await,
"close" => {
if let Some(sub_id) = state.generators.get(&id).and_then(|f| f.delegating_to) {
let _ = Box::pin(dispatch_generator_method(
state,
&Value::Generator { id: sub_id },
"close",
&[],
&indexmap::IndexMap::new(),
tools,
))
.await?;
if let Some(frame) = state.generators.get_mut(&id) {
frame.delegating_to = None;
}
}
let live = state
.generators
.get(&id)
.is_some_and(|f| f.started && !f.finished && !f.closed && f.resume_at_yield);
if live {
let gexit = Value::Exception(Box::new(crate::value::ExceptionValue::new(
"GeneratorExit",
"",
)));
let result =
throw_into_generator(state, id, std::slice::from_ref(&gexit), tools).await;
mark_generator_closed(state, id);
return match result {
Err(EvalError::Exception(e))
if e.type_name == "GeneratorExit" || e.type_name == "StopIteration" =>
{
Ok(Value::None)
}
Ok(_) => {
Err(InterpreterError::Runtime("generator ignored GeneratorExit".into())
.into())
}
Err(e) => Err(e),
};
}
mark_generator_closed(state, id);
Ok(Value::None)
}
"__iter__" => Ok(Value::Generator { id }),
_ => Err(InterpreterError::AttributeError(format!(
"'generator' object has no attribute '{method}'"
))
.into()),
}
}
pub(crate) async fn finalize_generators(state: &mut InterpreterState, tools: &crate::tools::Tools) {
let mut ids: Vec<u64> = state
.generators
.iter()
.filter(|(_, f)| f.started && !f.finished && !f.closed)
.map(|(id, _)| *id)
.collect();
ids.sort_unstable();
let empty = IndexMap::new();
for id in ids {
let live = state.generators.get(&id).is_some_and(|f| f.started && !f.finished && !f.closed);
if !live {
continue;
}
let _ =
dispatch_generator_method(state, &Value::Generator { id }, "close", &[], &empty, tools)
.await;
}
}
fn mark_generator_closed(state: &mut InterpreterState, id: u64) {
if let Some(frame) = state.generators.get_mut(&id) {
frame.finished = true;
frame.closed = true;
frame.for_stack.clear();
frame.try_stack.clear();
frame.with_stack.clear();
frame.while_resume.clear();
}
}
async fn throw_into_generator(
state: &mut InterpreterState,
id: u64,
args: &[Value],
tools: &crate::tools::Tools,
) -> EvalResult {
let exc = match args.first() {
Some(Value::Exception(e)) => (**e).clone(),
Some(Value::ExceptionType(n)) => {
let msg = args.get(1).map(|v| format!("{v}")).unwrap_or_default();
ExceptionValue::new(n.clone(), msg)
}
Some(other) => {
return Err(InterpreterError::TypeError(format!(
"exceptions must derive from BaseException, not '{}'",
other.type_name()
))
.into());
}
None => {
return Err(
InterpreterError::TypeError("throw() takes at least 1 argument".into()).into()
);
}
};
let live = state
.generators
.get(&id)
.is_some_and(|f| f.started && !f.finished && !f.closed && f.resume_at_yield);
if !live {
if let Some(frame) = state.generators.get_mut(&id) {
frame.finished = true;
frame.closed = true;
}
return Err(EvalError::Exception(exc));
}
if let Some(frame) = state.generators.get_mut(&id) {
frame.pending_throw = Some(Box::new(exc));
}
step_generator(state, id, Value::None, tools).await
}
async fn step_generator(
state: &mut InterpreterState,
id: u64,
send_value: Value,
tools: &crate::tools::Tools,
) -> EvalResult {
let (locals_snapshot, source, touched, body, mut stmt_index, early) = {
let frame = state
.generators
.get_mut(&id)
.ok_or_else(|| InterpreterError::Runtime("generator frame missing".into()))?;
if frame.closed || frame.finished {
return Err(EvalError::Exception(ExceptionValue::new("StopIteration", String::new())));
}
if !frame.for_stack.is_empty() && frame.started {
let top = frame
.for_stack
.last_mut()
.ok_or_else(|| InterpreterError::Runtime("for_stack empty".into()))?;
if top.target.is_empty() {
if top.pos < top.items.len() {
let v = top.items[top.pos].clone();
top.pos += 1;
if top.pos >= top.items.len() {
frame.for_stack.pop();
}
return Ok(v);
}
frame.for_stack.pop();
}
}
frame.started = true;
frame.send_value = send_value;
let locals_snapshot: Vec<(String, Value)> =
frame.locals.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
(
locals_snapshot,
frame.source.clone(),
frame.touched.clone(),
frame.body.clone(),
frame.stmt_index,
false,
)
};
let _ = early;
for (k, v) in locals_snapshot {
let _ = state.set_variable(&k, v);
}
state.body_source_stack.push(source);
state.active_generator_stack.push(id);
let result = run_generator_body(state, &body, &mut stmt_index, tools).await;
let _ = state.active_generator_stack.pop();
state.body_source_stack.pop();
if let Some(frame) = state.generators.get_mut(&id) {
frame.stmt_index = stmt_index;
for name in &touched {
if let Some(v) = state.variables.get(name) {
frame.locals.insert(name.clone(), v.clone());
}
}
}
match result {
Ok(()) => {
if let Some(frame) = state.generators.get_mut(&id) {
frame.finished = true;
}
Err(EvalError::Exception(ExceptionValue::new("StopIteration", String::new())))
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => Ok(*v),
Err(EvalError::Signal(ControlFlow::Return(v))) => {
if let Some(frame) = state.generators.get_mut(&id) {
frame.finished = true;
}
Err(EvalError::Exception(stop_iteration_with_value(*v)))
}
Err(e) => {
if let Some(frame) = state.generators.get_mut(&id) {
frame.finished = true;
}
Err(e)
}
}
}
pub(crate) fn stop_iteration_with_value(value: Value) -> ExceptionValue {
let exc = ExceptionValue::new("StopIteration", String::new());
if matches!(value, Value::None) { exc } else { exc.with_args(vec![value]) }
}
async fn run_generator_body(
state: &mut InterpreterState,
body: &[Stmt],
stmt_index: &mut usize,
tools: &crate::tools::Tools,
) -> Result<(), EvalError> {
while *stmt_index < body.len() {
let stmt = &body[*stmt_index];
if let Stmt::For(for_node) = stmt {
if let rustpython_parser::ast::Expr::Name(t) = for_node.target.as_ref() {
let target = t.id.as_str().to_string();
match run_for_suspendable(state, for_node, &target, tools).await {
Ok(()) => {
*stmt_index += 1;
continue;
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
Err(e) => return Err(e),
}
}
}
if let Stmt::While(while_node) = stmt {
match run_while_suspendable(state, while_node, tools).await {
Ok(()) => {
*stmt_index += 1;
continue;
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
Err(e) => return Err(e),
}
}
if let Stmt::Try(try_node) = stmt {
match run_try_suspendable(state, try_node, tools).await {
Ok(()) => {
*stmt_index += 1;
continue;
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
Err(e) => return Err(e),
}
}
if let Stmt::With(with_node) = stmt {
if if_branch_suspendable(&with_node.body)
&& super::definitions::contains_yield_stmts(std::slice::from_ref(stmt))
{
match run_with_suspendable(state, with_node, tools).await {
Ok(()) => {
*stmt_index += 1;
continue;
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
Err(e) => return Err(e),
}
}
}
if let Stmt::If(if_node) = stmt {
if if_branch_suspendable(&if_node.body)
&& if_branch_suspendable(&if_node.orelse)
&& super::definitions::contains_yield_stmts(std::slice::from_ref(stmt))
{
match run_if_suspendable(state, if_node, tools).await {
Ok(()) => {
*stmt_index += 1;
continue;
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
Err(e) => return Err(e),
}
}
}
match eval_stmt(state, stmt, tools).await {
Ok(_) => {
*stmt_index += 1;
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.resume_at_yield = false;
}
}
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.resume_at_yield = true;
}
}
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
Err(EvalError::Signal(ControlFlow::Return(v))) => {
return Err(EvalError::Signal(ControlFlow::Return(v)));
}
Err(e) => return Err(e),
}
}
Ok(())
}
fn upsert_for_frame(for_stack: &mut Vec<GeneratorForState>, entry: GeneratorForState) {
if let Some(slot) = for_stack.iter_mut().find(|fs| fs.target == entry.target) {
*slot = entry;
return;
}
let insert_at = for_stack.iter().rposition(|fs| !fs.target.is_empty()).map_or(0, |i| i + 1);
for_stack.insert(insert_at, entry);
}
async fn run_for_suspendable(
state: &mut InterpreterState,
node: &rustpython_parser::ast::StmtFor,
target: &str,
tools: &crate::tools::Tools,
) -> Result<(), EvalError> {
use crate::eval::eval_expr;
use crate::eval::functions::resolve_proxy;
let resume = {
let id = state.active_generator_stack.last().copied();
id.and_then(|id| state.generators.get(&id))
.and_then(|frame| frame.for_stack.iter().rev().find(|fs| fs.target == target))
.map(|fs| {
(
fs.lazy_source.clone(),
fs.current_item.clone(),
fs.items.clone(),
fs.pos,
fs.body_index,
)
})
};
let (items, mut pos, mut body_index) = match resume {
Some((Some(source), current_item, _, _, bi)) => {
return run_for_lazy(state, node, target, source, current_item, bi, tools).await;
}
Some((None, _, items, pos, bi)) => (items, pos, bi),
None => {
let iterable = resolve_proxy(&eval_expr(state, &node.iter, tools).await?).await?;
if matches!(
iterable,
Value::Generator { .. } | Value::Lazy { .. } | Value::BuiltinIter { .. }
) {
return run_for_lazy(state, node, target, iterable, None, 0, tools).await;
}
(Arc::new(crate::eval::op::iter(state, &iterable, tools).await?), 0, 0)
}
};
while pos < items.len() {
let item = items[pos].clone();
if body_index == 0 {
assign_target(state, &node.target, item, tools).await?;
}
while body_index < node.body.len() {
let stmt = &node.body[body_index];
let stmt_yields = super::definitions::contains_yield_stmts(std::slice::from_ref(stmt));
let (result, nested) = match stmt {
Stmt::If(if_node)
if stmt_yields
&& loop_nest_suspendable(&if_node.body)
&& loop_nest_suspendable(&if_node.orelse) =>
{
(run_if_suspendable(state, if_node, tools).await.map(|()| Value::None), true)
}
Stmt::While(w) if stmt_yields && while_delegatable(w) => {
(
Box::pin(run_while_suspendable(state, w, tools))
.await
.map(|()| Value::None),
true,
)
}
Stmt::For(inner) if stmt_yields && for_delegatable(inner) => {
let inner_target = for_name_target(inner).unwrap_or_default().to_string();
(
Box::pin(run_for_suspendable(state, inner, &inner_target, tools))
.await
.map(|()| Value::None),
true,
)
}
_ => (eval_stmt(state, stmt, tools).await, false),
};
match result {
Ok(_) => {
body_index += 1;
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.resume_at_yield = false;
}
}
}
Err(EvalError::Signal(ControlFlow::Continue)) => {
body_index = node.body.len(); }
Err(EvalError::Signal(ControlFlow::Break)) => {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.for_stack.retain(|fs| fs.target != target);
}
}
return Ok(());
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
if !nested {
frame.resume_at_yield = true;
}
let entry = GeneratorForState {
items: items.clone(),
pos,
body_index, target: target.to_string(),
lazy_source: None,
current_item: None,
};
upsert_for_frame(&mut frame.for_stack, entry);
}
}
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
Err(e) => return Err(e),
}
}
pos += 1;
body_index = 0;
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
if let Some(slot) = frame.for_stack.iter_mut().find(|fs| fs.target == target) {
slot.pos = pos;
slot.body_index = 0;
}
}
}
}
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.for_stack.retain(|fs| fs.target != target);
}
}
Ok(())
}
async fn run_for_lazy(
state: &mut InterpreterState,
node: &rustpython_parser::ast::StmtFor,
target: &str,
source: Value,
mut current_item: Option<Value>,
mut body_index: usize,
tools: &crate::tools::Tools,
) -> Result<(), EvalError> {
let empty = IndexMap::new();
let mut resuming = current_item.is_some();
loop {
if resuming {
resuming = false;
} else {
let next =
Box::pin(dispatch_generator_method(state, &source, "__next__", &[], &empty, tools))
.await;
let item = match next {
Ok(v) => v,
Err(EvalError::Exception(e)) if e.type_name == "StopIteration" => break,
Err(e) => return Err(e),
};
assign_target(state, &node.target, item.clone(), tools).await?;
current_item = Some(item);
body_index = 0;
}
while body_index < node.body.len() {
let stmt = &node.body[body_index];
let stmt_yields = super::definitions::contains_yield_stmts(std::slice::from_ref(stmt));
let (result, nested) = match stmt {
Stmt::If(if_node)
if stmt_yields
&& loop_nest_suspendable(&if_node.body)
&& loop_nest_suspendable(&if_node.orelse) =>
{
(run_if_suspendable(state, if_node, tools).await.map(|()| Value::None), true)
}
Stmt::While(w) if stmt_yields && while_delegatable(w) => (
Box::pin(run_while_suspendable(state, w, tools)).await.map(|()| Value::None),
true,
),
Stmt::For(inner) if stmt_yields && for_delegatable(inner) => {
let inner_target = for_name_target(inner).unwrap_or_default().to_string();
(
Box::pin(run_for_suspendable(state, inner, &inner_target, tools))
.await
.map(|()| Value::None),
true,
)
}
_ => (eval_stmt(state, stmt, tools).await, false),
};
match result {
Ok(_) => {
body_index += 1;
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.resume_at_yield = false;
}
}
}
Err(EvalError::Signal(ControlFlow::Continue)) => {
body_index = node.body.len();
}
Err(EvalError::Signal(ControlFlow::Break)) => {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.for_stack.retain(|fs| fs.target != target);
}
}
return Ok(());
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
if !nested {
frame.resume_at_yield = true;
}
let entry = GeneratorForState {
items: Arc::new(Vec::new()),
pos: 0,
body_index, target: target.to_string(),
lazy_source: Some(source.clone()),
current_item: current_item.clone(),
};
upsert_for_frame(&mut frame.for_stack, entry);
}
}
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
Err(e) => return Err(e),
}
}
body_index = 0;
}
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.for_stack.retain(|fs| fs.target != target);
}
}
Ok(())
}
async fn run_while_suspendable(
state: &mut InterpreterState,
node: &rustpython_parser::ast::StmtWhile,
tools: &crate::tools::Tools,
) -> Result<(), EvalError> {
use crate::eval::eval_expr;
let node_key = node.range.start().to_u32();
let clear_resume = move |state: &mut InterpreterState| {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.while_resume.retain(|(k, _)| *k != node_key);
}
}
};
let (mut body_index, mut resuming) = {
let saved = state
.active_generator_stack
.last()
.copied()
.and_then(|id| state.generators.get(&id))
.and_then(|frame| {
frame.while_resume.iter().rev().find(|(k, _)| *k == node_key).map(|(_, i)| *i)
});
match saved {
Some(i) => (i, true),
None => (0, false),
}
};
loop {
if !resuming {
let cond = eval_expr(state, &node.test, tools).await?;
if !crate::eval::op::truthy(state, &cond, tools).await? {
break;
}
}
resuming = false;
while body_index < node.body.len() {
let stmt = &node.body[body_index];
let stmt_yields = super::definitions::contains_yield_stmts(std::slice::from_ref(stmt));
let (result, nested) = match stmt {
Stmt::If(if_node) => {
(run_if_suspendable(state, if_node, tools).await.map(|()| Value::None), true)
}
Stmt::For(f) if stmt_yields && for_delegatable(f) => {
let target = for_name_target(f).unwrap_or_default().to_string();
(
Box::pin(run_for_suspendable(state, f, &target, tools))
.await
.map(|()| Value::None),
true,
)
}
Stmt::While(w) if stmt_yields && while_delegatable(w) => {
(
Box::pin(run_while_suspendable(state, w, tools))
.await
.map(|()| Value::None),
true,
)
}
_ => (eval_stmt(state, stmt, tools).await, false),
};
match result {
Ok(_) => {
body_index += 1;
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.resume_at_yield = false;
}
}
}
Err(EvalError::Signal(ControlFlow::Continue)) => {
body_index = node.body.len(); }
Err(EvalError::Signal(ControlFlow::Break)) => {
clear_resume(state);
return Ok(());
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
if !nested {
frame.resume_at_yield = true;
}
if let Some(slot) =
frame.while_resume.iter_mut().find(|(k, _)| *k == node_key)
{
slot.1 = body_index;
} else {
frame.while_resume.push((node_key, body_index));
}
}
}
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
Err(e) => return Err(e),
}
}
body_index = 0; }
clear_resume(state);
Ok(())
}
fn run_try_suspendable<'a>(
state: &'a mut InterpreterState,
node: &'a rustpython_parser::ast::StmtTry,
tools: &'a crate::tools::Tools,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), EvalError>> + Send + 'a>> {
Box::pin(run_try_suspendable_inner(state, node, tools))
}
async fn run_try_suspendable_inner(
state: &mut InterpreterState,
node: &rustpython_parser::ast::StmtTry,
tools: &crate::tools::Tools,
) -> Result<(), EvalError> {
use crate::state::{TryPhase, TryResume};
let resume = pop_try_resume(state);
if resume.is_none_or(|r| matches!(r.phase, TryPhase::Body)) {
let start = resume.map_or(0, |r| r.index);
match step_try_phase(state, &node.body, start, TryPhase::Body, tools).await {
TryStep::Yielded(v) => return Err(EvalError::Signal(ControlFlow::Yield(v))),
TryStep::Completed => {
match step_try_phase(state, &node.orelse, 0, TryPhase::Orelse, tools).await {
TryStep::Yielded(v) => {
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
TryStep::Completed => {
return finish_try(state, &node.finalbody, None, tools).await;
}
TryStep::Errored(e) => {
return finish_try(state, &node.finalbody, Some(e), tools).await;
}
}
}
TryStep::Errored(err) => {
return enter_or_propagate(state, node, err, tools).await;
}
}
}
if let Some(TryResume { phase: TryPhase::Handler(h), index }) = resume {
let handler = &node.handlers[h];
let rustpython_parser::ast::ExceptHandler::ExceptHandler(h_node) = handler;
match step_try_phase(state, &h_node.body, index, TryPhase::Handler(h), tools).await {
TryStep::Yielded(v) => return Err(EvalError::Signal(ControlFlow::Yield(v))),
TryStep::Completed => {
cleanup_handler(state, h_node);
return finish_try(state, &node.finalbody, None, tools).await;
}
TryStep::Errored(e) => {
cleanup_handler(state, h_node);
return finish_try(state, &node.finalbody, Some(e), tools).await;
}
}
}
match resume {
Some(TryResume { phase: TryPhase::Orelse, index }) => {
match step_try_phase(state, &node.orelse, index, TryPhase::Orelse, tools).await {
TryStep::Yielded(v) => Err(EvalError::Signal(ControlFlow::Yield(v))),
TryStep::Completed => finish_try(state, &node.finalbody, None, tools).await,
TryStep::Errored(e) => finish_try(state, &node.finalbody, Some(e), tools).await,
}
}
Some(TryResume { phase: TryPhase::Finally, index }) => {
match step_try_phase(state, &node.finalbody, index, TryPhase::Finally, tools).await {
TryStep::Yielded(v) => Err(EvalError::Signal(ControlFlow::Yield(v))),
TryStep::Completed => Ok(()),
TryStep::Errored(e) => Err(e),
}
}
_ => Ok(()),
}
}
fn run_with_suspendable<'a>(
state: &'a mut InterpreterState,
node: &'a rustpython_parser::ast::StmtWith,
tools: &'a crate::tools::Tools,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), EvalError>> + Send + 'a>> {
Box::pin(run_with_suspendable_inner(state, node, tools))
}
async fn run_with_suspendable_inner(
state: &mut InterpreterState,
node: &rustpython_parser::ast::StmtWith,
tools: &crate::tools::Tools,
) -> Result<(), EvalError> {
use crate::eval::control_flow::{call_context_method, exit_context_managers};
let (start, managers) = match pop_with_resume(state) {
Some(resume) => (resume.index, resume.managers),
None => {
let mut managers = Vec::with_capacity(node.items.len());
for item in &node.items {
let cm = crate::eval::eval_expr(state, &item.context_expr, tools).await?;
let cm = super::resolve_proxy(&cm).await?;
let entered = call_context_method(state, &cm, "__enter__", &[], tools).await?;
if let Some(var_expr) = &item.optional_vars {
crate::eval::statements::assign_target(state, var_expr, entered, tools).await?;
}
managers.push(cm);
}
(0, managers)
}
};
match step_with_body(state, &node.body, start, &managers, tools).await {
TryStep::Yielded(v) => Err(EvalError::Signal(ControlFlow::Yield(v))),
TryStep::Completed => exit_context_managers(state, managers, None, tools).await,
TryStep::Errored(e) => exit_context_managers(state, managers, Some(e), tools).await,
}
}
async fn step_with_body(
state: &mut InterpreterState,
stmts: &[Stmt],
start: usize,
managers: &[Value],
tools: &crate::tools::Tools,
) -> TryStep {
let mut i = start;
while i < stmts.len() {
let stmt_yields = super::definitions::contains_yield_stmts(std::slice::from_ref(&stmts[i]));
let (result, nested) = match &stmts[i] {
Stmt::Try(nested_try) => {
(run_try_suspendable(state, nested_try, tools).await.map(|()| Value::None), true)
}
Stmt::If(if_node)
if stmt_yields
&& if_branch_suspendable(&if_node.body)
&& if_branch_suspendable(&if_node.orelse) =>
{
(run_if_suspendable(state, if_node, tools).await.map(|()| Value::None), true)
}
Stmt::While(w) if stmt_yields && while_delegatable(w) => {
(run_while_suspendable(state, w, tools).await.map(|()| Value::None), true)
}
Stmt::For(f) if stmt_yields && for_delegatable(f) => {
let target = for_name_target(f).unwrap_or_default().to_string();
(run_for_suspendable(state, f, &target, tools).await.map(|()| Value::None), true)
}
Stmt::With(w) if stmt_yields && if_branch_suspendable(&w.body) => {
(run_with_suspendable(state, w, tools).await.map(|()| Value::None), true)
}
_ => (eval_stmt(state, &stmts[i], tools).await, false),
};
match result {
Ok(_) => {
i += 1;
clear_resume_at_yield(state);
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
push_with_resume(
state,
crate::state::WithResume { index: i, managers: managers.to_vec() },
);
if !nested {
set_resume_at_yield(state);
}
return TryStep::Yielded(v);
}
Err(other) => return TryStep::Errored(other),
}
}
TryStep::Completed
}
fn push_with_resume(state: &mut InterpreterState, resume: crate::state::WithResume) {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.with_stack.push(resume);
}
}
}
fn pop_with_resume(state: &mut InterpreterState) -> Option<crate::state::WithResume> {
let id = *state.active_generator_stack.last()?;
state.generators.get_mut(&id)?.with_stack.pop()
}
fn push_if_resume(state: &mut InterpreterState, resume: crate::state::IfResume) {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.if_stack.push(resume);
}
}
}
fn pop_if_resume(state: &mut InterpreterState) -> Option<crate::state::IfResume> {
let id = *state.active_generator_stack.last()?;
state.generators.get_mut(&id)?.if_stack.pop()
}
fn run_if_suspendable<'a>(
state: &'a mut InterpreterState,
node: &'a rustpython_parser::ast::StmtIf,
tools: &'a crate::tools::Tools,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), EvalError>> + Send + 'a>> {
Box::pin(run_if_suspendable_inner(state, node, tools))
}
async fn run_if_suspendable_inner(
state: &mut InterpreterState,
node: &rustpython_parser::ast::StmtIf,
tools: &crate::tools::Tools,
) -> Result<(), EvalError> {
let (in_orelse, start) = match pop_if_resume(state) {
Some(r) => (r.in_orelse, r.index),
None => {
let cond = crate::eval::eval_expr(state, &node.test, tools).await?;
let took_else = !crate::eval::op::truthy(state, &cond, tools).await?;
(took_else, 0)
}
};
let branch: Vec<Stmt> = if in_orelse { node.orelse.clone() } else { node.body.clone() };
let mut i = start;
while i < branch.len() {
let stmt_yields =
super::definitions::contains_yield_stmts(std::slice::from_ref(&branch[i]));
let (result, nested) = match &branch[i] {
Stmt::Try(t) => {
(run_try_suspendable(state, t, tools).await.map(|()| Value::None), true)
}
Stmt::If(n) => (run_if_suspendable(state, n, tools).await.map(|()| Value::None), true),
Stmt::While(w) if stmt_yields && while_delegatable(w) => {
(run_while_suspendable(state, w, tools).await.map(|()| Value::None), true)
}
Stmt::For(f) if stmt_yields && for_delegatable(f) => {
let target = for_name_target(f).unwrap_or_default().to_string();
(run_for_suspendable(state, f, &target, tools).await.map(|()| Value::None), true)
}
_ => (eval_stmt(state, &branch[i], tools).await, false),
};
match result {
Ok(_) => {
i += 1;
clear_resume_at_yield(state);
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
push_if_resume(state, crate::state::IfResume { in_orelse, index: i });
if !nested {
set_resume_at_yield(state);
}
return Err(EvalError::Signal(ControlFlow::Yield(v)));
}
Err(other) => return Err(other),
}
}
Ok(())
}
enum TryStep {
Completed,
Yielded(Box<Value>),
Errored(EvalError),
}
async fn step_try_phase(
state: &mut InterpreterState,
stmts: &[Stmt],
start: usize,
phase: crate::state::TryPhase,
tools: &crate::tools::Tools,
) -> TryStep {
let mut i = start;
while i < stmts.len() {
let stmt_yields = super::definitions::contains_yield_stmts(std::slice::from_ref(&stmts[i]));
let (result, nested) = match &stmts[i] {
Stmt::Try(nested_try) => {
(run_try_suspendable(state, nested_try, tools).await.map(|()| Value::None), true)
}
Stmt::If(if_node)
if stmt_yields
&& if_branch_suspendable(&if_node.body)
&& if_branch_suspendable(&if_node.orelse) =>
{
(run_if_suspendable(state, if_node, tools).await.map(|()| Value::None), true)
}
Stmt::While(w) if stmt_yields && while_delegatable(w) => {
(run_while_suspendable(state, w, tools).await.map(|()| Value::None), true)
}
Stmt::For(f) if stmt_yields && for_delegatable(f) => {
let target = for_name_target(f).unwrap_or_default().to_string();
(run_for_suspendable(state, f, &target, tools).await.map(|()| Value::None), true)
}
_ => (eval_stmt(state, &stmts[i], tools).await, false),
};
match result {
Ok(_) => {
i += 1;
clear_resume_at_yield(state);
}
Err(EvalError::Signal(ControlFlow::Yield(v))) => {
push_try_resume(state, crate::state::TryResume { phase, index: i });
if !nested {
set_resume_at_yield(state);
}
return TryStep::Yielded(v);
}
Err(other) => return TryStep::Errored(other),
}
}
TryStep::Completed
}
fn clear_resume_at_yield(state: &mut InterpreterState) {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.resume_at_yield = false;
}
}
}
fn set_resume_at_yield(state: &mut InterpreterState) {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.resume_at_yield = true;
}
}
}
fn push_try_resume(state: &mut InterpreterState, resume: crate::state::TryResume) {
if let Some(&id) = state.active_generator_stack.last() {
if let Some(frame) = state.generators.get_mut(&id) {
frame.try_stack.push(resume);
}
}
}
fn pop_try_resume(state: &mut InterpreterState) -> Option<crate::state::TryResume> {
let id = *state.active_generator_stack.last()?;
state.generators.get_mut(&id)?.try_stack.pop()
}
async fn enter_or_propagate(
state: &mut InterpreterState,
node: &rustpython_parser::ast::StmtTry,
err: EvalError,
tools: &crate::tools::Tools,
) -> Result<(), EvalError> {
use crate::eval::exceptions::{interpreter_error_to_exception_pub, matches_handler};
let exc = match &err {
EvalError::Exception(e) => e.clone(),
EvalError::Interpreter(ie) => interpreter_error_to_exception_pub(ie),
EvalError::Signal(_) => return finish_try(state, &node.finalbody, Some(err), tools).await,
};
for (h, handler) in node.handlers.iter().enumerate() {
let rustpython_parser::ast::ExceptHandler::ExceptHandler(h_node) = handler;
if !matches_handler(state, &exc, h_node, tools).await? {
continue;
}
if let Some(name) = &h_node.name {
state
.set_variable(name.as_str(), Value::Exception(Box::new(exc.clone())))
.map_err(EvalError::Interpreter)?;
}
state.active_exception_stack.push(exc.clone());
return match step_try_phase(state, &h_node.body, 0, TryPhase::Handler(h), tools).await {
TryStep::Yielded(v) => Err(EvalError::Signal(ControlFlow::Yield(v))),
TryStep::Completed => {
cleanup_handler(state, h_node);
finish_try(state, &node.finalbody, None, tools).await
}
TryStep::Errored(e) => {
cleanup_handler(state, h_node);
finish_try(state, &node.finalbody, Some(e), tools).await
}
};
}
finish_try(state, &node.finalbody, Some(err), tools).await
}
fn cleanup_handler(
state: &mut InterpreterState,
h_node: &rustpython_parser::ast::ExceptHandlerExceptHandler,
) {
state.active_exception_stack.pop();
if let Some(name) = &h_node.name {
let _ = state.delete_variable(name.as_str());
}
}
use crate::state::TryPhase;
async fn finish_try(
state: &mut InterpreterState,
finalbody: &[Stmt],
pending: Option<EvalError>,
tools: &crate::tools::Tools,
) -> Result<(), EvalError> {
match step_try_phase(state, finalbody, 0, TryPhase::Finally, tools).await {
TryStep::Yielded(v) => Err(EvalError::Signal(ControlFlow::Yield(v))),
TryStep::Completed => pending.map_or(Ok(()), Err),
TryStep::Errored(e) => Err(e),
}
}
fn dispatch_builtin_iter(
state: &mut InterpreterState,
receiver: &Value,
id: u64,
method: &str,
args: &[Value],
) -> EvalResult {
match method {
"__iter__" => Ok(receiver.clone()),
"__next__" => {
if !args.is_empty() {
return Err(
InterpreterError::TypeError("__next__() takes no arguments".into()).into()
);
}
state.step_builtin_iter(id).ok_or_else(|| {
EvalError::Exception(crate::value::ExceptionValue::new("StopIteration", ""))
})
}
_ => Err(InterpreterError::AttributeError(format!(
"'{}' object has no attribute '{method}'",
receiver.type_name()
))
.into()),
}
}
fn dispatch_lazy(
state: &mut InterpreterState,
receiver: &Value,
method: &str,
args: &[Value],
) -> EvalResult {
let Value::Lazy { items, cursor_id, .. } = receiver else {
return Err(InterpreterError::TypeError(format!(
"'{}' object has no attribute '{method}'",
receiver.type_name()
))
.into());
};
let cursor = state.lazy_cursors.get(cursor_id).copied().unwrap_or(0);
match method {
"__next__" | "send" => {
if method == "send" {
let value = args.first().cloned().unwrap_or(Value::None);
if cursor == 0 && !matches!(value, Value::None) {
return Err(InterpreterError::TypeError(
"can't send non-None value to a just-started generator".into(),
)
.into());
}
} else if !args.is_empty() {
return Err(
InterpreterError::TypeError("__next__() takes no arguments".into()).into()
);
}
if cursor < items.len() {
state.lazy_cursors.insert(*cursor_id, cursor + 1);
Ok(items[cursor].clone())
} else {
Err(EvalError::Exception(ExceptionValue::new("StopIteration", String::new())))
}
}
"throw" => {
state.lazy_cursors.insert(*cursor_id, items.len());
let (type_name, message) = match args.first() {
Some(Value::Exception(e)) => (e.type_name.clone(), e.message.clone()),
Some(Value::ExceptionType(n)) => {
let msg = args.get(1).map(|v| format!("{v}")).unwrap_or_default();
(n.clone(), msg)
}
Some(other) => (
"TypeError".into(),
format!(
"exceptions must derive from BaseException, not '{}'",
other.type_name()
),
),
None => {
return Err(InterpreterError::TypeError(
"throw() takes at least 1 argument".into(),
)
.into());
}
};
Err(EvalError::Exception(ExceptionValue::new(type_name, message)))
}
"close" => {
state.lazy_cursors.insert(*cursor_id, items.len());
Ok(Value::None)
}
"__iter__" => Ok(receiver.clone()),
_ => Err(InterpreterError::AttributeError(format!(
"'generator' object has no attribute '{method}'"
))
.into()),
}
}