#![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, literals::value_to_key},
state::InterpreterState,
tools::Tools,
value::{Value, shared_list},
};
const MAX_COLLECTION_SIZE: usize = 10_000_000;
const MAX_STRING_SIZE: usize = 100 * 1024 * 1024;
fn repeat_count(n: i64) -> usize {
usize::try_from(n).unwrap_or(usize::MAX)
}
fn bounded_u32(n: i64) -> Result<u32, EvalError> {
u32::try_from(n).map_err(|_| {
InterpreterError::Runtime(
"shift/exponent count out of u32 range (internal invariant)".into(),
)
.into()
})
}
pub async fn eval_binop(
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) -> Result<Value, EvalError> {
match op {
ast::Operator::Add => crate::types::dispatch_binop(crate::types::BinOp::Add, left, right),
ast::Operator::Sub => crate::types::dispatch_binop(crate::types::BinOp::Sub, left, right),
ast::Operator::Mult => crate::types::dispatch_binop(crate::types::BinOp::Mul, left, right),
ast::Operator::Div => crate::types::dispatch_binop(crate::types::BinOp::Div, left, right),
ast::Operator::FloorDiv => {
crate::types::dispatch_binop(crate::types::BinOp::FloorDiv, left, right)
}
ast::Operator::Mod => crate::types::dispatch_binop(crate::types::BinOp::Mod, left, right),
ast::Operator::Pow => crate::types::dispatch_binop(crate::types::BinOp::Pow, left, right),
ast::Operator::LShift => lshift_values(left, right),
ast::Operator::RShift => rshift_values(left, right),
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),
}
}
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),
}
}
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 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::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 sub_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
if let (Value::Set(a), Value::Set(b)) = (left, right) {
let b_keys: Vec<_> = b.iter().filter_map(|v| value_to_key(v).ok()).collect();
let result: Vec<Value> = a
.iter()
.filter(|v| value_to_key(v).map_or(true, |k| !b_keys.contains(&k)))
.cloned()
.collect();
return Ok(Value::Set(result));
}
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::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))
}
_ => {
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 {
return Err(crate::value::ExceptionValue::zero_division_error("division by zero").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("division by zero").into()
);
}
Ok(Value::Float((l / r).floor()))
} else {
let l = to_int(left)?;
let r = to_int(right)?;
if r == 0 {
return Err(crate::value::ExceptionValue::zero_division_error(
"integer division or modulo by zero",
)
.into());
}
Ok(Value::Int(python_floordiv(l, 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);
}
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("modulo by zero").into());
}
Ok(Value::Float(r.mul_add(-(l / r).floor(), l)))
} else {
let l = to_int(left)?;
let r = to_int(right)?;
if r == 0 {
return Err(crate::value::ExceptionValue::zero_division_error(
"integer division or modulo by zero",
)
.into());
}
Ok(Value::Int(python_mod(l, 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();
let b_guard = b.lock();
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) -> Result<Value, EvalError> {
if either_is_float(left, right) {
let l = to_float(left)?;
let r = to_float(right)?;
Ok(Value::Float(l.powf(r)))
} 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) {
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",
)));
}
Ok(crate::value::int_from_bigint(l.pow(exp)))
}
}
}
fn lshift_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
let l = to_int(left)?;
let r = to_int(right)?;
if r < 0 {
return Err(InterpreterError::ValueError("negative shift count".into()).into());
}
if r >= 64 {
return Ok(Value::Int(0));
}
let shift = bounded_u32(r)?;
Ok(Value::Int(
l.checked_shl(shift)
.ok_or_else(|| EvalError::from(InterpreterError::Runtime("integer overflow".into())))?,
))
}
fn rshift_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
let l = to_int(left)?;
let r = to_int(right)?;
if r < 0 {
return Err(InterpreterError::ValueError("negative shift count".into()).into());
}
if r >= 64 {
return Ok(Value::Int(if l < 0 { -1 } else { 0 }));
}
let shift = bounded_u32(r)?;
Ok(Value::Int(
l.checked_shr(shift)
.ok_or_else(|| EvalError::from(InterpreterError::Runtime("integer overflow".into())))?,
))
}
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 (Value::Set(a), Value::Set(b)) = (left, right) {
let mut result = a.clone();
for item in b {
let key = value_to_key(item).ok();
let already_has = result.iter().any(|r| value_to_key(r).ok() == key);
if !already_has {
result.push(item.clone());
}
}
return Ok(Value::Set(result));
}
if let (Value::Dict(a), Value::Dict(b)) = (left, right) {
let mut result = a.clone();
for (k, v) in b {
result.insert(k.clone(), v.clone());
}
return Ok(Value::Dict(result));
}
let l = to_int(left)?;
let r = to_int(right)?;
Ok(Value::Int(l | r))
}
fn bitxor_values(left: &Value, right: &Value) -> Result<Value, EvalError> {
if let (Value::Set(a), Value::Set(b)) = (left, right) {
let a_keys: Vec<_> = a.iter().filter_map(|v| value_to_key(v).ok()).collect();
let b_keys: Vec<_> = b.iter().filter_map(|v| value_to_key(v).ok()).collect();
let mut result: Vec<Value> = a
.iter()
.filter(|v| value_to_key(v).map_or(true, |k| !b_keys.contains(&k)))
.cloned()
.collect();
for item in b {
if let Ok(k) = value_to_key(item) {
if !a_keys.contains(&k) {
result.push(item.clone());
}
}
}
return Ok(Value::Set(result));
}
let l = to_int(left)?;
let r = to_int(right)?;
Ok(Value::Int(l ^ r))
}
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 (Value::Set(a), Value::Set(b)) = (left, right) {
let b_keys: Vec<_> = b.iter().filter_map(|v| value_to_key(v).ok()).collect();
let result: Vec<Value> = a
.iter()
.filter(|v| value_to_key(v).is_ok_and(|k| b_keys.contains(&k)))
.cloned()
.collect();
return Ok(Value::Set(result));
}
let l = to_int(left)?;
let r = to_int(right)?;
Ok(Value::Int(l & r))
}
pub async fn eval_unaryop(
state: &mut InterpreterState,
node: &ast::ExprUnaryOp,
tools: &Tools,
) -> EvalResult {
let operand = eval_expr(state, &node.operand, tools).await?;
let operand = resolve_proxy(&operand).await?;
match node.op {
ast::UnaryOp::UAdd => match &operand {
Value::Int(i) => Ok(Value::Int(*i)),
Value::Float(f) => Ok(Value::Float(*f)),
Value::Bool(b) => Ok(Value::Int(i64::from(*b))),
_ => Err(InterpreterError::TypeError(format!(
"bad operand type for unary +: '{}'",
operand.type_name()
))
.into()),
},
ast::UnaryOp::USub => match &operand {
Value::Int(i) => Ok(Value::Int(-*i)),
Value::Float(f) => Ok(Value::Float(-*f)),
Value::Bool(b) => Ok(Value::Int(if *b { -1 } else { 0 })),
_ => 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 => {
let i = to_int(&operand)?;
Ok(Value::Int(!i))
}
}
}
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> {
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 || values_equal(left, right))
}
ast::CmpOp::Gt => crate::types::dispatch_lt(right, left),
ast::CmpOp::GtE => {
let gt = crate::types::dispatch_lt(right, left)?;
Ok(gt || values_equal(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")
}
}
}
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 {
match (left, right) {
(Value::None, Value::None) => 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::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::List(a), Value::List(b)) => {
if std::sync::Arc::ptr_eq(a, b) {
return true;
}
let a_guard = a.lock();
let b_guard = b.lock();
a_guard.len() == b_guard.len()
&& a_guard.iter().zip(b_guard.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::Dict(a), Value::Dict(b)) => {
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(a), Value::Set(b)) => {
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();
let bf = b.fields.lock();
if af.len() != bf.len() {
return false;
}
af.iter().all(|(name, va)| bf.get(name).is_some_and(|vb| values_equal(va, vb)))
}
_ => false,
}
}
const fn values_is(left: &Value, right: &Value) -> bool {
matches!((left, right), (Value::None, Value::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 {
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
}
}