Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply source visibility to lens fields #9

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions lens-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ extern crate proc_macro;

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, Visibility};
use syn::{parse_macro_input, Meta, Data, DataStruct, DeriveInput, Fields, Visibility};

/// Handles the `#derive(Lenses)` applied to a struct by generating a `Lens` implementation for
/// each field in the struct.
#[proc_macro_derive(Lenses)]
#[proc_macro_derive(Lenses,attributes(leaf))]
pub fn lenses_derive(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as DeriveInput);
Expand Down Expand Up @@ -130,21 +130,27 @@ pub fn lenses_derive(input: TokenStream) -> TokenStream {
// }
let lenses_struct_name = format_ident!("{}Lenses", struct_name);
let lenses_struct_fields = fields.iter().map(|field| {
let field_visibility = match field.vis {
Visibility::Public(..) => quote!(pub),
_ => quote!(), // default to private for now
};
if let Some(field_name) = &field.ident {
let field_lens_name = format_ident!(
"{}{}Lens",
struct_name,
to_camel_case(&field_name.to_string())
);
if is_primitive(&field.ty) {
quote!(#field_name: #field_lens_name)
} else {
quote!(#field_visibility #field_name: #field_lens_name)
} else if is_leaf(&field) {
quote!(#field_visibility #field_name: #field_lens_name)
}else {
let field_parent_lenses_field_name = format_ident!("{}_lenses", field_name);
let field_parent_lenses_type_name =
format_ident!("{}Lenses", to_camel_case(&field_name.to_string()));
quote!(
#field_name: #field_lens_name,
#field_parent_lenses_field_name: #field_parent_lenses_type_name
#field_visibility #field_name: #field_lens_name,
#field_visibility #field_parent_lenses_field_name: #field_parent_lenses_type_name
)
}
} else {
Expand Down Expand Up @@ -176,6 +182,8 @@ pub fn lenses_derive(input: TokenStream) -> TokenStream {
let field_lens_name = format_ident!("{}{}Lens", struct_name, to_camel_case(&field_name.to_string()));
if is_primitive(&field.ty) {
quote!(#field_name: #field_lens_name)
} else if is_leaf(&field) {
quote!(#field_name: #field_lens_name)
} else {
let field_parent_lenses_field_name = format_ident!("{}_lenses", field_name);
let field_parent_lenses_type_name = format_ident!("_{}Lenses", to_camel_case(&field_name.to_string()));
Expand Down Expand Up @@ -212,6 +220,25 @@ pub fn lenses_derive(input: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}

/// Return true if the field is marked as a leaf field, which means that it doesn't have lenses
/// defined and it should be accessed similarly to a primitive. This is done with the #[leaf]
/// attribute marker on the struct field. Internally, this is currently treated exactly the same as
/// is the case for is_primitive, with the exception that it does _not_ generate a ValueLens for
/// leaf fields.
fn is_leaf(field: &syn::Field) -> bool {
let result = field.attrs.iter()
.filter_map(|attr| match attr.parse_meta().unwrap() {
Meta::Path(ref p) => {
p.get_ident().map(|id| id.to_string())
},
_ => None
})
.filter(|id| id=="leaf")
.nth(0)
.is_some();
result
}

/// Return true if the given type should be considered a primitive, i.e., whether
/// it doesn't have lenses defined for it.
fn is_primitive(ty: &syn::Type) -> bool {
Expand All @@ -222,7 +249,7 @@ fn is_primitive(ty: &syn::Type) -> bool {
// lenses derived
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" | "f32" | "f64" | "String" => {
true
}
},
_ => false,
}
}
Expand Down
2 changes: 2 additions & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct Address {
street: String,
city: String,
postcode: String,
#[leaf] names: Vec<String>
}

#[derive(Lenses)]
Expand All @@ -30,6 +31,7 @@ fn a_simple_nested_data_structure_should_be_lensable() {
street: "123 Needmore Rd".to_string(),
city: "Dayton".to_string(),
postcode: "99999".to_string(),
names: vec!["a".into(),"vec".into()],
},
};
assert_eq!(lens!(Person.name).get_ref(&p0), "Pop Zeus");
Expand Down