use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{Ident, Type};
use crate::slot_key;
pub(crate) struct PackedParams<'a> {
pub(crate) params: Vec<(&'a Ident, &'a Type)>,
pub(crate) returns: Option<&'a Type>,
}
impl PackedParams<'_> {
pub(crate) fn slot() -> Ident {
Ident::new("__params_slot", Span::mixed_site())
}
pub(crate) fn holds_return(&self) -> bool {
self.returns.is_some() && !self.params.is_empty()
}
pub(crate) fn state_type(&self, core_path: &TokenStream2) -> TokenStream2 {
let types = self.params.iter().map(|(_, ty)| ty);
match self.returns {
Some(returns) => quote! { #core_path::ParamReturnState<(#(#types,)*), #returns> },
None => quote! { #core_path::ParamState<(#(#types,)*)> },
}
}
pub(crate) fn slot_stmt(&self, core_path: &TokenStream2, composer: &Ident) -> TokenStream2 {
let state = self.state_type(core_path);
let slot = Self::slot();
let key = slot_key(core_path);
quote! {
let #slot = #composer.__use_param_slot(#key, || <#state>::default());
}
}
pub(crate) fn with_setup(
&self,
core_path: &TokenStream2,
composer: &Ident,
(previous, skippable): (&Ident, bool),
mut setup: Vec<TokenStream2>,
) -> Vec<TokenStream2> {
if self.params.is_empty() {
return setup;
}
let state = self.state_type(core_path);
let slot = Self::slot();
let key = slot_key(core_path);
let param_state = Ident::new("__param_state", Span::mixed_site());
let stored = Ident::new("__stored", Span::mixed_site());
let idents = self.params.iter().map(|(ident, _)| ident);
let refreshes = self.params.iter().enumerate().map(|(index, (ident, ty))| {
param_field_refresh(core_path, &stored, (ident, ty), index, skippable)
});
let update = quote! {
#param_state.update_fields(
|| (#(::core::clone::Clone::clone(&#idents),)*),
|#stored| false #(| #refreshes)*,
)
};
let statement = if !self.holds_return() {
quote! {
let (_, #slot) = #composer.__update_param_slot(
#key,
|| <#state>::default(),
|#param_state: &mut #state| #update,
);
if #slot {
__changed = true;
}
}
} else if skippable && setup.is_empty() {
quote! {
let (#slot, #previous) = #composer.__update_param_slot(
#key,
|| <#state>::default(),
|#param_state: &mut #state| {
if #update {
__changed = true;
}
(!__changed).then(|| #param_state.returned()).flatten()
},
);
}
} else {
if skippable {
setup.push(quote! {
let #previous = if __changed {
None
} else {
#composer.with_slot_value::<#state, _>(#slot, |#param_state| {
#param_state.returned()
})
};
});
}
quote! {
let (#slot, ()) = #composer.__update_param_slot(
#key,
|| <#state>::default(),
|#param_state: &mut #state| {
if #update {
__changed = true;
}
},
);
}
};
setup.insert(0, statement);
setup
}
pub(crate) fn store_return(
&self,
core_path: &TokenStream2,
composer: &Ident,
value: &Ident,
) -> TokenStream2 {
let state = self.state_type(core_path);
let slot = Self::slot();
quote! {
#composer.with_slot_value_mut::<#state, _>(#slot, |state| {
state.store_return(::core::clone::Clone::clone(&#value));
});
}
}
pub(crate) fn with_slot(
&self,
core_path: &TokenStream2,
composer: &Ident,
mut setup: Vec<TokenStream2>,
) -> Vec<TokenStream2> {
if !self.params.is_empty() {
setup.insert(0, self.slot_stmt(core_path, composer));
}
setup
}
pub(crate) fn reads(&self, core_path: &TokenStream2, composer: &Ident) -> Vec<TokenStream2> {
if self.params.is_empty() {
return Vec::new();
}
let state = self.state_type(core_path);
let slot = Self::slot();
let param_state = Ident::new("__param_state", Span::mixed_site());
let idents = self.params.iter().map(|(ident, _)| ident);
vec![quote! {
let (#(#idents,)*) = #composer.with_slot_value::<#state, _>(#slot, |#param_state| {
#param_state
.value()
.expect("composable parameter missing for recomposition")
});
}]
}
}
fn param_field_refresh(
core_path: &TokenStream2,
stored: &Ident,
(ident, ty): (&Ident, &Type),
index: usize,
skippable: bool,
) -> TokenStream2 {
let index = syn::Index::from(index);
if !skippable {
quote! { #core_path::store_param(&mut #stored.#index, &#ident) }
} else if is_shared_pointer(ty) {
quote! { #core_path::refresh_shared_param(&mut #stored.#index, &#ident) }
} else {
quote! { #core_path::refresh_param(&mut #stored.#index, &#ident) }
}
}
fn is_shared_pointer(ty: &Type) -> bool {
match ty {
Type::Path(path) if path.qself.is_none() => path
.path
.segments
.last()
.is_some_and(|segment| segment.ident == "Rc" || segment.ident == "Arc"),
_ => false,
}
}