use indexmap::IndexMap;
use rustc_hash::FxHashMap;
use rustpython_parser::ast;
use crate::{
error::{ControlFlow, EvalError, EvalResult, InterpreterError},
eval::{eval_expr, eval_stmt},
state::InterpreterState,
tools::Tools,
value::{FunctionParams, Value, ValueKey},
};
async fn eval_default_source(
state: &mut InterpreterState,
tools: &Tools,
source: &str,
) -> Result<Value, EvalError> {
let stmts = crate::parser::parse(source).map_err(|e| {
EvalError::from(InterpreterError::Runtime(format!(
"failed to parse default expression '{source}': {e}"
)))
})?;
let first = stmts.into_iter().next().ok_or_else(|| {
EvalError::from(InterpreterError::Runtime(format!(
"default expression '{source}' produced no statements"
)))
})?;
let ast::Stmt::Expr(expr_stmt) = first else {
return Err(InterpreterError::Runtime(format!(
"default expression '{source}' did not parse as a bare expression"
))
.into());
};
eval_expr(state, &expr_stmt.value, tools).await
}
pub(crate) async fn evaluate_param_defaults(
state: &mut InterpreterState,
params: &mut FunctionParams,
tools: &Tools,
) -> Result<(), EvalError> {
if params.default_values.is_empty() && !params.defaults.is_empty() {
let mut values = Vec::with_capacity(params.defaults.len());
for src in ¶ms.defaults {
values.push(eval_default_source(state, tools, src).await?);
}
params.default_values = values;
}
if params.kw_default_values.is_empty() && !params.kw_defaults.is_empty() {
let mut values = Vec::with_capacity(params.kw_defaults.len());
for opt_src in ¶ms.kw_defaults {
let v = match opt_src {
Some(src) => Some(eval_default_source(state, tools, src).await?),
None => None,
};
values.push(v);
}
params.kw_default_values = values;
}
Ok(())
}
fn missing_positional_error(func_name: &str, missing: &[String]) -> EvalError {
let names = match missing {
[] => String::new(),
[a] => format!("'{a}'"),
[a, b] => format!("'{a}' and '{b}'"),
many => {
let init = many[..many.len() - 1]
.iter()
.map(|s| format!("'{s}'"))
.collect::<Vec<_>>()
.join(", ");
format!("{init}, and '{}'", many[many.len() - 1])
}
};
let n = missing.len();
let plural = if n == 1 { "" } else { "s" };
let prefix = if func_name.is_empty() { String::new() } else { format!("{func_name}() ") };
InterpreterError::TypeError(format!(
"{prefix}missing {n} required positional argument{plural}: {names}"
))
.into()
}
pub(crate) async fn bind_params_named(
params: &FunctionParams,
func_name: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
state: &mut InterpreterState,
tools: &Tools,
) -> Result<FxHashMap<String, Value>, EvalError> {
let capacity = params.args.len()
+ usize::from(params.vararg.is_some())
+ params.kwonlyargs.len()
+ usize::from(params.kwarg.is_some());
let mut scope = FxHashMap::with_capacity_and_hasher(capacity, Default::default());
let num_params = params.args.len();
let num_defaults = params.defaults.len();
let first_default = num_params.saturating_sub(num_defaults);
if params.vararg.is_none() && args.len() > num_params {
let prefix = if func_name.is_empty() { String::new() } else { format!("{func_name}() ") };
return Err(InterpreterError::TypeError(format!(
"{prefix}takes {num_params} positional argument{} but {} {} given",
if num_params == 1 { "" } else { "s" },
args.len(),
if args.len() == 1 { "was" } else { "were" },
))
.into());
}
let posonly = params.posonly_count.min(params.args.len());
if posonly > 0 && params.kwarg.is_none() {
let bad: Vec<String> = params.args[..posonly]
.iter()
.filter(|p| kwargs.contains_key(&p.name))
.map(|p| p.name.clone())
.collect();
if !bad.is_empty() {
let prefix =
if func_name.is_empty() { String::new() } else { format!("{func_name}() ") };
return Err(InterpreterError::TypeError(format!(
"{prefix}got some positional-only arguments passed as keyword arguments: '{}'",
bad.join(", ")
))
.into());
}
}
let mut missing: Vec<String> = Vec::new();
for (i, param) in params.args.iter().enumerate() {
let is_posonly = i < posonly;
if i < args.len() {
if !is_posonly && kwargs.contains_key(¶m.name) {
return Err(InterpreterError::TypeError(format!(
"got multiple values for argument '{}'",
param.name
))
.into());
}
scope.insert(param.name.clone(), args[i].clone());
} else if let Some(val) = kwargs.get(¶m.name).filter(|_| !is_posonly) {
scope.insert(param.name.clone(), val.clone());
} else {
let default_idx = i.checked_sub(first_default);
if let Some(di) = default_idx {
let default_val = if di < params.default_values.len() {
params.default_values[di].clone()
} else if di < params.defaults.len() {
eval_default_source(state, tools, ¶ms.defaults[di])
.await
.unwrap_or(Value::None)
} else {
missing.push(param.name.clone());
continue;
};
scope.insert(param.name.clone(), default_val);
} else {
missing.push(param.name.clone());
}
}
}
if !missing.is_empty() {
return Err(missing_positional_error(func_name, &missing));
}
if let Some(ref vararg_name) = params.vararg {
let extra: Vec<Value> = args.iter().skip(num_params).cloned().collect();
scope.insert(vararg_name.clone(), Value::Tuple(extra));
}
for (i, kw_param) in params.kwonlyargs.iter().enumerate() {
if let Some(val) = kwargs.get(&kw_param.name) {
scope.insert(kw_param.name.clone(), val.clone());
} else if let Some(Some(default_val)) = params.kw_default_values.get(i) {
scope.insert(kw_param.name.clone(), default_val.clone());
} else if let Some(Some(default_src)) = params.kw_defaults.get(i) {
let default_val =
eval_default_source(state, tools, default_src).await.unwrap_or(Value::None);
scope.insert(kw_param.name.clone(), default_val);
} else {
return Err(InterpreterError::TypeError(format!(
"missing required keyword argument: '{}'",
kw_param.name
))
.into());
}
}
if let Some(ref kwarg_name) = params.kwarg {
let mut extra_kwargs = IndexMap::new();
let param_names: Vec<&str> = params.args[posonly..]
.iter()
.chain(params.kwonlyargs.iter())
.map(|p| p.name.as_str())
.collect();
for (k, v) in kwargs {
if !param_names.contains(&k.as_str()) {
extra_kwargs.insert(ValueKey::String(k.clone().into()), v.clone());
}
}
scope.insert(kwarg_name.clone(), Value::Dict(crate::value::shared_dict(extra_kwargs)));
} else {
for k in kwargs.keys() {
let claimed = params.args.iter().any(|p| &p.name == k)
|| params.kwonlyargs.iter().any(|p| &p.name == k);
if !claimed {
return Err(InterpreterError::TypeError(format!(
"got an unexpected keyword argument '{k}'"
))
.into());
}
}
}
Ok(scope)
}
pub(crate) async fn execute_body(
state: &mut InterpreterState,
body: &[ast::Stmt],
tools: &Tools,
) -> EvalResult {
let mut result = Value::None;
for stmt in body {
match eval_stmt(state, stmt, tools).await {
Ok(val) => result = val,
Err(EvalError::Signal(ControlFlow::Return(val))) => return Ok(*val),
Err(e) => return Err(e),
}
}
Ok(result)
}