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 Some(items) = namedtuple_items(state, container) {
let tuple = Value::Tuple(items);
if let Value::Slice(s) = index {
return crate::eval::names::apply_value_slice(
&tuple,
Some(&s.start),
Some(&s.stop),
Some(&s.step),
);
}
return crate::types::dispatch_getitem(&tuple, index);
}
if let (Some(map), Value::Instance(_)) = (container.as_dict(), 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)))
}
};
}
if let (Value::Counter(map), Value::Instance(_)) = (container, index) {
let h = hash(state, index, tools).await?;
for (k, v) in map {
if let crate::value::ValueKey::Instance { hash: kh, value } = k {
if *kh == h && eq(state, value, index, tools).await? {
return Ok(v.clone());
}
}
}
return Ok(Value::Int(0));
}
if let (Value::Class(class_name), Value::String(member)) = (container, index) {
if let Some(class) = state.classes.get(class_name) {
if class.enum_kind.is_some() {
return class.class_attrs.get(member.as_str()).cloned().ok_or_else(|| {
EvalError::Exception(crate::value::ExceptionValue::new(
"KeyError",
format!("'{member}'"),
))
});
}
}
}
if matches!(index, Value::Instance(_))
&& matches!(
container,
Value::List(_)
| Value::Tuple(_)
| Value::String(_)
| Value::Bytes(_)
| Value::ByteArray(_)
| Value::Range { .. }
)
{
let coerced = coerce_index(state, index.clone(), tools).await?;
if !matches!(coerced, Value::Instance(_)) {
return crate::types::dispatch_getitem(container, &coerced);
}
}
crate::types::dispatch_getitem(container, index)
}
pub async fn coerce_index(
state: &mut InterpreterState,
val: Value,
tools: &Tools,
) -> Result<Value, EvalError> {
if matches!(val, Value::Instance(_)) {
if let Some(resolved) = instance_unary_dunder(state, &val, "__index__", tools).await {
let idx = resolved?;
return match idx {
Value::Int(_) | Value::Bool(_) => Ok(idx),
other => Err(InterpreterError::TypeError(format!(
"__index__ returned non-int (type {})",
other.type_name()
))
.into()),
};
}
}
Ok(val)
}
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> {
Ok(drain_generator_with_return(state, id, tools).await?.0)
}
pub(crate) async fn drain_generator_with_return(
state: &mut InterpreterState,
id: u64,
tools: &Tools,
) -> Result<(Vec<Value>, 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" => {
let ret = exc.args.first().cloned().unwrap_or(Value::None);
return Ok((out, ret));
}
Err(e) => return Err(e),
}
}
}
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 Value::BuiltinIter {
id,
kind:
crate::value::BuiltinIterName::ListIterator
| crate::value::BuiltinIterName::BytearrayIterator,
} = value
{
let mut out = Vec::new();
while let Some(v) = state.step_builtin_iter(*id) {
out.push(v);
}
return Ok(out);
}
if let Value::Class(class_name) = value {
if let Some(class) = state.classes.get(class_name) {
if class.enum_kind.is_some() {
let members = class
.enum_members
.iter()
.filter_map(|name| class.class_attrs.get(name).cloned())
.collect();
return Ok(members);
}
}
}
if let Some(items) = namedtuple_items(state, value) {
return Ok(items);
}
let Some(iter_method) = instance_slot(state, value, "__iter__") else {
if instance_slot(state, value, "__getitem__").is_some() {
return Box::pin(getitem_iterate(state, value, tools)).await;
}
return crate::types::dispatch_iter(value);
};
let (iterator, _self) = invoke_slot(state, value, &iter_method, &[], tools).await?;
match &iterator {
Value::Lazy { .. } | Value::Generator { .. } | Value::BuiltinIter { .. } => {
return Box::pin(iter(state, &iterator, tools)).await;
}
Value::List(_) | Value::Tuple(_) | Value::Range { .. } => {
return Box::pin(iter(state, &iterator, 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())
}
async fn getitem_iterate(
state: &mut InterpreterState,
value: &Value,
tools: &Tools,
) -> Result<Vec<Value>, EvalError> {
let max_iters = state.config.max_while_iterations;
let mut items: Vec<Value> = Vec::new();
for i in 0..max_iters {
let idx = i64::try_from(i).map_err(|_| {
EvalError::from(InterpreterError::Runtime("sequence index overflow".into()))
})?;
match getitem(state, value, &Value::Int(idx), tools).await {
Ok(item) => items.push(item),
Err(EvalError::Exception(exc)) if exc.type_name == "IndexError" => {
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);
}
if let (rustpython_parser::ast::Operator::Add, Value::List(items)) = (op, left) {
let extension = crate::eval::op::iter(state, right, tools).await?;
items.lock().extend(extension);
return Ok(left.clone());
}
{
use rustpython_parser::ast::Operator as Op;
if let Value::Set(s) = left {
if matches!(op, Op::BitOr | Op::BitAnd | Op::Sub | Op::BitXor) {
let other = match right {
Value::Set(o) => Some(o.lock().clone()),
Value::Frozenset(o) => Some((**o).clone()),
_ => None,
};
if let Some(other) = other {
let mut body = s.lock();
match op {
Op::BitOr => body.merge_from(&other),
Op::Sub => body.difference_from(&other),
Op::BitAnd => {
let intersected = body.intersection_with(&other);
*body = intersected;
}
Op::BitXor => {
for v in other.iter_ordered() {
if !body.discard_value(&v) {
body.add_value(v);
}
}
}
_ => unreachable!(),
}
drop(body);
return Ok(left.clone());
}
}
}
}
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__",
}
}
fn flag_bitwise(
state: &InterpreterState,
op: rustpython_parser::ast::Operator,
left: &Value,
right: &Value,
) -> Option<EvalResult> {
use rustpython_parser::ast::Operator;
let (
Value::EnumMember { class_name: c1, kind, value: v1, .. },
Value::EnumMember { class_name: c2, value: v2, .. },
) = (left, right)
else {
return None;
};
if !kind.is_flag() || c1 != c2 {
return None;
}
let a = crate::value::value_as_i64(v1)?;
let b = crate::value::value_as_i64(v2)?;
let combined = match op {
Operator::BitOr => a | b,
Operator::BitAnd => a & b,
Operator::BitXor => a ^ b,
_ => return None,
};
Some(Ok(Value::EnumMember {
class_name: c1.clone(),
member_name: compose_flag_name(state, c1, combined),
value: Box::new(Value::Int(combined)),
kind: *kind,
}))
}
fn compose_flag_name(state: &InterpreterState, class_name: &str, value: i64) -> String {
let Some(class) = state.classes.get(class_name) else {
return value.to_string();
};
let mut parts = Vec::new();
for member in &class.enum_members {
if let Some(Value::EnumMember { value: mv, .. }) = class.class_attrs.get(member) {
if let Some(bit) = crate::value::value_as_i64(mv) {
if bit != 0 && value & bit == bit {
parts.push(member.clone());
}
}
}
}
if parts.is_empty() { value.to_string() } else { parts.join("|") }
}
pub async fn binop(
state: &mut InterpreterState,
op: rustpython_parser::ast::Operator,
left: &Value,
right: &Value,
tools: &Tools,
) -> EvalResult {
use rustpython_parser::ast::Operator;
if matches!(op, Operator::BitOr | Operator::BitAnd | Operator::BitXor) {
if let Some(result) = flag_bitwise(state, op, left, right) {
return result;
}
}
if matches!(op, Operator::Mod) {
match left {
Value::String(t) if percent_arg_has_instance(right) => {
return crate::eval::strings::str_percent_format_async(state, t, right, tools)
.await;
}
Value::Bytes(t) if percent_arg_has_instance(right) => {
return crate::eval::strings::bytes_percent_format_async(state, t, right, tools)
.await;
}
Value::ByteArray(t) if percent_arg_has_instance(right) => {
let snapshot = t.lock().clone();
return crate::eval::strings::bytes_percent_format_async(
state, &snapshot, right, tools,
)
.await
.map(|v| match v {
Value::Bytes(b) => Value::ByteArray(crate::value::shared_bytes(b)),
other => other,
});
}
_ => {}
}
}
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);
}
}
let same_class = matches!(
(left, right),
(Value::Instance(a), Value::Instance(b)) if a.class_name == b.class_name
);
if !same_class {
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);
}
}
}
if matches!(op, Operator::Add | Operator::Mult) {
let l = namedtuple_items(state, left).map(Value::Tuple);
let r = namedtuple_items(state, right).map(Value::Tuple);
if l.is_some() || r.is_some() {
return crate::eval::operations::apply_binop(
l.as_ref().unwrap_or(left),
r.as_ref().unwrap_or(right),
op,
state.decimal_prec,
state.config.max_int_bits,
);
}
}
crate::eval::operations::apply_binop(
left,
right,
op,
state.decimal_prec,
state.config.max_int_bits,
)
}
pub async fn unaryop(
state: &mut InterpreterState,
op: rustpython_parser::ast::UnaryOp,
operand: &Value,
tools: &Tools,
) -> EvalResult {
use rustpython_parser::ast::UnaryOp;
let slot = match op {
UnaryOp::UAdd => Some("__pos__"),
UnaryOp::USub => Some("__neg__"),
UnaryOp::Invert => Some("__invert__"),
UnaryOp::Not => None,
};
if let Some(slot) = slot {
if let Some(method) = instance_slot(state, operand, slot) {
let (returned, _self) = invoke_slot(state, operand, &method, &[], tools).await?;
return Ok(returned);
}
}
crate::eval::operations::apply_unaryop(state, op, operand, tools).await
}
fn percent_arg_has_instance(arg: &Value) -> bool {
match arg {
Value::Instance(_) => true,
Value::Tuple(items) => items.iter().any(|v| matches!(v, Value::Instance(_))),
Value::Dict(d) | Value::OrderedDict(d) => {
d.lock().values().any(|v| matches!(v, Value::Instance(_)))
}
_ => false,
}
}
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) {
let result = if matches!(op, CmpOp::NotEq) {
!returned.is_truthy()
} else {
returned.is_truthy()
};
return Ok((result, None, Some(post_self)));
}
}
}
}
if let Some(r) = dataclass_order_compare(state, op, left, right) {
return Ok((r?, None, None));
}
if let Some(r) = total_ordering_derive(state, op, left, right, tools).await? {
return Ok((r, None, None));
}
if matches!(op, CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE)
&& (matches!(left, Value::Instance(_)) || matches!(right, Value::Instance(_)))
{
let sym = match op {
CmpOp::Lt => "<",
CmpOp::LtE => "<=",
CmpOp::Gt => ">",
CmpOp::GtE => ">=",
_ => unreachable!("guarded by the matches! above"),
};
return Err(crate::types::type_error_unsupported(sym, left, right));
}
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))
}
fn total_ordering_derive<'a>(
state: &'a mut InterpreterState,
op: rustpython_parser::ast::CmpOp,
left: &'a Value,
right: &'a Value,
tools: &'a Tools,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<bool>, EvalError>> + Send + 'a>>
{
use rustpython_parser::ast::CmpOp;
Box::pin(async move {
if !matches!(op, CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE) {
return Ok(None);
}
let Value::Instance(inst) = left else { return Ok(None) };
let Some(class) = state.classes.get(&inst.class_name) else { return Ok(None) };
if !class.total_ordering {
return Ok(None);
}
let root = [
("__lt__", CmpOp::Lt),
("__le__", CmpOp::LtE),
("__gt__", CmpOp::Gt),
("__ge__", CmpOp::GtE),
]
.into_iter()
.find(|(m, _)| class.methods.contains_key(*m));
let Some((_, root_op)) = root else { return Ok(None) };
let (r, _, _) = compare(state, root_op, left, right, tools).await?;
let (e, _, _) = compare(state, CmpOp::Eq, left, right, tools).await?;
let result = match (root_op, op) {
(CmpOp::Lt, CmpOp::LtE) => r || e,
(CmpOp::Lt, CmpOp::Gt) => !r && !e,
(CmpOp::Lt, CmpOp::GtE) => !r,
(CmpOp::LtE, CmpOp::Lt) => r && !e,
(CmpOp::LtE, CmpOp::Gt) => !r,
(CmpOp::LtE, CmpOp::GtE) => !r || e,
(CmpOp::Gt, CmpOp::Lt) => !r && !e,
(CmpOp::Gt, CmpOp::LtE) => !r,
(CmpOp::Gt, CmpOp::GtE) => r || e,
(CmpOp::GtE, CmpOp::Lt) => !r,
(CmpOp::GtE, CmpOp::LtE) => !r || e,
(CmpOp::GtE, CmpOp::Gt) => r && !e,
_ => return Ok(None),
};
Ok(Some(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::Eq | CmpOp::NotEq => Some("__eq__"),
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()),
};
}
if let Value::Instance(inst) = value {
if let Some(class) = state.classes.get(&inst.class_name) {
if class.frozen {
if let Some(dc_fields) = class.dataclass_fields.clone() {
let values: Vec<Value> = {
let fields = inst.fields.lock();
dc_fields
.iter()
.filter(|f| f.compare)
.map(|f| fields.get(&f.name).cloned().unwrap_or(Value::None))
.collect()
};
return crate::types::dispatch_hash(state, &Value::Tuple(values));
}
}
}
}
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: &crate::value::SharedDict,
needle: &Value,
tools: &Tools,
) -> Result<Option<Value>, EvalError> {
let h = hash(state, needle, tools).await?;
let snapshot = map.lock().clone();
for (k, v) in &snapshot {
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 instance_unary_dunder(
state: &mut InterpreterState,
value: &Value,
slot: &str,
tools: &Tools,
) -> Option<Result<Value, EvalError>> {
let method = instance_slot(state, value, slot)?;
Some(invoke_slot(state, value, &method, &[], tools).await.map(|(returned, _self)| returned))
}
pub async fn instance_round_dunder(
state: &mut InterpreterState,
value: &Value,
ndigits: Option<&Value>,
tools: &Tools,
) -> Option<Result<Value, EvalError>> {
let method = instance_slot(state, value, "__round__")?;
let args: &[Value] = match ndigits {
Some(n) => std::slice::from_ref(n),
None => &[],
};
Some(invoke_slot(state, value, &method, args, tools).await.map(|(returned, _self)| returned))
}
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());
}
if let Value::Class(class_name) = value {
if let Some(class) = state.classes.get(class_name) {
if class.enum_kind.is_some() {
return Ok(class.enum_members.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 let Value::Class(class_name) = container {
if state.classes.get(class_name).is_some_and(|c| c.enum_kind.is_some()) {
return Ok(matches!(item, Value::EnumMember { class_name: m, .. } if m == class_name));
}
}
if matches!(container, Value::Instance(_)) {
let items = crate::eval::op::iter(state, container, tools).await?;
for stored in &items {
if eq(state, stored, item, tools).await? {
return Ok(true);
}
}
return Ok(false);
}
if matches!(item, Value::Instance(_)) {
if let Some(map) = container.as_dict() {
return Ok(dict_get_instance_key(state, map, item, tools).await?.is_some());
}
}
let container_has_instance = || match container {
Value::List(items) => items.lock().iter().any(|v| matches!(v, Value::Instance(_))),
Value::Tuple(items) => items.iter().any(|v| matches!(v, Value::Instance(_))),
_ => false,
};
if matches!(item, Value::Instance(_)) || container_has_instance() {
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) = container {
for stored in items {
if eq(state, stored, item, tools).await? {
return Ok(true);
}
}
return Ok(false);
}
if let Some(items) = container.set_items() {
hash(state, item, tools).await?;
for stored in &items {
if eq(state, stored, item, tools).await? {
return Ok(true);
}
}
return Ok(false);
}
}
if matches!(container, Value::Generator { .. } | Value::Lazy { .. }) {
let items = crate::eval::op::iter(state, container, tools).await?;
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())
}