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)
}
type MethodsHandler =
fn(&mut Value, &str, &[Value], &IndexMap<String, Value>) -> Result<MethodOutcome, EvalError>;
fn str_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::String(s) = obj else {
return Err(type_mismatch("str"));
};
methods::str::dispatch_string_method(s, method, args, kwargs).map(MethodOutcome::pure)
}
fn list_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::List(items) = obj else {
return Err(type_mismatch("list"));
};
let mut guard = items.lock();
methods::list::dispatch_list_method(&mut guard, method, args, kwargs)
}
fn dict_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::Dict(map) = obj else {
return Err(type_mismatch("dict"));
};
methods::dict::dispatch_dict_method(map, method, args, kwargs)
}
fn counter_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::Counter(map) = obj else {
return Err(type_mismatch("Counter"));
};
methods::counter::dispatch_counter_method(map, method, args, kwargs)
}
fn deque_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::Deque { items, maxlen } = obj else {
return Err(type_mismatch("deque"));
};
methods::deque::dispatch_deque_method(items, maxlen.as_ref(), method, args, kwargs)
}
fn defaultdict_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::DefaultDict(data) = obj else {
return Err(type_mismatch("defaultdict"));
};
methods::dict::dispatch_dict_method(&mut data.items, method, args, kwargs)
}
fn set_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::Set(items) = obj else {
return Err(type_mismatch("set"));
};
methods::set::dispatch_set_method(items, method, args, kwargs)
}
fn tuple_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::Tuple(items) = obj else {
return Err(type_mismatch("tuple"));
};
methods::tuple::dispatch_tuple_method(items, method, args, kwargs).map(MethodOutcome::pure)
}
fn int_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
match obj {
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",
))),
},
_ => Err(type_mismatch("int")),
}
}
fn bytes_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::Bytes(b) = obj else {
return Err(type_mismatch("bytes"));
};
methods::bytes::dispatch_bytes_method(b, method, args, kwargs).map(MethodOutcome::pure)
}
fn date_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::Date(date) = obj else {
return Err(type_mismatch("date"));
};
crate::eval::modules::datetime::dispatch_date_method(*date, method, args, kwargs)
.map(MethodOutcome::pure)
}
fn datetime_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::DateTime { dt, tz_offset_secs } = obj else {
return Err(type_mismatch("datetime"));
};
crate::eval::modules::datetime::dispatch_datetime_method(
*dt,
*tz_offset_secs,
method,
args,
kwargs,
)
.map(MethodOutcome::pure)
}
fn time_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::Time(t) = obj else {
return Err(type_mismatch("time"));
};
crate::eval::modules::datetime::dispatch_time_method(*t, method, args, kwargs)
.map(MethodOutcome::pure)
}
fn timedelta_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::TimeDelta(micros) = obj else {
return Err(type_mismatch("timedelta"));
};
crate::eval::modules::datetime::dispatch_timedelta_method(*micros, method, args, kwargs)
.map(MethodOutcome::pure)
}
fn re_match_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::ReMatch(m) = obj else {
return Err(type_mismatch("re.Match"));
};
crate::eval::modules::re::dispatch_match_method(m, method, args, kwargs)
.map(MethodOutcome::pure)
}
fn hash_digest_methods(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Value::HashDigest { algo, bytes } = obj else {
return Err(type_mismatch("HASH"));
};
crate::eval::modules::hashlib::dispatch_hash_method(algo, bytes, method, args, kwargs)
.map(MethodOutcome::pure)
}
fn type_mismatch(expected: &str) -> EvalError {
InterpreterError::TypeError(format!("internal: method table expected {expected}")).into()
}
fn methods_handler_for(obj: &Value) -> Option<MethodsHandler> {
match obj {
Value::String(_) => Some(str_methods),
Value::List(_) => Some(list_methods),
Value::Dict(_) => Some(dict_methods),
Value::Counter(_) => Some(counter_methods),
Value::Deque { .. } => Some(deque_methods),
Value::DefaultDict(_) => Some(defaultdict_methods),
Value::Set(_) => Some(set_methods),
Value::Tuple(_) => Some(tuple_methods),
Value::Int(_) | Value::BigInt(_) => Some(int_methods),
Value::Bytes(_) => Some(bytes_methods),
Value::Date(_) => Some(date_methods),
Value::DateTime { .. } => Some(datetime_methods),
Value::Time(_) => Some(time_methods),
Value::TimeDelta(_) => Some(timedelta_methods),
Value::ReMatch(_) => Some(re_match_methods),
Value::HashDigest { .. } => Some(hash_digest_methods),
_ => None,
}
}
pub(super) fn dispatch_method(
obj: &mut Value,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
) -> Result<MethodOutcome, EvalError> {
let Some(handler) = methods_handler_for(obj) else {
debug_assert!(
!crate::types::type_has_methods_table(obj),
"type {} claims has_methods_table but has no handler",
crate::types::type_name_of(obj)
);
return Err(InterpreterError::AttributeError(format!(
"'{}' object has no attribute '{method}'",
obj.type_name()
))
.into());
};
handler(obj, method, args, kwargs)
}
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")))
})
}