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

Adds support for a lens(ignore) attribute. #1133

Merged
merged 4 commits into from
Aug 18, 2020
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ You can find its changes [documented below](#060---2020-06-01).
- Selection text color to textbox. ([#1093] by [@sysint64])
- `BoxConstraints::UNBOUNDED` constant. ([#1126] by [@danieldulaney])
- Close requests from the shell can now be intercepted ([#1118] by [@jneem])
- The Lens derive now supports an `ignore` attribute. ([#1133] by [@jneem])

### Changed

Expand Down Expand Up @@ -388,6 +389,7 @@ Last release without a changelog :(
[#1119]: https://github.com/linebender/druid/pull/1119
[#1120]: https://github.com/linebender/druid/pull/1120
[#1126]: https://github.com/linebender/druid/pull/1120
[#1133]: https://github.com/linebender/druid/pull/1133

[Unreleased]: https://github.com/linebender/druid/compare/v0.6.0...master
[0.6.0]: https://github.com/linebender/druid/compare/v0.5.0...v0.6.0
Expand Down
118 changes: 94 additions & 24 deletions druid-derive/src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ const LENS_NAME_OVERRIDE_ATTR_PATH: &str = "name";

/// The fields for a struct or an enum variant.
#[derive(Debug)]
pub struct Fields {
pub struct Fields<Attrs> {
pub kind: FieldKind,
fields: Vec<Field>,
fields: Vec<Field<Attrs>>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -60,18 +60,45 @@ impl FieldIdent {
}

#[derive(Debug)]
pub struct Field {
pub struct Field<Attrs> {
pub ident: FieldIdent,
pub ty: syn::Type,

pub attrs: Attrs,
}

#[derive(Debug)]
pub struct DataAttrs {
/// `true` if this field should be ignored.
pub ignore: bool,
pub same_fn: Option<ExprPath>,
}

#[derive(Debug)]
pub struct LensAttrs {
/// `true` if this field should be ignored.
pub ignore: bool,
pub lens_name_override: Option<Ident>,
//TODO: more attrs here
}

impl Fields {
impl Fields<DataAttrs> {
pub fn parse_ast(fields: &syn::Fields) -> Result<Self, Error> {
let kind = match fields {
syn::Fields::Named(_) => FieldKind::Named,
syn::Fields::Unnamed(_) | syn::Fields::Unit => FieldKind::Unnamed,
};

let fields = fields
.iter()
.enumerate()
.map(|(i, field)| Field::<DataAttrs>::parse_ast(field, i))
.collect::<Result<Vec<_>, _>>()?;

Ok(Fields { kind, fields })
}
}

impl Fields<LensAttrs> {
pub fn parse_ast(fields: &syn::Fields) -> Result<Self, Error> {
let kind = match fields {
syn::Fields::Named(_) => FieldKind::Named,
Expand All @@ -81,22 +108,24 @@ impl Fields {
let fields = fields
.iter()
.enumerate()
.map(|(i, field)| Field::parse_ast(field, i))
.map(|(i, field)| Field::<LensAttrs>::parse_ast(field, i))
.collect::<Result<Vec<_>, _>>()?;

Ok(Fields { kind, fields })
}
}

impl<Attrs> Fields<Attrs> {
pub fn len(&self) -> usize {
self.fields.len()
}

pub fn iter(&self) -> impl Iterator<Item = &Field> {
pub fn iter(&self) -> impl Iterator<Item = &Field<Attrs>> {
self.fields.iter()
}
}

impl Field {
impl Field<DataAttrs> {
pub fn parse_ast(field: &syn::Field, index: usize) -> Result<Self, Error> {
let ident = match field.ident.as_ref() {
Some(ident) => FieldIdent::Named(ident.to_string().trim_start_matches("r#").to_owned()),
Expand All @@ -107,7 +136,6 @@ impl Field {

let mut ignore = false;
let mut same_fn = None;
let mut lens_name_override = None;

for attr in field.attrs.iter() {
if attr.path.is_ident(BASE_DRUID_DEPRECATED_ATTR_PATH) {
Expand Down Expand Up @@ -152,11 +180,61 @@ impl Field {
));
}
}
}
}
Ok(Field {
ident,
ty,
attrs: DataAttrs { ignore, same_fn },
})
}

/// The tokens to be used as the function for 'same'.
pub fn same_fn_path_tokens(&self) -> TokenStream {
match self.attrs.same_fn {
Some(ref f) => quote!(#f),
None => {
let span = Span::call_site();
quote_spanned!(span=> druid::Data::same)
}
}
}
}

impl Field<LensAttrs> {
pub fn parse_ast(field: &syn::Field, index: usize) -> Result<Self, Error> {
let ident = match field.ident.as_ref() {
Some(ident) => FieldIdent::Named(ident.to_string().trim_start_matches("r#").to_owned()),
None => FieldIdent::Unnamed(index),
};

let ty = field.ty.clone();

let mut ignore = false;
let mut lens_name_override = None;

for attr in field.attrs.iter() {
if attr.path.is_ident(BASE_DRUID_DEPRECATED_ATTR_PATH) {
panic!(
"The 'druid' attribute has been replaced with separate \
'lens' and 'data' attributes.",
);
} else if attr.path.is_ident(BASE_LENS_ATTR_PATH) {
match attr.parse_meta()? {
Meta::List(meta) => {
for nested in meta.nested.iter() {
match nested {
NestedMeta::Meta(Meta::Path(path))
if path.is_ident(IGNORE_ATTR_PATH) =>
{
if ignore {
return Err(Error::new(
nested.span(),
"Duplicate attribute",
));
}
ignore = true;
}
NestedMeta::Meta(Meta::NameValue(meta))
if meta.path.is_ident(LENS_NAME_OVERRIDE_ATTR_PATH) =>
{
Expand All @@ -174,7 +252,7 @@ impl Field {
other => {
return Err(Error::new(
other.span(),
"Expected attribute list (the form #[data(one, two)])",
"Expected attribute list (the form #[lens(one, two)])",
));
}
}
Expand All @@ -183,12 +261,15 @@ impl Field {
Ok(Field {
ident,
ty,
ignore,
same_fn,
lens_name_override,
attrs: LensAttrs {
ignore,
lens_name_override,
},
})
}
}

impl<Attrs> Field<Attrs> {
pub fn ident_tokens(&self) -> TokenTree {
match self.ident {
FieldIdent::Named(ref s) => Ident::new(&s, Span::call_site()).into(),
Expand All @@ -202,17 +283,6 @@ impl Field {
FieldIdent::Unnamed(num) => num.to_string(),
}
}

/// The tokens to be used as the function for 'same'.
pub fn same_fn_path_tokens(&self) -> TokenStream {
match self.same_fn {
Some(ref f) => quote!(#f),
None => {
let span = Span::call_site();
quote_spanned!(span=> druid::Data::same)
}
}
}
}

fn parse_lit_into_expr_path(lit: &syn::Lit) -> Result<ExprPath, Error> {
Expand Down
17 changes: 10 additions & 7 deletions druid-derive/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

//! The implementation for #[derive(Data)]

use crate::attr::{Field, FieldKind, Fields};
use crate::attr::{DataAttrs, Field, FieldKind, Fields};

use quote::{quote, quote_spanned};
use syn::{spanned::Spanned, Data, DataEnum, DataStruct};
Expand All @@ -40,14 +40,17 @@ fn derive_struct(
let impl_generics = generics_bounds(&input.generics);
let (_, ty_generics, where_clause) = &input.generics.split_for_impl();

let fields = Fields::parse_ast(&s.fields)?;
let fields = Fields::<DataAttrs>::parse_ast(&s.fields)?;

let diff = if fields.len() > 0 {
let same_fns = fields
.iter()
.filter(|f| !f.ignore)
.filter(|f| !f.attrs.ignore)
.map(Field::same_fn_path_tokens);
let fields = fields.iter().filter(|f| !f.ignore).map(Field::ident_tokens);
let fields = fields
.iter()
.filter(|f| !f.attrs.ignore)
.map(Field::ident_tokens);
quote!( #( #same_fns(&self.#fields, &other.#fields) )&&* )
} else {
quote!(true)
Expand Down Expand Up @@ -97,13 +100,13 @@ fn derive_enum(
.variants
.iter()
.map(|variant| {
let fields = Fields::parse_ast(&variant.fields)?;
let fields = Fields::<DataAttrs>::parse_ast(&variant.fields)?;
let variant = &variant.ident;

// the various inner `same()` calls, to the right of the match arm.
let tests: Vec<_> = fields
.iter()
.filter(|field| !field.ignore)
.filter(|field| !field.attrs.ignore)
.map(|field| {
let same_fn = field.same_fn_path_tokens();
let var_left = ident_from_str(&format!("__self_{}", field.ident_string()));
Expand Down Expand Up @@ -145,7 +148,7 @@ fn derive_enum(
.map(|field| ident_from_str(&format!("__other_{}", field.ident_string())))
.collect();

if fields.iter().filter(|field| !field.ignore).count() > 0 {
if fields.iter().count() > 0 {
Ok(quote! {
( #ident :: #variant( #(#vars_left),* ), #ident :: #variant( #(#vars_right),* )) => {
#( #tests )&&*
Expand Down
12 changes: 6 additions & 6 deletions druid-derive/src/lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use super::attr::{FieldKind, Fields};
use super::attr::{FieldKind, Fields, LensAttrs};
use proc_macro2::{Ident, Span};
use quote::quote;
use std::collections::HashSet;
Expand All @@ -38,7 +38,7 @@ fn derive_struct(input: &syn::DeriveInput) -> Result<proc_macro2::TokenStream, s
let ty = &input.ident;

let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &input.data {
Fields::parse_ast(fields)?
Fields::<LensAttrs>::parse_ast(fields)?
} else {
return Err(syn::Error::new(
input.span(),
Expand All @@ -64,7 +64,7 @@ fn derive_struct(input: &syn::DeriveInput) -> Result<proc_macro2::TokenStream, s
};

// Define lens types for each field
let defs = fields.iter().map(|f| {
let defs = fields.iter().filter(|f| !f.attrs.ignore).map(|f| {
let field_name = &f.ident.unwrap_named();

quote! {
Expand Down Expand Up @@ -99,7 +99,7 @@ fn derive_struct(input: &syn::DeriveInput) -> Result<proc_macro2::TokenStream, s
let func_ty_par = gen_new_param("F");
let val_ty_par = gen_new_param("V");

let impls = fields.iter().map(|f| {
let impls = fields.iter().filter(|f| !f.attrs.ignore).map(|f| {
let field_name = &f.ident.unwrap_named();
let field_ty = &f.ty;

Expand All @@ -116,9 +116,9 @@ fn derive_struct(input: &syn::DeriveInput) -> Result<proc_macro2::TokenStream, s
}
});

let associated_items = fields.iter().map(|f| {
let associated_items = fields.iter().filter(|f| !f.attrs.ignore).map(|f| {
let field_name = &f.ident.unwrap_named();
let lens_field_name = f.lens_name_override.as_ref().unwrap_or(&field_name);
let lens_field_name = f.attrs.lens_name_override.as_ref().unwrap_or(&field_name);

quote! {
/// Lens for the corresponding field
Expand Down
52 changes: 51 additions & 1 deletion druid-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ use proc_macro::TokenStream;
use syn::parse_macro_input;

/// Generates implementations of the `Data` trait.
///
/// This macro supports a `data` field attribute with the following arguments:
///
/// - `#[data(ignore)]` makes the generated `Data::same` function skip comparing this field.
/// - `#[data(same_fn="foo")]` uses the function `foo` for comparing this field. `foo` should
/// be the name of a function with signature `fn(&T, &T) -> bool`, where `T` is the type of
/// the field.
///
/// # Example
///
/// ```rust
/// use druid_derive::Data;
///
/// #[derive(Clone, Data)]
/// struct State {
/// number: f64,
/// // `Vec` doesn't implement `Data`, so we need to either ignore it or supply a `same_fn`.
/// #[data(same_fn="PartialEq::eq")]
/// indices: Vec<usize>,
/// // This is just some sort of cache; it isn't important for sameness comparison.
/// #[data(ignore)]
/// cached_indices: Vec<usize>,
/// }
/// ```
#[proc_macro_derive(Data, attributes(data))]
pub fn derive_data(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
Expand All @@ -34,10 +58,36 @@ pub fn derive_data(input: TokenStream) -> TokenStream {
.into()
}

/// Generates lenses to access the fields of a struct
/// Generates lenses to access the fields of a struct.
///
/// An associated constant is defined on the struct for each field,
/// having the same name as the field.
///
/// This macro supports a `lens` field attribute with the following arguments:
///
/// - `#[lens(ignore)]` skips creating a lens for one field.
/// - `#[lens(name="foo")]` gives the lens the specified name (instead of the default, which is to
/// create a lens with the same name as the field).
///
/// # Example
///
/// ```rust
/// use druid_derive::Lens;
///
/// #[derive(Lens)]
/// struct State {
/// // The Lens derive will create a `State::text` constant implementing
/// // `druid::Lens<State, String>`
/// text: String,
/// // The Lens derive will create a `State::lens_number` constant implementing
/// // `druid::Lens<State, f64>`
/// #[lens(name = "lens_number")]
/// number: f64,
/// // The Lens derive won't create anything for this field.
/// #[lens(ignore)]
/// blah: f64,
/// }
/// ```
#[proc_macro_derive(Lens, attributes(lens))]
pub fn derive_lens(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
Expand Down
Loading