diff --git a/bad-optics-derive/src/lib.rs b/bad-optics-derive/src/lib.rs index 16e86f5..0fc7d3b 100644 --- a/bad-optics-derive/src/lib.rs +++ b/bad-optics-derive/src/lib.rs @@ -13,9 +13,10 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let name = &input.ident; let mod_name = Ident::new(&get_mod_name(&input), Span::call_site()); + let container_name = Ident::new(&get_container_name(&input), Span::call_site()); let expanded = match input.data { - syn::Data::Struct(s) => expand_struct(s, name, &mod_name), + syn::Data::Struct(s) => expand_struct(s, name, &mod_name, &container_name), syn::Data::Enum(_) => todo!("not yet implemented for prisms"), syn::Data::Union(_) => panic!("this macro does not work on unions"), }; @@ -40,7 +41,28 @@ fn get_mod_name(input: &DeriveInput) -> String { input.ident.to_string().to_lowercase() } -fn expand_struct(data: DataStruct, name: &Ident, mod_name: &Ident) -> TokenStream { +fn get_container_name(input: &DeriveInput) -> String { + for i in &input.attrs { + if let Ok(Meta::NameValue(meta)) = i.parse_meta() { + if let Some(ident) = meta.path.get_ident() { + if ident == "container_name" { + if let Lit::Str(a) = meta.lit { + return a.value(); + } + } + } + } + } + + format!("{}LensContainer", input.ident.to_string()) +} + +fn expand_struct( + data: DataStruct, + name: &Ident, + mod_name: &Ident, + container_name: &Ident, +) -> TokenStream { let fields = match &data.fields { syn::Fields::Named(n) => n.named.iter(), syn::Fields::Unnamed(_) => todo!(), @@ -87,8 +109,8 @@ fn expand_struct(data: DataStruct, name: &Ident, mod_name: &Ident) -> TokenStrea .collect::(); quote! { - impl Lenses<#ty> { - pub fn get() -> + impl bad_optics::has_lens::HasLensOf<#ty> for #name { + fn get() -> Vec< bad_optics::lenses::Lens< bad_optics::lenses::lens_with_ref::LensWithRef< @@ -113,12 +135,19 @@ fn expand_struct(data: DataStruct, name: &Ident, mod_name: &Ident) -> TokenStrea .collect::(); quote! { - pub mod #mod_name { + mod #mod_name { use super::*; - #lens_funcs + pub struct #container_name; + + impl HasLens for #name { + type Lenses = #container_name; + } + + impl #container_name { + #lens_funcs + } - pub struct Lenses(std::marker::PhantomData); #group_impls } } diff --git a/examples/derive.rs b/examples/derive.rs index 1e86a8b..9d26828 100644 --- a/examples/derive.rs +++ b/examples/derive.rs @@ -1,4 +1,7 @@ -use bad_optics::prelude::Optics; +use bad_optics::prelude::*; + +#[macro_use] +extern crate bad_optics; // the Optics derive macro will implement lenses for every public field in the struct // it makes a module named whatever the struct is called, but in lower case @@ -26,20 +29,24 @@ fn main() { // // these lenses work for `MyStruct` with `view` and `over`, // and for `&MyStruct` with `view` - let field1 = mystruct::field1(); - let field2 = mystruct::field2(); + let field1 = ::Lenses::field1(); + // short macro for convenience, expands to the line above + let field2 = lens!(MyStruct::field2); // the lenses work normally as any other lens :) assert_eq!(field1(&o), "first field"); assert_eq!(field2(&o), "second field"); // we can get a vec with all the lenses that match a type - let string_lenses = mystruct::Lenses::::get(); + let string_lenses = >::get(); assert_eq!(string_lenses.len(), 2); // since _field4 is private, there's no lens for it - let vec_string_lenses = mystruct::Lenses::::get(); - assert_eq!(vec_string_lenses.len(), 1); + let u8_lenses = >::get(); + assert_eq!(u8_lenses.len(), 1); + + // short macro for convenience, expands to the line above + let _u8_lenses = lenses!(MyStruct::u8); let mut o = o; for lens in string_lenses { diff --git a/src/lib.rs b/src/lib.rs index 29c280a..9434180 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,4 +15,33 @@ pub mod prelude { pub use crate::lenses::*; pub use crate::prisms::*; pub use crate::traversals::*; + + pub use crate::has_lens::*; +} + +pub mod has_lens { + use crate::prelude::{lens::FuncLens, lens_with_ref::LensWithRef, to::ToRefInner}; + + use super::prelude::*; + + pub trait HasLens { + type Lenses; + } + + pub trait HasLensOf: Sized { + fn get() -> Vec>, Lens>, Self>>>; + } + + #[macro_export] + macro_rules! lens { + ($name:ident :: $func:ident) => { + <$name as HasLens>::Lenses::$func() + }; + } + #[macro_export] + macro_rules! lenses { + ($name:ident :: $ty:ident) => { + <$name as HasLensOf<$ty>>::get() + }; + } }