use std::cmp::{max, min};
use super::*;
pub fn map_variants(
discr_type: &Option<Type>,
variants: Vec<initial::Variant>,
context: &Context,
) -> Result<Vec<Variant>> {
let discr_info = determine_discriminants(discr_type, &variants, context)?;
if discr_info.variant_discrs.len() != variants.len() {
bail!("Enum::map_node() variant/discriminant length mismatch");
}
variants
.into_iter()
.zip(discr_info.variant_discrs)
.map(move |(v, discr)| {
let mut child_context = context.clone();
let context = &mut child_context;
context.update_from_variant(&v)?;
Ok(Variant {
fields_kind: records::fields_kind(&v.fields),
discr,
orig_name: v.orig_name,
name: rename::variant(v.name, context)?,
fields: v.fields.map_node(context)?,
docstring: v.docstring,
})
})
.collect()
}
pub fn discr_type(en: &initial::Enum, context: &Context) -> Result<TypeNode> {
Ok(determine_discriminants(&en.discr_type, &en.variants, context)?.discr_type)
}
pub fn is_flat(en: &initial::Enum) -> bool {
match en.shape {
EnumShape::Error { flat } => flat,
EnumShape::Enum => en.variants.iter().all(|v| v.fields.is_empty()),
}
}
pub struct DiscriminantInfo {
pub discr_type: TypeNode,
pub variant_discrs: Vec<Literal>,
}
pub fn determine_discriminants(
discr_type: &Option<Type>,
variants: &[initial::Variant],
context: &Context,
) -> Result<DiscriminantInfo> {
let signed = match discr_type {
Some(ty) => match ty {
Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 => false,
Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 => true,
ty => bail!("Invalid enum discriminant type: {ty:?}"),
},
None => true,
};
let placeholder_type_node: TypeNode = Type::UInt8.map_node(context)?;
let mut max_value: u64 = 0;
let mut min_value: i64 = 0;
let mut variant_discrs: Vec<Literal> = vec![];
for variant in variants.iter() {
let discr = match &variant.discr {
None => match variant_discrs.last() {
None => {
if signed {
Literal::Int(0, Radix::Decimal, placeholder_type_node.clone())
} else {
Literal::UInt(0, Radix::Decimal, placeholder_type_node.clone())
}
}
Some(lit) => match lit {
Literal::UInt(val, _, _) => {
Literal::UInt(val + 1, Radix::Decimal, placeholder_type_node.clone())
}
Literal::Int(val, _, _) => {
Literal::Int(val + 1, Radix::Decimal, placeholder_type_node.clone())
}
lit => bail!("Invalid enum discriminant literal: {lit:?}"),
},
},
Some(lit) => lit.clone().map_node(context)?,
};
match &discr {
Literal::UInt(val, _, _) => {
max_value = max(max_value, *val);
}
Literal::Int(val, _, _) => {
if *val >= 0 {
max_value = max(max_value, *val as u64);
} else {
min_value = min(min_value, *val);
}
}
_ => unreachable!(),
}
variant_discrs.push(discr);
}
let discr_type: TypeNode = match discr_type {
Some(ty) => ty.clone().map_node(context)?,
None => {
if min_value >= i8::MIN as i64 && max_value <= i8::MAX as u64 {
Type::Int8.map_node(context)?
} else if min_value >= i16::MIN as i64 && max_value <= i16::MAX as u64 {
Type::Int16.map_node(context)?
} else if min_value >= i32::MIN as i64 && max_value <= i32::MAX as u64 {
Type::Int32.map_node(context)?
} else if max_value <= i64::MAX as u64 {
Type::Int64.map_node(context)?
} else {
bail!("Enum repr not set and magnitude exceeds i64::MAX");
}
}
};
for lit in variant_discrs.iter_mut() {
match lit {
Literal::UInt(_, _, type_node) | Literal::Int(_, _, type_node) => {
*type_node = discr_type.clone();
}
_ => unreachable!(),
}
}
Ok(DiscriminantInfo {
discr_type,
variant_discrs,
})
}