use indexmap::IndexMap;
use super::{methods, resolve_proxy};
use crate::{
error::{EvalError, InterpreterError},
eval::place,
value::{Value, shared_list},
};
#[derive(Clone, Copy)]
pub(crate) struct CallArgs<'a> {
pub positional: &'a [Value],
pub keyword: &'a IndexMap<String, Value>,
}
pub(crate) struct MethodOutcome {
pub value: Value,
pub mem_delta: isize,
}
impl MethodOutcome {
pub(crate) const fn pure(value: Value) -> Self {
Self { value, mem_delta: 0 }
}
pub(crate) fn grew(value: Value, bytes: usize) -> Self {
Self { value, mem_delta: place::to_isize(bytes) }
}
pub(crate) fn shrank(value: Value, bytes: usize) -> Self {
Self { value, mem_delta: -place::to_isize(bytes) }
}
}
pub(crate) fn reject_kwargs(
method: &str,
kwargs: &IndexMap<String, Value>,
) -> Result<(), EvalError> {
if let Some((name, _)) = kwargs.first() {
return Err(InterpreterError::TypeError(format!(
"{method}() got an unexpected keyword argument '{name}'"
))
.into());
}
Ok(())
}
pub(crate) fn bind_method_params(
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
params: &[&str],
) -> Result<Vec<Option<Value>>, EvalError> {
if args.len() > params.len() {
return Err(InterpreterError::TypeError(format!(
"{method}() takes at most {} argument{} ({} given)",
params.len(),
if params.len() == 1 { "" } else { "s" },
args.len()
))
.into());
}
let mut bound: Vec<Option<Value>> = params.iter().map(|_| None).collect();
for (i, arg) in args.iter().enumerate() {
bound[i] = Some(arg.clone());
}
for (name, value) in kwargs {
let Some(idx) = params.iter().position(|p| *p == name.as_str()) else {
return Err(InterpreterError::TypeError(format!(
"{method}() got an unexpected keyword argument '{name}'"
))
.into());
};
if bound[idx].is_some() {
return Err(InterpreterError::TypeError(format!(
"{method}() got multiple values for argument '{name}'"
))
.into());
}
bound[idx] = Some(value.clone());
}
Ok(bound)
}
pub(crate) fn require_param<'a>(
method: &str,
bound: &'a [Option<Value>],
idx: usize,
name: &str,
) -> Result<&'a Value, EvalError> {
bound.get(idx).and_then(Option::as_ref).ok_or_else(|| {
EvalError::from(InterpreterError::TypeError(format!(
"{method}() missing required argument: '{name}'"
)))
})
}
pub(super) async fn resolve_method_args(args: &[Value]) -> Result<Vec<Value>, EvalError> {
let mut resolved_args = Vec::with_capacity(args.len());
for arg in args {
let resolved = resolve_proxy(arg).await?;
match resolved {
Value::List(items) => {
let snapshot = items.lock().clone();
let mut resolved_items = Vec::with_capacity(snapshot.len());
for item in &snapshot {
resolved_items.push(resolve_proxy(item).await?);
}
resolved_args.push(Value::List(shared_list(resolved_items)));
}
Value::Tuple(items) => {
let mut resolved_items = Vec::with_capacity(items.len());
for item in &items {
resolved_items.push(resolve_proxy(item).await?);
}
resolved_args.push(Value::Tuple(resolved_items));
}
other => resolved_args.push(other),
}
}
Ok(resolved_args)
}
pub(super) async fn resolve_method_kwargs(
kwargs: &IndexMap<String, Value>,
) -> Result<IndexMap<String, Value>, EvalError> {
let mut resolved = IndexMap::with_capacity(kwargs.len());
for (k, v) in kwargs {
resolved.insert(k.clone(), resolve_proxy(v).await?);
}
Ok(resolved)
}
pub(super) fn dispatch_method(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
match obj {
Value::String(s) => {
methods::str::dispatch_string_method(s, method, args, kwargs).map(MethodOutcome::pure)
}
Value::List(items) => {
let mut guard = items.lock();
methods::list::dispatch_list_method(&mut guard, method, args, kwargs)
}
Value::Dict(map) => methods::dict::dispatch_dict_method(map, method, args, kwargs),
Value::Counter(map) => methods::counter::dispatch_counter_method(map, method, args, kwargs),
Value::Deque { items, maxlen } => {
methods::deque::dispatch_deque_method(items, maxlen.as_ref(), method, args, kwargs)
}
Value::DefaultDict(data) => {
methods::dict::dispatch_dict_method(&mut data.items, method, args, kwargs)
}
Value::Set(items) => methods::set::dispatch_set_method(items, method, args, kwargs),
Value::Tuple(items) => methods::tuple::dispatch_tuple_method(items, method, args, kwargs)
.map(MethodOutcome::pure),
Value::Date(date) => {
crate::eval::modules::datetime::dispatch_date_method(*date, method, args, kwargs)
.map(MethodOutcome::pure)
}
Value::DateTime { dt, tz_offset_secs } => {
crate::eval::modules::datetime::dispatch_datetime_method(
*dt,
*tz_offset_secs,
method,
args,
kwargs,
)
.map(MethodOutcome::pure)
}
Value::Time(t) => {
crate::eval::modules::datetime::dispatch_time_method(*t, method, args, kwargs)
.map(MethodOutcome::pure)
}
Value::TimeDelta(micros) => {
crate::eval::modules::datetime::dispatch_timedelta_method(*micros, method, args, kwargs)
.map(MethodOutcome::pure)
}
Value::ReMatch(m) => {
crate::eval::modules::re::dispatch_match_method(m, method, args, kwargs)
.map(MethodOutcome::pure)
}
Value::HashDigest { algo, bytes } => {
crate::eval::modules::hashlib::dispatch_hash_method(algo, bytes, method, args, kwargs)
.map(MethodOutcome::pure)
}
Value::Int(i) => {
methods::int::dispatch_int_method(*i, method, args, kwargs).map(MethodOutcome::pure)
}
Value::BigInt(i) => {
match i64::try_from(i.as_ref()) {
Ok(n) => methods::int::dispatch_int_method(n, method, args, kwargs)
.map(MethodOutcome::pure),
Err(_) => Err(EvalError::Exception(crate::value::ExceptionValue::new(
"OverflowError",
"Python int too large to convert to C long",
))),
}
}
Value::Bytes(b) => {
methods::bytes::dispatch_bytes_method(b, method, args, kwargs).map(MethodOutcome::pure)
}
other => {
debug_assert!(
!crate::types::type_has_methods_table(other),
"type {} claims has_methods_table but has no match arm",
crate::types::type_name_of(other)
);
Err(InterpreterError::AttributeError(format!(
"'{}' object has no attribute '{method}'",
other.type_name()
))
.into())
}
}
}
pub(crate) fn arg1<'a>(method: &str, args: &'a [Value]) -> Result<&'a Value, EvalError> {
args.first().ok_or_else(|| {
EvalError::from(InterpreterError::TypeError(format!("{method}() takes exactly 1 argument")))
})
}