Skip to content

Commit e2e28ef

Browse files
authored
fix: allowing deriving Patch for a struct with generic argument (closes #4163) (#4175)
1 parent a5e0053 commit e2e28ef

File tree

1 file changed

+133
-17
lines changed
  • reactive_stores_macro/src

1 file changed

+133
-17
lines changed

reactive_stores_macro/src/lib.rs

Lines changed: 133 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use syn::{
66
parse::{Parse, ParseStream, Parser},
77
punctuated::Punctuated,
88
token::Comma,
9-
ExprClosure, Field, Fields, Generics, Ident, Index, Meta, Result, Token,
10-
Type, Variant, Visibility, WhereClause,
9+
ExprClosure, Field, Fields, GenericParam, Generics, Ident, Index, Meta,
10+
Result, Token, Type, TypeParam, Variant, Visibility, WhereClause,
1111
};
1212

1313
#[proc_macro_error]
@@ -26,6 +26,103 @@ pub fn derive_patch(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
2626
.into()
2727
}
2828

29+
/// Removes all constraints from generics arguments list.
30+
///
31+
/// # Example
32+
///
33+
/// ```rust,ignore
34+
/// struct Data<
35+
/// 'a,
36+
/// T1: ToString + PatchField,
37+
/// T2: PatchField,
38+
/// T3: 'static + PatchField,
39+
/// T4,
40+
/// >
41+
/// where
42+
/// T3: ToString,
43+
/// T4: ToString + PatchField,
44+
/// {
45+
/// data1: &'a T1,
46+
/// data2: T2,
47+
/// data3: T3,
48+
/// data4: T4,
49+
/// }
50+
/// ```
51+
///
52+
/// Fort the struct above the `[syn::DeriveInput::parse]` will return the instance of [syn::Generics]
53+
/// which will conceptually look like this
54+
///
55+
/// ```text
56+
/// Generics:
57+
/// params:
58+
/// [
59+
/// 'a,
60+
/// T1: ToString + PatchField,
61+
/// T2: PatchField,
62+
/// T3: 'static + PatchField,
63+
/// T4,
64+
/// ]
65+
/// where_clause:
66+
/// [
67+
/// T3: ToString,
68+
/// T4: ToString + PatchField,
69+
/// ]
70+
/// ```
71+
///
72+
/// This method would return a new instance of [syn::Generics] which will conceptually look like this
73+
///
74+
/// ```text
75+
/// Generics:
76+
/// params:
77+
/// [
78+
/// 'a,
79+
/// T1,
80+
/// T2,
81+
/// T3,
82+
/// T4,
83+
/// ]
84+
/// where_clause:
85+
/// []
86+
/// ```
87+
///
88+
/// This is useful when you want to use a generic arguments list for `impl` sections for type definitions.
89+
fn remove_constraint_from_generics(generics: &Generics) -> Generics {
90+
let mut new_generics = generics.clone();
91+
92+
// remove contraints directly placed in the generic arguments list
93+
//
94+
// For generics for `struct A<T: MyTrait>` the `T: MyTrait` becomes `T`
95+
for param in new_generics.params.iter_mut() {
96+
match param {
97+
GenericParam::Lifetime(lifetime) => {
98+
lifetime.bounds.clear(); // remove bounds
99+
lifetime.colon_token = None;
100+
}
101+
GenericParam::Type(type_param) => {
102+
type_param.bounds.clear(); // remove bounds
103+
type_param.colon_token = None;
104+
type_param.eq_token = None;
105+
type_param.default = None;
106+
}
107+
GenericParam::Const(const_param) => {
108+
// replaces const generic with type param without bounds which is basically an `ident` token
109+
*param = GenericParam::Type(TypeParam {
110+
attrs: const_param.attrs.clone(),
111+
ident: const_param.ident.clone(),
112+
colon_token: None,
113+
bounds: Punctuated::new(),
114+
eq_token: None,
115+
default: None,
116+
});
117+
}
118+
}
119+
}
120+
121+
new_generics.where_clause = None; // remove where clause
122+
123+
new_generics
124+
}
125+
29126
struct Model {
30127
vis: Visibility,
31128
name: Ident,
@@ -111,7 +208,9 @@ impl ToTokens for Model {
111208
} = &self;
112209
let any_store_field = Ident::new("AnyStoreField", Span::call_site());
113210
let trait_name = Ident::new(&format!("{name}StoreFields"), name.span());
211+
let clear_generics = remove_constraint_from_generics(generics);
114212
let params = &generics.params;
213+
let clear_params = &clear_generics.params;
115214
let generics_with_orig = quote! { <#any_store_field, #params> };
116215
let where_with_orig = {
117216
generics
@@ -124,17 +223,22 @@ impl ToTokens for Model {
124223
} = &w;
125224
quote! {
126225
#where_token
127-
#any_store_field: #library_path::StoreField<Value = #name #generics>,
226+
#any_store_field: #library_path::StoreField<Value = #name < #clear_params > >,
128227
#predicates
129228
}
130229
})
131-
.unwrap_or_else(|| quote! { where #any_store_field: #library_path::StoreField<Value = #name #generics> })
230+
.unwrap_or_else(|| quote! { where #any_store_field: #library_path::StoreField<Value = #name < #clear_params > > })
132231
};
133232

134233
// define an extension trait that matches this struct
135234
// and implement that trait for all StoreFields
136-
let (trait_fields, read_fields): (Vec<_>, Vec<_>) =
137-
ty.to_field_data(&library_path, generics, &any_store_field, name);
235+
let (trait_fields, read_fields): (Vec<_>, Vec<_>) = ty.to_field_data(
236+
&library_path,
237+
generics,
238+
&clear_generics,
239+
&any_store_field,
240+
name,
241+
);
138242

139243
// read access
140244
tokens.extend(quote! {
@@ -144,7 +248,7 @@ impl ToTokens for Model {
144248
#(#trait_fields)*
145249
}
146250

147-
impl #generics_with_orig #trait_name <AnyStoreField, #params> for AnyStoreField
251+
impl #generics_with_orig #trait_name <AnyStoreField, #clear_params> for AnyStoreField
148252
#where_with_orig
149253
{
150254
#(#read_fields)*
@@ -158,6 +262,7 @@ impl ModelTy {
158262
&self,
159263
library_path: &TokenStream,
160264
generics: &Generics,
265+
clear_generics: &Generics,
161266
any_store_field: &Ident,
162267
name: &Ident,
163268
) -> (Vec<TokenStream>, Vec<TokenStream>) {
@@ -204,6 +309,7 @@ impl ModelTy {
204309
library_path,
205310
ident.as_ref(),
206311
generics,
312+
clear_generics,
207313
any_store_field,
208314
name,
209315
ty,
@@ -215,6 +321,7 @@ impl ModelTy {
215321
library_path,
216322
ident.as_ref(),
217323
generics,
324+
clear_generics,
218325
any_store_field,
219326
name,
220327
ty,
@@ -233,6 +340,7 @@ impl ModelTy {
233340
library_path,
234341
ident,
235342
generics,
343+
clear_generics,
236344
any_store_field,
237345
name,
238346
fields,
@@ -242,6 +350,7 @@ impl ModelTy {
242350
library_path,
243351
ident,
244352
generics,
353+
clear_generics,
245354
any_store_field,
246355
name,
247356
fields,
@@ -260,7 +369,8 @@ fn field_to_tokens(
260369
modes: Option<&[SubfieldMode]>,
261370
library_path: &proc_macro2::TokenStream,
262371
orig_ident: Option<&Ident>,
263-
generics: &Generics,
372+
_generics: &Generics,
373+
clear_generics: &Generics,
264374
any_store_field: &Ident,
265375
name: &Ident,
266376
ty: &Type,
@@ -285,7 +395,7 @@ fn field_to_tokens(
285395
SubfieldMode::Keyed(keyed_by, key_ty) => {
286396
let signature = quote! {
287397
#[track_caller]
288-
fn #ident(self) -> #library_path::KeyedSubfield<#any_store_field, #name #generics, #key_ty, #ty>
398+
fn #ident(self) -> #library_path::KeyedSubfield<#any_store_field, #name #clear_generics, #key_ty, #ty>
289399
};
290400
return if include_body {
291401
quote! {
@@ -318,7 +428,7 @@ fn field_to_tokens(
318428
// default subfield
319429
if include_body {
320430
quote! {
321-
fn #ident(self) -> #library_path::Subfield<#any_store_field, #name #generics, #ty> {
431+
fn #ident(self) -> #library_path::Subfield<#any_store_field, #name #clear_generics, #ty> {
322432
#library_path::Subfield::new(
323433
self,
324434
#idx.into(),
@@ -329,7 +439,7 @@ fn field_to_tokens(
329439
}
330440
} else {
331441
quote! {
332-
fn #ident(self) -> #library_path::Subfield<#any_store_field, #name #generics, #ty>;
442+
fn #ident(self) -> #library_path::Subfield<#any_store_field, #name #clear_generics, #ty>;
333443
}
334444
}
335445
}
@@ -339,7 +449,8 @@ fn variant_to_tokens(
339449
include_body: bool,
340450
library_path: &proc_macro2::TokenStream,
341451
ident: &Ident,
342-
generics: &Generics,
452+
_generics: &Generics,
453+
clear_generics: &Generics,
343454
any_store_field: &Ident,
344455
name: &Ident,
345456
fields: &Fields,
@@ -408,7 +519,7 @@ fn variant_to_tokens(
408519
// default subfield
409520
if include_body {
410521
quote! {
411-
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #generics, #field_ty>> {
522+
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #clear_generics, #field_ty>> {
412523
#library_path::StoreField::track_field(&self);
413524
let reader = #library_path::StoreField::reader(&self);
414525
let matches = reader
@@ -440,7 +551,7 @@ fn variant_to_tokens(
440551
}
441552
} else {
442553
quote! {
443-
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #generics, #field_ty>>;
554+
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #clear_generics, #field_ty>>;
444555
}
445556
}
446557
}));
@@ -491,7 +602,7 @@ fn variant_to_tokens(
491602
// default subfield
492603
if include_body {
493604
quote! {
494-
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #generics, #field_ty>> {
605+
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #clear_generics, #field_ty>> {
495606
#library_path::StoreField::track_field(&self);
496607
let reader = #library_path::StoreField::reader(&self);
497608
let matches = reader
@@ -523,7 +634,7 @@ fn variant_to_tokens(
523634
}
524635
} else {
525636
quote! {
526-
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #generics, #field_ty>>;
637+
fn #combined_ident(self) -> Option<#library_path::Subfield<#any_store_field, #name #clear_generics, #field_ty>>;
527638
}
528639
}
529640
}));
@@ -665,9 +776,14 @@ impl ToTokens for PatchModel {
665776
}
666777
};
667778

779+
let clear_generics = remove_constraint_from_generics(generics);
780+
let params = clear_generics.params;
781+
let where_clause = &generics.where_clause;
782+
668783
// read access
669784
tokens.extend(quote! {
670-
impl #library_path::PatchField for #name #generics
785+
impl #generics #library_path::PatchField for #name <#params>
786+
#where_clause
671787
{
672788
fn patch_field(
673789
&mut self,

0 commit comments

Comments
 (0)