diff --git a/CHANGELOG.md b/CHANGELOG.md index d55bbcf56a..6bda1fd677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ You can find its changes [documented below](#060---2020-06-01). - `ViewSwitcher` now skips the update after switching widgets. ([#1113] by [@finnerale]) - Key and KeyOrValue derive Clone ([#1119] by [@rjwittams]) - Allow submit_command from the layout method in Widgets ([#1119] by [@rjwittams]) +- Allow derivation of lenses for generic types ([#1120]) by [@rjwittams]) ### Visual @@ -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 diff --git a/druid-derive/src/lens.rs b/druid-derive/src/lens.rs index d8a27d1ef7..d1010213c0 100644 --- a/druid-derive/src/lens.rs +++ b/druid-derive/src/lens.rs @@ -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, @@ -72,18 +74,42 @@ fn derive_struct(input: &syn::DeriveInput) -> Result = 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 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>(&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>(&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) } } @@ -100,8 +126,6 @@ fn derive_struct(input: &syn::DeriveInput) -> Result { + x: T, +} + +#[test] +fn one_plain_param() { + let wrap = Wrapper:: { x: 45 }; + let val = Wrapper::::x.with(&wrap, |val| *val); + assert_eq!(wrap.x, val); + + let wrap = Wrapper:: { x: "pop".into() }; + let val = Wrapper::::x.with(&wrap, |val| val.clone()); + assert_eq!(wrap.x, val) +} + +#[derive(Lens)] +struct DebugWrapper { + x: T, +} + +#[test] +fn one_trait_param() { + let wrap = DebugWrapper:: { x: 45 }; + let val = DebugWrapper::::x.with(&wrap, |val| *val); + assert_eq!(wrap.x, val); + + let wrap = DebugWrapper:: { x: "pop".into() }; + let val = DebugWrapper::::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:: { + x: 45, + phantom_a: Default::default(), + }; + let val = LifetimeWrapper::::x.with(&wrap, |val| *val); + assert_eq!(wrap.x, val); + + let wrap = LifetimeWrapper:: { + x: "pop".into(), + phantom_a: Default::default(), + }; + let val = LifetimeWrapper::::x.with(&wrap, |val| val.clone()); + assert_eq!(wrap.x, val) +} + +trait XT { + type I: YT; +} + +trait YT { + type P; +} + +#[derive(Lens)] +struct WhereWrapper +where + T: XT, + 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; + + 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: F, // We were using V and F as method params + v: V, +} + +#[test] +fn reserved() { + let rp = ReservedParams:: { + f: 56, + v: "Go".into(), + }; + let val = ReservedParams::::f.with(&rp, |val| *val); + assert_eq!(rp.f, val); +}