Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -117,6 +117,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
- `LifeCycle::Size` event to inform widgets that their size changed. ([#953] by [@xStrom])
- `Button::dynamic` constructor. ([#963] by [@totsteps])
- `Spinner` widget to represent loading states. ([#1003] by [@futurepaul])
- Allow derivation of lenses for generic types ([#1120]) by [@rjwittams])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in the "Unreleased" section, not the "0.6.0" section


### Changed

Expand Down Expand Up @@ -382,6 +383,7 @@ Last release without a changelog :(
[#1100]: https://github.com/linebender/druid/pull/1100
[#1103]: https://github.com/linebender/druid/pull/1103
[#1119]: https://github.com/linebender/druid/pull/1119
[#1120]: https://github.com/linebender/druid/pull/1120

[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
37 changes: 31 additions & 6 deletions druid-derive/src/lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
// limitations under the License.

use super::attr::{FieldKind, Fields};
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{spanned::Spanned, Data};
use std::collections::HashSet;
use syn::{spanned::Spanned, Data, GenericParam, TypeParam};

pub(crate) fn derive_lens_impl(
input: syn::DeriveInput,
Expand Down Expand Up @@ -72,18 +74,43 @@ fn derive_struct(input: &syn::DeriveInput) -> Result<proc_macro2::TokenStream, s
pub struct #field_name;
}
});
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

let used_params: HashSet<String> = input
.generics
.params
.iter()
.flat_map(|gp: &GenericParam| match gp {
GenericParam::Type(TypeParam { ident, .. }) => Some(ident.to_string()),
_ => None,
})
.collect();

let gen_new_param = |name: &str| {
let mut candidate: String = name.into();
let mut count = 1usize;
while used_params.contains(&candidate) {
candidate = format!("{}_{}", name, count);
count += 1;
}
Ident::new(&candidate, Span::call_site())
};

//let ret_ty_par = G;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗑️

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll get rid of that..

let func_ty_par = gen_new_param("F");
let val_ty_par = gen_new_param("V");

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

quote! {
impl druid::Lens<#ty, #field_ty> for #twizzled_name::#field_name {
fn with<V, F: FnOnce(&#field_ty) -> V>(&self, data: &#ty, f: F) -> V {
impl #impl_generics druid::Lens<#ty#ty_generics, #field_ty> for #twizzled_name::#field_name #where_clause {
fn with<#val_ty_par, #func_ty_par: FnOnce(&#field_ty) -> #val_ty_par>(&self, data: &#ty#ty_generics, f: #func_ty_par) -> #val_ty_par {
f(&data.#field_name)
}

fn with_mut<V, F: FnOnce(&mut #field_ty) -> V>(&self, data: &mut #ty, f: F) -> V {
fn with_mut<#val_ty_par, #func_ty_par: FnOnce(&mut #field_ty) -> #val_ty_par>(&self, data: &mut #ty#ty_generics, f: #func_ty_par) -> #val_ty_par {
f(&mut data.#field_name)
}
}
Expand All @@ -100,8 +127,6 @@ fn derive_struct(input: &syn::DeriveInput) -> Result<proc_macro2::TokenStream, s
}
});

let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

let expanded = quote! {
pub mod #twizzled_name {
#(#defs)*
Expand Down
123 changes: 123 additions & 0 deletions druid-derive/tests/lens_generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use druid::Lens;
use std::fmt::Debug;
use std::marker::PhantomData;

#[derive(Lens)]
struct Wrapper<T> {
x: T,
}

#[test]
fn one_plain_param() {
let wrap = Wrapper::<u64> { x: 45 };
let val = Wrapper::<u64>::x.with(&wrap, |val| *val);
assert_eq!(wrap.x, val);

let wrap = Wrapper::<String> { x: "pop".into() };
let val = Wrapper::<String>::x.with(&wrap, |val| val.clone());
assert_eq!(wrap.x, val)
}

#[derive(Lens)]
struct DebugWrapper<T: Debug> {
x: T,
}

#[test]
fn one_trait_param() {
let wrap = DebugWrapper::<u64> { x: 45 };
let val = DebugWrapper::<u64>::x.with(&wrap, |val| *val);
assert_eq!(wrap.x, val);

let wrap = DebugWrapper::<String> { x: "pop".into() };
let val = DebugWrapper::<String>::x.with(&wrap, |val| val.clone());
assert_eq!(wrap.x, val)
}

#[derive(Lens)]
struct LifetimeWrapper<'a, T: 'a> {
x: T,
phantom_a: PhantomData<&'a T>,
}

#[test]
fn one_lifetime_param() {
let wrap = LifetimeWrapper::<u64> {
x: 45,
phantom_a: Default::default(),
};
let val = LifetimeWrapper::<u64>::x.with(&wrap, |val| *val);
assert_eq!(wrap.x, val);

let wrap = LifetimeWrapper::<String> {
x: "pop".into(),
phantom_a: Default::default(),
};
let val = LifetimeWrapper::<String>::x.with(&wrap, |val| val.clone());
assert_eq!(wrap.x, val)
}

trait XT {
type I: YT;
}

trait YT {
type P;
}

#[derive(Lens)]
struct WhereWrapper<T, U, W>
where
T: XT<I = U>,
U: YT,
{
t: T,
u: U,
w: W,
}

impl XT for u64 {
type I = i32;
}

impl YT for i32 {
type P = bool;
}

#[test]
fn where_clause() {
type WW = WhereWrapper<u64, i32, bool>;

let mut wrap = WW {
t: 45,
u: 1_000_000,
w: true,
};
let ext = (
WW::t.with(&wrap, |val| *val),
WW::u.with(&wrap, |val| *val),
WW::w.with(&wrap, |val| *val),
);

assert_eq!((wrap.t, wrap.u, wrap.w), ext);

WW::t.with_mut(&mut wrap, |val| *val = 67);

assert_eq!(wrap.t, 67)
}

#[derive(Lens)]
struct ReservedParams<F, V> {
f: F, // We were using V and F as method params
v: V,
}

#[test]
fn reserved() {
let rp = ReservedParams::<u64, String> {
f: 56,
v: "Go".into(),
};
let val = ReservedParams::<u64, String>::f.with(&rp, |val| *val);
assert_eq!(rp.f, val);
}