diff --git a/leptos_macro/src/signal_bundle.rs b/leptos_macro/src/signal_bundle.rs index 1fedacd0da..6153a5c06e 100644 --- a/leptos_macro/src/signal_bundle.rs +++ b/leptos_macro/src/signal_bundle.rs @@ -1,286 +1,4 @@ -use syn::parse::Parse; +mod codegen; +mod parsing; -pub struct Model { - modes: Modes, - vis: syn::Visibility, - struct_name: syn::Ident, - generics: syn::Generics, - is_tuple_struct: bool, - fields: Vec, -} - -impl Parse for Model { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let input = syn::DeriveInput::parse(input)?; - - let modes = input - .attrs - .into_iter() - .filter(|attr| attr.meta.path().is_ident("bundle")) - .map(|attr| attr.parse_args::()) - .collect::>()?; - - let syn::Data::Struct(s) = input.data else { - abort_call_site!("only structs can be used with `SignalBundle`"); - }; - - let (is_tuple_struct, fields) = match s.fields { - syn::Fields::Unit => { - abort!(s.semi_token, "unit structs are not supported"); - } - syn::Fields::Named(fields) => ( - false, - fields.named.into_iter().map(Into::into).collect::>(), - ), - syn::Fields::Unnamed(fields) => ( - true, - fields - .unnamed - .into_iter() - .map(Into::into) - .collect::>(), - ), - }; - - Ok(Self { - modes, - vis: input.vis, - struct_name: input.ident, - generics: input.generics, - is_tuple_struct, - fields, - }) - } -} - -impl quote::ToTokens for Model { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let signal_bundle = self.generate_signal_bundle(); - let rw_signal_bundle = self.generate_rw_signal_bundle(); - let store_bundle = self.generate_store_bundle(); - - let tokens_ = quote! { - #signal_bundle - #rw_signal_bundle - #store_bundle - }; - - tokens.extend(tokens_); - } -} - -impl Model { - fn generate_signal_bundle(&self) -> Option { - if !self.modes.signal { - return None; - } - - let Self { - modes: _, - vis, - struct_name, - generics, - is_tuple_struct, - fields, - } = self; - - let (impl_generic_types, generic_types, where_clause) = - generics.split_for_impl(); - - let tuple_where_clause = is_tuple_struct - .then_some(quote! { #where_clause }) - .unwrap_or_default(); - let struct_where_clause = (!is_tuple_struct) - .then_some(quote! { #where_clause }) - .unwrap_or_default(); - let tuple_semi_token = is_tuple_struct.then_some(quote!(;)); - - let read_name = quote::format_ident!("{struct_name}Read"); - let write_name = quote::format_ident!("{struct_name}Write"); - - let read_fields = { - let fields = fields.into_iter().map(|field| { - field.to_tokens_with_mode(FieldModeKind::ReadSignal) - }); - - if self.is_tuple_struct { - quote!((#(#fields),*)) - } else { - quote!({ #(#fields),* }) - } - }; - - let write_fields = { - let fields = fields.into_iter().map(|field| { - field.to_tokens_with_mode(FieldModeKind::WriteSignal) - }); - - if self.is_tuple_struct { - quote!((#(#fields),*)) - } else { - quote!({ #(#fields),* }) - } - }; - - Some(quote! { - #[derive(Clone, Copy)] - #vis struct #read_name - #generic_types - #struct_where_clause - #read_fields - #tuple_where_clause - #tuple_semi_token - - #[derive(Clone, Copy)] - #vis struct #write_name - #generic_types - #struct_where_clause - #read_fields - #tuple_where_clause - #tuple_semi_token - - impl #impl_generic_types #struct_name #generic_types #where_clause { - #vis fn into_signal_bundle(self) -> (#read_name, #write_name) { - todo!() - } - } - }) - } - - fn generate_rw_signal_bundle(&self) -> Option { - todo!() - } - - fn generate_store_bundle(&self) -> Option { - todo!() - } -} - -#[derive(Default)] -struct Modes { - /// Generates seperate read/write structs. - signal: bool, - /// Generates single read/write struct. - rw_signal: bool, - /// Generates stored value struct. - store: bool, -} - -impl std::ops::BitOr for Modes { - type Output = Self; - - fn bitor(mut self, rhs: Self) -> Self::Output { - self.signal |= rhs.signal; - self.rw_signal |= rhs.rw_signal; - self.store |= rhs.store; - - self - } -} - -impl std::ops::BitOr for Modes { - type Output = Self; - - fn bitor(mut self, mode: ModeKind) -> Self::Output { - match mode { - ModeKind::Signal => self.signal = true, - ModeKind::RwSignal => self.rw_signal = true, - ModeKind::Store => self.store = true, - } - - self - } -} - -impl FromIterator for Modes { - fn from_iter>(iter: T) -> Self { - iter.into_iter() - .fold(Self::default(), std::ops::BitOr::bitor) - } -} - -impl Parse for Modes { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let this = syn::punctuated::Punctuated::< - ModeKind, - syn::Token![,] - >::parse_terminated(input)? - .into_iter() - .fold(Self::default(), std::ops::BitOr::bitor); - - Ok(this) - } -} - -enum ModeKind { - Signal, - RwSignal, - Store, -} - -impl Parse for ModeKind { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let ident = syn::Ident::parse(input)?; - - let this = match ident.to_string().as_str() { - "signal" => Self::Signal, - "rw_signal" => Self::RwSignal, - "store" => Self::Store, - _ => abort!( - ident, "unknown argument to `#[bundle()]`"; - hint = "must be any of `signal`, `rw_signal`, and `store`" - ), - }; - - Ok(this) - } -} - -enum Field { - Named { name: syn::Ident, ty: syn::Type }, - Unnamed(syn::Type), -} - -impl From for Field { - fn from(field: syn::Field) -> Self { - if let Some(name) = field.ident { - Self::Named { name, ty: field.ty } - } else { - Self::Unnamed(field.ty) - } - } -} - -impl Field { - fn to_tokens_with_mode( - &self, - mode: FieldModeKind, - ) -> proc_macro2::TokenStream { - match self { - Field::Named { name, ty } => quote! { #ty: #mode<#ty> }, - Field::Unnamed(ty) => quote! { #mode<#ty> }, - } - } -} - -enum FieldModeKind { - ReadSignal, - WriteSignal, - RwSignal, - Store, -} - -impl quote::ToTokens for FieldModeKind { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let ty_prefix = quote! { ::leptos::leptos_reactive }; - - let tokens_ = match self { - FieldModeKind::ReadSignal => quote! { #ty_prefix::ReadSignal }, - FieldModeKind::WriteSignal => quote! { #ty_prefix::WriteSignal }, - FieldModeKind::RwSignal => quote! { #ty_prefix::RwSignal }, - FieldModeKind::Store => quote! { #ty_prefix::StoredValue }, - }; - - tokens.extend(tokens_); - } -} +pub use parsing::Model; diff --git a/leptos_macro/src/signal_bundle/codegen.rs b/leptos_macro/src/signal_bundle/codegen.rs new file mode 100644 index 0000000000..db17b60fd9 --- /dev/null +++ b/leptos_macro/src/signal_bundle/codegen.rs @@ -0,0 +1,238 @@ +use super::parsing::{Field, Model}; +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::spanned::Spanned; + +impl ToTokens for Model { + fn to_tokens(&self, tokens: &mut TokenStream) { + let signal_bundle = generate_signal_bundle(self); + let rw_signal_bundle = self.generate_rw_signal_bundle(); + let store_bundle = self.generate_store_bundle(); + + let tokens_ = quote! { + #signal_bundle + #rw_signal_bundle + #store_bundle + }; + + tokens.extend(tokens_); + } +} + +impl Model { + fn generate_rw_signal_bundle(&self) -> Option { + todo!() + } + + fn generate_store_bundle(&self) -> Option { + todo!() + } +} + +enum FieldModeKind { + ReadSignal, + WriteSignal, + RwSignal, + Store, +} + +impl ToTokens for FieldModeKind { + fn to_tokens(&self, tokens: &mut TokenStream) { + let ty_prefix = quote! { ::leptos::leptos_reactive }; + + let tokens_ = match self { + FieldModeKind::ReadSignal => quote! { #ty_prefix::ReadSignal }, + FieldModeKind::WriteSignal => quote! { #ty_prefix::WriteSignal }, + FieldModeKind::RwSignal => quote! { #ty_prefix::RwSignal }, + FieldModeKind::Store => quote! { #ty_prefix::StoredValue }, + }; + + tokens.extend(tokens_); + } +} + +fn generate_signal_bundle(model: &Model) -> Option { + if !model.modes.signal { + return None; + } + + let Model { + modes: _, + vis, + struct_name, + generics, + is_tuple_struct, + fields, + } = model; + + let is_tuple_struct = *is_tuple_struct; + + let (impl_generic_types, generic_types, where_clause) = + generics.split_for_impl(); + + let tuple_where_clause = is_tuple_struct + .then_some(quote! { #where_clause }) + .unwrap_or_default(); + let struct_where_clause = (!is_tuple_struct) + .then_some(quote! { #where_clause }) + .unwrap_or_default(); + let tuple_semi_token = is_tuple_struct.then_some(quote!(;)); + + let read_name = quote::format_ident!("{struct_name}Read"); + let write_name = quote::format_ident!("{struct_name}Write"); + + let read_fields = { + let fields = fields + .iter() + .zip(field_names(fields)) + .zip(field_types(fields)) + .map(|((field, name), ty)| { + ( + field, + name, + wrap_with_signal_type(FieldModeKind::ReadSignal, ty), + ) + }) + .map(|(field, name, ty)| { + if matches!(field, Field::Named { .. }) { + quote! { #name: #ty } + } else { + quote! { #ty } + } + }); + + wrap_with_struct_or_tuple_braces( + is_tuple_struct, + quote! { #(#fields),* }, + ) + }; + + let write_fields = { + let fields = fields + .iter() + .zip(field_names(fields)) + .zip(field_types(fields)) + .map(|((field, name), ty)| { + ( + field, + name, + wrap_with_signal_type(FieldModeKind::WriteSignal, ty), + ) + }) + .map(|(field, name, ty)| { + if matches!(field, Field::Named { .. }) { + quote! { #name: #ty } + } else { + quote! { #ty } + } + }); + + wrap_with_struct_or_tuple_braces( + is_tuple_struct, + quote! { #(#fields),* }, + ) + }; + + let self_fields = field_names(fields); + let self_fields = wrap_with_struct_or_tuple_braces( + is_tuple_struct, + quote! { #(#self_fields),* }, + ); + + let field_names_ = field_names(fields); + + let read_field_names = + field_names(fields).map(|name| quote::format_ident!("read_{name}")); + + let write_field_names = + field_names(fields).map(|name| quote::format_ident!("write_{name}")); + + let read_output_fields = + fields.iter().zip(field_names(fields)).map(|(field, name)| { + if matches!(field, Field::Named { .. }) { + let read_name = quote::format_ident!("read_{name}"); + + quote! { #name: #read_name } + } else { + quote! { #name } + } + }); + let read_output_fields = wrap_with_struct_or_tuple_braces( + is_tuple_struct, + quote! { #(#read_output_fields),* }, + ); + + Some(quote! { + #[derive(Clone, Copy)] + #vis struct #read_name + #generic_types + #struct_where_clause + #read_fields + #tuple_where_clause + #tuple_semi_token + + #[derive(Clone, Copy)] + #vis struct #write_name + #generic_types + #struct_where_clause + #write_fields + #tuple_where_clause + #tuple_semi_token + + impl #impl_generic_types #struct_name #generic_types #where_clause { + #vis fn into_signal_bundle(self) -> (#read_name, #write_name) { + let Self #self_fields = self; + } + #( + let (#read_field_names, #write_field_names) + = ::leptos::leptos_reactive::create_signal(#field_names_); + )* + + ( + #read_name #read_output_fields, + // #write_name #write_output_fields, + ) + } + }) +} + +/// Wraps `inner` with `()` or `{}` depending if it's a tuple +/// struct or not. +fn wrap_with_struct_or_tuple_braces( + is_tuple_struct: bool, + inner: impl ToTokens, +) -> TokenStream { + if is_tuple_struct { + quote! { ( #inner ) } + } else { + quote! { { #inner } } + } +} + +/// Produces an iterator of field names. +fn field_names(fields: &[Field]) -> impl Iterator + '_ { + fields.iter().enumerate().map(|(i, field)| match field { + Field::Named { name, .. } => quote! { #name }, + Field::Unnamed(ty) => { + let field_name = quote::format_ident!("_{i}", span = ty.span()); + + quote! { #field_name } + } + }) +} + +/// Produces an iterator of field types. +fn field_types(fields: &[Field]) -> impl Iterator + '_ { + fields.iter().map(|field| match field { + Field::Named { ty, .. } => quote! { #ty }, + Field::Unnamed(ty) => quote! { #ty }, + }) +} + +/// Wraps a type with the appropriate signal type. +fn wrap_with_signal_type( + mode: FieldModeKind, + ty: impl ToTokens, +) -> TokenStream { + quote! { #mode<#ty> } +} diff --git a/leptos_macro/src/signal_bundle/parsing.rs b/leptos_macro/src/signal_bundle/parsing.rs new file mode 100644 index 0000000000..2ceedc1a66 --- /dev/null +++ b/leptos_macro/src/signal_bundle/parsing.rs @@ -0,0 +1,149 @@ +use syn::parse::Parse; + +pub struct Model { + pub modes: Modes, + pub vis: syn::Visibility, + pub struct_name: syn::Ident, + pub generics: syn::Generics, + pub is_tuple_struct: bool, + pub fields: Vec, +} + +impl Parse for Model { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let input = syn::DeriveInput::parse(input)?; + + let modes = input + .attrs + .into_iter() + .filter(|attr| attr.meta.path().is_ident("bundle")) + .map(|attr| attr.parse_args::()) + .collect::>()?; + + let syn::Data::Struct(s) = input.data else { + abort_call_site!("only structs can be used with `SignalBundle`"); + }; + + let (is_tuple_struct, fields) = match s.fields { + syn::Fields::Unit => { + abort!(s.semi_token, "unit structs are not supported"); + } + syn::Fields::Named(fields) => ( + false, + fields.named.into_iter().map(Into::into).collect::>(), + ), + syn::Fields::Unnamed(fields) => ( + true, + fields + .unnamed + .into_iter() + .map(Into::into) + .collect::>(), + ), + }; + + Ok(Self { + modes, + vis: input.vis, + struct_name: input.ident, + generics: input.generics, + is_tuple_struct, + fields, + }) + } +} + +#[derive(Default)] +pub struct Modes { + /// Generates seperate read/write structs. + pub signal: bool, + /// Generates single read/write struct. + pub rw_signal: bool, + /// Generates stored value struct. + pub store: bool, +} + +impl std::ops::BitOr for Modes { + type Output = Self; + + fn bitor(mut self, rhs: Self) -> Self::Output { + self.signal |= rhs.signal; + self.rw_signal |= rhs.rw_signal; + self.store |= rhs.store; + + self + } +} + +impl std::ops::BitOr for Modes { + type Output = Self; + + fn bitor(mut self, mode: ModeKind) -> Self::Output { + match mode { + ModeKind::Signal => self.signal = true, + ModeKind::RwSignal => self.rw_signal = true, + ModeKind::Store => self.store = true, + } + + self + } +} + +impl FromIterator for Modes { + fn from_iter>(iter: T) -> Self { + iter.into_iter() + .fold(Self::default(), std::ops::BitOr::bitor) + } +} + +impl Parse for Modes { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let this = syn::punctuated::Punctuated::< + ModeKind, + syn::Token![,] + >::parse_terminated(input)? + .into_iter() + .fold(Self::default(), std::ops::BitOr::bitor); + + Ok(this) + } +} + +enum ModeKind { + Signal, + RwSignal, + Store, +} + +impl Parse for ModeKind { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ident = syn::Ident::parse(input)?; + + let this = match ident.to_string().as_str() { + "signal" => Self::Signal, + "rw_signal" => Self::RwSignal, + "store" => Self::Store, + _ => abort!( + ident, "unknown argument to `#[bundle()]`"; + hint = "must be any of `signal`, `rw_signal`, and `store`" + ), + }; + + Ok(this) + } +} + +pub enum Field { + Named { name: syn::Ident, ty: syn::Type }, + Unnamed(syn::Type), +} + +impl From for Field { + fn from(field: syn::Field) -> Self { + if let Some(name) = field.ident { + Self::Named { name, ty: field.ty } + } else { + Self::Unnamed(field.ty) + } + } +}