use rustpython_parser::ast;
use crate::{
error::{EvalError, EvalResult, InterpreterError},
eval::{eval_expr, functions::resolve_proxy},
security::validator,
state::InterpreterState,
tools::Tools,
value::{ExceptionValue, Value, shared_list},
};
pub fn eval_name(state: &InterpreterState, node: &ast::ExprName, tools: &Tools) -> EvalResult {
let name = node.id.as_str();
validator::validate_name(validator::NameContext::Access, name)?;
if let Some(val) = state.get_variable(name) {
return Ok(val.clone());
}
if tools.contains_key(name) {
return Ok(Value::ToolName(name.to_string()));
}
if name == "NotImplemented" {
return Ok(Value::NotImplemented);
}
let builtin_functions = [
"print",
"len",
"range",
"str",
"int",
"float",
"bool",
"type",
"isinstance",
"issubclass",
"super",
"hasattr",
"callable",
"abs",
"round",
"min",
"max",
"sum",
"all",
"any",
"sorted",
"enumerate",
"zip",
"reversed",
"chr",
"ord",
"list",
"tuple",
"dict",
"set",
"iter",
"next",
"filter",
"map",
"repr",
"hash",
"id",
"input",
"pow",
"divmod",
"format",
"object",
"bin",
"oct",
"hex",
"bytes",
"bytearray",
];
if builtin_functions.contains(&name) {
return Ok(Value::BuiltinName(name.to_string()));
}
let exception_types = [
"Exception",
"ValueError",
"TypeError",
"KeyError",
"IndexError",
"AttributeError",
"RuntimeError",
"StopIteration",
"ZeroDivisionError",
"OverflowError",
"AssertionError",
"NotImplementedError",
"FileNotFoundError",
"IOError",
"OSError",
"NameError",
"ArithmeticError",
"LookupError",
"ExceptionGroup",
"BaseExceptionGroup",
];
if exception_types.contains(&name) {
return Ok(Value::ExceptionType(name.to_string()));
}
if crate::eval::modules::is_auto_imported(name) {
return Ok(Value::Module(name.to_string()));
}
Err(InterpreterError::name_not_defined(name).into())
}
pub async fn eval_named_expr(
state: &mut InterpreterState,
node: &ast::ExprNamedExpr,
tools: &Tools,
) -> EvalResult {
let value = eval_expr(state, &node.value, tools).await?;
match node.target.as_ref() {
ast::Expr::Name(name_node) => {
let name = name_node.id.as_str();
validator::validate_name(validator::NameContext::Assignment, name)?;
state.set_variable(name, value.clone()).map_err(EvalError::Interpreter)?;
Ok(value)
}
other => Err(InterpreterError::Runtime(format!(
"walrus assignment target must be a name, not {:?}",
std::mem::discriminant(other)
))
.into()),
}
}
pub async fn eval_attribute(
state: &mut InterpreterState,
node: &ast::ExprAttribute,
tools: &Tools,
) -> EvalResult {
let attr_name = node.attr.as_str();
validator::validate_attribute(attr_name)?;
let place_opt = crate::eval::place::eval_place(state, &node.value, tools).await?;
let usable_place: Option<&crate::eval::place::Place> = match &place_opt {
Some(p) if p.is_navigable() && state.variables.contains_key(&p.root) => Some(p),
_ => None,
};
let nav_value: Option<Value> = match usable_place {
Some(place) => {
let mut root = state
.get_variable(&place.root)
.cloned()
.ok_or_else(|| EvalError::from(InterpreterError::name_not_defined(&place.root)))?;
crate::eval::place::with_navigate_mut(&mut root, &place.steps, |target| target.clone())
.ok()
}
None => None,
};
let (obj, place_for_upgrade) = match nav_value {
Some(v) => (v, usable_place),
None => (eval_expr(state, &node.value, tools).await?, None),
};
let obj = resolve_proxy(&obj).await?;
getattr_on_value(state, obj, attr_name, tools, place_for_upgrade).await
}
pub(crate) async fn getattr_on_value(
state: &mut InterpreterState,
obj: Value,
attr_name: &str,
tools: &Tools,
place_for_upgrade: Option<&crate::eval::place::Place>,
) -> EvalResult {
validator::validate_attribute(attr_name)?;
if let Value::Instance(inst) = &obj {
if let Some(prop) =
crate::eval::classes::lookup_property(state, &inst.class_name, attr_name)
{
return crate::eval::classes::invoke_property_getter(
state,
&prop.getter,
obj.clone(),
tools,
)
.await;
}
if let Some(desc) =
crate::eval::classes::lookup_class_attr_instance(state, &inst.class_name, attr_name)
{
let has_get =
crate::eval::classes::lookup_method_in_mro(state, &desc.class_name, "__get__")
.is_some();
if has_get {
let is_data =
crate::eval::classes::lookup_method_in_mro(state, &desc.class_name, "__set__")
.is_some()
|| crate::eval::classes::lookup_method_in_mro(
state,
&desc.class_name,
"__delete__",
)
.is_some();
if !is_data {
if let Some(v) = inst.fields.lock().get(attr_name) {
return Ok(v.clone());
}
}
if let Some((_, get_method)) =
crate::eval::classes::lookup_method_in_mro(state, &desc.class_name, "__get__")
{
let owner = Value::Class(inst.class_name.clone());
let call = crate::eval::functions::CallArgs {
positional: &[obj.clone(), owner],
keyword: &indexmap::IndexMap::new(),
};
let (returned, _) = crate::eval::classes::call_method(
state,
&get_method,
Value::Instance(desc),
call,
tools,
)
.await?;
return Ok(returned);
}
}
}
}
if let Value::BuiltinName(type_name) = &obj {
return Ok(Value::BuiltinTypeMethod {
type_name: type_name.clone(),
method: attr_name.to_string(),
});
}
if let Some(val) = crate::types::dispatch_getattr_opt(&obj, attr_name)? {
if let (
Value::BoundMethod { receiver: crate::value::BoundMethodReceiver::Snapshot(_), method },
Some(place),
) = (&val, place_for_upgrade)
{
let bm_steps: Vec<crate::value::BoundMethodStep> = place
.steps
.iter()
.filter_map(|s| match s {
crate::eval::place::PlaceStep::Index(v) => {
Some(crate::value::BoundMethodStep::Index(v.clone()))
}
crate::eval::place::PlaceStep::Attr(n) => {
Some(crate::value::BoundMethodStep::Attr(n.clone()))
}
crate::eval::place::PlaceStep::Slice(_) => None,
})
.collect();
return Ok(Value::BoundMethod {
receiver: crate::value::BoundMethodReceiver::Place {
root: place.root.clone(),
steps: bm_steps,
},
method: method.clone(),
});
}
return Ok(val);
}
match legacy_attribute(state, &obj, attr_name) {
Ok(v) => Ok(v),
Err(err) => {
let is_attribute_error =
matches!(err, EvalError::Interpreter(InterpreterError::AttributeError(_)));
if !is_attribute_error {
return Err(err);
}
if let Value::Instance(inst) = &obj {
if let Some((_, method)) = crate::eval::classes::lookup_method_in_mro(
state,
&inst.class_name,
"__getattr__",
) {
let attr_arg = Value::String(attr_name.into());
let call = crate::eval::functions::CallArgs {
positional: std::slice::from_ref(&attr_arg),
keyword: &indexmap::IndexMap::new(),
};
let (returned, _self) =
crate::eval::classes::call_method(state, &method, obj.clone(), call, tools)
.await?;
return Ok(returned);
}
}
Err(err)
}
}
}
fn legacy_attribute(state: &InterpreterState, obj: &Value, attr_name: &str) -> EvalResult {
match obj {
Value::Exception(exc) => exception_attribute(exc, attr_name),
Value::Instance(inst) => crate::eval::classes::instance_attribute(state, inst, attr_name),
Value::Class(class_name) => {
crate::eval::classes::class_attribute(state, class_name, attr_name)
}
Value::Type(type_name) | Value::ExceptionType(type_name) => {
if attr_name == "__name__" || attr_name == "__qualname__" {
Ok(Value::String(type_name.clone().into()))
} else {
Err(attribute_error("type", attr_name))
}
}
Value::Function(func_def) => {
if attr_name == "__name__" || attr_name == "__qualname__" {
Ok(Value::String(func_def.name.clone().into()))
} else {
Err(attribute_error("function", attr_name))
}
}
Value::Lambda(_) => {
if attr_name == "__name__" || attr_name == "__qualname__" {
Ok(Value::String("<lambda>".into()))
} else {
Err(attribute_error("function", attr_name))
}
}
Value::Module(module) => crate::eval::modules::module_member(module, attr_name),
Value::ModuleFunction { module, name } => {
if let Some(func) = crate::eval::modules::type_classmethod(module, name, attr_name) {
Ok(Value::ModuleFunction { module: module.clone(), name: func.into() })
} else {
Err(attribute_error(obj.type_name(), attr_name))
}
}
_ => Err(attribute_error(obj.type_name(), attr_name)),
}
}
fn attribute_error(type_name: &str, attr_name: &str) -> EvalError {
InterpreterError::AttributeError(format!("'{type_name}' object has no attribute '{attr_name}'"))
.into()
}
fn exception_attribute(exc: &ExceptionValue, attr_name: &str) -> EvalResult {
match attr_name {
"exceptions" => {
let items = exc
.exceptions
.as_ref()
.map(|xs| xs.iter().cloned().map(Value::Exception).collect())
.unwrap_or_default();
Ok(Value::Tuple(items))
}
"subgroup" | "split" => {
Ok(Value::ExceptionMethod { method: attr_name.to_string(), exception: exc.clone() })
}
"args" => Ok(Value::Tuple(exc.args.clone())),
"__cause__" | "__context__" => {
Ok(exc.cause.as_ref().map_or(Value::None, |cause| Value::Exception((**cause).clone())))
}
"message" => Ok(Value::String(exc.message.clone().into())),
_ => Err(attribute_error(&exc.type_name, attr_name)),
}
}
pub async fn eval_subscript(
state: &mut InterpreterState,
node: &ast::ExprSubscript,
tools: &Tools,
) -> EvalResult {
if let ast::Expr::Name(name_node) = node.value.as_ref() {
if !matches!(node.slice.as_ref(), ast::Expr::Slice(_)) {
let container_name = name_node.id.as_str();
let slice_is_static = match node.slice.as_ref() {
ast::Expr::Constant(_) => true,
ast::Expr::Name(slice_name) => slice_name.id.as_str() != container_name,
_ => false,
};
if slice_is_static {
let index = match crate::eval::try_eval_expr_sync(state, &node.slice, tools) {
Some(r) => r?,
None => eval_expr(state, &node.slice, tools).await?,
};
if let Some(container) = state.variables.get(container_name) {
let take_fast_path = matches!(
container,
Value::Dict(_)
| Value::List(_)
| Value::Tuple(_)
| Value::String(_)
| Value::Range { .. }
| Value::Bytes(_)
);
if take_fast_path && !matches!(index, Value::Instance(_)) {
return crate::types::dispatch_getitem(container, &index);
}
}
}
}
}
let obj = eval_expr(state, &node.value, tools).await?;
let obj = resolve_proxy(&obj).await?;
if let ast::Expr::Slice(slice_node) = node.slice.as_ref() {
return eval_subscript_slice(state, &obj, slice_node, tools).await;
}
let index = eval_expr(state, &node.slice, tools).await?;
if let Value::Instance(inst) = &obj {
if let Some(Value::Tuple(field_names)) =
state.classes.get(&inst.class_name).and_then(|c| c.class_attrs.get("_fields"))
{
if let Value::Int(i) = &index {
let len = field_names.len();
let idx = if *i < 0 {
usize::try_from(i64::try_from(len).unwrap_or(i64::MAX) + *i).ok()
} else {
usize::try_from(*i).ok()
};
if let Some(idx) = idx.filter(|&n| n < len) {
if let Value::String(field_name) = &field_names[idx] {
return Ok(inst
.fields
.lock()
.get(field_name.as_str())
.cloned()
.unwrap_or(Value::None));
}
}
return Err(InterpreterError::Runtime(format!(
"tuple index out of range: {i} (len {len})"
))
.into());
}
}
}
if let Value::DefaultDict(data) = &obj {
let key = crate::eval::literals::value_to_key(&index)?;
if let Some(value) = data.items.get(&key) {
return Ok(value.clone());
}
let synthesised = invoke_factory(state, &data.factory, tools).await?;
if let ast::Expr::Name(name_node) = node.value.as_ref() {
let name = name_node.id.as_str().to_string();
if let Some(Value::DefaultDict(data)) = state.variables.get(&name).cloned() {
let mut new_data = *data;
new_data.items.insert(key, synthesised.clone());
state
.set_variable(&name, Value::DefaultDict(Box::new(new_data)))
.map_err(EvalError::Interpreter)?;
}
}
return Ok(synthesised);
}
crate::eval::op::getitem(state, &obj, &index, tools).await
}
pub async fn invoke_factory_pub(
state: &mut InterpreterState,
factory: &Value,
tools: &Tools,
) -> EvalResult {
invoke_factory(state, factory, tools).await
}
async fn invoke_factory(
state: &mut InterpreterState,
factory: &Value,
tools: &Tools,
) -> EvalResult {
let kwargs: indexmap::IndexMap<String, Value> = indexmap::IndexMap::new();
let empty: [Value; 0] = [];
match factory {
Value::Function(def) => {
crate::eval::functions::call_user_function(state, def, &empty, &kwargs, tools).await
}
Value::Lambda(def) => {
crate::eval::functions::call_lambda(state, def, &empty, &kwargs, tools).await
}
Value::Class(name) => {
crate::eval::classes::instantiate(state, name, &empty, &kwargs, tools).await
}
Value::None => Ok(Value::None),
Value::BuiltinName(builtin) => {
match builtin.as_str() {
"int" => Ok(Value::Int(0)),
"float" => Ok(Value::Float(0.0)),
"bool" => Ok(Value::Bool(false)),
"str" => Ok(Value::String("".into())),
"bytes" => Ok(Value::Bytes(Vec::new())),
"list" => Ok(Value::List(shared_list(Vec::new()))),
"tuple" => Ok(Value::Tuple(Vec::new())),
"dict" => Ok(Value::Dict(indexmap::IndexMap::new())),
"set" => Ok(Value::Set(Vec::new())),
_ => Err(InterpreterError::TypeError(format!(
"defaultdict factory builtin '{builtin}' is not zero-arg constructable"
))
.into()),
}
}
other => Err(InterpreterError::TypeError(format!(
"defaultdict factory must be callable (got '{}')",
other.type_name()
))
.into()),
}
}
async fn eval_subscript_slice(
state: &mut InterpreterState,
obj: &Value,
slice_node: &ast::ExprSlice,
tools: &Tools,
) -> EvalResult {
let lower = if let Some(ref expr) = slice_node.lower {
Some(eval_expr(state, expr, tools).await?)
} else {
None
};
let upper = if let Some(ref expr) = slice_node.upper {
Some(eval_expr(state, expr, tools).await?)
} else {
None
};
let step_expr = if let Some(ref expr) = slice_node.step {
Some(eval_expr(state, expr, tools).await?)
} else {
None
};
let stride = match &step_expr {
Some(Value::Int(s)) => {
if *s == 0 {
return Err(InterpreterError::ValueError("slice step cannot be zero".into()).into());
}
*s
}
Some(Value::None) | None => 1,
Some(other) => {
return Err(InterpreterError::TypeError(format!(
"slice indices must be integers or None, not '{}'",
other.type_name()
))
.into());
}
};
match obj {
Value::List(items) => {
let snapshot = items.lock().clone();
let sliced = slice_sequence(&snapshot, lower.as_ref(), upper.as_ref(), stride)?;
Ok(Value::List(shared_list(sliced)))
}
Value::Tuple(items) => {
let sliced = slice_sequence(items, lower.as_ref(), upper.as_ref(), stride)?;
Ok(Value::Tuple(sliced))
}
Value::String(s) => {
let chars: Vec<Value> =
s.chars().map(|c| Value::String(c.to_string().into())).collect();
let sliced = slice_sequence(&chars, lower.as_ref(), upper.as_ref(), stride)?;
let result: String = sliced
.into_iter()
.map(|v| match v {
Value::String(s) => s.into(),
_ => String::new(),
})
.collect();
Ok(Value::String(result.into()))
}
Value::Bytes(b) => {
let elems: Vec<Value> = b.iter().map(|&n| Value::Int(i64::from(n))).collect();
let sliced = slice_sequence(&elems, lower.as_ref(), upper.as_ref(), stride)?;
let bytes: Vec<u8> = sliced
.into_iter()
.filter_map(|v| match v {
Value::Int(n) => u8::try_from(n & 0xFF).ok(),
_ => None,
})
.collect();
Ok(Value::Bytes(bytes))
}
_ => Err(InterpreterError::TypeError(format!(
"'{}' object is not subscriptable",
obj.type_name()
))
.into()),
}
}
fn slice_sequence(
items: &[Value],
lower: Option<&Value>,
upper: Option<&Value>,
stride: i64,
) -> Result<Vec<Value>, EvalError> {
let len = i64::try_from(items.len()).map_err(|_| {
InterpreterError::Runtime("sequence length overflows i64 for slicing".into())
})?;
let resolve_index = |val: Option<&Value>, default: i64| -> Result<i64, EvalError> {
match val {
None | Some(Value::None) => Ok(default),
Some(Value::Int(i)) => Ok(*i),
Some(Value::Bool(b)) => Ok(i64::from(*b)),
Some(other) => Err(InterpreterError::TypeError(format!(
"slice indices must be integers or None, not '{}'",
other.type_name()
))
.into()),
}
};
let to_index = |i: i64| -> Result<usize, EvalError> {
usize::try_from(i).map_err(|_| {
InterpreterError::Runtime("slice index overflow (internal invariant)".into()).into()
})
};
if stride > 0 {
let raw_start = resolve_index(lower, 0)?;
let raw_stop = resolve_index(upper, len)?;
let begin = clamp_slice_index(raw_start, len);
let end = clamp_slice_index(raw_stop, len);
let mut result = Vec::new();
let mut i = begin;
while i < end {
result.push(items[to_index(i)?].clone());
i += stride;
}
Ok(result)
} else {
let raw_start = resolve_index(lower, len - 1)?;
let raw_stop = resolve_index(upper, -(len + 1))?;
let begin = clamp_slice_index_neg(raw_start, len);
let end = clamp_slice_index_neg(raw_stop, len);
let mut result = Vec::new();
let mut i = begin;
while i > end {
result.push(items[to_index(i)?].clone());
i += stride;
}
Ok(result)
}
}
fn clamp_slice_index(idx: i64, len: i64) -> i64 {
let adjusted = if idx < 0 { idx + len } else { idx };
adjusted.max(0).min(len)
}
fn clamp_slice_index_neg(idx: i64, len: i64) -> i64 {
let adjusted = if idx < 0 { idx + len } else { idx };
adjusted.max(-1).min(len - 1)
}
pub async fn eval_slice(
state: &mut InterpreterState,
node: &ast::ExprSlice,
tools: &Tools,
) -> EvalResult {
let lower = if let Some(ref expr) = node.lower {
eval_expr(state, expr, tools).await?
} else {
Value::None
};
let upper = if let Some(ref expr) = node.upper {
eval_expr(state, expr, tools).await?
} else {
Value::None
};
let stride = if let Some(ref expr) = node.step {
eval_expr(state, expr, tools).await?
} else {
Value::None
};
Ok(Value::Tuple(vec![lower, upper, stride]))
}