use indexmap::IndexMap;
use rustpython_parser::ast::{self, Ranged};
use super::{
builtins::try_builtin,
definitions::{
VariableCheckpoint, apply_function_scope, apply_lambda_scope, contains_yield_stmts,
writeback_nonlocal_cell,
},
helpers::{bytes_fromhex, dict_fromkeys},
method_dispatch::{CallArgs, dispatch_method},
params::{bind_params_named, execute_body},
};
use crate::{
error::{ControlFlow, EvalError, EvalResult, InterpreterError},
state::InterpreterState,
tools::Tools,
value::{FunctionDef, LambdaDef, Value},
};
const STACK_RED_ZONE: usize = 1024 * 1024;
const STACK_GROW_SIZE: usize = 32 * 1024 * 1024;
pub(crate) fn grow_stack<'a, T: 'a>(
inner: impl std::future::Future<Output = T> + 'a,
) -> impl std::future::Future<Output = T> + 'a {
let mut boxed = Box::pin(inner);
std::future::poll_fn(move |cx| {
stacker::maybe_grow(STACK_RED_ZONE, STACK_GROW_SIZE, || boxed.as_mut().poll(cx))
})
}
async fn bound_str_format(
state: &mut InterpreterState,
template: &str,
method: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
tools: &Tools,
) -> EvalResult {
if method == "format" {
return crate::eval::strings::str_format(state, template, args, kwargs, tools).await;
}
let mapping = args.first().and_then(Value::as_dict).ok_or_else(|| {
EvalError::from(InterpreterError::TypeError(
"format_map() requires a mapping argument".into(),
))
})?;
let kw: IndexMap<String, Value> = mapping
.lock()
.iter()
.filter_map(|(k, v)| match k {
crate::value::ValueKey::String(s) => Some((s.as_str().to_string(), v.clone())),
_ => None,
})
.collect();
crate::eval::strings::str_format(state, template, &[], &kw, tools).await
}
async fn bound_list_sort(
state: &mut InterpreterState,
items: Vec<Value>,
args: &[Value],
kwargs: &IndexMap<String, Value>,
tools: &Tools,
) -> Result<Vec<Value>, EvalError> {
if !args.is_empty() {
return Err(
InterpreterError::TypeError("sort() takes no positional arguments".into()).into()
);
}
let key_fn = kwargs.get("key").cloned();
let reverse = kwargs.get("reverse").is_some_and(Value::is_truthy);
super::helpers::dsu_sort(
state,
tools,
super::helpers::SortRequest { items, key_fn: key_fn.as_ref(), reverse },
)
.await
}
pub(crate) async fn call_user_function(
state: &mut InterpreterState,
func_def: &FunctionDef,
args: &[Value],
kwargs: &IndexMap<String, Value>,
tools: &Tools,
) -> EvalResult {
if func_def.is_async && !func_def.is_generator {
return Ok(Value::Coroutine(Box::new(crate::value::CoroutineValue {
func: std::sync::Arc::new(func_def.clone()),
args: args.to_vec(),
kwargs: kwargs.clone(),
awaited: false,
})));
}
grow_stack(call_user_function_inner(state, func_def, args, kwargs, tools)).await
}
pub(crate) async fn drive_coroutine(
state: &mut InterpreterState,
coro: &crate::value::CoroutineValue,
tools: &Tools,
) -> EvalResult {
grow_stack(call_user_function_inner(state, &coro.func, &coro.args, &coro.kwargs, tools)).await
}
async fn call_user_function_inner(
state: &mut InterpreterState,
func_def: &FunctionDef,
args: &[Value],
kwargs: &IndexMap<String, Value>,
tools: &Tools,
) -> EvalResult {
state.enter_call().map_err(EvalError::Interpreter)?;
if args.is_empty()
&& kwargs.is_empty()
&& func_def.params.args.is_empty()
&& func_def.params.vararg.is_none()
&& func_def.params.kwonlyargs.is_empty()
&& func_def.params.kwarg.is_none()
&& func_def.nonlocal_names.is_empty()
&& func_def.assigned_names.is_empty()
&& func_def.global_names.is_empty()
&& !func_def.is_generator
&& (func_def.closure.is_empty() || func_def.is_module_level)
{
let func_name = func_def.body_cache_key().to_string();
let body = state.function_bodies.get(&func_name).cloned();
if let Some(body_stmts) = body.as_ref() {
if let Some(direct) = try_eval_trivial_body(body_stmts) {
state.exit_call();
return Ok(direct);
}
}
state.body_source_stack.push(func_def.source.clone());
state.qualname_stack.push(format!("{}.<locals>", func_def.display_qualname()));
state.frame_cell_owners.push(rustc_hash::FxHashMap::default());
let outcome = if let Some(body_stmts) = body {
match execute_body(state, body_stmts.as_slice(), tools).await {
Ok(v) => Ok(v),
Err(EvalError::Signal(ControlFlow::Return(v))) => Ok(*v),
Err(e) => Err(e),
}
} else {
Ok(Value::None)
};
state.frame_cell_owners.pop();
state.body_source_stack.pop();
state.qualname_stack.pop();
state.exit_call();
return outcome;
}
state.frame_cell_owners.push(rustc_hash::FxHashMap::default());
let bind_outcome = bind_params_named(
&func_def.params,
func_def.display_qualname(),
args,
kwargs,
state,
tools,
)
.await;
let local_scope = match bind_outcome {
Ok(s) => s,
Err(e) => {
state.frame_cell_owners.pop();
state.exit_call();
return Err(e);
}
};
let closure_touched = func_def.closure.iter().filter(|(name, value)| {
if func_def.is_module_level {
return false;
}
!state.variables.get(*name).is_some_and(|live| live == *value)
});
let touched: Vec<String> = func_def
.params
.args
.iter()
.map(|p| p.name.clone())
.chain(func_def.params.vararg.iter().cloned())
.chain(func_def.params.kwonlyargs.iter().map(|p| p.name.clone()))
.chain(func_def.params.kwarg.iter().cloned())
.chain(closure_touched.map(|(name, _)| name.clone()))
.chain(func_def.assigned_names.iter().cloned())
.filter(|n| !func_def.global_names.contains(n) && !func_def.nonlocal_names.contains(n))
.collect();
let checkpoint = VariableCheckpoint::capture(state, &touched);
if let Err(e) = apply_function_scope(state, func_def, &local_scope) {
checkpoint.restore(state);
state.frame_cell_owners.pop();
state.exit_call();
return Err(e);
}
let func_name = func_def.body_cache_key().to_string();
let body = state.function_bodies.get(&func_name).cloned();
state.body_source_stack.push(func_def.source.clone());
state.qualname_stack.push(format!("{}.<locals>", func_def.display_qualname()));
let is_generator =
func_def.is_generator || body.as_ref().is_some_and(|stmts| contains_yield_stmts(stmts));
let exec_result = if let Some(body_stmts) = body {
if is_generator {
let use_suspend = super::generators::generator_suspendable(body_stmts.as_slice());
if use_suspend {
let mut locals = rustc_hash::FxHashMap::default();
for name in &touched {
if let Some(v) = state.variables.get(name) {
locals.insert(name.clone(), v.clone());
}
}
let generator = super::generators::create_generator(
state,
func_def,
body_stmts,
locals,
touched.clone(),
);
writeback_nonlocal_cell(state, func_def);
checkpoint.restore(state);
state.frame_cell_owners.pop();
state.exit_call();
state.body_source_stack.pop();
state.qualname_stack.pop();
return Ok(generator);
}
state.yield_stack.push(Vec::new());
let body_result = execute_body(state, body_stmts.as_slice(), tools).await;
let collected = state.yield_stack.pop().unwrap_or_default();
match body_result {
Ok(_) | Err(EvalError::Signal(ControlFlow::Return(_))) => {
let cursor_id = state.next_cursor_id;
state.next_cursor_id = state.next_cursor_id.wrapping_add(1);
state.lazy_cursors.insert(cursor_id, 0);
Ok(Value::Lazy {
items: collected,
cursor_id,
kind: crate::value::LazyKind::Generator,
})
}
Err(EvalError::Signal(ControlFlow::Yield(_))) => {
Err(InterpreterError::Runtime("internal yield without stack".into()).into())
}
Err(e) => Err(e),
}
} else {
execute_body(state, body_stmts.as_slice(), tools).await
}
} else {
Ok(Value::None)
};
writeback_nonlocal_cell(state, func_def);
checkpoint.restore(state);
state.frame_cell_owners.pop();
state.exit_call();
state.body_source_stack.pop();
state.qualname_stack.pop();
match exec_result {
Ok(val) => Ok(val),
Err(EvalError::Signal(ControlFlow::Return(val))) => Ok(*val),
Err(e) => Err(e),
}
}
fn try_eval_trivial_body(body: &[ast::Stmt]) -> Option<Value> {
if body.len() != 1 {
return None;
}
match &body[0] {
ast::Stmt::Pass(_) => Some(Value::None),
ast::Stmt::Return(node) => {
node.value.as_ref().map_or(Some(Value::None), |expr| match expr.as_ref() {
ast::Expr::Constant(c) => Some(crate::eval::literals::eval_constant(&c.value)),
_ => None,
})
}
_ => None,
}
}
pub(crate) async fn call_lambda(
state: &mut InterpreterState,
lambda_def: &LambdaDef,
args: &[Value],
kwargs: &IndexMap<String, Value>,
tools: &Tools,
) -> EvalResult {
grow_stack(call_lambda_inner(state, lambda_def, args, kwargs, tools)).await
}
async fn call_lambda_inner(
state: &mut InterpreterState,
lambda_def: &LambdaDef,
args: &[Value],
kwargs: &IndexMap<String, Value>,
tools: &Tools,
) -> EvalResult {
state.enter_call().map_err(EvalError::Interpreter)?;
let lambda_qualname =
if lambda_def.qualname.is_empty() { "<lambda>" } else { lambda_def.qualname.as_str() };
let bind_outcome =
bind_params_named(&lambda_def.params, lambda_qualname, args, kwargs, state, tools).await;
let local_scope = match bind_outcome {
Ok(s) => s,
Err(e) => {
state.exit_call();
return Err(e);
}
};
let closure_touched = lambda_def.closure.iter().filter(|(name, value)| {
if lambda_def.is_module_level {
return false;
}
!state.variables.get(*name).is_some_and(|live| live == *value)
});
let touched: Vec<String> = lambda_def
.params
.args
.iter()
.map(|p| p.name.clone())
.chain(lambda_def.params.vararg.iter().cloned())
.chain(lambda_def.params.kwonlyargs.iter().map(|p| p.name.clone()))
.chain(lambda_def.params.kwarg.iter().cloned())
.chain(closure_touched.map(|(name, _)| name.clone()))
.chain(lambda_def.assigned_names.iter().cloned())
.collect();
let checkpoint = VariableCheckpoint::capture(state, &touched);
if let Err(e) = apply_lambda_scope(state, lambda_def, &local_scope) {
checkpoint.restore(state);
state.exit_call();
return Err(e);
}
let body = state.lambda_bodies.get(&lambda_def.lambda_id).cloned();
state.body_source_stack.push(lambda_def.source.clone());
state.qualname_stack.push(format!("{lambda_qualname}.<locals>"));
let result = if let Some(body_expr) = body {
let body_line =
crate::eval::line_of(&lambda_def.source, body_expr.range().start().to_usize());
crate::eval::eval_expr(state, &body_expr, tools)
.await
.map_err(|e| crate::eval::stamp_line(e, body_line))
} else {
Ok(Value::None)
};
state.body_source_stack.pop();
state.qualname_stack.pop();
checkpoint.restore(state);
state.exit_call();
result
}
pub(crate) async fn call_value_as_function(
state: &mut InterpreterState,
func: &Value,
args: &[Value],
kwargs: &IndexMap<String, Value>,
tools: &Tools,
) -> EvalResult {
match func {
Value::Function(func_def) => call_user_function(state, func_def, args, kwargs, tools).await,
Value::Lambda(lambda_def) => call_lambda(state, lambda_def, args, kwargs, tools).await,
Value::Class(class_name) => {
crate::eval::classes::instantiate(state, class_name, args, kwargs, tools).await
}
Value::BoundMethod { receiver, method } => {
match receiver {
crate::value::BoundMethodReceiver::Snapshot(value) => {
if matches!(
**value,
Value::Lazy { .. } | Value::Generator { .. } | Value::BuiltinIter { .. }
) && super::generators::is_generator_method(method)
{
return super::generators::dispatch_generator_method(
state, value, method, args, kwargs, tools,
)
.await;
}
if let Value::Instance(inst) = &**value {
if let Some((_, def)) = crate::eval::classes::lookup_method_in_mro(
state,
&inst.class_name,
method,
) {
let call = crate::eval::functions::CallArgs {
positional: args,
keyword: kwargs,
};
let (returned, _self) = crate::eval::classes::call_method(
state,
&def,
(**value).clone(),
call,
tools,
)
.await?;
return Ok(returned);
}
}
if method == "__iter__"
&& args.is_empty()
&& crate::types::builtin_dunder_present(value, "__iter__")
{
return super::builtins::make_iterator(state, value, tools).await;
}
if let Some((type_name, m)) = crate::types::instance_classmethod(value, method)
{
let unbound = Value::BuiltinTypeMethod {
type_name: type_name.to_string(),
method: m.to_string(),
};
return Box::pin(call_value_as_function(
state, &unbound, args, kwargs, tools,
))
.await;
}
if let Value::String(template) = &**value {
if matches!(method.as_str(), "format" | "format_map") {
return bound_str_format(state, template, method, args, kwargs, tools)
.await;
}
}
if method == "sort" {
if let Value::List(list) = &**value {
let list = list.clone();
let items = std::mem::take(&mut **list.lock());
let sorted =
Box::pin(bound_list_sort(state, items, args, kwargs, tools))
.await?;
list.lock().set_items(sorted);
return Ok(Value::None);
}
}
let mut recv = (**value).clone();
Ok(dispatch_method(&mut recv, method, args, kwargs)?.value)
}
crate::value::BoundMethodReceiver::Place { root, steps } => {
use crate::{
eval::place::{PlaceStep, apply_mem_delta, with_navigate_mut},
value::BoundMethodStep,
};
let pl_steps: Vec<PlaceStep> = steps
.iter()
.map(|s| match s {
BoundMethodStep::Index(v) => PlaceStep::Index(v.clone()),
BoundMethodStep::Attr(n) => PlaceStep::Attr(n.clone()),
})
.collect();
let gen_recv = {
let root_slot = state.variables.get_mut(root).ok_or_else(|| {
EvalError::from(InterpreterError::name_not_defined(root))
})?;
with_navigate_mut(root_slot, &pl_steps, |target| {
if matches!(
target,
Value::Lazy { .. }
| Value::Generator { .. }
| Value::BuiltinIter { .. }
) && super::generators::is_generator_method(method)
{
Ok::<Option<Value>, EvalError>(Some(target.clone()))
} else {
Ok(None)
}
})??
};
if let Some(recv) = gen_recv {
return super::generators::dispatch_generator_method(
state, &recv, method, args, kwargs, tools,
)
.await;
}
if matches!(method.as_str(), "format" | "format_map") {
let template = {
let root_slot = state.variables.get_mut(root).ok_or_else(|| {
EvalError::from(InterpreterError::name_not_defined(root))
})?;
with_navigate_mut(root_slot, &pl_steps, |target| {
if let Value::String(s) = target {
Some(s.to_string())
} else {
None
}
})?
};
if let Some(template) = template {
return bound_str_format(state, &template, method, args, kwargs, tools)
.await;
}
}
if method == "sort" {
let taken = {
let root_slot = state.variables.get_mut(root).ok_or_else(|| {
EvalError::from(InterpreterError::name_not_defined(root))
})?;
with_navigate_mut(root_slot, &pl_steps, |target| match target {
Value::List(items) => Some(std::mem::take(&mut **items.lock())),
_ => None,
})?
};
if let Some(items) = taken {
let sorted =
Box::pin(bound_list_sort(state, items, args, kwargs, tools))
.await?;
let root_slot = state.variables.get_mut(root).ok_or_else(|| {
EvalError::from(InterpreterError::name_not_defined(root))
})?;
with_navigate_mut(root_slot, &pl_steps, |target| {
if let Value::List(items) = target {
items.lock().set_items(sorted);
}
})?;
return Ok(Value::None);
}
}
let outcome = {
let root_slot = state.variables.get_mut(root).ok_or_else(|| {
EvalError::from(InterpreterError::name_not_defined(root))
})?;
with_navigate_mut(root_slot, &pl_steps, |target| {
dispatch_method(target, method, args, kwargs)
})??
};
apply_mem_delta(state, outcome.mem_delta)?;
Ok(outcome.value)
}
}
}
Value::BuiltinTypeMethod { type_name, method } => {
if type_name == "dict" && method == "fromkeys" {
return dict_fromkeys(state, args, tools).await;
}
if (type_name == "bytes" || type_name == "bytearray") && method == "fromhex" {
let parsed = bytes_fromhex(args)?;
return Ok(match (type_name.as_str(), parsed) {
("bytearray", Value::Bytes(b)) => {
Value::ByteArray(crate::value::shared_bytes(b))
}
(_, other) => other,
});
}
if type_name == "int" && method == "from_bytes" {
return crate::eval::functions::helpers::int_from_bytes(args, kwargs);
}
if type_name == "bool" && method == "from_bytes" {
let n = crate::eval::functions::helpers::int_from_bytes(args, kwargs)?;
return Ok(Value::Bool(n.is_truthy()));
}
if type_name == "str" && method == "maketrans" {
return crate::eval::functions::helpers::str_maketrans(args);
}
if (type_name == "bytes" || type_name == "bytearray") && method == "maketrans" {
return bytes_maketrans(args);
}
if type_name == "float" && method == "fromhex" {
return crate::eval::functions::helpers::float_fromhex(args);
}
if type_name == "object" {
return object_default_method(state, method, args, tools).await;
}
let Some((recv_arg, rest)) = args.split_first() else {
return Err(InterpreterError::TypeError(format!(
"unbound method {type_name}.{method}() needs a {type_name} as first argument"
))
.into());
};
let mut recv = recv_arg.clone();
Ok(dispatch_method(&mut recv, method, rest, kwargs)?.value)
}
Value::ModuleFunction { module, name } => {
crate::eval::modules::call_function(state, module, name, args, kwargs, tools).await
}
Value::BuiltinName(builtin_name) => {
Box::pin(try_builtin(state, builtin_name, args, kwargs, tools)).await?.ok_or_else(
|| InterpreterError::TypeError(format!("'{builtin_name}' is not callable")).into(),
)
}
Value::ToolName(tool_name) => crate::tools::resolver::resolve_and_dispatch(
state,
crate::tools::resolver::ToolCallDescriptor { name: tool_name, args, kwargs },
tools,
)
.await?
.ok_or_else(|| {
InterpreterError::TypeError(format!("'{tool_name}' is not callable")).into()
}),
Value::UnboundClassMethod { class, method } => {
let Some(def) = crate::eval::classes::lookup_class_method(state, class, method) else {
return Err(InterpreterError::AttributeError(format!(
"type object '{class}' has no classmethod '{method}'"
))
.into());
};
let call = CallArgs { positional: args, keyword: kwargs };
let (returned, _self) = crate::eval::classes::call_method(
state,
&def,
Value::Class(class.clone()),
call,
tools,
)
.await?;
Ok(returned)
}
Value::ExceptionMethod { method, exception } => {
crate::eval::exceptions::call_exception_method(method, exception, args)
}
Value::ExceptionType(type_name) => {
crate::eval::exceptions::construct_exception_type(type_name, args)
}
Value::Partial(data) => {
let target = &data.func;
let mut combined: Vec<Value> = Vec::with_capacity(data.args.len() + args.len());
combined.extend(data.args.iter().cloned());
combined.extend_from_slice(args);
let merged_kwargs = if data.keywords.is_empty() {
kwargs.clone()
} else {
let mut merged = data.keywords.clone();
for (k, v) in kwargs {
merged.insert(k.clone(), v.clone());
}
merged
};
return Box::pin(call_value_as_function(
state,
target,
&combined,
&merged_kwargs,
tools,
))
.await;
}
Value::OperatorGetter(getter) => {
let [obj] = args else {
return Err(InterpreterError::TypeError(format!(
"{} expected 1 argument, got {}",
func.type_name(),
args.len()
))
.into());
};
return apply_operator_getter(state, getter, obj, tools).await;
}
Value::SingleDispatch(sd) => {
let impl_fn =
crate::eval::modules::functools::resolve_dispatch_impl(sd, args.first(), state);
return Box::pin(call_value_as_function(state, &impl_fn, args, kwargs, tools)).await;
}
Value::LruCache(data) => {
use crate::eval::literals::value_to_key;
let mut key: Vec<_> =
args.iter().map(value_to_key).collect::<Result<Vec<_>, _>>().map_err(|_| {
InterpreterError::TypeError("lru_cache arguments must be hashable".into())
})?;
let mut kw: Vec<(&String, &Value)> = kwargs.iter().collect();
kw.sort_by(|a, b| a.0.cmp(b.0));
for (name, value) in kw {
key.push(crate::value::ValueKey::String(name.as_str().into()));
key.push(value_to_key(value).map_err(|_| {
InterpreterError::TypeError("lru_cache arguments must be hashable".into())
})?);
}
{
let mut cache = data.cache.lock();
if let Some(hit) = cache.get(&key) {
let hit = hit.clone();
cache.shift_remove(&key);
cache.insert(key.clone(), hit.clone());
data.hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
return Ok(hit);
}
}
data.misses.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let result =
Box::pin(call_value_as_function(state, &data.func, args, kwargs, tools)).await?;
let mut cache = data.cache.lock();
if let Some(max) = data.maxsize {
while cache.len() >= max && max > 0 {
cache.shift_remove_index(0);
}
}
if data.maxsize != Some(0) {
cache.insert(key, result.clone());
}
Ok(result)
}
Value::Instance(inst) => {
let class_name = inst.class_name.clone();
if let Some((_, method)) =
crate::eval::classes::lookup_method_in_mro(state, &class_name, "__call__")
{
let call = CallArgs { positional: args, keyword: kwargs };
let (returned, _self) =
crate::eval::classes::call_method(state, &method, func.clone(), call, tools)
.await?;
return Ok(returned);
}
Err(InterpreterError::TypeError(format!("'{class_name}' object is not callable"))
.into())
}
Value::None => {
Err(InterpreterError::TypeError("'NoneType' object is not callable".into()).into())
}
_ => Err(InterpreterError::TypeError(format!(
"'{}' object is not callable",
func.type_name()
))
.into()),
}
}
fn bytes_maketrans(args: &[Value]) -> EvalResult {
let bytes_of = |v: Option<&Value>| -> Option<Vec<u8>> {
match v {
Some(Value::Bytes(b)) => Some(b.clone()),
Some(Value::ByteArray(b)) => Some(b.lock().clone()),
_ => None,
}
};
let (Some(from), Some(to)) = (bytes_of(args.first()), bytes_of(args.get(1))) else {
return Err(InterpreterError::TypeError(
"maketrans() requires two bytes-like objects".into(),
)
.into());
};
if from.len() != to.len() {
return Err(InterpreterError::ValueError(
"maketrans arguments must have same length".into(),
)
.into());
}
let mut table: Vec<u8> = (0..=255).collect();
for (&f, &t) in from.iter().zip(&to) {
table[f as usize] = t;
}
Ok(Value::Bytes(table))
}
async fn object_default_method(
state: &mut InterpreterState,
method: &str,
args: &[Value],
tools: &Tools,
) -> EvalResult {
let inst = match args.first() {
Some(Value::Instance(inst)) => inst,
_ => {
return Err(InterpreterError::TypeError(format!(
"descriptor '{method}' requires a 'object' instance"
))
.into());
}
};
match method {
"__setattr__" => {
let (Some(Value::String(name)), Some(value)) = (args.get(1), args.get(2)) else {
return Err(InterpreterError::TypeError(
"object.__setattr__ requires a name and a value".into(),
)
.into());
};
inst.fields.lock().insert(name.to_string(), value.clone());
Ok(Value::None)
}
"__delattr__" => {
let Some(Value::String(name)) = args.get(1) else {
return Err(InterpreterError::TypeError(
"object.__delattr__ requires a name".into(),
)
.into());
};
if inst.fields.lock().remove(name.as_str()).is_none() {
return Err(InterpreterError::AttributeError(name.to_string()).into());
}
Ok(Value::None)
}
"__getattribute__" => {
let Some(Value::String(name)) = args.get(1) else {
return Err(InterpreterError::TypeError(
"object.__getattribute__ requires a name".into(),
)
.into());
};
crate::eval::names::getattr_normal_lookup(
state,
Value::Instance(inst.clone()),
name.as_str(),
tools,
None,
)
.await
}
"__init__" => Ok(Value::None),
_ => Err(InterpreterError::AttributeError(format!(
"type object 'object' has no attribute '{method}'"
))
.into()),
}
}
async fn apply_operator_getter(
state: &mut crate::state::InterpreterState,
getter: &crate::value::OperatorGetter,
obj: &Value,
tools: &Tools,
) -> EvalResult {
use crate::value::OperatorGetter;
match getter {
OperatorGetter::ItemGetter(items) => {
let mut results = Vec::with_capacity(items.len());
for item in items {
results.push(crate::eval::op::getitem(state, obj, item, tools).await?);
}
Ok(single_or_tuple(results))
}
OperatorGetter::AttrGetter(attrs) => {
let mut results = Vec::with_capacity(attrs.len());
for parts in attrs {
let mut current = obj.clone();
for part in parts {
current =
crate::eval::names::getattr_on_value(state, current, part, tools, None)
.await?;
}
results.push(current);
}
Ok(single_or_tuple(results))
}
OperatorGetter::MethodCaller { name, args, kwargs } => {
if let Value::Instance(inst) = obj {
if let Some((_, method)) =
crate::eval::classes::lookup_method_in_mro(state, &inst.class_name, name)
{
let call = CallArgs { positional: args, keyword: kwargs };
let (returned, _self) =
crate::eval::classes::call_method(state, &method, obj.clone(), call, tools)
.await?;
return Ok(returned);
}
}
let mut receiver = obj.clone();
Ok(dispatch_method(&mut receiver, name, args, kwargs)?.value)
}
}
}
fn single_or_tuple(mut results: Vec<Value>) -> Value {
if results.len() == 1 { results.pop().unwrap_or(Value::None) } else { Value::Tuple(results) }
}