#![expect(
clippy::cast_precision_loss,
reason = "Python's numeric tower coerces Int -> Float for mixed arithmetic and for certain \
integer ops (negative exponents, very large exponents, int->float shifts at the \
boundary); these coercions lose precision above 2^53, which matches CPython's \
`float(int)` behavior exactly — the loss is the specified semantic. Scoping \
the allow to this arithmetic module keeps it from sliding elsewhere"
)]
#![expect(
clippy::float_cmp,
reason = "Python's `==` on floats uses exact bit equality (with IEEE NaN oddity); \
`3.0 == 3.0` is a supported Python operation and users rely on it. We cannot \
fold float comparisons into an epsilon-based check without changing visible \
language semantics"
)]
use rustpython_parser::ast;
use crate::{
error::{EvalError, EvalResult, InterpreterError},
eval::{eval_expr, functions::resolve_proxy},
state::InterpreterState,
tools::Tools,
value::{Value, shared_list},
};
pub(crate) const MAX_COLLECTION_SIZE: usize = 10_000_000;
pub(crate) const MAX_STRING_SIZE: usize = 100 * 1024 * 1024;
fn repeat_count(n: i64) -> usize {
usize::try_from(n).unwrap_or(usize::MAX)
}
pub async fn eval_binop(
state: &mut InterpreterState,
node: &ast::ExprBinOp,
tools: &Tools,
) -> EvalResult {
state.enter_expr().map_err(EvalError::Interpreter)?;
let out = eval_binop_inner(state, node, tools).await;
state.exit_expr();
out
}
async fn eval_binop_inner(
state: &mut InterpreterState,
node: &ast::ExprBinOp,
tools: &Tools,
) -> EvalResult {
let left = match crate::eval::try_eval_expr_sync(state, &node.left, tools) {
Some(r) => r?,
None => eval_expr(state, &node.left, tools).await?,
};
let left = resolve_proxy(&left).await?;
let right = match crate::eval::try_eval_expr_sync(state, &node.right, tools) {
Some(r) => r?,
None => eval_expr(state, &node.right, tools).await?,
};
let right = resolve_proxy(&right).await?;
if let (Value::Int(a), Value::Int(b)) = (&left, &right) {
match node.op {
ast::Operator::Add => {
return Ok(match a.checked_add(*b) {
Some(v) => Value::Int(v),
None => crate::value::int_from_bigint(
num_bigint::BigInt::from(*a) + num_bigint::BigInt::from(*b),
),
});
}
ast::Operator::Sub => {
return Ok(match a.checked_sub(*b) {
Some(v) => Value::Int(v),
None => crate::value::int_from_bigint(
num_bigint::BigInt::from(*a) - num_bigint::BigInt::from(*b),
),
});
}
ast::Operator::Mult => {
return Ok(match a.checked_mul(*b) {
Some(v) => Value::Int(v),
None => crate::value::int_from_bigint(
num_bigint::BigInt::from(*a) * num_bigint::BigInt::from(*b),
),
});
}
_ => {}
}
}
crate::eval::op::binop(state, node.op, &left, &right, tools).await
}
pub fn apply_binop(
left: &Value,
right: &Value,
op: ast::Operator,
decimal_prec: i64,
max_int_bits: u64,
) -> Result<Value, EvalError> {
let left_set = dictview_as_set(left);
let right_set = dictview_as_set(right);
let left = left_set.as_ref().unwrap_or(left);
let right = right_set.as_ref().unwrap_or(right);
match op {
ast::Operator::Add => {
crate::types::dispatch_binop(crate::types::BinOp::Add, left, right, decimal_prec)
}
ast::Operator::Sub => {
crate::types::dispatch_binop(crate::types::BinOp::Sub, left, right, decimal_prec)
}
ast::Operator::Mult => {
crate::types::dispatch_binop(crate::types::BinOp::Mul, left, right, decimal_prec)
}
ast::Operator::Div => {
crate::types::dispatch_binop(crate::types::BinOp::Div, left, right, decimal_prec)
}
ast::Operator::FloorDiv => {
crate::types::dispatch_binop(crate::types::BinOp::FloorDiv, left, right, decimal_prec)
}
ast::Operator::Mod => {
crate::types::dispatch_binop(crate::types::BinOp::Mod, left, right, decimal_prec)
}
ast::Operator::Pow => {
if matches!(left, Value::Int(_) | Value::BigInt(_) | Value::Bool(_) | Value::Float(_))
&& matches!(
right,
Value::Int(_) | Value::BigInt(_) | Value::Bool(_) | Value::Float(_)
)
{
pow_values(left, right, max_int_bits)
} else {
crate::types::dispatch_binop(crate::types::BinOp::Pow, left, right, decimal_prec)
}
}
ast::Operator::LShift => lshift_values(left, right, max_int_bits),
ast::Operator::RShift => rshift_values(left, right, max_int_bits),
ast::Operator::BitOr => bitor_values(left, right),
ast::Operator::BitXor => bitxor_values(left, right),
ast::Operator::BitAnd => bitand_values(left, right),
ast::Operator::MatMult => matmult_values(left, right),
}
}
fn dictview_as_set(value: &Value) -> Option<Value> {
let Value::DictView { dict, kind } = value else { return None };
let guard = dict.lock();
let items: Vec<Value> = match kind {
crate::value::DictViewKind::Keys => {
guard.keys().map(crate::value::ValueKey::to_value).collect()
}
crate::value::DictViewKind::Items => {
guard.iter().map(|(k, v)| Value::Tuple(vec![k.to_value(), v.clone()])).collect()
}
crate::value::DictViewKind::Values => return None,
};
Some(Value::Set(crate::value::shared_set(crate::pyset::SetBody::from_items(items))))
}
pub fn apply_binop_builtin(
op: crate::types::BinOp,
left: &Value,
right: &Value,
) -> Result<Value, EvalError> {
let left_unwrapped = unwrap_enum_for_arith(left);
let right_unwrapped = unwrap_enum_for_arith(right);
if !std::ptr::eq(left_unwrapped, left) || !std::ptr::eq(right_unwrapped, right) {
return apply_binop_builtin(op, left_unwrapped, right_unwrapped);
}
match op {
crate::types::BinOp::Add => add_values(left, right),
crate::types::BinOp::Sub => sub_values(left, right),
crate::types::BinOp::Mul => mult_values(left, right),
crate::types::BinOp::Div => div_values(left, right),
crate::types::BinOp::FloorDiv => floordiv_values(left, right),
crate::types::BinOp::Mod => mod_values(left, right),
crate::types::BinOp::Pow => pow_values(left, right, 1_048_576),
}
}
fn unwrap_enum_for_arith(value: &Value) -> &Value {
match value {
Value::EnumMember {
value: inner,
kind: crate::value::EnumKind::Int | crate::value::EnumKind::Str,
..
} => inner.as_ref(),
_ => value,
}
}
fn to_float(v: &Value) -> Result<f64, EvalError> {
match v {
Value::Int(i) => Ok(*i as f64),
Value::BigInt(b) => {
use num_traits::ToPrimitive as _;
b.to_f64().ok_or_else(|| {
EvalError::Exception(crate::value::ExceptionValue::new(
"OverflowError",
"int too large to convert to float",
))
})
}
Value::Float(f) => Ok(*f),
Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
_ => Err(InterpreterError::TypeError(format!(
"unsupported operand type for numeric operation: '{}'",
v.type_name()
))
.into()),
}
}
fn to_int(v: &Value) -> Result<i64, EvalError> {
crate::value::value_as_i64(v).ok_or_else(|| {
if matches!(v, Value::BigInt(_)) {
EvalError::Exception(crate::value::ExceptionValue::new(
"OverflowError",
"Python int too large to convert to C long",
))
} else {
InterpreterError::TypeError(format!(
"unsupported operand type for integer operation: '{}'",
v.type_name()
))
.into()
}
})
}
fn require_int_operands(op: &str, left: &Value, right: &Value) -> Result<(), EvalError> {
if crate::value::value_as_bigint(left).is_none()
|| crate::value::value_as_bigint(right).is_none()
{
return Err(InterpreterError::TypeError(format!(
"unsupported operand type(s) for {op}: '{}' and '{}'",
left.type_name(),
right.type_name()
))
.into());
}
Ok(())
}
fn to_bigint(v: &Value) -> Result<num_bigint::BigInt, EvalError> {
crate::value::value_as_bigint(v).ok_or_else(|| {
InterpreterError::TypeError(format!(
"unsupported operand type for integer operation: '{}'",
v.type_name()
))
.into()
})
}
fn int_add(left: &Value, right: &Value) -> Result<Value, EvalError> {
if let (Value::Int(a), Value::Int(b)) = (left, right) {
if let Some(v) = a.checked_add(*b) {
return Ok(Value::Int(v));
}
}
Ok(crate::value::int_from_bigint(to_bigint(left)? + to_bigint(right)?))
}
fn int_sub(left: &Value, right: &Value) -> Result<Value, EvalError> {
if let (Value::Int(a), Value::Int(b)) = (left, right) {
if let Some(v) = a.checked_sub(*b) {
return Ok(Value::Int(v));
}
}
Ok(crate::value::int_from_bigint(to_bigint(left)? - to_bigint(right)?))
}
fn int_mul(left: &Value, right: &Value) -> Result<Value, EvalError> {
if let (Value::Int(a), Value::Int(b)) = (left, right) {
if let Some(v) = a.checked_mul(*b) {
return Ok(Value::Int(v));
}
}
Ok(crate::value::int_from_bigint(to_bigint(left)? * to_bigint(right)?))
}
const fn either_is_float(left: &Value, right: &Value) -> bool {
matches!(left, Value::Float(_)) || matches!(right, Value::Float(_))
}
fn add_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
match (left, right) {
(Value::String(a), Value::String(b)) => Ok(Value::String(format!("{a}{b}").into())),
(Value::Bytes(a), Value::Bytes(b)) => {
let mut result = a.clone();
result.extend_from_slice(b);
Ok(Value::Bytes(result))
}
(Value::ByteArray(a), Value::Bytes(b)) => {
let mut result = a.lock().clone();
result.extend_from_slice(b);
Ok(Value::ByteArray(crate::value::shared_bytes(result)))
}
(Value::ByteArray(a), Value::ByteArray(b)) => {
let mut result = a.lock().clone();
result.extend_from_slice(&b.lock());
Ok(Value::ByteArray(crate::value::shared_bytes(result)))
}
(Value::Bytes(a), Value::ByteArray(b)) => {
let mut result = a.clone();
result.extend_from_slice(&b.lock());
Ok(Value::Bytes(result))
}
(Value::List(a), Value::List(b)) => {
let a_snapshot = a.lock().clone();
let b_snapshot = b.lock().clone();
let mut result = Vec::with_capacity(a_snapshot.len() + b_snapshot.len());
result.extend(a_snapshot);
result.extend(b_snapshot);
Ok(Value::List(shared_list(result)))
}
(Value::Tuple(a), Value::Tuple(b)) => {
let mut result = a.clone();
result.extend(b.iter().cloned());
Ok(Value::Tuple(result))
}
_ => {
if either_is_float(left, right) {
Ok(Value::Float(to_float(left)? + to_float(right)?))
} else {
int_add(left, right)
}
}
}
}
fn set_like_body(v: &Value) -> Option<crate::pyset::SetBody> {
match v {
Value::Set(s) => Some(s.lock().clone()),
Value::Frozenset(f) => Some((**f).clone()),
_ => None,
}
}
fn wrap_set_body(left: &Value, body: crate::pyset::SetBody) -> Value {
if matches!(left, Value::Frozenset(_)) {
Value::Frozenset(std::sync::Arc::new(body))
} else {
Value::Set(crate::value::shared_set(body))
}
}
fn sub_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
if let (Some(a), Some(b)) = (set_like_body(left), set_like_body(right)) {
return Ok(wrap_set_body(left, a.difference_with(&b)));
}
if either_is_float(left, right) {
Ok(Value::Float(to_float(left)? - to_float(right)?))
} else {
int_sub(left, right)
}
}
fn mult_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
match (left, right) {
(Value::String(s), _) if matches!(right, Value::Int(_) | Value::Bool(_)) => {
let n = to_int(right)?;
if n <= 0 {
return Ok(Value::String("".into()));
}
let result_size = s.len().saturating_mul(repeat_count(n));
if result_size > MAX_STRING_SIZE {
return Err(InterpreterError::LimitExceeded(format!(
"string repetition would create {result_size} bytes (limit: {MAX_STRING_SIZE})"
))
.into());
}
Ok(Value::String(s.repeat(repeat_count(n))))
}
(Value::Int(_) | Value::Bool(_), Value::String(s)) => {
let n = to_int(left)?;
if n <= 0 {
return Ok(Value::String("".into()));
}
let result_size = s.len().saturating_mul(repeat_count(n));
if result_size > MAX_STRING_SIZE {
return Err(InterpreterError::LimitExceeded(format!(
"string repetition would create {result_size} bytes (limit: {MAX_STRING_SIZE})"
))
.into());
}
Ok(Value::String(s.repeat(repeat_count(n))))
}
(Value::Bytes(b), _) if matches!(right, Value::Int(_) | Value::Bool(_)) => {
let n = to_int(right)?;
if n <= 0 {
return Ok(Value::Bytes(Vec::new()));
}
let result_size = b.len().saturating_mul(repeat_count(n));
if result_size > MAX_STRING_SIZE {
return Err(InterpreterError::LimitExceeded(format!(
"bytes repetition would create {result_size} bytes (limit: {MAX_STRING_SIZE})"
))
.into());
}
Ok(Value::Bytes(b.repeat(repeat_count(n))))
}
(Value::ByteArray(b), _) if matches!(right, Value::Int(_) | Value::Bool(_)) => {
let n = to_int(right)?;
let src = b.lock();
if n <= 0 {
return Ok(Value::ByteArray(crate::value::shared_bytes(Vec::new())));
}
let result_size = src.len().saturating_mul(repeat_count(n));
if result_size > MAX_STRING_SIZE {
return Err(InterpreterError::LimitExceeded(format!(
"bytes repetition would create {result_size} bytes (limit: {MAX_STRING_SIZE})"
))
.into());
}
Ok(Value::ByteArray(crate::value::shared_bytes(src.repeat(repeat_count(n)))))
}
(Value::Int(_) | Value::Bool(_), Value::Bytes(b)) => {
let n = to_int(left)?;
if n <= 0 {
return Ok(Value::Bytes(Vec::new()));
}
let result_size = b.len().saturating_mul(repeat_count(n));
if result_size > MAX_STRING_SIZE {
return Err(InterpreterError::LimitExceeded(format!(
"bytes repetition would create {result_size} bytes (limit: {MAX_STRING_SIZE})"
))
.into());
}
Ok(Value::Bytes(b.repeat(repeat_count(n))))
}
(Value::List(items), _) if matches!(right, Value::Int(_) | Value::Bool(_)) => {
let n = to_int(right)?;
if n <= 0 {
return Ok(Value::List(shared_list(Vec::new())));
}
let snapshot = items.lock().clone();
let result_size = snapshot.len().saturating_mul(repeat_count(n));
if result_size > MAX_COLLECTION_SIZE {
return Err(InterpreterError::LimitExceeded(format!(
"list repetition would create {result_size} elements (limit: {MAX_COLLECTION_SIZE})"
)).into());
}
let mut result = Vec::with_capacity(result_size);
for _ in 0..n {
result.extend(snapshot.iter().cloned());
}
Ok(Value::List(shared_list(result)))
}
(_, Value::List(items)) if matches!(left, Value::Int(_) | Value::Bool(_)) => {
let n = to_int(left)?;
if n <= 0 {
return Ok(Value::List(shared_list(Vec::new())));
}
let snapshot = items.lock().clone();
let result_size = snapshot.len().saturating_mul(repeat_count(n));
if result_size > MAX_COLLECTION_SIZE {
return Err(InterpreterError::LimitExceeded(format!(
"list repetition would create {result_size} elements (limit: {MAX_COLLECTION_SIZE})"
)).into());
}
let mut result = Vec::with_capacity(result_size);
for _ in 0..n {
result.extend(snapshot.iter().cloned());
}
Ok(Value::List(shared_list(result)))
}
(Value::Tuple(items), _) if matches!(right, Value::Int(_) | Value::Bool(_)) => {
let n = to_int(right)?;
if n <= 0 {
return Ok(Value::Tuple(Vec::new()));
}
let result_size = items.len().saturating_mul(repeat_count(n));
if result_size > MAX_COLLECTION_SIZE {
return Err(InterpreterError::LimitExceeded(format!(
"tuple repetition would create {result_size} elements (limit: {MAX_COLLECTION_SIZE})"
)).into());
}
let mut result = Vec::with_capacity(result_size);
for _ in 0..n {
result.extend(items.iter().cloned());
}
Ok(Value::Tuple(result))
}
_ => {
let is_seq = |v: &Value| {
matches!(
v,
Value::String(_)
| Value::List(_)
| Value::Tuple(_)
| Value::Bytes(_)
| Value::ByteArray(_)
)
};
let is_int = |v: &Value| matches!(v, Value::Int(_) | Value::Bool(_) | Value::BigInt(_));
if is_seq(left) && !is_int(right) {
return Err(InterpreterError::TypeError(format!(
"can't multiply sequence by non-int of type '{}'",
right.type_name()
))
.into());
}
if is_seq(right) && !is_int(left) {
return Err(InterpreterError::TypeError(format!(
"can't multiply sequence by non-int of type '{}'",
left.type_name()
))
.into());
}
if either_is_float(left, right) {
Ok(Value::Float(to_float(left)? * to_float(right)?))
} else {
int_mul(left, right)
}
}
}
}
fn div_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
let l = to_float(left)?;
let r = to_float(right)?;
if r == 0.0 {
let msg = if either_is_float(left, right) {
"float division by zero"
} else {
"division by zero"
};
return Err(crate::value::ExceptionValue::zero_division_error(msg).into());
}
Ok(Value::Float(l / r))
}
fn floordiv_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
if either_is_float(left, right) {
let l = to_float(left)?;
let r = to_float(right)?;
if r == 0.0 {
return Err(crate::value::ExceptionValue::zero_division_error(
"float floor division by zero",
)
.into());
}
Ok(Value::Float((l / r).floor()))
} else {
if let (Value::Int(a), Value::Int(b)) = (left, right) {
if *b == 0 {
return Err(crate::value::ExceptionValue::zero_division_error(
"integer division or modulo by zero",
)
.into());
}
if !(*a == i64::MIN && *b == -1) {
return Ok(Value::Int(python_floordiv(*a, *b)));
}
}
use num_integer::Integer as _;
use num_traits::Zero as _;
let l = to_bigint(left)?;
let r = to_bigint(right)?;
if r.is_zero() {
return Err(crate::value::ExceptionValue::zero_division_error(
"integer division or modulo by zero",
)
.into());
}
Ok(crate::value::int_from_bigint(l.div_floor(&r)))
}
}
const fn python_floordiv(a: i64, b: i64) -> i64 {
let d = a / b;
let r = a % b;
if (r != 0) && ((r ^ b) < 0) { d - 1 } else { d }
}
fn mod_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
if let Value::String(template) = left {
return crate::eval::strings::str_percent_format(template, right);
}
match left {
Value::Bytes(template) => {
return crate::eval::strings::bytes_percent_format(template, right);
}
Value::ByteArray(template) => {
let snapshot = template.lock().clone();
return crate::eval::strings::bytes_percent_format(&snapshot, right).map(|v| match v {
Value::Bytes(b) => Value::ByteArray(crate::value::shared_bytes(b)),
other => other,
});
}
_ => {}
}
if either_is_float(left, right) {
let l = to_float(left)?;
let r = to_float(right)?;
if r == 0.0 {
return Err(crate::value::ExceptionValue::zero_division_error("float modulo").into());
}
Ok(Value::Float(r.mul_add(-(l / r).floor(), l)))
} else {
if let (Value::Int(a), Value::Int(b)) = (left, right) {
if *b == 0 {
return Err(crate::value::ExceptionValue::zero_division_error(
"integer modulo by zero",
)
.into());
}
if !(*a == i64::MIN && *b == -1) {
return Ok(Value::Int(python_mod(*a, *b)));
}
}
use num_integer::Integer as _;
use num_traits::Zero as _;
let l = to_bigint(left)?;
let r = to_bigint(right)?;
if r.is_zero() {
return Err(crate::value::ExceptionValue::zero_division_error(
"integer modulo by zero",
)
.into());
}
Ok(crate::value::int_from_bigint(l.mod_floor(&r)))
}
}
const fn python_mod(a: i64, b: i64) -> i64 {
let r = a % b;
if (r != 0) && ((r ^ b) < 0) { r + b } else { r }
}
fn matmult_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
let (Value::List(a), Value::List(b)) = (left, right) else {
return Err(InterpreterError::TypeError(format!(
"unsupported operand type(s) for @: '{}' and '{}' (see CONFORMANCE.md#unsupported-language-features)",
left.type_name(),
right.type_name()
))
.into());
};
let a_guard = a.lock().clone();
let b_guard = b.lock().clone();
if a_guard.is_empty() || b_guard.is_empty() {
return Ok(Value::List(shared_list(Vec::new())));
}
let mut a_rows: Vec<Vec<f64>> = Vec::with_capacity(a_guard.len());
let mut n_cols_a = None;
for row in a_guard.iter() {
let Value::List(cells) = row else {
return Err(InterpreterError::TypeError(
"@ requires a list of lists on the left".into(),
)
.into());
};
let cells = cells.lock();
if let Some(n) = n_cols_a {
if cells.len() != n {
return Err(InterpreterError::ValueError(
"matmul: left operand rows must have equal length".into(),
)
.into());
}
} else {
n_cols_a = Some(cells.len());
}
let mut r = Vec::with_capacity(cells.len());
for c in cells.iter() {
r.push(to_float(c)?);
}
a_rows.push(r);
}
let k = n_cols_a.unwrap_or(0);
let mut b_rows: Vec<Vec<f64>> = Vec::with_capacity(b_guard.len());
let mut n_cols_b = None;
for row in b_guard.iter() {
let Value::List(cells) = row else {
return Err(InterpreterError::TypeError(
"@ requires a list of lists on the right".into(),
)
.into());
};
let cells = cells.lock();
if let Some(n) = n_cols_b {
if cells.len() != n {
return Err(InterpreterError::ValueError(
"matmul: right operand rows must have equal length".into(),
)
.into());
}
} else {
n_cols_b = Some(cells.len());
}
let mut r = Vec::with_capacity(cells.len());
for c in cells.iter() {
r.push(to_float(c)?);
}
b_rows.push(r);
}
if b_rows.len() != k {
return Err(InterpreterError::ValueError(format!(
"matmul: shapes ({},{}) and ({},{}) not aligned",
a_rows.len(),
k,
b_rows.len(),
n_cols_b.unwrap_or(0)
))
.into());
}
let n = n_cols_b.unwrap_or(0);
let mut out = Vec::with_capacity(a_rows.len());
for row in &a_rows {
let mut out_row = Vec::with_capacity(n);
for col in 0..n {
let mut sum = 0.0;
for (ai, brow) in row.iter().zip(b_rows.iter()) {
sum += ai * brow[col];
}
#[allow(clippy::cast_possible_truncation, clippy::float_cmp)]
let cell = if sum.fract() == 0.0 && sum >= i64::MIN as f64 && sum <= i64::MAX as f64 {
Value::Int(sum as i64)
} else {
Value::Float(sum)
};
out_row.push(cell);
}
out.push(Value::List(shared_list(out_row)));
}
Ok(Value::List(shared_list(out)))
}
fn pow_values(left: &Value, right: &Value, max_int_bits: u64) -> Result<Value, EvalError> {
if either_is_float(left, right) {
let l = to_float(left)?;
let r = to_float(right)?;
if l == 0.0 && r < 0.0 {
return Err(crate::value::ExceptionValue::zero_division_error(
"0.0 cannot be raised to a negative power",
)
.into());
}
if l < 0.0 && r.fract() != 0.0 {
let len = l.abs().powf(r);
let phase = 0.0f64.atan2(l) * r;
let c = num_complex::Complex64::new(len * phase.cos(), len * phase.sin());
return Ok(Value::Complex(Box::new(c)));
}
let result = l.powf(r);
if result.is_infinite() && l.is_finite() && r.is_finite() {
let os = std::io::Error::from_raw_os_error(34).to_string();
let strerror = os.split(" (os error").next().unwrap_or(&os);
return Err(EvalError::Exception(crate::value::ExceptionValue::new(
"OverflowError",
format!("(34, '{strerror}')"),
)));
}
Ok(Value::Float(result))
} else {
let l = crate::value::value_as_bigint(left).ok_or_else(|| {
InterpreterError::TypeError(format!(
"unsupported operand type(s) for **: '{}' and '{}'",
left.type_name(),
right.type_name()
))
})?;
let r = crate::value::value_as_bigint(right).ok_or_else(|| {
InterpreterError::TypeError(format!(
"unsupported operand type(s) for **: '{}' and '{}'",
left.type_name(),
right.type_name()
))
})?;
use num_traits::{Pow, ToPrimitive as _, Zero as _};
if r < num_bigint::BigInt::from(0) {
if l.is_zero() {
return Err(crate::value::ExceptionValue::zero_division_error(
"0.0 cannot be raised to a negative power",
)
.into());
}
let l_f = l.to_f64().unwrap_or(f64::INFINITY);
let r_f = r.to_f64().unwrap_or(f64::NEG_INFINITY);
Ok(Value::Float(l_f.powf(r_f)))
} else if r.is_zero() {
Ok(Value::Int(1))
} else {
let exp = u32::try_from(&r).map_err(|_| {
EvalError::Exception(crate::value::ExceptionValue::new(
"OverflowError",
"exponent too large for integer power",
))
})?;
if exp > 1_000_000 {
return Err(EvalError::Exception(crate::value::ExceptionValue::new(
"OverflowError",
"exponent too large for integer power",
)));
}
let nbits = l.bits();
if nbits >= 2 && (nbits - 1).saturating_mul(u64::from(exp)) >= max_int_bits {
return Err(EvalError::Exception(crate::value::ExceptionValue::new(
"OverflowError",
"integer power result too large to represent",
)));
}
crate::value::int_from_bigint_limited(l.pow(exp), max_int_bits)
}
}
}
fn lshift_values(left: &Value, right: &Value, max_int_bits: u64) -> Result<Value, EvalError> {
use num_traits::{Signed, ToPrimitive as _};
require_int_operands("<<", left, right)?;
let l = to_bigint(left)?;
let r = to_bigint(right)?;
if r.is_negative() {
return Err(InterpreterError::ValueError("negative shift count".into()).into());
}
let shift = r.to_u32().ok_or_else(|| {
EvalError::Exception(crate::value::ExceptionValue::new(
"OverflowError",
"shift count too large",
))
})?;
if shift > 1_000_000 {
return Err(EvalError::Exception(crate::value::ExceptionValue::new(
"OverflowError",
"shift count too large",
)));
}
crate::value::int_from_bigint_limited(l << shift, max_int_bits)
}
fn rshift_values(left: &Value, right: &Value, max_int_bits: u64) -> Result<Value, EvalError> {
use num_traits::{Signed, ToPrimitive as _};
require_int_operands(">>", left, right)?;
let l = to_bigint(left)?;
let r = to_bigint(right)?;
if r.is_negative() {
return Err(InterpreterError::ValueError("negative shift count".into()).into());
}
let shift = r.to_u32().ok_or_else(|| {
EvalError::Exception(crate::value::ExceptionValue::new(
"OverflowError",
"shift count too large",
))
})?;
crate::value::int_from_bigint_limited(l >> shift, max_int_bits)
}
fn bitor_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
if let (Value::Counter(a), Value::Counter(b)) = (left, right) {
return Ok(Value::Counter(crate::types::counter_combine_op(a, b, std::cmp::Ord::max)));
}
if let (Some(a), Some(b)) = (set_like_body(left), set_like_body(right)) {
return Ok(wrap_set_body(left, a.union_with(&b)));
}
if let (Some(a), Some(b)) = (left.as_dict(), right.as_dict()) {
let mut result = a.lock().clone();
for (k, v) in b.lock().iter() {
result.insert(k.clone(), v.clone());
}
let merged = crate::value::shared_dict(result);
return Ok(
if matches!(left, Value::OrderedDict(_)) || matches!(right, Value::OrderedDict(_)) {
Value::OrderedDict(merged)
} else {
Value::Dict(merged)
},
);
}
if let (Value::Bool(a), Value::Bool(b)) = (left, right) {
return Ok(Value::Bool(*a | *b));
}
if let (Value::Int(a), Value::Int(b)) = (left, right) {
return Ok(Value::Int(a | b));
}
require_int_operands("|", left, right)?;
Ok(crate::value::int_from_bigint(to_bigint(left)? | to_bigint(right)?))
}
fn bitxor_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
if let (Some(a), Some(b)) = (set_like_body(left), set_like_body(right)) {
return Ok(wrap_set_body(left, a.symmetric_difference_with(&b)));
}
if let (Value::Bool(a), Value::Bool(b)) = (left, right) {
return Ok(Value::Bool(*a ^ *b));
}
if let (Value::Int(a), Value::Int(b)) = (left, right) {
return Ok(Value::Int(a ^ b));
}
require_int_operands("^", left, right)?;
Ok(crate::value::int_from_bigint(to_bigint(left)? ^ to_bigint(right)?))
}
fn bitand_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
if let (Value::Counter(a), Value::Counter(b)) = (left, right) {
return Ok(Value::Counter(crate::types::counter_combine_op(a, b, std::cmp::Ord::min)));
}
if let (Some(a), Some(b)) = (set_like_body(left), set_like_body(right)) {
return Ok(wrap_set_body(left, a.intersection_with(&b)));
}
if let (Value::Bool(a), Value::Bool(b)) = (left, right) {
return Ok(Value::Bool(*a & *b));
}
if let (Value::Int(a), Value::Int(b)) = (left, right) {
return Ok(Value::Int(a & b));
}
require_int_operands("&", left, right)?;
Ok(crate::value::int_from_bigint(to_bigint(left)? & to_bigint(right)?))
}
fn counter_unary(
c: &indexmap::IndexMap<crate::value::ValueKey, Value>,
negate: bool,
) -> indexmap::IndexMap<crate::value::ValueKey, Value> {
let mut result = indexmap::IndexMap::new();
for (key, val) in c {
let n = crate::value::value_as_i64(val).unwrap_or(0);
let kept = if negate { -n } else { n };
if kept > 0 {
result.insert(key.clone(), Value::Int(kept));
}
}
result
}
pub async fn eval_unaryop(
state: &mut InterpreterState,
node: &ast::ExprUnaryOp,
tools: &Tools,
) -> EvalResult {
state.enter_expr().map_err(EvalError::Interpreter)?;
let out = eval_unaryop_inner(state, node, tools).await;
state.exit_expr();
out
}
async fn eval_unaryop_inner(
state: &mut InterpreterState,
node: &ast::ExprUnaryOp,
tools: &Tools,
) -> EvalResult {
let operand = eval_expr(state, &node.operand, tools).await?;
let operand = resolve_proxy(&operand).await?;
crate::eval::op::unaryop(state, node.op, &operand, tools).await
}
pub async fn apply_unaryop(
state: &mut InterpreterState,
op: ast::UnaryOp,
operand: &Value,
tools: &Tools,
) -> EvalResult {
let operand = match operand {
Value::EnumMember {
value,
kind: crate::value::EnumKind::Int | crate::value::EnumKind::IntFlag,
..
} => value.as_ref(),
other => other,
};
match op {
ast::UnaryOp::UAdd => match operand {
Value::Int(_)
| Value::BigInt(_)
| Value::Float(_)
| Value::Complex(_)
| Value::Decimal(..)
| Value::Fraction(_) => Ok(operand.clone()),
Value::Bool(b) => Ok(Value::Int(i64::from(*b))),
Value::TimeDelta(_) => Ok(operand.clone()),
Value::Counter(c) => Ok(Value::Counter(counter_unary(c, false))),
_ => Err(InterpreterError::TypeError(format!(
"bad operand type for unary +: '{}'",
operand.type_name()
))
.into()),
},
ast::UnaryOp::USub => match operand {
Value::Int(i) => Ok(i.checked_neg().map_or_else(
|| crate::value::int_from_bigint(-num_bigint::BigInt::from(*i)),
Value::Int,
)),
Value::BigInt(b) => Ok(crate::value::int_from_bigint(-(*b.clone()))),
Value::Float(f) => Ok(Value::Float(-*f)),
Value::Complex(c) => Ok(Value::Complex(Box::new(-(**c)))),
Value::Bool(b) => Ok(Value::Int(if *b { -1 } else { 0 })),
Value::Decimal(d, k) => Ok(crate::eval::modules::decimal::neg_decimal(d, *k)),
Value::Fraction(fr) => Ok(Value::Fraction(Box::new(-(*fr.clone())))),
Value::Counter(c) => Ok(Value::Counter(counter_unary(c, true))),
Value::TimeDelta(us) => Ok(Value::TimeDelta(-*us)),
_ => Err(InterpreterError::TypeError(format!(
"bad operand type for unary -: '{}'",
operand.type_name()
))
.into()),
},
ast::UnaryOp::Not => {
let cond = match crate::eval::op::try_truthy_sync(operand) {
Some(b) => b,
None => crate::eval::op::truthy(state, operand, tools).await?,
};
Ok(Value::Bool(!cond))
}
ast::UnaryOp::Invert => match operand {
Value::Int(i) => Ok(Value::Int(!*i)),
Value::Bool(b) => Ok(Value::Int(!i64::from(*b))),
Value::BigInt(_) => {
let n = to_bigint(operand)?;
Ok(crate::value::int_from_bigint(-n - 1))
}
_ => Err(InterpreterError::TypeError(format!(
"bad operand type for unary ~: '{}'",
operand.type_name()
))
.into()),
},
}
}
pub async fn eval_compare(
state: &mut InterpreterState,
node: &ast::ExprCompare,
tools: &Tools,
) -> EvalResult {
let mut left = match crate::eval::try_eval_expr_sync(state, &node.left, tools) {
Some(r) => r?,
None => eval_expr(state, &node.left, tools).await?,
};
left = resolve_proxy(&left).await?;
let mut left_var: Option<String> = match &*node.left {
ast::Expr::Name(n) => Some(n.id.as_str().to_string()),
_ => None,
};
for (op, comparator) in node.ops.iter().zip(node.comparators.iter()) {
let right = match crate::eval::try_eval_expr_sync(state, comparator, tools) {
Some(r) => r?,
None => eval_expr(state, comparator, tools).await?,
};
let right = resolve_proxy(&right).await?;
let right_var: Option<String> = match comparator {
ast::Expr::Name(n) => Some(n.id.as_str().to_string()),
_ => None,
};
let result = match op {
ast::CmpOp::In => crate::eval::op::contains(state, &right, &left, tools).await?,
ast::CmpOp::NotIn => !crate::eval::op::contains(state, &right, &left, tools).await?,
ast::CmpOp::Is => values_is(&left, &right),
ast::CmpOp::IsNot => !values_is(&left, &right),
_ => {
let (cmp, post_left, post_right) =
crate::eval::op::compare(state, *op, &left, &right, tools).await?;
if let (Some(name), Some(v)) = (&left_var, post_left) {
state.set_variable(name, v).map_err(EvalError::Interpreter)?;
}
if let (Some(name), Some(v)) = (&right_var, post_right) {
state.set_variable(name, v).map_err(EvalError::Interpreter)?;
}
cmp
}
};
if !result {
return Ok(Value::Bool(false));
}
left = right;
left_var = right_var;
}
Ok(Value::Bool(true))
}
pub fn compare_builtin(
state: &InterpreterState,
op: ast::CmpOp,
left: &Value,
right: &Value,
) -> Result<bool, EvalError> {
let result = match op {
ast::CmpOp::Eq => {
let Value::Bool(b) = crate::types::dispatch_eq(state, left, right)? else {
unreachable!("dispatch_eq always returns Value::Bool");
};
Ok(b)
}
ast::CmpOp::NotEq => {
let Value::Bool(b) = crate::types::dispatch_eq(state, left, right)? else {
unreachable!("dispatch_eq always returns Value::Bool");
};
Ok(!b)
}
ast::CmpOp::Lt => crate::types::dispatch_lt(left, right),
ast::CmpOp::LtE => {
let lt = crate::types::dispatch_lt(left, right)?;
Ok(lt || eq_via_dispatch(state, left, right)?)
}
ast::CmpOp::Gt => crate::types::dispatch_lt(right, left),
ast::CmpOp::GtE => {
let gt = crate::types::dispatch_lt(right, left)?;
Ok(gt || eq_via_dispatch(state, left, right)?)
}
ast::CmpOp::Is | ast::CmpOp::IsNot | ast::CmpOp::In | ast::CmpOp::NotIn => {
unreachable!("identity/membership ops handled at eval_compare before reaching here")
}
}?;
if crate::cycle::take_eq_overflow() {
return Err(InterpreterError::RecursionLimitExceeded {
limit: crate::cycle::EQ_RECURSION_LIMIT,
}
.into());
}
Ok(result)
}
fn eq_via_dispatch(
state: &InterpreterState,
left: &Value,
right: &Value,
) -> Result<bool, EvalError> {
let Value::Bool(b) = crate::types::dispatch_eq(state, left, right)? else {
unreachable!("dispatch_eq always returns Value::Bool");
};
Ok(b)
}
pub fn values_equal_pub(left: &Value, right: &Value) -> bool {
values_equal(left, right)
}
pub fn compare_lt(left: &Value, right: &Value) -> Result<bool, EvalError> {
crate::types::dispatch_lt(left, right)
}
fn values_equal(left: &Value, right: &Value) -> bool {
let Some(_depth) = crate::cycle::eq_depth_enter() else {
return false;
};
match (left, right) {
(Value::None, Value::None) => true,
(Value::Ellipsis, Value::Ellipsis) | (Value::NotImplemented, Value::NotImplemented) => true,
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::Int(a), Value::Int(b)) => a == b,
(Value::Float(a), Value::Float(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Bytes(a), Value::Bytes(b)) => a == b,
(Value::ByteArray(a), Value::ByteArray(b)) => {
std::sync::Arc::ptr_eq(a, b) || *a.lock() == *b.lock()
}
(Value::ByteArray(a), Value::Bytes(b)) | (Value::Bytes(b), Value::ByteArray(a)) => {
*a.lock() == *b
}
(Value::Bool(b), Value::Int(i)) | (Value::Int(i), Value::Bool(b)) => *i == i64::from(*b),
(Value::Bool(b), Value::Float(f)) | (Value::Float(f), Value::Bool(b)) => {
*f == if *b { 1.0 } else { 0.0 }
}
(Value::Int(i), Value::Float(f)) | (Value::Float(f), Value::Int(i)) => *f == (*i as f64),
(Value::Function(a), Value::Function(b)) => std::sync::Arc::ptr_eq(a, b),
(Value::Lambda(a), Value::Lambda(b)) => std::sync::Arc::ptr_eq(a, b),
(Value::List(a), Value::List(b)) => {
if std::sync::Arc::ptr_eq(a, b) {
return true;
}
let a_guard = a.lock().clone();
let b_guard = b.lock().clone();
a_guard.len() == b_guard.len()
&& a_guard.iter().zip(b_guard.iter()).all(|(x, y)| values_equal(x, y))
}
(Value::Array { items: a, .. }, Value::Array { items: b, .. }) => {
if std::sync::Arc::ptr_eq(a, b) {
return true;
}
let a_guard = a.lock().clone();
let b_guard = b.lock().clone();
a_guard.len() == b_guard.len()
&& a_guard.iter().zip(b_guard.iter()).all(|(x, y)| values_equal(x, y))
}
(Value::Deque { items: a, .. }, Value::Deque { items: b, .. }) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
}
(Value::Tuple(a), Value::Tuple(b)) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
}
(Value::OrderedDict(a), Value::OrderedDict(b)) => {
if std::sync::Arc::ptr_eq(a, b) {
return true;
}
let a = a.lock().clone();
let b = b.lock().clone();
a.len() == b.len()
&& a.iter()
.zip(b.iter())
.all(|((ka, va), (kb, vb))| ka == kb && values_equal(va, vb))
}
(Value::Dict(a) | Value::OrderedDict(a), Value::Dict(b) | Value::OrderedDict(b)) => {
if std::sync::Arc::ptr_eq(a, b) {
return true;
}
let a = a.lock().clone();
let b = b.lock().clone();
if a.len() != b.len() {
return false;
}
a.iter().all(|(k, v)| b.get(k).is_some_and(|bv| values_equal(v, bv)))
}
(Value::Set(_) | Value::Frozenset(_), Value::Set(_) | Value::Frozenset(_)) => {
let (Some(a), Some(b)) = (left.set_items(), right.set_items()) else {
return false;
};
if a.len() != b.len() {
return false;
}
a.iter().all(|av| b.iter().any(|bv| values_equal(av, bv)))
}
(
Value::EnumMember { class_name: c1, member_name: m1, .. },
Value::EnumMember { class_name: c2, member_name: m2, .. },
) => c1 == c2 && m1 == m2,
(
Value::EnumMember {
value,
kind: crate::value::EnumKind::Int | crate::value::EnumKind::Str,
..
},
other,
) => values_equal(value.as_ref(), other),
(
other,
Value::EnumMember {
value,
kind: crate::value::EnumKind::Int | crate::value::EnumKind::Str,
..
},
) => values_equal(other, value.as_ref()),
(Value::Instance(a), Value::Instance(b)) => {
if a.class_name != b.class_name {
return false;
}
if std::sync::Arc::ptr_eq(&a.fields, &b.fields) {
return true;
}
let af = a.fields.lock().clone();
let bf = b.fields.lock().clone();
if af.len() != bf.len() {
return false;
}
af.iter().all(|(name, va)| bf.get(name).is_some_and(|vb| values_equal(va, vb)))
}
(Value::RePattern(a), Value::RePattern(b)) => a == b,
(Value::Slice(a), Value::Slice(b)) => {
values_equal(&a.start, &b.start)
&& values_equal(&a.stop, &b.stop)
&& values_equal(&a.step, &b.step)
}
(
Value::ExceptionType(a) | Value::Type(a) | Value::Class(a) | Value::BuiltinName(a),
Value::ExceptionType(b) | Value::Type(b) | Value::Class(b) | Value::BuiltinName(b),
) => a == b || union_type_eq(a, b),
(Value::Date(a), Value::Date(b)) => a == b,
(Value::Time(a), Value::Time(b)) => a == b,
(Value::TimeDelta(a), Value::TimeDelta(b)) => a == b,
(
Value::DateTime { dt: a, tz_offset_secs: ta },
Value::DateTime { dt: b, tz_offset_secs: tb },
) => match (ta, tb) {
(None, None) => a == b,
(Some(oa), Some(ob)) => {
(*a - chrono::Duration::seconds(i64::from(*oa)))
== (*b - chrono::Duration::seconds(i64::from(*ob)))
}
_ => false,
},
(Value::TimeZone(a), Value::TimeZone(b)) => a == b,
(
Value::Range { start: sa, stop: pa, step: ta },
Value::Range { start: sb, stop: pb, step: tb },
) => {
let la = crate::types::range_length(*sa, *pa, *ta);
la == crate::types::range_length(*sb, *pb, *tb)
&& (la == 0 || (sa == sb && (la == 1 || ta == tb)))
}
_ => false,
}
}
fn union_type_eq(a: &str, b: &str) -> bool {
let (Some(a_args), Some(b_args)) = (union_members(a), union_members(b)) else {
return false;
};
a_args.len() == b_args.len()
&& a_args.iter().all(|x| b_args.contains(x))
&& b_args.iter().all(|x| a_args.contains(x))
}
fn union_members(s: &str) -> Option<Vec<&str>> {
let inner = s.strip_prefix("typing.Union[")?.strip_suffix(']')?;
let mut members = Vec::new();
let mut depth = 0usize;
let mut start = 0usize;
for (i, c) in inner.char_indices() {
match c {
'[' => depth += 1,
']' => depth = depth.saturating_sub(1),
',' if depth == 0 => {
members.push(inner[start..i].trim());
start = i + 1;
}
_ => {}
}
}
members.push(inner[start..].trim());
Some(members)
}
pub(crate) fn values_is(left: &Value, right: &Value) -> bool {
use std::sync::Arc;
match (left, right) {
(Value::List(a), Value::List(b)) => Arc::ptr_eq(a, b),
(Value::Instance(a), Value::Instance(b)) => Arc::ptr_eq(&a.fields, &b.fields),
(Value::Function(a), Value::Function(b)) => Arc::ptr_eq(a, b),
(Value::Lambda(a), Value::Lambda(b)) => Arc::ptr_eq(a, b),
(Value::LruCache(a), Value::LruCache(b)) => Arc::ptr_eq(a, b),
(Value::Set(a), Value::Set(b)) => Arc::ptr_eq(a, b),
(Value::Frozenset(a), Value::Frozenset(b)) => Arc::ptr_eq(a, b),
(Value::Dict(a), Value::Dict(b)) => Arc::ptr_eq(a, b),
(Value::OrderedDict(a), Value::OrderedDict(b)) => Arc::ptr_eq(a, b),
(Value::ByteArray(a), Value::ByteArray(b)) => Arc::ptr_eq(a, b),
(Value::Array { items: a, .. }, Value::Array { items: b, .. }) => Arc::ptr_eq(a, b),
(Value::Generator { id: a }, Value::Generator { id: b }) => a == b,
(Value::Lazy { cursor_id: a, .. }, Value::Lazy { cursor_id: b, .. }) => a == b,
(Value::BuiltinIter { id: a, .. }, Value::BuiltinIter { id: b, .. }) => a == b,
(
Value::List(_)
| Value::Instance(_)
| Value::Function(_)
| Value::Lambda(_)
| Value::LruCache(_)
| Value::Generator { .. }
| Value::Lazy { .. }
| Value::BuiltinIter { .. }
| Value::Set(_)
| Value::Frozenset(_)
| Value::Dict(_)
| Value::OrderedDict(_)
| Value::ByteArray(_)
| Value::Array { .. },
_,
)
| (
_,
Value::List(_)
| Value::Instance(_)
| Value::Function(_)
| Value::Lambda(_)
| Value::LruCache(_)
| Value::Generator { .. }
| Value::Lazy { .. }
| Value::BuiltinIter { .. }
| Value::Set(_)
| Value::Frozenset(_)
| Value::Dict(_)
| Value::OrderedDict(_)
| Value::ByteArray(_)
| Value::Array { .. },
) => false,
_ => {
if let (Some(a), Some(b)) = (numeric_is_kind(left), numeric_is_kind(right)) {
if a != b {
return false;
}
}
values_equal(left, right)
}
}
}
fn numeric_is_kind(v: &Value) -> Option<u8> {
match v {
Value::Bool(_) => Some(0),
Value::Int(_) | Value::BigInt(_) => Some(1),
Value::Float(_) => Some(2),
Value::Complex(_) => Some(3),
_ => None,
}
}
pub async fn eval_boolop(
state: &mut InterpreterState,
node: &ast::ExprBoolOp,
tools: &Tools,
) -> EvalResult {
match node.op {
ast::BoolOp::And => {
let mut last = Value::Bool(true);
for value_node in &node.values {
last = eval_expr(state, value_node, tools).await?;
last = resolve_proxy(&last).await?;
let cond = match crate::eval::op::try_truthy_sync(&last) {
Some(b) => b,
None => crate::eval::op::truthy(state, &last, tools).await?,
};
if !cond {
return Ok(last);
}
}
Ok(last)
}
ast::BoolOp::Or => {
let mut last = Value::Bool(false);
for value_node in &node.values {
last = eval_expr(state, value_node, tools).await?;
last = resolve_proxy(&last).await?;
let cond = match crate::eval::op::try_truthy_sync(&last) {
Some(b) => b,
None => crate::eval::op::truthy(state, &last, tools).await?,
};
if cond {
return Ok(last);
}
}
Ok(last)
}
}
}
pub async fn eval_ifexp(
state: &mut InterpreterState,
node: &ast::ExprIfExp,
tools: &Tools,
) -> EvalResult {
state.enter_expr().map_err(EvalError::Interpreter)?;
let out = eval_ifexp_inner(state, node, tools).await;
state.exit_expr();
out
}
async fn eval_ifexp_inner(
state: &mut InterpreterState,
node: &ast::ExprIfExp,
tools: &Tools,
) -> EvalResult {
let test = eval_expr(state, &node.test, tools).await?;
let test = resolve_proxy(&test).await?;
let cond = match crate::eval::op::try_truthy_sync(&test) {
Some(b) => b,
None => crate::eval::op::truthy(state, &test, tools).await?,
};
if cond {
eval_expr(state, &node.body, tools).await
} else {
eval_expr(state, &node.orelse, tools).await
}
}