use rustpython_parser::ast::{self, Constant};
use crate::{
error::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 | Constant::Ellipsis => Value::None,
Constant::Bool(b) => Value::Bool(*b),
Constant::Int(i) => {
if let Ok(n) = i64::try_from(i) {
Value::Int(n)
} else {
use std::str::FromStr as _;
let s = i.to_string();
let big = num_bigint::BigInt::from_str(&s)
.unwrap_or_else(|_| num_bigint::BigInt::from(0));
crate::value::int_from_bigint(big)
}
}
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::Float(*real)
}
}
}
pub async fn eval_list(
state: &mut InterpreterState,
node: &ast::ExprList,
tools: &Tools,
) -> EvalResult {
let mut items = Vec::with_capacity(node.elts.len());
for elt in &node.elts {
items.push(eval_expr(state, elt, tools).await?);
}
Ok(Value::List(shared_list(items)))
}
pub async fn eval_tuple(
state: &mut InterpreterState,
node: &ast::ExprTuple,
tools: &Tools,
) -> EvalResult {
let mut items = Vec::with_capacity(node.elts.len());
for elt in &node.elts {
items.push(eval_expr(state, elt, tools).await?);
}
Ok(Value::Tuple(items))
}
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?;
match unpacked {
Value::Dict(d) => {
for (k, v) in d {
map.insert(k, v);
}
}
_ => {
return Err(crate::error::InterpreterError::TypeError(
"cannot unpack non-dict in dict literal".into(),
)
.into());
}
}
}
}
Ok(Value::Dict(map))
}
pub async fn eval_set(
state: &mut InterpreterState,
node: &ast::ExprSet,
tools: &Tools,
) -> EvalResult {
let mut items: Vec<Value> = Vec::with_capacity(node.elts.len());
#[expect(
clippy::mutable_key_type,
reason = "ValueKey only carries hashable variants in practice; \
unhashable Value variants are rejected upstream by value_to_key"
)]
let mut seen: rustc_hash::FxHashSet<crate::value::ValueKey> = rustc_hash::FxHashSet::default();
for elt in &node.elts {
let candidate = eval_expr(state, elt, tools).await?;
let exists = if let Value::Instance(_) = &candidate {
let mut found = false;
for v in &items {
if crate::eval::op::eq(state, v, &candidate, tools).await? {
found = true;
break;
}
}
found
} else {
value_to_key(&candidate).is_ok_and(|ck| !seen.insert(ck))
};
if !exists {
items.push(candidate);
}
}
Ok(Value::Set(items))
}
#[inline]
pub fn value_to_key(val: &Value) -> Result<ValueKey, crate::error::EvalError> {
match val {
Value::None => Ok(ValueKey::None),
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::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?))
}
_ => Err(crate::error::InterpreterError::TypeError(format!(
"unhashable type: '{}'",
val.type_name()
))
.into()),
}
}
#[expect(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::float_cmp,
reason = "round-trip guarded: `Int(as_int)` is returned only when \
`as_int as f64 == f` — an exact equality check is the point (an \
epsilon comparison would mis-fold non-integral values), so the \
truncating cast is exact and any precision loss falls through to \
the bit-pattern key"
)]
fn float_to_key(f: f64) -> ValueKey {
if f.is_finite() && f.fract() == 0.0 {
let as_int = f as i64;
if as_int as f64 == f {
return ValueKey::Int(as_int);
}
}
ValueKey::Float(f.to_bits())
}