use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Ident, Pat};
use crate::{
ParamInfo, held_callback::HeldCallback, is_fn_param, is_generic_fn_like, slot_key,
type_bare_generic_ident,
};
enum CallbackPass<'a> {
Held(&'a HeldCallback),
Boxed,
Direct,
}
pub(crate) struct CallbackParam<'a> {
info: &'a ParamInfo,
pub(crate) slot: &'a Ident,
pass: CallbackPass<'a>,
holder_type: TokenStream2,
}
impl<'a> CallbackParam<'a> {
pub(crate) fn of(
core_path: &TokenStream2,
info: &'a ParamInfo,
slot: &'a Ident,
held: Option<&'a HeldCallback>,
generics: &syn::Generics,
) -> Option<Self> {
let pass = match held {
Some(held) => CallbackPass::Held(held),
None if info.is_impl_trait => return None,
None if type_bare_generic_ident(&info.ty).is_some()
&& is_generic_fn_like(&info.ty, generics) =>
{
CallbackPass::Boxed
}
None if is_fn_param(&info.ty, generics) => CallbackPass::Direct,
None => return None,
};
let holder_type = match &pass {
CallbackPass::Held(held) => {
let dyn_type = held.dyn_type();
quote! { #core_path::CallbackHolder<#dyn_type> }
}
CallbackPass::Boxed | CallbackPass::Direct => quote! { #core_path::CallbackHolder },
};
Some(Self {
info,
slot,
pass,
holder_type,
})
}
pub(crate) fn boxes(&self) -> bool {
!matches!(self.pass, CallbackPass::Direct)
}
pub(crate) fn boxed_type(&self) -> Option<TokenStream2> {
match &self.pass {
CallbackPass::Held(held) => Some(held.dyn_type()),
CallbackPass::Boxed => Some(quote! { dyn ::core::ops::FnMut() + 'static }),
CallbackPass::Direct => None,
}
}
pub(crate) fn setup(&self, core_path: &TokenStream2, composer: &Ident) -> TokenStream2 {
let Self {
slot, holder_type, ..
} = self;
let ident = &self.info.ident;
let slot_key = slot_key(core_path);
let hold = match self.pass {
CallbackPass::Held(_) | CallbackPass::Boxed => {
quote! { <#holder_type>::__hold(#composer, #slot_key, #ident) }
}
CallbackPass::Direct => quote! {
#composer.__update_param_slot(
#slot_key,
|| <#holder_type>::new(),
|holder: &mut #holder_type| {
holder.update(#ident);
holder.renders()
},
)
},
};
quote! {
let (#slot, __renders) = #hold;
if __renders {
__changed = true;
}
}
}
pub(crate) fn recompose_setup(
&self,
core_path: &TokenStream2,
composer: &Ident,
) -> TokenStream2 {
let Self {
slot, holder_type, ..
} = self;
let slot_key = slot_key(core_path);
quote! {
let #slot = <#holder_type>::__slot(#composer, #slot_key);
}
}
pub(crate) fn body_input(&self, core_path: &TokenStream2) -> TokenStream2 {
let Self {
slot, holder_type, ..
} = self;
quote! { #slot: #core_path::ValueSlotHandle<'_, #holder_type> }
}
pub(crate) fn rebind(&self, core_path: &TokenStream2, composer: &Ident) -> TokenStream2 {
let Self {
slot, holder_type, ..
} = self;
let pat = &self.info.pat;
let binding = if matches!(pat.as_ref(), Pat::Ident(_)) && !self.info.pat_is_mut {
quote! { mut #pat }
} else {
quote! { #pat }
};
let value = match self.pass {
CallbackPass::Held(held) => held.forwarder("e! {
<#holder_type>::__forwarder(#composer, #slot)
}),
CallbackPass::Boxed | CallbackPass::Direct => quote! {
#composer.with_slot_value::<#holder_type, _>(
#slot,
#core_path::CallbackHolder::clone_rc,
)
},
};
quote! {
#[allow(unused_mut, reason = "only some callbacks change the binding")]
let #binding = #value;
}
}
}