use indexmap::IndexMap;
use super::super::{MethodOutcome, arg1, bind_method_params, reject_kwargs, require_param};
use crate::{
error::{EvalError, InterpreterError},
eval::{literals::value_to_key, place},
state::{estimate_key_size, estimate_value_size},
value::{ExceptionValue, Value, ValueKey, shared_list},
};
pub(crate) fn dispatch_dict_method(
map: &mut IndexMap<ValueKey, Value>,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
match method {
"keys" => {
reject_kwargs(method, kwargs)?;
Ok(MethodOutcome::pure(Value::List(shared_list(
map.keys().map(ValueKey::to_value).collect(),
))))
}
"values" => {
reject_kwargs(method, kwargs)?;
Ok(MethodOutcome::pure(Value::List(shared_list(map.values().cloned().collect()))))
}
"items" => {
reject_kwargs(method, kwargs)?;
Ok(MethodOutcome::pure(Value::List(shared_list(
map.iter().map(|(k, v)| Value::Tuple(vec![k.to_value(), v.clone()])).collect(),
))))
}
"get" => {
reject_kwargs(method, kwargs)?;
let key = value_to_key(arg1(method, args)?)?;
let default = args.get(1).cloned().unwrap_or(Value::None);
Ok(MethodOutcome::pure(map.get(&key).cloned().unwrap_or(default)))
}
"copy" => {
reject_kwargs(method, kwargs)?;
Ok(MethodOutcome::pure(Value::Dict(map.clone())))
}
"pop" => {
reject_kwargs(method, kwargs)?;
let key = value_to_key(arg1(method, args)?)?;
if let Some(val) = map.swap_remove(&key) {
let freed = estimate_key_size(&key) + estimate_value_size(&val);
return Ok(MethodOutcome::shrank(val, freed));
}
if let Some(def) = args.get(1) {
return Ok(MethodOutcome::pure(def.clone()));
}
Err(EvalError::Exception(ExceptionValue::key_error(key)))
}
"update" => {
if args.len() > 1 {
return Err(InterpreterError::TypeError(
"update() takes at most 1 positional argument".into(),
)
.into());
}
let mut delta = 0isize;
if let Some(arg) = args.first() {
let Value::Dict(new_entries) = arg else {
return Err(InterpreterError::TypeError(
"dict.update() argument must be a dict".into(),
)
.into());
};
for (k, v) in new_entries.clone() {
delta = delta.saturating_add(insert_entry(map, k, v));
}
}
for (k, v) in kwargs {
let key = ValueKey::String(k.clone().into());
delta = delta.saturating_add(insert_entry(map, key, v.clone()));
}
Ok(MethodOutcome { value: Value::None, mem_delta: delta })
}
"setdefault" => {
reject_kwargs(method, kwargs)?;
let key = value_to_key(arg1(method, args)?)?;
let default = args.get(1).cloned().unwrap_or(Value::None);
if let Some(existing) = map.get(&key) {
return Ok(MethodOutcome::pure(existing.clone()));
}
let entry_size = estimate_key_size(&key) + estimate_value_size(&default);
let returned = default.clone();
map.insert(key, default);
Ok(MethodOutcome::grew(returned, entry_size))
}
"clear" => {
reject_kwargs(method, kwargs)?;
let freed: usize =
map.iter().map(|(k, v)| estimate_key_size(k) + estimate_value_size(v)).sum();
map.clear();
Ok(MethodOutcome::shrank(Value::None, freed))
}
"move_to_end" => {
let bound = bind_method_params(method, args, kwargs, &["key", "last"])?;
let key = value_to_key(require_param(method, &bound, 0, "key")?)?;
let last = bound[1].as_ref().is_none_or(Value::is_truthy);
let Some((existing_key, val)) = map.shift_remove_entry(&key) else {
return Err(EvalError::Exception(ExceptionValue::key_error(key)));
};
if last {
map.insert(existing_key, val);
} else {
map.shift_insert(0, existing_key, val);
}
Ok(MethodOutcome::pure(Value::None))
}
_ => Err(InterpreterError::AttributeError(format!(
"'dict' object has no attribute '{method}'"
))
.into()),
}
}
fn insert_entry(map: &mut IndexMap<ValueKey, Value>, k: ValueKey, v: Value) -> isize {
let v_size = estimate_value_size(&v);
map.insert(k.clone(), v).map_or_else(
|| place::to_isize(estimate_key_size(&k) + v_size),
|old| place::size_delta(estimate_value_size(&old), v_size),
)
}