use rustpython_parser::ast::{self, Expr};
use crate::{
error::{EvalError, EvalResult, InterpreterError},
eval::eval_expr,
state::InterpreterState,
tools::Tools,
value::Value,
};
pub async fn eval_delete(
state: &mut InterpreterState,
node: &ast::StmtDelete,
tools: &Tools,
) -> EvalResult {
for target in &node.targets {
delete_target(state, target, tools).await?;
}
Ok(Value::None)
}
async fn delete_target(
state: &mut InterpreterState,
target: &Expr,
tools: &Tools,
) -> Result<(), EvalError> {
match target {
Expr::Name(name_node) => {
let name = name_node.id.as_str();
if tools.contains_key(name) {
return Err(InterpreterError::Security(format!(
"cannot delete static tool '{name}'"
))
.into());
}
state.delete_variable(name).map_err(EvalError::Interpreter)
}
Expr::Attribute(attr_node) => {
let attr_name = attr_node.attr.as_str().to_string();
crate::security::validator::validate_attribute(&attr_name)?;
if let Expr::Name(name_node) = attr_node.value.as_ref() {
let obj_name = name_node.id.as_str().to_string();
let obj = state.variables.get(&obj_name).cloned().ok_or_else(|| {
EvalError::from(InterpreterError::name_not_defined(&obj_name))
})?;
if let Value::Instance(inst) = &obj {
let class_name = inst.class_name.clone();
if let Some(desc) = crate::eval::classes::lookup_class_attr_instance(
state,
&class_name,
&attr_name,
) {
if let Some((_, del_method)) = crate::eval::classes::lookup_method_in_mro(
state,
&desc.class_name,
"__delete__",
) {
let call = crate::eval::functions::CallArgs {
positional: std::slice::from_ref(&obj),
keyword: &indexmap::IndexMap::new(),
};
let _ = crate::eval::classes::call_method(
state,
&del_method,
Value::Instance(desc),
call,
tools,
)
.await?;
return Ok(());
}
}
if let Some(prop) =
crate::eval::classes::lookup_property(state, &class_name, &attr_name)
{
let Some(deleter) = prop.deleter else {
return Err(InterpreterError::AttributeError(format!(
"property '{attr_name}' of '{class_name}' object has no deleter"
))
.into());
};
let configured = crate::eval::classes::invoke_property_deleter(
state, &deleter, obj, tools,
)
.await?;
state
.set_variable(&obj_name, configured)
.map_err(EvalError::Interpreter)?;
return Ok(());
}
if let Some((_, method)) = crate::eval::classes::lookup_method_in_mro(
state,
&class_name,
"__delattr__",
) {
let name_arg = Value::String(attr_name.as_str().into());
let call = crate::eval::functions::CallArgs {
positional: std::slice::from_ref(&name_arg),
keyword: &indexmap::IndexMap::new(),
};
let (_returned, updated_self) =
crate::eval::classes::call_method(state, &method, obj, call, tools)
.await?;
state
.set_variable(&obj_name, updated_self)
.map_err(EvalError::Interpreter)?;
return Ok(());
}
if inst.fields.lock().remove(&attr_name).is_some() {
return Ok(());
}
return Err(InterpreterError::AttributeError(format!(
"'{class_name}' object has no attribute '{attr_name}'"
))
.into());
}
}
let place = crate::eval::place::eval_place(state, target, tools)
.await?
.ok_or_else(|| {
EvalError::from(InterpreterError::Runtime(
"complex delete attribute target not supported (see CONFORMANCE.md#unsupported-language-features)".into(),
))
})?;
if !place.is_navigable() {
return Err(InterpreterError::Runtime(
"complex delete attribute target not supported (see CONFORMANCE.md#unsupported-language-features)".into(),
)
.into());
}
let Some((crate::eval::place::PlaceStep::Attr(name), prefix)) =
place.steps.split_last()
else {
return Err(InterpreterError::Runtime(
"complex delete attribute target not supported (see CONFORMANCE.md#unsupported-language-features)".into(),
)
.into());
};
let root = state
.variables
.get_mut(&place.root)
.ok_or_else(|| EvalError::from(InterpreterError::name_not_defined(&place.root)))?;
crate::eval::place::with_navigate_mut(root, prefix, |parent| {
let Value::Instance(inst) = parent else {
return Err(EvalError::from(InterpreterError::AttributeError(format!(
"'{}' object attribute deletion not supported",
parent.type_name()
))));
};
if inst.fields.lock().remove(name).is_none() {
return Err(EvalError::from(InterpreterError::AttributeError(format!(
"'{}' object has no attribute '{name}'",
inst.class_name
))));
}
Ok(())
})?
}
Expr::Subscript(sub_node) => {
if let Expr::Slice(slice_node) = sub_node.slice.as_ref() {
if let Expr::Name(name_node) = sub_node.value.as_ref() {
return delete_slice(state, name_node.id.as_str(), slice_node, tools).await;
}
return Err(InterpreterError::Runtime(
"slice deletion supported only on bare-name targets".into(),
)
.into());
}
let receiver = eval_expr(state, &sub_node.value, tools).await?;
let index = eval_expr(state, &sub_node.slice, tools).await?;
if let Some(updated_self) =
crate::eval::op::delitem(state, &receiver, &index, tools).await?
{
return writeback_receiver(state, sub_node.value.as_ref(), updated_self);
}
if let Expr::Name(name_node) = sub_node.value.as_ref() {
let name = name_node.id.as_str();
let delta: Result<isize, EvalError> = {
let obj = state.variables.get_mut(name).ok_or_else(|| {
EvalError::Interpreter(InterpreterError::name_not_defined(name))
})?;
crate::types::dispatch_delitem(obj, &index)
};
let freed = delta?.unsigned_abs();
state.release_allocation(freed);
return Ok(());
}
let place_opt = crate::eval::place::eval_place(state, &sub_node.value, tools).await?;
let place = place_opt.ok_or_else(|| {
EvalError::from(InterpreterError::Runtime(
"cannot resolve delete-subscript receiver".into(),
))
})?;
let delta: Result<isize, EvalError> = {
let root = state.variables.get_mut(&place.root).ok_or_else(|| {
EvalError::Interpreter(InterpreterError::name_not_defined(&place.root))
})?;
crate::eval::place::with_navigate_mut(root, &place.steps, |container| {
crate::types::dispatch_delitem(container, &index)
})?
};
let freed = delta?.unsigned_abs();
state.release_allocation(freed);
Ok(())
}
Expr::Tuple(tuple_node) => {
for elt in &tuple_node.elts {
Box::pin(delete_target(state, elt, tools)).await?;
}
Ok(())
}
Expr::List(list_node) => {
for elt in &list_node.elts {
Box::pin(delete_target(state, elt, tools)).await?;
}
Ok(())
}
_ => Err(InterpreterError::Runtime(format!(
"cannot delete target: {:?}",
std::mem::discriminant(target)
))
.into()),
}
}
fn writeback_receiver(
state: &mut InterpreterState,
receiver_expr: &Expr,
updated_self: Value,
) -> Result<(), EvalError> {
match receiver_expr {
Expr::Name(name_node) => {
state.set_variable(name_node.id.as_str(), updated_self).map_err(EvalError::Interpreter)
}
Expr::Attribute(attr_node) => {
if let Expr::Name(name_node) = attr_node.value.as_ref() {
let owner_name = name_node.id.as_str().to_string();
let attr_name = attr_node.attr.as_str().to_string();
if let Some(Value::Instance(inst)) = state.variables.get(&owner_name).cloned() {
inst.fields.lock().insert(attr_name, updated_self);
return state
.set_variable(&owner_name, Value::Instance(inst))
.map_err(EvalError::Interpreter);
}
}
Ok(())
}
_ => Ok(()),
}
}
async fn delete_slice(
state: &mut InterpreterState,
var_name: &str,
slice_node: &ast::ExprSlice,
tools: &Tools,
) -> Result<(), EvalError> {
let start = if let Some(ref expr) = slice_node.lower {
Some(eval_expr(state, expr, tools).await?)
} else {
None
};
let stop = if let Some(ref expr) = slice_node.upper {
Some(eval_expr(state, expr, tools).await?)
} else {
None
};
let released: Result<usize, EvalError> = {
let obj = state
.variables
.get_mut(var_name)
.ok_or_else(|| EvalError::Interpreter(InterpreterError::name_not_defined(var_name)))?;
if let Value::List(items) = obj {
let mut guard = items.lock();
let items_len = guard.len();
let len = i64::try_from(items_len).map_err(|_| {
InterpreterError::Runtime("list length overflows i64 for slicing".into())
})?;
let clamp = |val: &Option<Value>, default: usize| -> Result<usize, EvalError> {
match val {
Some(Value::Int(i)) => {
let idx = if *i < 0 { (len + *i).max(0) } else { *i };
let clamped = idx.min(len);
usize::try_from(clamped).map_err(|_| {
InterpreterError::Runtime(
"slice index overflow (internal invariant)".into(),
)
.into()
})
}
_ => Ok(default),
}
};
let s = clamp(&start, 0)?;
let e = clamp(&stop, items_len)?;
let result = if s < e && s < items_len {
let end = e.min(items_len);
let drained_size: usize =
guard[s..end].iter().map(crate::state::estimate_value_size).sum();
guard.drain(s..end);
Ok(drained_size)
} else {
Ok(0)
};
drop(guard);
result
} else {
Err(EvalError::Interpreter(InterpreterError::TypeError(format!(
"'{}' object does not support item deletion",
obj.type_name()
))))
}
};
let size = released?;
if size > 0 {
state.release_allocation(size);
}
Ok(())
}