use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::parse::discouraged::Speculative;
use syn::parse::{Parse, ParseBuffer, ParseStream};
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{parenthesized, Attribute, DeriveInput, Ident, Type};
pub(crate) struct WithNotionAttr {
notion_idents: Vec<Type>,
}
impl WithNotionAttr {
fn parse_parens_content(input: ParseStream<'_>) -> syn::Result<ParseBuffer<'_>> {
let content;
parenthesized!(content in input);
Ok(content)
}
fn try_parse(input: ParseStream<'_>) -> syn::Result<Option<Self>> {
let ident = input.parse::<Ident>()?;
if ident != "with_notion" {
return Ok(None);
}
let content = Self::parse_parens_content(input)?;
let idents = Punctuated::<Type, Comma>::parse_terminated(&content)?;
Ok(Some(Self {
notion_idents: idents.into_iter().collect(),
}))
}
}
pub(crate) struct ObservedAttr {
ident: Ident,
}
impl ObservedAttr {
fn try_parse(input: ParseStream<'_>) -> syn::Result<Option<Self>> {
let ident = input.parse::<Ident>()?;
if ident != "observed" {
return Ok(None);
}
Ok(Some(Self { ident }))
}
}
pub(crate) enum BounceAttr {
WithNotion(WithNotionAttr),
Observed(ObservedAttr),
}
impl Parse for BounceAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let forked_input = input.fork();
if let Some(m) = ObservedAttr::try_parse(&forked_input)? {
input.advance_to(&forked_input);
return Ok(Self::Observed(m));
}
let forked_input = input.fork();
if let Some(m) = WithNotionAttr::try_parse(&forked_input)? {
input.advance_to(&forked_input);
return Ok(Self::WithNotion(m));
}
Err(input.error("unknown attribute: expected either with_notion or observed"))
}
}
#[derive(Default)]
pub(crate) struct BounceAttrs {
pub notions: Vec<WithNotionAttr>,
pub observed: Option<ObservedAttr>,
}
impl Parse for BounceAttrs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let attrs = Punctuated::<BounceAttr, Comma>::parse_terminated(input)?;
let mut this = Self::default();
for attr in attrs {
match attr {
BounceAttr::WithNotion(m) => {
this.notions.push(m);
}
BounceAttr::Observed(m) => {
if this.observed.is_some() {
return Err(syn::Error::new_spanned(
m.ident,
"you can only have 1 observed attribute",
));
}
this.observed = Some(m);
}
}
}
Ok(this)
}
}
impl BounceAttrs {
pub fn parse_one(&mut self, attr: &Attribute) -> syn::Result<()> {
if !attr.path().is_ident("bounce") {
return Ok(());
}
let other = attr.parse_args::<BounceAttrs>()?;
if let Some(m) = other.observed {
if self.observed.is_some() {
return Err(syn::Error::new_spanned(
m.ident,
"you can only have 1 observed attribute",
));
}
self.observed = Some(m);
}
self.notions.extend(other.notions);
Ok(())
}
pub fn parse(attrs: &[Attribute]) -> syn::Result<Self> {
let mut this = Self::default();
for attr in attrs {
this.parse_one(attr)?;
}
Ok(this)
}
pub fn notion_idents(&self) -> Vec<Type> {
self.notions
.iter()
.flat_map(|m| m.notion_idents.clone())
.collect()
}
pub fn create_notion_apply_impls(&self, notion_ident: &Ident) -> Vec<TokenStream> {
let idents = self.notion_idents();
let mut notion_apply_impls = Vec::new();
for ident in idents {
let notion_apply_impl = quote! {
let #notion_ident = match <::std::rc::Rc::<dyn std::any::Any>>::downcast::<#ident>(#notion_ident) {
::std::result::Result::Ok(m) => return ::bounce::WithNotion::<#ident>::apply(::std::clone::Clone::clone(&self), m),
::std::result::Result::Err(e) => e,
};
};
notion_apply_impls.push(notion_apply_impl);
}
notion_apply_impls
}
pub fn create_notion_id_impls(&self) -> Vec<TokenStream> {
self.notion_idents()
.iter()
.map(|m| {
quote! { ::std::any::TypeId::of::<#m>() }
})
.collect()
}
}
pub(crate) fn macro_fn(input: DeriveInput) -> TokenStream {
let bounce_attrs = match BounceAttrs::parse(&input.attrs) {
Ok(m) => m,
Err(e) => return e.into_compile_error(),
};
let notion_ident = Ident::new("notion", Span::mixed_site());
let notion_apply_impls = bounce_attrs.create_notion_apply_impls(¬ion_ident);
let notion_ids_impls = bounce_attrs.create_notion_id_impls();
let type_ident = input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let impl_observed = bounce_attrs.observed.is_some().then(|| {
quote! {
fn changed(self: ::std::rc::Rc<Self>) {
::bounce::Observed::changed(self);
}
}
});
quote! {
#[automatically_derived]
impl #impl_generics ::bounce::Slice for #type_ident #ty_generics #where_clause {
type Action = <Self as ::bounce::__vendored::yew::functional::Reducible>::Action;
fn reduce(self: ::std::rc::Rc<Self>, action: Self::Action) -> ::std::rc::Rc<Self> {
::bounce::__vendored::yew::functional::Reducible::reduce(self, action)
}
fn apply(self: ::std::rc::Rc<Self>, #notion_ident: ::std::rc::Rc<dyn ::std::any::Any>) -> ::std::rc::Rc<Self> {
#(#notion_apply_impls)*
self
}
fn notion_ids(&self) -> ::std::vec::Vec<::std::any::TypeId> {
::std::vec![#(#notion_ids_impls,)*]
}
#impl_observed
}
}
}