use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::collections::HashSet;
use crate::{EnumDeclaration, PatternTypeDeclaration, VariantPattern};
pub fn generate_strictness_system(
enum_decl: &EnumDeclaration,
pattern_types: &[&PatternTypeDeclaration],
conditional_variants: &HashSet<String>,
) -> TokenStream2 {
let mut output = TokenStream2::new();
let enum_name = &enum_decl.name;
let strictness_trait_name = if let Some((_, trait_name)) = &enum_decl.pattern_param {
trait_name.clone()
} else {
syn::Ident::new(&format!("{enum_name}Strictness"), enum_name.span())
};
let mut strictness_assoc_types = Vec::new();
for conditional_variant in conditional_variants {
let assoc_type_name = syn::Ident::new(&format!("{conditional_variant}Allowed"), enum_name.span());
strictness_assoc_types.push(quote! {
type #assoc_type_name;
});
}
output.extend(quote! {
pub trait #strictness_trait_name: Clone + Copy + std::fmt::Debug {
#(#strictness_assoc_types)*
}
});
let unrestricted_type_name = syn::Ident::new(&format!("{enum_name}Type"), enum_name.span());
output.extend(quote! {
#[derive(Debug, Clone, Copy)]
pub struct #unrestricted_type_name;
});
let mut unrestricted_assoc_type_impls = Vec::new();
for conditional_variant in conditional_variants {
let assoc_type_name = syn::Ident::new(&format!("{conditional_variant}Allowed"), enum_name.span());
unrestricted_assoc_type_impls.push(quote! {
type #assoc_type_name = ();
});
}
output.extend(quote! {
impl #strictness_trait_name for #unrestricted_type_name {
#(#unrestricted_assoc_type_impls)*
}
});
for pattern_type in pattern_types {
let pattern_name = &pattern_type.name;
let strictness_type_name = syn::Ident::new(&format!("{pattern_name}Type"), pattern_name.span());
output.extend(quote! {
#[derive(Debug, Clone, Copy)]
pub struct #strictness_type_name;
});
let mut assoc_type_impls = Vec::new();
for conditional_variant in conditional_variants {
let assoc_type_name = syn::Ident::new(&format!("{conditional_variant}Allowed"), enum_name.span());
let allowed = match &pattern_type.pattern {
VariantPattern::Wildcard => quote! { () },
VariantPattern::Variants(variants) => {
if variants.iter().any(|v| *v == *conditional_variant) {
quote! { () }
} else {
quote! { ! }
}
}
};
assoc_type_impls.push(quote! {
type #assoc_type_name = #allowed;
});
}
output.extend(quote! {
impl #strictness_trait_name for #strictness_type_name {
#(#assoc_type_impls)*
}
});
}
for pattern_type in pattern_types {
let pattern_name = &pattern_type.name;
let strictness_type_name = syn::Ident::new(&format!("{pattern_name}Type"), pattern_name.span());
output.extend(quote! {
pub type #pattern_name = #enum_name<#strictness_type_name>;
});
}
output
}
pub fn identify_conditional_variants(pattern_types: &[&PatternTypeDeclaration], all_variant_names: &HashSet<String>) -> HashSet<String> {
let mut conditional_variants = HashSet::new();
for pattern_type in pattern_types {
match &pattern_type.pattern {
VariantPattern::Wildcard => {
}
VariantPattern::Variants(variants) => {
let pattern_variant_names: HashSet<String> = variants.iter().map(|v| v.to_string()).collect();
for variant_name in all_variant_names {
if !pattern_variant_names.contains(variant_name) {
conditional_variants.insert(variant_name.clone());
}
}
}
}
}
conditional_variants
}