From 0d2463fd559464254166596c492556029e45c604 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Sat, 8 Aug 2020 21:49:30 +0100 Subject: [PATCH 1/3] Allow derivation of lenses for generic types. Types, lifetimes and where clauses covered plus interior type parameter name clashes. --- CHANGELOG.md | 2 + druid-derive/src/lens.rs | 37 +++++++-- druid-derive/tests/lens_generic.rs | 123 +++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 druid-derive/tests/lens_generic.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d55bbcf56a..1c06858404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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]) ### Changed @@ -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..4ac636e88b 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,43 @@ 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 ret_ty_par = G; + 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 +127,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); +} From ed755889252c8a4fb1068a1a60214d044d48e130 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Sun, 9 Aug 2020 15:47:43 +0100 Subject: [PATCH 2/3] Fix CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c06858404..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 @@ -117,7 +118,6 @@ 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]) ### Changed From ee99be1062b0a740175dbcbfb2bf3cc56ca1fc22 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Mon, 10 Aug 2020 21:39:24 +0100 Subject: [PATCH 3/3] Remove extraneous comment in Lens derivation --- druid-derive/src/lens.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/druid-derive/src/lens.rs b/druid-derive/src/lens.rs index 4ac636e88b..d1010213c0 100644 --- a/druid-derive/src/lens.rs +++ b/druid-derive/src/lens.rs @@ -96,7 +96,6 @@ fn derive_struct(input: &syn::DeriveInput) -> Result