Skip to content

Commit ca913b8

Browse files
committed
Add support for generic bounds
1 parent ff23719 commit ca913b8

3 files changed

Lines changed: 111 additions & 35 deletions

File tree

bon-macros/src/builder/builder_gen/generic_setters.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ impl<'a> GenericSettersCtx<'a> {
2020
for (index, param) in generics.iter().enumerate() {
2121
match param {
2222
syn::GenericParam::Type(type_param) => {
23-
methods.push(self.generic_setter_method(index, &type_param.ident));
23+
methods.push(self.generic_setter_method(index, type_param));
2424
}
2525
syn::GenericParam::Const(const_param) => {
2626
bail!(
@@ -40,11 +40,16 @@ impl<'a> GenericSettersCtx<'a> {
4040
})
4141
}
4242

43-
fn generic_setter_method(&self, param_index: usize, param_ident: &syn::Ident) -> TokenStream {
43+
fn generic_setter_method(
44+
&self,
45+
param_index: usize,
46+
type_param: &syn::TypeParam,
47+
) -> TokenStream {
4448
let builder_ident = &self.base.builder_type.ident;
4549
let state_var = &self.base.state_var;
4650
let where_clause = &self.base.generics.where_clause;
4751

52+
let param_ident = &type_param.ident;
4853
let method_name = self.method_name(param_ident);
4954

5055
let vis = self
@@ -59,6 +64,15 @@ impl<'a> GenericSettersCtx<'a> {
5964
// Build the generic arguments for the output type, where the current parameter
6065
// is replaced with a new type variable
6166
let new_type_var = self.base.namespace.unique_ident(param_ident.to_string());
67+
68+
// Copy the bounds from the original type parameter to the new one
69+
let bounds = &type_param.bounds;
70+
let new_type_param = if bounds.is_empty() {
71+
quote!(#new_type_var)
72+
} else {
73+
quote!(#new_type_var: #bounds)
74+
};
75+
6276
let output_generic_args = self
6377
.base
6478
.generics
@@ -117,7 +131,7 @@ impl<'a> GenericSettersCtx<'a> {
117131
quote! {
118132
#(#docs)*
119133
#[inline(always)]
120-
#vis fn #method_name<#new_type_var>(
134+
#vis fn #method_name<#new_type_param>(
121135
self
122136
) -> #builder_ident<#(#output_generic_args,)* #state_var>
123137
#where_clause
@@ -204,6 +218,20 @@ fn type_uses_generic_param(ty: &syn::Type, param_ident: &syn::Ident) -> bool {
204218
return;
205219
}
206220

221+
// For qualified paths like T::Assoc or <T as Trait>::Assoc,
222+
// check if the first segment (or qself) uses the generic parameter
223+
224+
if let Some(qself) = &type_path.qself {
225+
// For <T as Trait>::Assoc syntax
226+
self.visit_type(&qself.ty);
227+
} else if let Some(segment) = type_path.path.segments.first() {
228+
// For T::Assoc syntax
229+
if segment.ident == *self.param_ident {
230+
self.found = true;
231+
return;
232+
}
233+
}
234+
207235
// Continue visiting the rest of the type path
208236
syn::visit::visit_type_path(self, type_path);
209237
}

bon/tests/integration/builder/generics_setters.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,83 @@ fn test_complex_syntax_with_docs() {
8888
let result = Sut::<()>::builder().conv_t::<u32>().value(42).build();
8989
assert_eq!(result.value, 42);
9090
}
91+
92+
#[test]
93+
fn test_with_trait_bounds() {
94+
trait MyTrait {
95+
type Assoc;
96+
}
97+
98+
impl MyTrait for () {
99+
type Assoc = u32;
100+
}
101+
102+
impl MyTrait for bool {
103+
type Assoc = u64;
104+
}
105+
106+
#[derive(Builder)]
107+
#[builder(generics(setters(name = "conv_{}")))]
108+
struct AssocTypeField<T: MyTrait> {
109+
value: T::Assoc,
110+
}
111+
112+
#[derive(Builder)]
113+
#[builder(generics(setters(name = "conv_{}")))]
114+
struct QualifiedPathField<T: MyTrait> {
115+
value: <T as MyTrait>::Assoc,
116+
}
117+
118+
let result1 = AssocTypeField::<()>::builder()
119+
.conv_t::<bool>()
120+
.value(42u64)
121+
.build();
122+
assert_eq!(result1.value, 42u64);
123+
124+
let result2 = QualifiedPathField::<()>::builder()
125+
.conv_t::<bool>()
126+
.value(99u64)
127+
.build();
128+
assert_eq!(result2.value, 99u64);
129+
}
130+
131+
#[test]
132+
fn test_with_trait_bounds_false_friend() {
133+
// The associated type is also called T, but should not be replaced by the generic <T>
134+
135+
trait MyTrait {
136+
type T;
137+
}
138+
139+
impl MyTrait for () {
140+
type T = u32;
141+
}
142+
143+
impl MyTrait for bool {
144+
type T = u64;
145+
}
146+
147+
#[derive(Builder)]
148+
#[builder(generics(setters(name = "conv_{}")))]
149+
struct AssocTypeField<T: MyTrait> {
150+
value: T::T,
151+
}
152+
153+
#[derive(Builder)]
154+
#[builder(generics(setters(name = "conv_{}")))]
155+
struct QualifiedPathField<T: MyTrait> {
156+
value: <T as MyTrait>::T,
157+
}
158+
159+
let result1 = AssocTypeField::<()>::builder()
160+
.conv_t::<bool>()
161+
.value(42u64)
162+
.build();
163+
assert_eq!(result1.value, 42u64);
164+
165+
let result2 = QualifiedPathField::<()>::builder()
166+
.conv_t::<bool>()
167+
.value(99u64)
168+
.build();
169+
assert_eq!(result2.value, 99u64);
170+
}

bon/tests/integration/ui/compile_fail/generics_setters.stderr

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,3 @@
1-
error[E0277]: the trait bound `T_: MyTrait` is not satisfied
2-
--> tests/integration/ui/compile_fail/generics_setters.rs:42:10
3-
|
4-
42 | #[derive(Builder)]
5-
| ^^^^^^^ the trait `MyTrait` is not implemented for `T_`
6-
|
7-
note: required by a bound in `AssocTypeFieldBuilder`
8-
--> tests/integration/ui/compile_fail/generics_setters.rs:44:26
9-
|
10-
42 | #[derive(Builder)]
11-
| ------- required by a bound in this struct
12-
43 | #[builder(generics(setters(name = "conv_{}")))]
13-
44 | struct AssocTypeField<T: MyTrait> {
14-
| ^^^^^^^ required by this bound in `AssocTypeFieldBuilder`
15-
= note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info)
16-
17-
error[E0277]: the trait bound `T_: MyTrait` is not satisfied
18-
--> tests/integration/ui/compile_fail/generics_setters.rs:48:10
19-
|
20-
48 | #[derive(Builder)]
21-
| ^^^^^^^ the trait `MyTrait` is not implemented for `T_`
22-
|
23-
note: required by a bound in `QualifiedPathFieldBuilder`
24-
--> tests/integration/ui/compile_fail/generics_setters.rs:50:30
25-
|
26-
48 | #[derive(Builder)]
27-
| ------- required by a bound in this struct
28-
49 | #[builder(generics(setters(name = "conv_{}")))]
29-
50 | struct QualifiedPathField<T: MyTrait> {
30-
| ^^^^^^^ required by this bound in `QualifiedPathFieldBuilder`
31-
= note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info)
32-
331
error[E0308]: mismatched types
342
--> tests/integration/ui/compile_fail/generics_setters.rs:63:16
353
|

0 commit comments

Comments
 (0)