use rustpython_parser::ast::{self, Constant, Expr};
use crate::{
error::{EvalError, EvalResult},
eval::eval_expr,
state::InterpreterState,
tools::Tools,
value::{Value, ValueKey, shared_list},
};
#[inline]
pub fn eval_constant(constant: &Constant) -> Value {
match constant {
Constant::None => Value::None,
Constant::Ellipsis => Value::Ellipsis,
Constant::Bool(b) => Value::Bool(*b),
Constant::Int(i) => {
crate::value::int_from_bigint(i.clone())
}
Constant::Float(f) => Value::Float(*f),
Constant::Str(s) => Value::String(s.as_str().into()),
Constant::Bytes(b) => Value::Bytes(b.clone()),
Constant::Tuple(items) => Value::Tuple(items.iter().map(eval_constant).collect()),
Constant::Complex { real, imag } => {
Value::Complex(Box::new(num_complex::Complex64::new(*real, *imag)))
}
}
}
async fn eval_display_elements(
state: &mut InterpreterState,
elts: &[Expr],
tools: &Tools,
) -> Result<Vec<Value>, EvalError> {
let mut items = Vec::with_capacity(elts.len());
for elt in elts {
if let Expr::Starred(star) = elt {
let value = eval_expr(state, &star.value, tools).await?;
items.extend(crate::eval::op::iter(state, &value, tools).await?);
} else {
items.push(eval_expr(state, elt, tools).await?);
}
}
Ok(items)
}
pub async fn eval_list(
state: &mut InterpreterState,
node: &ast::ExprList,
tools: &Tools,
) -> EvalResult {
Ok(Value::List(shared_list(eval_display_elements(state, &node.elts, tools).await?)))
}
pub async fn eval_tuple(
state: &mut InterpreterState,
node: &ast::ExprTuple,
tools: &Tools,
) -> EvalResult {
Ok(Value::Tuple(eval_display_elements(state, &node.elts, tools).await?))
}
pub async fn eval_dict(
state: &mut InterpreterState,
node: &ast::ExprDict,
tools: &Tools,
) -> EvalResult {
let mut map = indexmap::IndexMap::new();
for (key_opt, value_expr) in node.keys.iter().zip(node.values.iter()) {
if let Some(key_expr) = key_opt {
let key = eval_expr(state, key_expr, tools).await?;
let val = eval_expr(state, value_expr, tools).await?;
if matches!(key, Value::Instance(_)) {
crate::eval::op::dict_insert_instance_key_pub(state, &mut map, &key, val, tools)
.await?;
} else {
map.insert(crate::eval::op::key(state, &key, tools).await?, val);
}
} else {
let unpacked = eval_expr(state, value_expr, tools).await?;
if let Some(d) = unpacked.as_dict() {
for (k, v) in d.lock().iter() {
map.insert(k.clone(), v.clone());
}
} else {
return Err(crate::error::InterpreterError::TypeError(
"cannot unpack non-dict in dict literal".into(),
)
.into());
}
}
}
Ok(Value::Dict(crate::value::shared_dict(map)))
}
pub(crate) async fn build_set(
state: &mut InterpreterState,
candidates: Vec<Value>,
constant: bool,
tools: &Tools,
) -> EvalResult {
let mut items: Vec<Value> = Vec::with_capacity(candidates.len());
#[expect(
clippy::mutable_key_type,
reason = "ValueKey only carries hashable variants; unhashable Values are rejected by \
value_to_key before reaching the set"
)]
let mut seen: rustc_hash::FxHashSet<crate::value::ValueKey> = rustc_hash::FxHashSet::default();
for candidate in candidates {
let exists = if let Value::Instance(_) = &candidate {
crate::eval::op::hash(state, &candidate, tools).await?;
let mut found = false;
for v in &items {
if crate::eval::op::eq(state, v, &candidate, tools).await? {
found = true;
break;
}
}
found
} else {
let ck = value_to_key(&candidate)?;
!seen.insert(ck)
};
if !exists {
items.push(candidate);
}
}
let body = if constant {
crate::pyset::SetBody::from_constant_literal(items)
} else {
crate::pyset::SetBody::from_items(items)
};
Ok(Value::Set(crate::value::shared_set(body)))
}
pub async fn eval_set(
state: &mut InterpreterState,
node: &ast::ExprSet,
tools: &Tools,
) -> EvalResult {
let candidates = eval_display_elements(state, &node.elts, tools).await?;
let constant =
!node.elts.is_empty() && node.elts.iter().all(|e| matches!(e, ast::Expr::Constant(_)));
build_set(state, candidates, constant, tools).await
}
#[inline]
pub fn value_to_key(val: &Value) -> Result<ValueKey, crate::error::EvalError> {
match val {
Value::None => Ok(ValueKey::None),
Value::Ellipsis => Ok(ValueKey::Ellipsis),
Value::Bool(b) => Ok(ValueKey::Bool(*b)),
Value::Int(i) => Ok(ValueKey::Int(*i)),
Value::BigInt(i) => Ok(ValueKey::BigInt((**i).clone())),
Value::Float(f) => Ok(float_to_key(*f)),
Value::Complex(c) if c.im == 0.0 => Ok(float_to_key(c.re)),
Value::Complex(c) => Ok(ValueKey::Complex((c.re + 0.0).to_bits(), (c.im + 0.0).to_bits())),
Value::String(s) => Ok(ValueKey::String(s.clone())),
Value::Tuple(items) => {
let keys: Result<Vec<ValueKey>, _> = items.iter().map(value_to_key).collect();
Ok(ValueKey::Tuple(keys?))
}
Value::Frozenset(body) => {
let keys: Result<Vec<ValueKey>, _> =
body.iter_ordered().iter().map(value_to_key).collect();
Ok(ValueKey::Frozenset(keys?))
}
Value::Date(d) => Ok(ValueKey::Date(*d)),
Value::Time(t) => Ok(ValueKey::Time(*t)),
Value::TimeDelta(m) => Ok(ValueKey::TimeDelta(*m)),
Value::DateTime { dt, tz_offset_secs } => {
Ok(ValueKey::DateTime { dt: *dt, tz_offset_secs: *tz_offset_secs })
}
Value::Decimal(d, _) => {
if d.normalized().fractional_digit_count() <= 0 {
let n = d.with_scale(0).as_bigint_and_exponent().0;
Ok(bigint_to_key(n))
} else {
Ok(ValueKey::Decimal(Box::new((**d).clone())))
}
}
Value::Fraction(fr) => {
if fr.is_integer() {
Ok(bigint_to_key(fr.to_integer()))
} else {
Ok(ValueKey::Fraction(Box::new((**fr).clone())))
}
}
Value::EnumMember { value: inner, kind, class_name, member_name } => match kind {
crate::value::EnumKind::Int
| crate::value::EnumKind::IntFlag
| crate::value::EnumKind::Str => value_to_key(inner),
crate::value::EnumKind::Plain | crate::value::EnumKind::Flag => {
use std::hash::{Hash as _, Hasher as _};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
class_name.hash(&mut hasher);
member_name.hash(&mut hasher);
#[expect(
clippy::cast_possible_wrap,
reason = "hash bits reinterpreted as i64 — CPython hashes are also signed"
)]
Ok(ValueKey::Instance {
hash: hasher.finish() as i64,
value: Box::new(val.clone()),
})
}
},
Value::Function(fd) => Ok(ValueKey::Instance {
hash: std::sync::Arc::as_ptr(fd) as *const () as usize as i64,
value: Box::new(val.clone()),
}),
Value::Lambda(ld) => Ok(ValueKey::Instance {
hash: std::sync::Arc::as_ptr(ld) as *const () as usize as i64,
value: Box::new(val.clone()),
}),
Value::Class(name)
| Value::Type(name)
| Value::ExceptionType(name)
| Value::BuiltinName(name) => {
use std::hash::{Hash as _, Hasher as _};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
name.hash(&mut hasher);
#[expect(
clippy::cast_possible_wrap,
reason = "hash bits reinterpreted as i64 — CPython hashes are also signed"
)]
Ok(ValueKey::Instance { hash: hasher.finish() as i64, value: Box::new(val.clone()) })
}
_ => Err(crate::error::InterpreterError::TypeError(format!(
"unhashable type: '{}'",
val.type_name()
))
.into()),
}
}
fn float_to_key(f: f64) -> ValueKey {
ValueKey::Float(f.to_bits())
}
fn bigint_to_key(n: num_bigint::BigInt) -> ValueKey {
match i64::try_from(&n) {
Ok(i) => ValueKey::Int(i),
Err(_) => ValueKey::BigInt(n),
}
}