Compare commits

...

2 Commits

Author SHA1 Message Date
annieversary b12bcfc92a improve ergonomics for the derive macro generated api 2022-01-17 15:34:20 +00:00
annieversary b86d5ccb27 clippy 2021-11-18 12:21:55 +00:00
5 changed files with 82 additions and 21 deletions

View File

@ -13,9 +13,10 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let name = &input.ident; let name = &input.ident;
let mod_name = Ident::new(&get_mod_name(&input), Span::call_site()); 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 { 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::Enum(_) => todo!("not yet implemented for prisms"),
syn::Data::Union(_) => panic!("this macro does not work on unions"), 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() 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 { let fields = match &data.fields {
syn::Fields::Named(n) => n.named.iter(), syn::Fields::Named(n) => n.named.iter(),
syn::Fields::Unnamed(_) => todo!(), syn::Fields::Unnamed(_) => todo!(),
@ -87,8 +109,8 @@ fn expand_struct(data: DataStruct, name: &Ident, mod_name: &Ident) -> TokenStrea
.collect::<TokenStream>(); .collect::<TokenStream>();
quote! { quote! {
impl Lenses<#ty> { impl bad_optics::has_lens::HasLensOf<#ty> for #name {
pub fn get() -> fn get() ->
Vec< Vec<
bad_optics::lenses::Lens< bad_optics::lenses::Lens<
bad_optics::lenses::lens_with_ref::LensWithRef< bad_optics::lenses::lens_with_ref::LensWithRef<
@ -113,12 +135,19 @@ fn expand_struct(data: DataStruct, name: &Ident, mod_name: &Ident) -> TokenStrea
.collect::<TokenStream>(); .collect::<TokenStream>();
quote! { quote! {
pub mod #mod_name { mod #mod_name {
use super::*; use super::*;
#lens_funcs pub struct #container_name;
impl HasLens for #name {
type Lenses = #container_name;
}
impl #container_name {
#lens_funcs
}
pub struct Lenses<T>(std::marker::PhantomData<T>);
#group_impls #group_impls
} }
} }

View File

@ -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 // 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 // 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`, // these lenses work for `MyStruct` with `view` and `over`,
// and for `&MyStruct` with `view` // and for `&MyStruct` with `view`
let field1 = mystruct::field1(); let field1 = <MyStruct as HasLens>::Lenses::field1();
let field2 = mystruct::field2(); // short macro for convenience, expands to the line above
let field2 = lens!(MyStruct::field2);
// the lenses work normally as any other lens :) // the lenses work normally as any other lens :)
assert_eq!(field1(&o), "first field"); assert_eq!(field1(&o), "first field");
assert_eq!(field2(&o), "second field"); assert_eq!(field2(&o), "second field");
// we can get a vec with all the lenses that match a type // we can get a vec with all the lenses that match a type
let string_lenses = mystruct::Lenses::<String>::get(); let string_lenses = <MyStruct as HasLensOf<String>>::get();
assert_eq!(string_lenses.len(), 2); assert_eq!(string_lenses.len(), 2);
// since _field4 is private, there's no lens for it // since _field4 is private, there's no lens for it
let vec_string_lenses = mystruct::Lenses::<u8>::get(); let u8_lenses = <MyStruct as HasLensOf<u8>>::get();
assert_eq!(vec_string_lenses.len(), 1); 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; let mut o = o;
for lens in string_lenses { for lens in string_lenses {

View File

@ -144,8 +144,7 @@ where
fn traverse(&self, thing: T) -> Vec<Self::Field> { fn traverse(&self, thing: T) -> Vec<Self::Field> {
let a = A::traverse(&self.0 .0, thing); let a = A::traverse(&self.0 .0, thing);
a.into_iter() a.into_iter()
.map(|v| B::traverse(&self.1 .0, v)) .flat_map(|v| B::traverse(&self.1 .0, v))
.flatten()
.collect() .collect()
} }
} }

View File

@ -1,4 +1,5 @@
#![feature(unboxed_closures, fn_traits, const_trait_impl)] #![feature(unboxed_closures, fn_traits, const_trait_impl)]
#![allow(clippy::type_complexity)]
pub mod combinations; pub mod combinations;
mod fns; mod fns;
@ -14,4 +15,33 @@ pub mod prelude {
pub use crate::lenses::*; pub use crate::lenses::*;
pub use crate::prisms::*; pub use crate::prisms::*;
pub use crate::traversals::*; 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<T>: Sized {
fn get() -> Vec<Lens<LensWithRef<Lens<FuncLens<Self, T>>, Lens<ToRefInner<Self, T>>, 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()
};
}
} }

View File

@ -21,11 +21,7 @@ pub trait PrismPreview<T> {
F: FnOnce(Self::Field) -> Self::Field, F: FnOnce(Self::Field) -> Self::Field,
T: Clone, T: Clone,
{ {
if let Some(a) = Self::preview(&self, thing.clone()) { Self::preview(self, thing.clone()).map_or(thing, |a| Self::review(self, f(a)))
Self::review(&self, f(a))
} else {
thing
}
} }
fn set(&self, thing: T, v: Self::Field) -> T fn set(&self, thing: T, v: Self::Field) -> T
@ -33,7 +29,7 @@ pub trait PrismPreview<T> {
T: Clone, T: Clone,
Self::Field: Clone, Self::Field: Clone,
{ {
Self::over(self, thing, move |_| v.clone()) Self::over(self, thing, move |_| v)
} }
} }