use std::{collections::BTreeMap, sync::Arc};
use rustpython_parser::ast::{self};
use super::params::evaluate_param_defaults;
use crate::{
error::{EvalError, EvalResult, InterpreterError},
eval::eval_expr,
state::InterpreterState,
tools::Tools,
value::{FunctionDef, FunctionParams, LambdaDef, Param, Value},
};
pub async fn eval_function_def(
state: &mut InterpreterState,
node: &ast::StmtFunctionDef,
tools: &Tools,
) -> EvalResult {
let name = node.name.as_str();
crate::security::validator::validate_name(
crate::security::validator::NameContext::FunctionDefinition,
name,
)?;
if tools.contains_key(name) {
return Err(InterpreterError::Security(format!(
"'{name}' is not allowed to be overridden"
))
.into());
}
let mut params = build_function_params(&node.args);
evaluate_param_defaults(state, &mut params, tools).await?;
state.function_bodies.insert(name.to_string(), Arc::new(node.body.clone()));
let source = extract_function_source(&state.current_source, node);
let closure: BTreeMap<String, Value> =
state.variables.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let nonlocal_names = collect_nonlocal_names(&node.body);
let nonlocal_cell_id = if nonlocal_names.is_empty() {
None
} else {
let cell_id = state.next_nonlocal_cell_id;
state.next_nonlocal_cell_id = state.next_nonlocal_cell_id.wrapping_add(1);
let mut cell: rustc_hash::FxHashMap<String, Value> = rustc_hash::FxHashMap::default();
for n in &nonlocal_names {
if let Some(v) = state.variables.get(n) {
cell.insert(n.clone(), v.clone());
}
}
state.nonlocal_cells.insert(cell_id, cell);
if let Some(owners) = state.frame_cell_owners.last_mut() {
for n in &nonlocal_names {
owners.insert(n.clone(), cell_id);
}
}
Some(cell_id)
};
let (mut assigned_names, global_names) = collect_assigned_names(&node.body);
assigned_names.retain(|n| !nonlocal_names.contains(n) && !global_names.contains(n));
let is_module_level = state.call_depth == 0;
let is_generator = contains_yield_stmts(&node.body);
let mut func = Value::Function(std::sync::Arc::new(FunctionDef {
name: name.to_string(),
params,
closure,
source,
nonlocal_names,
is_generator,
nonlocal_cell_id,
assigned_names,
global_names,
is_module_level,
}));
for decorator in node.decorator_list.iter().rev() {
let dec_val = eval_expr(state, decorator, tools).await?;
func = crate::eval::classes::apply_decorator(state, &dec_val, func, tools).await?;
}
state.set_variable(name, func).map_err(EvalError::Interpreter)?;
Ok(Value::None)
}
pub(super) fn apply_nonlocal_cell(
state: &mut InterpreterState,
func_def: &FunctionDef,
local_scope: &std::collections::HashMap<String, Value>,
) -> Result<(), EvalError> {
let Some(cell_id) = func_def.nonlocal_cell_id else { return Ok(()) };
let Some(cell) = state.nonlocal_cells.get(&cell_id).cloned() else { return Ok(()) };
for (name, value) in cell {
if !local_scope.contains_key(&name) {
state.set_variable(&name, value).map_err(EvalError::Interpreter)?;
}
}
Ok(())
}
pub(super) fn apply_function_scope(
state: &mut InterpreterState,
func_def: &FunctionDef,
local_scope: &std::collections::HashMap<String, Value>,
) -> Result<(), EvalError> {
for (name, value) in &func_def.closure {
if local_scope.contains_key(name) || func_def.global_names.contains(name) {
continue;
}
if func_def.is_module_level && state.variables.contains_key(name) {
continue;
}
if let Some(live) = state.variables.get(name) {
if live == value {
continue;
}
}
state.set_variable(name, value.clone()).map_err(EvalError::Interpreter)?;
}
apply_nonlocal_cell(state, func_def, local_scope)?;
for (name, value) in local_scope {
state.set_variable(name, value.clone()).map_err(EvalError::Interpreter)?;
}
Ok(())
}
pub(super) fn apply_lambda_scope(
state: &mut InterpreterState,
lambda_def: &LambdaDef,
local_scope: &std::collections::HashMap<String, Value>,
) -> Result<(), EvalError> {
for (name, value) in &lambda_def.closure {
if local_scope.contains_key(name) {
continue;
}
if lambda_def.is_module_level && state.variables.contains_key(name) {
continue;
}
if let Some(live) = state.variables.get(name) {
if live == value {
continue;
}
}
state.set_variable(name, value.clone()).map_err(EvalError::Interpreter)?;
}
for (name, value) in local_scope {
state.set_variable(name, value.clone()).map_err(EvalError::Interpreter)?;
}
Ok(())
}
pub(super) fn writeback_nonlocal_cell(state: &mut InterpreterState, func_def: &FunctionDef) {
let Some(cell_id) = func_def.nonlocal_cell_id else { return };
let writeback: Vec<(String, Value)> = func_def
.nonlocal_names
.iter()
.filter_map(|n| state.variables.get(n).map(|v| (n.clone(), v.clone())))
.collect();
if let Some(cell) = state.nonlocal_cells.get_mut(&cell_id) {
for (n, v) in writeback {
cell.insert(n, v);
}
}
}
fn collect_nonlocal_names(body: &[ast::Stmt]) -> Vec<String> {
let mut names = Vec::new();
collect_nonlocal_names_inner(body, &mut names);
names
}
fn collect_nonlocal_names_inner(body: &[ast::Stmt], out: &mut Vec<String>) {
for stmt in body {
match stmt {
ast::Stmt::Nonlocal(node) => {
for ident in &node.names {
let n = ident.as_str().to_string();
if !out.contains(&n) {
out.push(n);
}
}
}
ast::Stmt::If(node) => {
collect_nonlocal_names_inner(&node.body, out);
collect_nonlocal_names_inner(&node.orelse, out);
}
ast::Stmt::For(node) => {
collect_nonlocal_names_inner(&node.body, out);
collect_nonlocal_names_inner(&node.orelse, out);
}
ast::Stmt::While(node) => {
collect_nonlocal_names_inner(&node.body, out);
collect_nonlocal_names_inner(&node.orelse, out);
}
ast::Stmt::With(node) => {
collect_nonlocal_names_inner(&node.body, out);
}
ast::Stmt::Try(node) => {
collect_nonlocal_names_inner(&node.body, out);
collect_nonlocal_names_inner(&node.orelse, out);
collect_nonlocal_names_inner(&node.finalbody, out);
for handler in &node.handlers {
let ast::ExceptHandler::ExceptHandler(h) = handler;
collect_nonlocal_names_inner(&h.body, out);
}
}
_ => {}
}
}
}
pub(crate) fn collect_assigned_names(body: &[ast::Stmt]) -> (Vec<String>, Vec<String>) {
let mut assigned = Vec::new();
let mut globals = Vec::new();
collect_assigned_names_inner(body, &mut assigned, &mut globals);
(assigned, globals)
}
fn push_unique(out: &mut Vec<String>, name: &str) {
let s = name.to_string();
if !out.contains(&s) {
out.push(s);
}
}
fn collect_target_names(target: &ast::Expr, out: &mut Vec<String>) {
match target {
ast::Expr::Name(n) => push_unique(out, n.id.as_str()),
ast::Expr::Tuple(t) => {
for elt in &t.elts {
collect_target_names(elt, out);
}
}
ast::Expr::List(l) => {
for elt in &l.elts {
collect_target_names(elt, out);
}
}
ast::Expr::Starred(s) => collect_target_names(&s.value, out),
_ => {}
}
}
fn collect_walrus_targets(expr: &ast::Expr, out: &mut Vec<String>) {
match expr {
ast::Expr::NamedExpr(node) => {
collect_target_names(&node.target, out);
collect_walrus_targets(&node.value, out);
}
ast::Expr::BoolOp(node) => {
for v in &node.values {
collect_walrus_targets(v, out);
}
}
ast::Expr::BinOp(node) => {
collect_walrus_targets(&node.left, out);
collect_walrus_targets(&node.right, out);
}
ast::Expr::UnaryOp(node) => collect_walrus_targets(&node.operand, out),
ast::Expr::IfExp(node) => {
collect_walrus_targets(&node.test, out);
collect_walrus_targets(&node.body, out);
collect_walrus_targets(&node.orelse, out);
}
ast::Expr::Compare(node) => {
collect_walrus_targets(&node.left, out);
for c in &node.comparators {
collect_walrus_targets(c, out);
}
}
ast::Expr::Call(node) => {
collect_walrus_targets(&node.func, out);
for a in &node.args {
collect_walrus_targets(a, out);
}
for kw in &node.keywords {
collect_walrus_targets(&kw.value, out);
}
}
ast::Expr::Attribute(node) => collect_walrus_targets(&node.value, out),
ast::Expr::Subscript(node) => {
collect_walrus_targets(&node.value, out);
collect_walrus_targets(&node.slice, out);
}
ast::Expr::Starred(node) => collect_walrus_targets(&node.value, out),
ast::Expr::Tuple(node) => {
for e in &node.elts {
collect_walrus_targets(e, out);
}
}
ast::Expr::List(node) => {
for e in &node.elts {
collect_walrus_targets(e, out);
}
}
ast::Expr::Set(node) => {
for e in &node.elts {
collect_walrus_targets(e, out);
}
}
ast::Expr::Dict(node) => {
for k in node.keys.iter().flatten() {
collect_walrus_targets(k, out);
}
for v in &node.values {
collect_walrus_targets(v, out);
}
}
ast::Expr::FormattedValue(node) => {
collect_walrus_targets(&node.value, out);
if let Some(fmt) = &node.format_spec {
collect_walrus_targets(fmt, out);
}
}
ast::Expr::JoinedStr(node) => {
for v in &node.values {
collect_walrus_targets(v, out);
}
}
ast::Expr::Slice(node) => {
if let Some(l) = &node.lower {
collect_walrus_targets(l, out);
}
if let Some(u) = &node.upper {
collect_walrus_targets(u, out);
}
if let Some(s) = &node.step {
collect_walrus_targets(s, out);
}
}
ast::Expr::Yield(node) => {
if let Some(v) = &node.value {
collect_walrus_targets(v, out);
}
}
ast::Expr::YieldFrom(node) => collect_walrus_targets(&node.value, out),
ast::Expr::Await(node) => collect_walrus_targets(&node.value, out),
ast::Expr::ListComp(node) => {
collect_walrus_targets(&node.elt, out);
for g in &node.generators {
collect_walrus_targets(&g.iter, out);
for c in &g.ifs {
collect_walrus_targets(c, out);
}
}
}
ast::Expr::SetComp(node) => {
collect_walrus_targets(&node.elt, out);
for g in &node.generators {
collect_walrus_targets(&g.iter, out);
for c in &g.ifs {
collect_walrus_targets(c, out);
}
}
}
ast::Expr::DictComp(node) => {
collect_walrus_targets(&node.key, out);
collect_walrus_targets(&node.value, out);
for g in &node.generators {
collect_walrus_targets(&g.iter, out);
for c in &g.ifs {
collect_walrus_targets(c, out);
}
}
}
ast::Expr::GeneratorExp(node) => {
collect_walrus_targets(&node.elt, out);
for g in &node.generators {
collect_walrus_targets(&g.iter, out);
for c in &g.ifs {
collect_walrus_targets(c, out);
}
}
}
_ => {}
}
}
fn collect_assigned_names_inner(
body: &[ast::Stmt],
assigned: &mut Vec<String>,
globals: &mut Vec<String>,
) {
for stmt in body {
match stmt {
ast::Stmt::Global(node) => {
for ident in &node.names {
push_unique(globals, ident.as_str());
}
}
ast::Stmt::Assign(node) => {
for target in &node.targets {
collect_target_names(target, assigned);
}
collect_walrus_targets(&node.value, assigned);
}
ast::Stmt::AugAssign(node) => {
collect_target_names(&node.target, assigned);
collect_walrus_targets(&node.value, assigned);
}
ast::Stmt::AnnAssign(node) => {
collect_target_names(&node.target, assigned);
if let Some(v) = &node.value {
collect_walrus_targets(v, assigned);
}
}
ast::Stmt::Delete(node) => {
for target in &node.targets {
collect_target_names(target, assigned);
}
}
ast::Stmt::Expr(node) => collect_walrus_targets(&node.value, assigned),
ast::Stmt::Return(node) => {
if let Some(v) = &node.value {
collect_walrus_targets(v, assigned);
}
}
ast::Stmt::Raise(node) => {
if let Some(exc) = &node.exc {
collect_walrus_targets(exc, assigned);
}
if let Some(cause) = &node.cause {
collect_walrus_targets(cause, assigned);
}
}
ast::Stmt::Assert(node) => {
collect_walrus_targets(&node.test, assigned);
if let Some(msg) = &node.msg {
collect_walrus_targets(msg, assigned);
}
}
ast::Stmt::For(node) => {
collect_target_names(&node.target, assigned);
collect_walrus_targets(&node.iter, assigned);
collect_assigned_names_inner(&node.body, assigned, globals);
collect_assigned_names_inner(&node.orelse, assigned, globals);
}
ast::Stmt::AsyncFor(node) => {
collect_target_names(&node.target, assigned);
collect_walrus_targets(&node.iter, assigned);
collect_assigned_names_inner(&node.body, assigned, globals);
collect_assigned_names_inner(&node.orelse, assigned, globals);
}
ast::Stmt::While(node) => {
collect_walrus_targets(&node.test, assigned);
collect_assigned_names_inner(&node.body, assigned, globals);
collect_assigned_names_inner(&node.orelse, assigned, globals);
}
ast::Stmt::If(node) => {
collect_walrus_targets(&node.test, assigned);
collect_assigned_names_inner(&node.body, assigned, globals);
collect_assigned_names_inner(&node.orelse, assigned, globals);
}
ast::Stmt::With(node) => {
for item in &node.items {
collect_walrus_targets(&item.context_expr, assigned);
if let Some(target) = &item.optional_vars {
collect_target_names(target, assigned);
}
}
collect_assigned_names_inner(&node.body, assigned, globals);
}
ast::Stmt::AsyncWith(node) => {
for item in &node.items {
collect_walrus_targets(&item.context_expr, assigned);
if let Some(target) = &item.optional_vars {
collect_target_names(target, assigned);
}
}
collect_assigned_names_inner(&node.body, assigned, globals);
}
ast::Stmt::Try(node) => {
collect_assigned_names_inner(&node.body, assigned, globals);
collect_assigned_names_inner(&node.orelse, assigned, globals);
collect_assigned_names_inner(&node.finalbody, assigned, globals);
for handler in &node.handlers {
let ast::ExceptHandler::ExceptHandler(h) = handler;
if let Some(name) = &h.name {
push_unique(assigned, name.as_str());
}
if let Some(t) = &h.type_ {
collect_walrus_targets(t, assigned);
}
collect_assigned_names_inner(&h.body, assigned, globals);
}
}
ast::Stmt::Import(node) => {
for alias in &node.names {
let name = alias.asname.as_ref().map_or_else(
|| {
alias
.name
.as_str()
.split('.')
.next()
.unwrap_or(alias.name.as_str())
.to_string()
},
|asname| asname.as_str().to_string(),
);
push_unique(assigned, &name);
}
}
ast::Stmt::ImportFrom(node) => {
for alias in &node.names {
if alias.name.as_str() == "*" {
continue;
}
let name = alias.asname.as_ref().map_or_else(
|| alias.name.as_str().to_string(),
|a| a.as_str().to_string(),
);
push_unique(assigned, &name);
}
}
ast::Stmt::FunctionDef(node) => {
push_unique(assigned, node.name.as_str());
}
ast::Stmt::AsyncFunctionDef(node) => {
push_unique(assigned, node.name.as_str());
}
ast::Stmt::ClassDef(node) => {
push_unique(assigned, node.name.as_str());
}
_ => {}
}
}
}
pub(crate) struct VariableCheckpoint {
snapshots: Vec<(String, Option<Value>)>,
}
impl VariableCheckpoint {
pub(crate) fn capture<I: IntoIterator<Item = String>>(
state: &InterpreterState,
names: I,
) -> Self {
let snapshots: Vec<(String, Option<Value>)> = names
.into_iter()
.map(|n| {
let prev = state.variables.get(&n).cloned();
(n, prev)
})
.collect();
Self { snapshots }
}
pub(crate) fn restore(self, state: &mut InterpreterState) {
for (name, prev) in self.snapshots {
match prev {
Some(v) => {
state.variables.insert(name, v);
}
None => {
state.variables.remove(&name);
}
}
}
}
}
pub(crate) fn extract_function_source(source: &str, node: &ast::StmtFunctionDef) -> String {
use rustpython_parser::text_size::TextRange;
let range: TextRange = node.range;
let start = range.start().to_usize();
let end = range.end().to_usize();
if start < source.len() && end <= source.len() && start < end {
source[start..end].to_string()
} else {
format!("def {}(): pass", node.name)
}
}
pub fn build_function_params(args: &ast::Arguments) -> FunctionParams {
let positional: Vec<Param> = args
.posonlyargs
.iter()
.chain(args.args.iter())
.map(|awd| Param { name: awd.def.arg.as_str().to_string() })
.collect();
let all_args_with_default: Vec<&ast::ArgWithDefault> =
args.posonlyargs.iter().chain(args.args.iter()).collect();
let mut defaults: Vec<String> = Vec::new();
for awd in &all_args_with_default {
if let Some(ref default_expr) = awd.default {
defaults.push(unparse_expr(default_expr));
}
}
let kwonlyargs: Vec<Param> = args
.kwonlyargs
.iter()
.map(|awd| Param { name: awd.def.arg.as_str().to_string() })
.collect();
let kw_defaults: Vec<Option<String>> =
args.kwonlyargs.iter().map(|awd| awd.default.as_ref().map(|d| unparse_expr(d))).collect();
let vararg = args.vararg.as_ref().map(|a| a.arg.as_str().to_string());
let kwarg = args.kwarg.as_ref().map(|a| a.arg.as_str().to_string());
FunctionParams {
args: positional,
defaults,
default_values: Vec::new(),
vararg,
kwonlyargs,
kw_defaults,
kw_default_values: Vec::new(),
kwarg,
}
}
fn unparse_expr(expr: &ast::Expr) -> String {
match expr {
ast::Expr::Constant(c) => match &c.value {
ast::Constant::None => "None".to_string(),
ast::Constant::Bool(true) => "True".to_string(),
ast::Constant::Bool(false) => "False".to_string(),
ast::Constant::Int(i) => format!("{i}"),
ast::Constant::Float(f) => {
if f.fract() == 0.0 && f.is_finite() {
format!("{f:.1}")
} else {
format!("{f}")
}
}
ast::Constant::Str(s) => format!("'{}'", s.replace('\\', "\\\\").replace('\'', "\\'")),
ast::Constant::Bytes(b) => format!("b'{}'", String::from_utf8_lossy(b)),
ast::Constant::Ellipsis => "...".to_string(),
ast::Constant::Tuple(items) => {
let parts: Vec<String> = items
.iter()
.map(|c| {
unparse_expr(&ast::Expr::Constant(ast::ExprConstant {
range: rustpython_parser::text_size::TextRange::default(),
value: c.clone(),
kind: None,
}))
})
.collect();
format!("({})", parts.join(", "))
}
ast::Constant::Complex { real, imag } => format!("complex({real}, {imag})"),
},
ast::Expr::Name(n) => n.id.to_string(),
ast::Expr::List(l) => {
let parts: Vec<String> = l.elts.iter().map(unparse_expr).collect();
format!("[{}]", parts.join(", "))
}
ast::Expr::Tuple(t) => {
let parts: Vec<String> = t.elts.iter().map(unparse_expr).collect();
if parts.len() == 1 {
format!("({},)", parts[0])
} else {
format!("({})", parts.join(", "))
}
}
ast::Expr::Dict(d) => {
let parts: Vec<String> = d
.keys
.iter()
.zip(d.values.iter())
.map(|(k, v)| {
k.as_ref().map_or_else(
|| format!("**{}", unparse_expr(v)),
|key| format!("{}: {}", unparse_expr(key), unparse_expr(v)),
)
})
.collect();
format!("{{{}}}", parts.join(", "))
}
ast::Expr::UnaryOp(u) => {
let op = match u.op {
ast::UnaryOp::USub => "-",
ast::UnaryOp::UAdd => "+",
ast::UnaryOp::Not => "not ",
ast::UnaryOp::Invert => "~",
};
format!("{op}{}", unparse_expr(&u.operand))
}
ast::Expr::BinOp(b) => {
let op = match b.op {
ast::Operator::Add => "+",
ast::Operator::Sub => "-",
ast::Operator::Mult => "*",
ast::Operator::Div => "/",
ast::Operator::FloorDiv => "//",
ast::Operator::Mod => "%",
ast::Operator::Pow => "**",
ast::Operator::LShift => "<<",
ast::Operator::RShift => ">>",
ast::Operator::BitOr => "|",
ast::Operator::BitXor => "^",
ast::Operator::BitAnd => "&",
ast::Operator::MatMult => "@",
};
format!("({} {op} {})", unparse_expr(&b.left), unparse_expr(&b.right))
}
ast::Expr::Call(c) => {
let func = unparse_expr(&c.func);
let mut arg_strs: Vec<String> = c.args.iter().map(unparse_expr).collect();
for kw in &c.keywords {
if let Some(ref name) = kw.arg {
arg_strs.push(format!("{}={}", name, unparse_expr(&kw.value)));
} else {
arg_strs.push(format!("**{}", unparse_expr(&kw.value)));
}
}
format!("{func}({})", arg_strs.join(", "))
}
_ => format!("None # unparseable: {:?}", std::mem::discriminant(expr)),
}
}
pub async fn eval_lambda_def(
state: &mut InterpreterState,
node: &ast::ExprLambda,
tools: &Tools,
) -> EvalResult {
let mut params = build_lambda_params(&node.args);
evaluate_param_defaults(state, &mut params, tools).await?;
let lambda_id = format!("__lambda_{}", state.lambda_bodies.len());
state.lambda_bodies.insert(lambda_id.clone(), Arc::new((*node.body).clone()));
let source = extract_lambda_source(&state.current_source, node);
let closure: BTreeMap<String, Value> =
state.variables.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let mut assigned_names = Vec::new();
collect_walrus_targets(&node.body, &mut assigned_names);
let is_module_level = state.call_depth == 0;
Ok(Value::Lambda(std::sync::Arc::new(LambdaDef {
params,
lambda_id,
source,
closure,
assigned_names,
is_module_level,
})))
}
fn extract_lambda_source(source: &str, node: &ast::ExprLambda) -> String {
use rustpython_parser::text_size::TextRange;
let range: TextRange = node.range;
let start = range.start().to_usize();
let end = range.end().to_usize();
if start < source.len() && end <= source.len() && start < end {
source[start..end].to_string()
} else {
"lambda: None".to_string()
}
}
fn build_lambda_params(args: &ast::Arguments) -> FunctionParams {
build_function_params(args)
}
pub(crate) fn contains_yield_stmts(stmts: &[ast::Stmt]) -> bool {
stmts.iter().any(contains_yield_stmt)
}
fn contains_yield_stmt(stmt: &ast::Stmt) -> bool {
use ast::Stmt;
match stmt {
Stmt::Expr(e) => contains_yield_expr(&e.value),
Stmt::Assign(a) => {
contains_yield_expr(&a.value) || a.targets.iter().any(contains_yield_expr)
}
Stmt::AugAssign(a) => contains_yield_expr(&a.value) || contains_yield_expr(&a.target),
Stmt::AnnAssign(a) => a.value.as_deref().is_some_and(contains_yield_expr),
Stmt::Return(r) => r.value.as_deref().is_some_and(contains_yield_expr),
Stmt::If(node) => {
contains_yield_expr(&node.test)
|| contains_yield_stmts(&node.body)
|| contains_yield_stmts(&node.orelse)
}
Stmt::For(node) => {
contains_yield_expr(&node.iter)
|| contains_yield_stmts(&node.body)
|| contains_yield_stmts(&node.orelse)
}
Stmt::While(node) => {
contains_yield_expr(&node.test)
|| contains_yield_stmts(&node.body)
|| contains_yield_stmts(&node.orelse)
}
Stmt::Try(node) => {
contains_yield_stmts(&node.body)
|| contains_yield_stmts(&node.orelse)
|| contains_yield_stmts(&node.finalbody)
|| node.handlers.iter().any(|h| match h {
ast::ExceptHandler::ExceptHandler(eh) => contains_yield_stmts(&eh.body),
})
}
Stmt::With(node) => contains_yield_stmts(&node.body),
Stmt::Match(node) => node.cases.iter().any(|c| contains_yield_stmts(&c.body)),
Stmt::Raise(node) => {
node.exc.as_deref().is_some_and(contains_yield_expr)
|| node.cause.as_deref().is_some_and(contains_yield_expr)
}
_ => false,
}
}
pub(super) fn contains_yield_expr(expr: &ast::Expr) -> bool {
use ast::Expr;
match expr {
Expr::Yield(_) | Expr::YieldFrom(_) => true,
Expr::BoolOp(node) => node.values.iter().any(contains_yield_expr),
Expr::BinOp(node) => contains_yield_expr(&node.left) || contains_yield_expr(&node.right),
Expr::UnaryOp(node) => contains_yield_expr(&node.operand),
Expr::IfExp(node) => {
contains_yield_expr(&node.test)
|| contains_yield_expr(&node.body)
|| contains_yield_expr(&node.orelse)
}
Expr::Compare(node) => {
contains_yield_expr(&node.left) || node.comparators.iter().any(contains_yield_expr)
}
Expr::Call(node) => {
contains_yield_expr(&node.func)
|| node.args.iter().any(contains_yield_expr)
|| node.keywords.iter().any(|kw| contains_yield_expr(&kw.value))
}
Expr::Attribute(node) => contains_yield_expr(&node.value),
Expr::Subscript(node) => {
contains_yield_expr(&node.value) || contains_yield_expr(&node.slice)
}
Expr::Starred(node) => contains_yield_expr(&node.value),
Expr::Tuple(node) => node.elts.iter().any(contains_yield_expr),
Expr::List(node) => node.elts.iter().any(contains_yield_expr),
Expr::Set(node) => node.elts.iter().any(contains_yield_expr),
Expr::Dict(node) => {
node.values.iter().any(contains_yield_expr)
|| node.keys.iter().any(|k| k.as_ref().is_some_and(contains_yield_expr))
}
Expr::JoinedStr(node) => node.values.iter().any(contains_yield_expr),
Expr::FormattedValue(node) => contains_yield_expr(&node.value),
Expr::NamedExpr(node) => contains_yield_expr(&node.value),
_ => false,
}
}