use std::rc::Rc;
use rustc_hash::FxHashMap;
use super::pattern_placement::PatternFields;
use super::ty::{GenericId, GenericParam, Type};
use crate::core::BindingId;
use crate::source::Span;
#[derive(Clone)]
pub enum VariantPayload {
None,
Positional(Vec<Type>),
Named(Vec<(Rc<str>, Type)>),
}
#[derive(Clone)]
pub struct VariantDefinition {
pub name: Rc<str>,
pub payload: VariantPayload,
pub payload_spans: Vec<Span>,
pub function_spans: Vec<Span>,
pub span: Span,
}
impl VariantDefinition {
pub fn pattern_fields(&self) -> PatternFields<'_> {
match &self.payload {
VariantPayload::None => PatternFields::Positional(0),
VariantPayload::Positional(types) => PatternFields::Positional(types.len()),
VariantPayload::Named(fields) => {
PatternFields::Named(fields.iter().map(|(name, _)| name.as_ref()).collect())
}
}
}
pub fn payload_types(&self) -> Vec<&Type> {
match &self.payload {
VariantPayload::None => Vec::new(),
VariantPayload::Positional(types) => types.iter().collect(),
VariantPayload::Named(fields) => {
fields.iter().map(|(_, field_type)| field_type).collect()
}
}
}
pub fn instantiated_payload(&self, replacements: &FxHashMap<GenericId, Type>) -> Vec<Type> {
match &self.payload {
VariantPayload::None => Vec::new(),
VariantPayload::Positional(types) => types
.iter()
.map(|payload| payload.without_written_lifetimes().substitute(replacements))
.collect(),
VariantPayload::Named(fields) => fields
.iter()
.map(|(_, payload)| payload.without_written_lifetimes().substitute(replacements))
.collect(),
}
}
}
#[derive(Clone, Copy)]
pub struct PayloadPosition {
pub variant: usize,
pub payload: usize,
}
#[derive(Clone)]
pub struct EnumDefinition {
pub lifetimes: Vec<BindingId>,
pub params: Vec<GenericParam>,
pub variants: Vec<VariantDefinition>,
pub rust: Option<crate::core::EnumRust>,
pub definer: Option<crate::core::EnumDefiner>,
pub derives: Vec<&'static str>,
pub derived_traits: Vec<BindingId>,
pub blocked: Vec<(BindingId, Rc<str>, Type)>,
pub written_derive: Option<Span>,
}
impl EnumDefinition {
pub fn writes_flat_fields(&self) -> bool {
self.definer
.as_ref()
.is_some_and(|definer| definer.flat_fields)
}
pub fn is_bare_rust(&self) -> bool {
self.rust
.as_ref()
.is_some_and(|rust| rust.bare && !rust.template_clause)
}
pub fn is_result_shaped(&self) -> bool {
self.is_bare_rust()
&& self.params.len() == 2
&& self.variants.len() == 2
&& self.holds_parameter(0, 0)
&& self.holds_parameter(1, 1)
}
pub fn is_option_shaped(&self) -> bool {
self.is_bare_rust()
&& self.params.len() == 1
&& self.variants.len() == 2
&& self.holds_parameter(0, 0)
&& matches!(
self.variants.get(1).map(|variant| &variant.payload),
Some(VariantPayload::None)
)
}
pub fn holds_parameter(&self, variant: usize, parameter: usize) -> bool {
let Some(parameter) = self.params.get(parameter) else {
return false;
};
matches!(
self.variants.get(variant).map(|variant| &variant.payload),
Some(VariantPayload::Positional(parts))
if matches!(parts.as_slice(), [Type::Generic(id)] if *id == parameter.id)
)
}
pub fn instantiated_payload(&self, index: usize, arguments: &[Type]) -> Option<Vec<Type>> {
let replacements = self.replacements(arguments);
self.variants
.get(index)
.map(|variant| variant.instantiated_payload(&replacements))
}
pub fn payload_types(&self) -> impl Iterator<Item = &Type> {
self.variants
.iter()
.flat_map(|variant| variant.payload_types())
}
pub fn blocking_variant(
&self,
satisfies: &dyn Fn(PayloadPosition, &Type) -> bool,
) -> Option<(Rc<str>, Type)> {
self.variants
.iter()
.enumerate()
.find_map(|(variant_position, variant)| {
variant
.payload_types()
.into_iter()
.enumerate()
.find(|(payload_position, payload)| {
let position = PayloadPosition {
variant: variant_position,
payload: *payload_position,
};
!satisfies(position, payload)
})
.map(|(_, payload)| (variant.name.clone(), payload.clone()))
})
}
fn replacements(&self, arguments: &[Type]) -> FxHashMap<GenericId, Type> {
self.params
.iter()
.map(|param| param.id)
.zip(arguments.iter().cloned())
.collect()
}
}
#[derive(Clone, Default)]
pub struct EnumTable {
enums: FxHashMap<BindingId, EnumDefinition>,
}
impl EnumTable {
pub fn add(&mut self, binding: BindingId, definition: EnumDefinition) {
self.enums.insert(binding, definition);
}
pub fn get(&self, binding: BindingId) -> Option<&EnumDefinition> {
self.enums.get(&binding)
}
pub fn get_mut(&mut self, binding: BindingId) -> Option<&mut EnumDefinition> {
self.enums.get_mut(&binding)
}
pub fn iter(&self) -> impl Iterator<Item = (BindingId, &EnumDefinition)> {
self.enums
.iter()
.map(|(binding, definition)| (*binding, definition))
}
}
#[cfg(test)]
mod tests {
use super::{EnumDefinition, VariantDefinition, VariantPayload};
use crate::core::PrimitiveType;
use crate::source::{SourceId, Span};
use crate::types::ty::{GenericId, GenericParam, Type};
struct Enums {
span: Span,
}
impl Enums {
fn new() -> Self {
Self {
span: Span::new(SourceId::new(0), 0..0),
}
}
fn variant(&self, name: &str, payload: VariantPayload) -> VariantDefinition {
VariantDefinition {
name: name.into(),
payload,
payload_spans: Vec::new(),
function_spans: Vec::new(),
span: self.span,
}
}
fn definition(&self, variants: Vec<VariantDefinition>) -> EnumDefinition {
EnumDefinition {
lifetimes: Vec::new(),
params: Vec::new(),
variants,
rust: None,
definer: None,
derives: Vec::new(),
derived_traits: Vec::new(),
blocked: Vec::new(),
written_derive: None,
}
}
}
#[test]
fn a_generic_payload_substitutes_its_argument() {
let fixture = Enums::new();
let parameter = GenericParam {
id: GenericId::new(0),
name: "a".into(),
bounds: Vec::new(),
};
let definition = EnumDefinition {
lifetimes: Vec::new(),
params: vec![parameter.clone()],
variants: vec![fixture.variant(
"value",
VariantPayload::Positional(vec![Type::Generic(parameter.id)]),
)],
rust: None,
definer: None,
derives: Vec::new(),
derived_traits: Vec::new(),
blocked: Vec::new(),
written_derive: None,
};
assert_eq!(
definition.instantiated_payload(0, &[Type::Primitive(PrimitiveType::I64)]),
Some(vec![Type::Primitive(PrimitiveType::I64)])
);
}
#[test]
fn a_named_payload_keeps_its_field_order() {
let fixture = Enums::new();
let definition = fixture.definition(vec![fixture.variant(
"labeled",
VariantPayload::Named(vec![
("text".into(), Type::Primitive(PrimitiveType::String)),
("size".into(), Type::Primitive(PrimitiveType::F64)),
]),
)]);
assert_eq!(
definition.instantiated_payload(0, &[]),
Some(vec![
Type::Primitive(PrimitiveType::String),
Type::Primitive(PrimitiveType::F64),
])
);
assert_eq!(definition.payload_types().count(), 2);
}
#[test]
fn a_variant_without_a_payload_has_no_payload_types() {
let fixture = Enums::new();
let definition = fixture.definition(vec![fixture.variant("empty", VariantPayload::None)]);
assert_eq!(definition.instantiated_payload(0, &[]), Some(Vec::new()));
assert_eq!(definition.payload_types().count(), 0);
}
#[test]
fn a_blocking_variant_names_the_payload_that_fails() {
let fixture = Enums::new();
let definition = fixture.definition(vec![
fixture.variant(
"text",
VariantPayload::Positional(vec![Type::Primitive(PrimitiveType::String)]),
),
fixture.variant(
"number",
VariantPayload::Positional(vec![Type::Primitive(PrimitiveType::F64)]),
),
]);
let blocking = definition
.blocking_variant(&|_, payload| {
matches!(payload, Type::Primitive(PrimitiveType::String))
})
.expect("the number payload does not satisfy the test");
assert_eq!(blocking.0.as_ref(), "number");
assert_eq!(blocking.1, Type::Primitive(PrimitiveType::F64));
}
}