use indexmap::IndexMap;
use crate::{
error::{EvalError, EvalResult, InterpreterError},
eval::{
classes::{call_method, lookup_method_in_mro},
functions::CallArgs,
},
state::InterpreterState,
tools::Tools,
value::Value,
};
fn instance_slot(
state: &InterpreterState,
value: &Value,
slot_name: &str,
) -> Option<crate::value::FunctionDef> {
let Value::Instance(inst) = value else { return None };
lookup_method_in_mro(state, &inst.class_name, slot_name).map(|(_, method)| method)
}
async fn invoke_slot(
state: &mut InterpreterState,
receiver: &Value,
method: &crate::value::FunctionDef,
args: &[Value],
tools: &Tools,
) -> Result<(Value, Value), EvalError> {
let call = CallArgs { positional: args, keyword: &IndexMap::new() };
call_method(state, method, receiver.clone(), call, tools).await
}
pub async fn getitem(
state: &mut InterpreterState,
container: &Value,
index: &Value,
tools: &Tools,
) -> EvalResult {
if let Some(method) = instance_slot(state, container, "__getitem__") {
let (returned, _self) =
invoke_slot(state, container, &method, std::slice::from_ref(index), tools).await?;
return Ok(returned);
}
if let (Value::Dict(map), Value::Instance(_)) = (container, index) {
let k = key(state, index, tools).await?;
return map
.get(&k)
.cloned()
.ok_or_else(|| EvalError::Exception(crate::value::ExceptionValue::key_error(&k)));
}
crate::types::dispatch_getitem(container, index)
}
pub async fn setitem(
state: &mut InterpreterState,
container: &Value,
key: &Value,
value: Value,
tools: &Tools,
) -> Result<Option<Value>, EvalError> {
let Some(method) = instance_slot(state, container, "__setitem__") else {
return Ok(None);
};
let (_returned, updated_self) =
invoke_slot(state, container, &method, &[key.clone(), value], tools).await?;
Ok(Some(updated_self))
}
pub async fn delitem(
state: &mut InterpreterState,
container: &Value,
key: &Value,
tools: &Tools,
) -> Result<Option<Value>, EvalError> {
let Some(method) = instance_slot(state, container, "__delitem__") else {
return Ok(None);
};
let (_returned, updated_self) =
invoke_slot(state, container, &method, std::slice::from_ref(key), tools).await?;
Ok(Some(updated_self))
}
pub async fn iter(
state: &mut InterpreterState,
value: &Value,
tools: &Tools,
) -> Result<Vec<Value>, EvalError> {
if let Value::Lazy { items, cursor_id } = value {
let cursor = state.lazy_cursors.get(cursor_id).copied().unwrap_or(0);
let remaining: Vec<Value> = items.iter().skip(cursor).cloned().collect();
state.lazy_cursors.insert(*cursor_id, items.len());
return Ok(remaining);
}
let Some(iter_method) = instance_slot(state, value, "__iter__") else {
return crate::types::dispatch_iter(value);
};
let (iterator, _self) = invoke_slot(state, value, &iter_method, &[], tools).await?;
let Value::Instance(iter_inst) = &iterator else {
return Err(InterpreterError::TypeError(format!(
"iter() returned non-iterator of type '{}'",
iterator.type_name()
))
.into());
};
let next_method = lookup_method_in_mro(state, &iter_inst.class_name, "__next__")
.map(|(_, m)| m)
.ok_or_else(|| {
InterpreterError::TypeError(format!(
"iter() returned non-iterator of type '{}' (no __next__)",
iter_inst.class_name
))
})?;
let max_iters = state.config.max_while_iterations;
let mut items: Vec<Value> = Vec::new();
let mut iterator_value = iterator;
for _ in 0..max_iters {
let next_result = invoke_slot(state, &iterator_value, &next_method, &[], tools).await;
match next_result {
Ok((item, updated_self)) => {
items.push(item);
iterator_value = updated_self;
}
Err(EvalError::Exception(exc)) if exc.type_name == "StopIteration" => {
return Ok(items);
}
Err(other) => return Err(other),
}
}
Err(InterpreterError::LimitExceeded(format!(
"iterator exceeded maximum iterations ({max_iters})"
))
.into())
}
pub async fn aug_binop(
state: &mut InterpreterState,
op: rustpython_parser::ast::Operator,
left: &Value,
right: &Value,
tools: &Tools,
) -> EvalResult {
if let Some(method) = instance_slot(state, left, inplace_arith_slot(op)) {
let (returned, _self) =
invoke_slot(state, left, &method, std::slice::from_ref(right), tools).await?;
return Ok(returned);
}
binop(state, op, left, right, tools).await
}
const fn inplace_arith_slot(op: rustpython_parser::ast::Operator) -> &'static str {
use rustpython_parser::ast::Operator;
match op {
Operator::Add => "__iadd__",
Operator::Sub => "__isub__",
Operator::Mult => "__imul__",
Operator::Div => "__itruediv__",
Operator::FloorDiv => "__ifloordiv__",
Operator::Mod => "__imod__",
Operator::Pow => "__ipow__",
Operator::MatMult => "__imatmul__",
Operator::LShift => "__ilshift__",
Operator::RShift => "__irshift__",
Operator::BitOr => "__ior__",
Operator::BitXor => "__ixor__",
Operator::BitAnd => "__iand__",
}
}
pub async fn binop(
state: &mut InterpreterState,
op: rustpython_parser::ast::Operator,
left: &Value,
right: &Value,
tools: &Tools,
) -> EvalResult {
if let Some(method) = instance_slot(state, left, arith_slot(op)) {
let (returned, _self) =
invoke_slot(state, left, &method, std::slice::from_ref(right), tools).await?;
return Ok(returned);
}
if let Some(method) = instance_slot(state, right, reflected_arith_slot(op)) {
let (returned, _self) =
invoke_slot(state, right, &method, std::slice::from_ref(left), tools).await?;
return Ok(returned);
}
crate::eval::operations::apply_binop(left, right, op)
}
const fn arith_slot(op: rustpython_parser::ast::Operator) -> &'static str {
use rustpython_parser::ast::Operator;
match op {
Operator::Add => "__add__",
Operator::Sub => "__sub__",
Operator::Mult => "__mul__",
Operator::Div => "__truediv__",
Operator::FloorDiv => "__floordiv__",
Operator::Mod => "__mod__",
Operator::Pow => "__pow__",
Operator::MatMult => "__matmul__",
Operator::LShift => "__lshift__",
Operator::RShift => "__rshift__",
Operator::BitOr => "__or__",
Operator::BitXor => "__xor__",
Operator::BitAnd => "__and__",
}
}
const fn reflected_arith_slot(op: rustpython_parser::ast::Operator) -> &'static str {
use rustpython_parser::ast::Operator;
match op {
Operator::Add => "__radd__",
Operator::Sub => "__rsub__",
Operator::Mult => "__rmul__",
Operator::Div => "__rtruediv__",
Operator::FloorDiv => "__rfloordiv__",
Operator::Mod => "__rmod__",
Operator::Pow => "__rpow__",
Operator::MatMult => "__rmatmul__",
Operator::LShift => "__rlshift__",
Operator::RShift => "__rrshift__",
Operator::BitOr => "__ror__",
Operator::BitXor => "__rxor__",
Operator::BitAnd => "__rand__",
}
}
pub async fn compare(
state: &mut InterpreterState,
op: rustpython_parser::ast::CmpOp,
left: &Value,
right: &Value,
tools: &Tools,
) -> Result<(bool, Option<Value>, Option<Value>), EvalError> {
use rustpython_parser::ast::CmpOp;
if let Some(slot) = forward_compare_slot(op) {
if let Some(method) = instance_slot(state, left, slot) {
let (returned, post_self) =
invoke_slot(state, left, &method, std::slice::from_ref(right), tools).await?;
let result = if matches!(op, CmpOp::NotEq) {
!returned.is_truthy()
} else {
returned.is_truthy()
};
return Ok((result, Some(post_self), None));
}
if let Some(reflected) = reflected_compare_slot(op) {
if let Some(method) = instance_slot(state, right, reflected) {
let (returned, post_self) =
invoke_slot(state, right, &method, std::slice::from_ref(left), tools).await?;
return Ok((returned.is_truthy(), None, Some(post_self)));
}
}
}
let r = crate::eval::operations::compare_builtin(state, op, left, right)?;
Ok((r, None, None))
}
pub async fn lt(
state: &mut InterpreterState,
left: &Value,
right: &Value,
tools: &Tools,
) -> Result<bool, EvalError> {
if let Some(method) = instance_slot(state, left, "__lt__") {
let (returned, _self) =
invoke_slot(state, left, &method, std::slice::from_ref(right), tools).await?;
return Ok(returned.is_truthy());
}
if let Some(method) = instance_slot(state, right, "__gt__") {
let (returned, _self) =
invoke_slot(state, right, &method, std::slice::from_ref(left), tools).await?;
return Ok(returned.is_truthy());
}
crate::types::dispatch_lt(left, right)
}
const fn forward_compare_slot(op: rustpython_parser::ast::CmpOp) -> Option<&'static str> {
use rustpython_parser::ast::CmpOp;
match op {
CmpOp::Eq | CmpOp::NotEq => Some("__eq__"),
CmpOp::Lt => Some("__lt__"),
CmpOp::LtE => Some("__le__"),
CmpOp::Gt => Some("__gt__"),
CmpOp::GtE => Some("__ge__"),
_ => None,
}
}
const fn reflected_compare_slot(op: rustpython_parser::ast::CmpOp) -> Option<&'static str> {
use rustpython_parser::ast::CmpOp;
match op {
CmpOp::Lt => Some("__gt__"),
CmpOp::LtE => Some("__ge__"),
CmpOp::Gt => Some("__lt__"),
CmpOp::GtE => Some("__le__"),
_ => None,
}
}
pub async fn hash(
state: &mut InterpreterState,
value: &Value,
tools: &Tools,
) -> Result<i64, EvalError> {
if let Some(method) = instance_slot(state, value, "__hash__") {
let (returned, _self) = invoke_slot(state, value, &method, &[], tools).await?;
return match returned {
Value::Int(n) => Ok(n),
Value::Bool(b) => Ok(i64::from(b)),
other => Err(InterpreterError::TypeError(format!(
"__hash__ method should return an integer, returned {}",
other.type_name()
))
.into()),
};
}
crate::types::dispatch_hash(state, value)
}
pub async fn key(
state: &mut InterpreterState,
value: &Value,
tools: &Tools,
) -> Result<crate::value::ValueKey, EvalError> {
if matches!(value, Value::Instance(_)) {
let h = hash(state, value, tools).await?;
return Ok(crate::value::ValueKey::Instance { hash: h, value: Box::new(value.clone()) });
}
crate::eval::literals::value_to_key(value)
}
pub async fn len(
state: &mut InterpreterState,
value: &Value,
tools: &Tools,
) -> Result<usize, EvalError> {
if let Some(method) = instance_slot(state, value, "__len__") {
let (returned, _self) = invoke_slot(state, value, &method, &[], tools).await?;
return match returned {
Value::Int(n) => usize::try_from(n).map_err(|_| {
InterpreterError::ValueError("__len__() should return >= 0".into()).into()
}),
other => Err(InterpreterError::TypeError(format!(
"'{}' object cannot be interpreted as an integer",
other.type_name()
))
.into()),
};
}
crate::types::dispatch_len(value)
}
pub async fn contains(
state: &mut InterpreterState,
container: &Value,
item: &Value,
tools: &Tools,
) -> Result<bool, EvalError> {
if let Some(method) = instance_slot(state, container, "__contains__") {
let (returned, _self) =
invoke_slot(state, container, &method, std::slice::from_ref(item), tools).await?;
return Ok(returned.is_truthy());
}
if matches!(item, Value::Instance(_)) {
if let Value::Dict(map) = container {
let k = key(state, item, tools).await?;
return Ok(map.contains_key(&k));
}
if let Value::Set(items) = container {
for stored in items {
if let Value::Instance(_) = stored {
if crate::eval::operations::values_equal_pub(stored, item) {
return Ok(true);
}
}
}
return Ok(false);
}
}
crate::types::dispatch_contains(container, item)
}
#[inline]
#[must_use]
pub fn try_truthy_sync(value: &Value) -> Option<bool> {
match value {
Value::Instance(_) => None,
other => Some(other.is_truthy()),
}
}
pub async fn truthy(
state: &mut InterpreterState,
value: &Value,
tools: &Tools,
) -> Result<bool, EvalError> {
if matches!(value, Value::Instance(_)) {
if let Some(method) = instance_slot(state, value, "__bool__") {
let (returned, _self) = invoke_slot(state, value, &method, &[], tools).await?;
return match returned {
Value::Bool(b) => Ok(b),
other => Err(InterpreterError::TypeError(format!(
"__bool__ should return bool, returned {}",
other.type_name()
))
.into()),
};
}
if let Some(method) = instance_slot(state, value, "__len__") {
let (returned, _self) = invoke_slot(state, value, &method, &[], tools).await?;
return match returned {
Value::Int(n) => Ok(n != 0),
Value::Bool(b) => Ok(b),
other => Err(InterpreterError::TypeError(format!(
"'{}' object cannot be interpreted as an integer",
other.type_name()
))
.into()),
};
}
}
Ok(value.is_truthy())
}