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) {
return match dict_get_instance_key(state, map, index, tools).await? {
Some(v) => Ok(v),
None => {
let k = key(state, index, tools).await?;
Err(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))
}
fn namedtuple_items(state: &InterpreterState, value: &Value) -> Option<Vec<Value>> {
let Value::Instance(inst) = value else {
return None;
};
let class = state.classes.get(&inst.class_name)?;
let Value::Tuple(field_names) = class.class_attrs.get("_fields")? else {
return None;
};
let mut items = Vec::with_capacity(field_names.len());
for name in field_names {
let Value::String(fname) = name else {
return None;
};
items.push(inst.fields.lock().get(fname.as_str()).cloned().unwrap_or(Value::None));
}
Some(items)
}
async fn drain_generator(
state: &mut InterpreterState,
id: u64,
tools: &Tools,
) -> Result<Vec<Value>, EvalError> {
let mut out = Vec::new();
loop {
match crate::eval::functions::dispatch_generator_method(
state,
&Value::Generator { id },
"__next__",
&[],
&indexmap::IndexMap::new(),
tools,
)
.await
{
Ok(v) => out.push(v),
Err(EvalError::Exception(exc)) if exc.type_name == "StopIteration" => break,
Err(e) => return Err(e),
}
}
Ok(out)
}
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);
}
if let Value::Generator { id } = value {
return Box::pin(drain_generator(state, *id, tools)).await;
}
if let Some(items) = namedtuple_items(state, value) {
return Ok(items);
}
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?;
if !matches!(returned, Value::NotImplemented) {
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?;
if !matches!(returned, Value::NotImplemented) {
return Ok(returned);
}
}
crate::eval::operations::apply_binop(
left,
right,
op,
state.decimal_prec,
state.config.max_int_bits,
)
}
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?;
if !matches!(returned, Value::NotImplemented) {
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?;
if !matches!(returned, Value::NotImplemented) {
return Ok((returned.is_truthy(), None, Some(post_self)));
}
}
}
}
if let Some(r) = dataclass_order_compare(state, op, left, right) {
return Ok((r?, None, None));
}
let r = crate::eval::operations::compare_builtin(state, op, left, right)?;
Ok((r, None, None))
}
fn dataclass_order_compare(
state: &InterpreterState,
op: rustpython_parser::ast::CmpOp,
left: &Value,
right: &Value,
) -> Option<Result<bool, EvalError>> {
use rustpython_parser::ast::CmpOp;
let (Value::Instance(a), Value::Instance(b)) = (left, right) else {
return None;
};
if a.class_name != b.class_name {
return None;
}
let class = state.classes.get(&a.class_name)?;
if !class.order {
return None;
}
let fields = class.dataclass_fields.as_ref()?;
let key_a: Vec<Value> = {
let af = a.fields.lock();
fields.iter().filter(|f| f.compare).filter_map(|f| af.get(&f.name).cloned()).collect()
};
let key_b: Vec<Value> = {
let bf = b.fields.lock();
fields.iter().filter(|f| f.compare).filter_map(|f| bf.get(&f.name).cloned()).collect()
};
let mut less = false;
let mut equal = true;
for (va, vb) in key_a.iter().zip(key_b.iter()) {
if crate::eval::operations::values_equal_pub(va, vb) {
continue;
}
equal = false;
match crate::types::dispatch_lt(va, vb) {
Ok(true) => {
less = true;
break;
}
Ok(false) => {
less = false;
break;
}
Err(e) => return Some(Err(e)),
}
}
if equal && key_a.len() != key_b.len() {
equal = false;
less = key_a.len() < key_b.len();
}
let result = match op {
CmpOp::Eq => equal,
CmpOp::NotEq => !equal,
CmpOp::Lt => less && !equal,
CmpOp::LtE => less || equal,
CmpOp::Gt => !less && !equal,
CmpOp::GtE => !less || equal,
_ => return None,
};
Some(Ok(result))
}
pub async fn lt(
state: &mut InterpreterState,
left: &Value,
right: &Value,
tools: &Tools,
) -> Result<bool, EvalError> {
use rustpython_parser::ast::CmpOp;
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());
}
if let Some(r) = dataclass_order_compare(state, CmpOp::Lt, left, right) {
return r;
}
if let Some(r) =
crate::eval::modules::functools::try_cmp_key_lt(state, left, right, tools).await
{
return r;
}
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 eq(
state: &mut InterpreterState,
left: &Value,
right: &Value,
tools: &Tools,
) -> Result<bool, EvalError> {
use rustpython_parser::ast::CmpOp;
let (result, _, _) = compare(state, CmpOp::Eq, left, right, tools).await?;
Ok(result)
}
async fn dict_get_instance_key(
state: &mut InterpreterState,
map: &indexmap::IndexMap<crate::value::ValueKey, Value>,
needle: &Value,
tools: &Tools,
) -> Result<Option<Value>, EvalError> {
let h = hash(state, needle, tools).await?;
for (k, v) in map {
if let crate::value::ValueKey::Instance { hash: kh, value } = k {
if *kh == h && eq(state, value, needle, tools).await? {
return Ok(Some(v.clone()));
}
}
}
Ok(None)
}
pub async fn dict_insert_instance_key_pub(
state: &mut InterpreterState,
map: &mut indexmap::IndexMap<crate::value::ValueKey, Value>,
needle: &Value,
value: Value,
tools: &Tools,
) -> Result<(), EvalError> {
let h = hash(state, needle, tools).await?;
let mut replace_at: Option<usize> = None;
for (idx, (k, _)) in map.iter().enumerate() {
if let crate::value::ValueKey::Instance { hash: kh, value: stored } = k {
if *kh == h && eq(state, stored, needle, tools).await? {
replace_at = Some(idx);
break;
}
}
}
if let Some(idx) = replace_at {
if let Some(entry) = map.get_index_mut(idx) {
*entry.1 = value;
}
} else {
map.insert(
crate::value::ValueKey::Instance { hash: h, value: Box::new(needle.clone()) },
value,
);
}
Ok(())
}
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()),
};
}
if let Some(items) = namedtuple_items(state, value) {
return Ok(items.len());
}
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 {
return Ok(dict_get_instance_key(state, map, item, tools).await?.is_some());
}
if let Value::List(items) = container {
let snapshot = items.lock().clone();
for stored in &snapshot {
if eq(state, stored, item, tools).await? {
return Ok(true);
}
}
return Ok(false);
}
if let Value::Tuple(items) | Value::Set(items) = container {
for stored in items {
if eq(state, stored, item, tools).await? {
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())
}