use super::*;
pub fn run(input_stream: TokenStream1, enum_stream: TokenStream1) -> Result<TokenStream> {
let table_attrs = parse_table_metas(input_stream)?;
let input_enum = syn::parse::<Enum<SynMeta, SynMeta>>(enum_stream)?;
let enum_def = input_enum.to_token_stream();
let SaneEnum {
ident: enum_ident,
ty: enum_ty,
variants,
} = sanitize_enum(input_enum)?;
let vis = Visibility::Public(Default::default());
let lf = Lifetime::new("'_r", Span::call_site());
let gen_t = Ident::new("Value", Span::call_site());
let mod_ident = if let _Some(SettingModName {
kw: _,
eq_token: _,
name,
}) = table_attrs.mod_name
{
name
} else {
let mut enum_lower = enum_ident.to_string();
enum_lower = enum_lower.to_case(Case::Snake);
Ident::new(&format!("{enum_lower}_discriminant_table"), Span::call_site())
};
let table_ident = if let _Some(SettingTypeName {
kw: _,
eq_token: _,
name,
}) = table_attrs.ty_name
{
name
} else {
Ident::new(&format!("{enum_ident}DiscriminantTable"), Span::call_site())
};
let gen_params = quote! { <#gen_t> };
let gen_args = &gen_params;
let gen_lf_params = quote! { <#lf, #gen_t> };
let table_ty = quote! { #table_ident::#gen_args };
let fields = variants
.iter()
.map(
|SaneVariant {
table_field_ident, ..
}| table_field_ident,
)
.collect::<Vec<_>>();
let var_cfgs = variants
.iter()
.map(|SaneVariant { cfg_attrs, .. }| cfg_attrs)
.collect::<Vec<_>>();
const MACRO_LINK: &str = "spire_enum_macros::discriminant_generic_table";
const DOCS_INTRO: &str = "This type was generated by an invocation of the macro [`discriminant_generic_table`](spire_enum_macros::discriminant_generic_table).";
let table_def = {
let attrs = table_attrs.syn_metas;
let docs = docs_tokens(format!(
"{DOCS_INTRO}\n\n\
A fixed-size collection that contains exactly one value of the generic [`{gen_t}`] for each variant of [`{enum_ident}`].\n\
Values can be accessed by calling [`get({enum_ident})`]({table_ident}::get) or [`get_mut`]({table_ident}::get_mut).\n\
For a full list of all methods generated for this type, see the [macro]({MACRO_LINK}) documentation.\n\n\
This type does not allocate heap memory(can be used in `no_std`), though no such guarantee is provided to the generic [`{gen_t}`]."
));
quote! {
#docs
#(#[#attrs])*
#vis struct #table_ident #gen_params {
#(
#var_cfgs
pub #fields: #gen_t
),*
}
}
};
let var_idents = variants.iter().map(|var| &var.ident).collect::<Vec<_>>();
let len_ident = {
let table_upper = table_ident.to_string().to_case(Case::Constant);
Ident::new(&format!("{}_LEN", table_upper), Span::call_site())
};
let len_def = length_definition(&len_ident, var_cfgs.iter().cloned());
let table_impls = {
let docs_new = docs_tokens(format!(
"Constructs a new instance of the type.\n\n\
Since this table guarantees that it contains exactly one value for each variant of [`{enum_ident}`],\
all variants' values must be initialized during construction."
));
let from_fn_example = r###"
```rust
use spire_enum_macros::discriminant_generic_table;
#[discriminant_generic_table(ty_name = VolumeTable)]
#[derive(Clone, Copy)]
enum VolumeSetting {
Main,
Sfx,
Music,
}
let default_values = VolumeTable::from_fn(
|setting| {
match setting {
VolumeSetting::Main => 0.5,
VolumeSetting::Sfx => 1.0,
VolumeSetting::Music => 0.4,
}
}
);
assert_eq!(default_values[VolumeSetting::Main], 0.5);
assert_eq!(default_values[VolumeSetting::Sfx], 1.0);
assert_eq!(default_values[VolumeSetting::Music], 0.4);
```
"###;
let docs_from_fn = docs_tokens(format!(
"Constructs a new instance of the type, invoking the closure `f` for every variant of [`{enum_ident}`].\n\n\
# Example\n{from_fn_example}"
));
let docs_into_iter = docs_tokens(format!(
"Convert this table into an iterator that yields all values of the generic [`{gen_t}`], that were mapped to each variant of [`{enum_ident}`].\n\
- This iterator yields tuples of `({enum_ident}, {gen_t})`, where the first element represents the variant that the second element was associated with.\n\
- It is guaranteed that values will be yielded in the exact order they were declared in [`{enum_ident}`] (Top to bottom).\n\
- The iterator returned does not allocate heap memory, it is merely a fixed-size array with length known at compile time."
));
let docs_iter = docs_tokens(format!(
"Iterates through references of all values of the generic [`{gen_t}`], that are mapped to each variant of [`{enum_ident}`].\n\
- This iterator yields tuples of `({enum_ident}, &{gen_t})`, where the first element represents the variant that the second element is associated with.\n\
- It is guaranteed that values will be yielded in the exact order they were declared in [`{enum_ident}`] (Top to bottom).\n\
- The iterator returned does not allocate heap memory, it is merely a fixed-size array with length known at compile time."
));
let docs_iter_mut = docs_tokens(format!(
"Iterates through mutable references of all values of the generic [`{gen_t}`], that are mapped to each variant of [`{enum_ident}`].\n\
- This iterator yields tuples of `({enum_ident}, &mut {gen_t})`, where the first element represents the variant that the second element is associated with.\n\
- It is guaranteed that values will be yielded in the exact order they were declared in [`{enum_ident}`] (Top to bottom).\n\
- The iterator returned does not allocate heap memory, it is merely a fixed-size array with length known at compile time."
));
quote! {
#[allow(clippy::too_many_arguments)]
#[allow(unused)]
impl #gen_params #table_ty {
#docs_new
pub const fn new(
#(
#var_cfgs
#fields: #gen_t
),*
) -> Self {
Self {
#(
#var_cfgs
#fields
),*
}
}
#[doc = "Constructs a new instance of the type, populating every variant's value with the parameter `__val`"]
pub fn filled_with(__val: #gen_t) -> Self where #gen_t: Clone {
Self {
#(
#var_cfgs
#fields: __val.clone()
),*
}
}
#docs_from_fn
pub fn from_fn(mut f: impl FnMut(#enum_ty) -> #gen_t) -> Self {
Self {
#(
#var_cfgs
#fields: f(#enum_ty::#var_idents)
),*
}
}
#[doc = "Returns a reference to the value associated with the variant `var` that is always present in this table.\n\n\
This method will never panic, it is guaranteed at compile time that the table contains the value associated with `var`."]
pub const fn get(&self, var: #enum_ty) -> & #gen_t {
match var {
#(
#var_cfgs
#enum_ty::#var_idents {..} => &self.#fields
),*
}
}
#[doc = "Returns a mutable reference to the value associated with the variant `var` that is always present in this table.\n\n\
This method will never panic, it is guaranteed at compile time that the table contains the value associated with `var`."]
pub const fn get_mut(&mut self, var: #enum_ty) -> &mut #gen_t {
match var {
#(
#var_cfgs
#enum_ty::#var_idents {..} => &mut self.#fields
),*
}
}
#[doc = "Replaces the field associated with the variant `var`, with the contents of `value`.\n\n\
This is shorthand for `*self.get_mut(var) = value;`.\n\
This is provided merely as \"sugar\" for those who aren't \
very familiar with dereferencing."]
pub fn set(&mut self, var: #enum_ty, value: #gen_t) {
*self.get_mut(var) = value;
}
#[allow(clippy::needless_lifetimes)]
#docs_iter
pub fn iter<#lf>(&#lf self) -> ::core::array::IntoIter<(#enum_ty, &#lf #gen_t), #len_ident> {
[
#(
#var_cfgs
(#enum_ty::#var_idents, &self.#fields)
),*
].into_iter()
}
#[allow(clippy::needless_lifetimes)]
#docs_iter_mut
pub fn iter_mut<#lf>(&#lf mut self) -> ::core::array::IntoIter<(#enum_ty, &#lf mut #gen_t), #len_ident> {
[
#(
#var_cfgs
(#enum_ty::#var_idents, &mut self.#fields)
),*
].into_iter()
}
}
impl #gen_params ::core::iter::IntoIterator for #table_ty {
type Item = (#enum_ty, #gen_t);
type IntoIter = ::core::array::IntoIter<(#enum_ty, #gen_t), #len_ident>;
#docs_into_iter
fn into_iter(self) -> Self::IntoIter {
[
#(
#var_cfgs
(#enum_ty::#var_idents, self.#fields)
),*
].into_iter()
}
}
impl #gen_lf_params ::core::iter::IntoIterator for &#lf #table_ty {
type Item = (#enum_ty, &#lf #gen_t);
type IntoIter = ::core::array::IntoIter<Self::Item, #len_ident>;
#[doc = "See [`iter`](Self::iter)"]
fn into_iter(self) -> Self::IntoIter { self.iter() }
}
impl #gen_lf_params ::core::iter::IntoIterator for &#lf mut #table_ty {
type Item = (#enum_ty, &#lf mut #gen_t);
type IntoIter = ::core::array::IntoIter<Self::Item, #len_ident>;
#[doc = "See [`iter_mut`](Self::iter_mut)"]
fn into_iter(self) -> Self::IntoIter { self.iter_mut() }
}
impl #gen_params ::core::ops::Index<#enum_ty> for #table_ty {
type Output = #gen_t;
#[doc = "See [`get`](Self::get)"]
fn index(&self, index: #enum_ty) -> &Self::Output {
self.get(index)
}
}
impl #gen_params ::core::ops::IndexMut<#enum_ty> for #table_ty {
#[doc = "See [`get_mut`](Self::get_mut)"]
fn index_mut(&mut self, index: #enum_ty) -> &mut Self::Output {
self.get_mut(index)
}
}
}
};
let from_const_fn_macro = {
let macro_ident = {
let mut str = table_ident.to_string();
str = str.to_case(Case::Snake);
Ident::new(&format!("{str}_from_const_fn"), Span::call_site())
};
quote! {
#[allow(unused_macros)]
macro_rules! #macro_ident {
( | $var: ident | $( -> $ret: ty )? $closure: block ) => {{
#table_ident {
#(
#var_cfgs
#fields: {
let $var = #enum_ty::#var_idents;
$closure
}
),*
}
}};
( |_| $( -> $ret: ty )? $closure: block ) => {{
#table_ident {
#(
#var_cfgs
#fields: $closure
),*
}
}};
}
}
};
Ok(quote! {
#enum_def
#[allow(unused_imports)]
pub(crate) use #mod_ident::#table_ident;
#[allow(unused_imports)]
mod #mod_ident {
use super::*;
#len_def
#table_def
#table_impls
#from_const_fn_macro
}
})
}
struct SaneEnum {
ident: Ident,
ty: Type,
variants: Vec<SaneVariant>,
}
struct SaneVariant {
cfg_attrs: Any<Attribute<CfgMeta>>,
ident: Ident,
table_field_ident: Ident,
}
fn sanitize_enum(input: Enum<SynMeta, SynMeta>) -> Result<SaneEnum> {
let Enum {
attrs: _,
vis: _,
enum_token: _,
ident,
generics,
where_clause,
variants,
} = input;
let generics = sanitize_generics(generics, where_clause)?;
let ty = new_ty_maybe_generic(&ident, &generics);
let variants = variants
.into_inner()
.inner
.into_iter()
.map(|Var { attrs, ident, fields, discriminant: _ }| {
const HELP: &str =
"A discriminant table can only be generated if all variants are units (have zero fields).";
let cfg_attrs = parse_cfg_attrs(attrs);
match fields {
VarFields::Named(named) => {
if named.is_empty() {
Ok(SaneVariant {
cfg_attrs,
table_field_ident: var_to_field_ident(&ident),
ident,
})
} else {
bail!(named => HELP)
}
}
VarFields::Unnamed(unnamed) => {
if unnamed.is_empty() {
Ok(SaneVariant {
cfg_attrs,
table_field_ident: var_to_field_ident(&ident),
ident,
})
} else {
bail!(unnamed => HELP)
}
}
VarFields::Unit => Ok(SaneVariant {
cfg_attrs,
table_field_ident: var_to_field_ident(&ident),
ident,
}),
}
})
.try_collect()?;
Ok(SaneEnum {
ident,
variants,
ty,
})
}