Skip to content

Commit 32e1838

Browse files
authored
Veetaha's followup for #[builder(generics(setters))] (#368)
Followup for #364. A bunch of assorted changes described in commit messages
1 parent d6cb79d commit 32e1838

File tree

6 files changed

+137
-106
lines changed

6 files changed

+137
-106
lines changed

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

Lines changed: 53 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use super::models::BuilderGenCtx;
22
use crate::parsing::ItemSigConfig;
33
use crate::util::prelude::*;
4+
use std::collections::BTreeSet;
45
use syn::punctuated::Punctuated;
56
use syn::token::Where;
7+
use syn::visit::Visit;
68

79
pub(super) struct GenericSettersCtx<'a> {
810
base: &'a BuilderGenCtx,
@@ -28,19 +30,24 @@ impl<'a> GenericSettersCtx<'a> {
2830
// Check for interdependent type parameters in generic bounds
2931
for param in generics {
3032
if let syn::GenericParam::Type(type_param) = param {
31-
let params_in_bounds =
32-
find_type_params_in_bounds(&type_param.bounds, &type_param_idents);
33-
if params_in_bounds.len() > 1
34-
|| (params_in_bounds.len() == 1
35-
&& params_in_bounds.first() != Some(&&type_param.ident))
36-
{
37-
let params_str = params_in_bounds
33+
let mut params = TypeParamFinder::new(&type_param_idents);
34+
35+
for bound in &type_param.bounds {
36+
params.visit_type_param_bound(bound);
37+
}
38+
39+
// Self-referential type params are fine
40+
params.found.remove(&type_param.ident);
41+
42+
if let Some(first_param) = params.found.iter().next() {
43+
let params_str = params
44+
.found
3845
.iter()
3946
.map(|p| format!("`{p}`"))
4047
.collect::<Vec<_>>()
4148
.join(", ");
4249
bail!(
43-
&type_param.bounds,
50+
first_param,
4451
"generic conversion methods cannot be generated for interdependent type parameters; \
4552
the bounds on generic parameter `{}` reference other type parameters: {}\n\
4653
\n\
@@ -55,10 +62,11 @@ impl<'a> GenericSettersCtx<'a> {
5562
// Check for interdependent type parameters in where clauses
5663
if let Some(where_clause) = &self.base.generics.where_clause {
5764
for predicate in &where_clause.predicates {
58-
let params_in_predicate =
59-
find_type_params_in_predicate(predicate, &type_param_idents);
60-
if params_in_predicate.len() > 1 {
61-
let params_str = params_in_predicate
65+
let mut params = TypeParamFinder::new(&type_param_idents);
66+
params.visit_where_predicate(predicate);
67+
if params.found.len() > 1 {
68+
let params_str = params
69+
.found
6270
.iter()
6371
.map(|p| format!("`{p}`"))
6472
.collect::<Vec<_>>()
@@ -85,8 +93,9 @@ impl<'a> GenericSettersCtx<'a> {
8593
syn::GenericParam::Const(const_param) => {
8694
bail!(
8795
&const_param.ident,
88-
"const generic parameters are not supported in `generics(setters(...))`; \
89-
only type parameters can be converted"
96+
"const generic parameters are not yet supported with `generics(setters(...))`; \
97+
only type parameters can be overridden, feel free to open an issue if you need \
98+
this feature"
9099
);
91100
}
92101
syn::GenericParam::Lifetime(_) => {
@@ -122,8 +131,12 @@ impl<'a> GenericSettersCtx<'a> {
122131
let docs = self.method_docs(param_ident);
123132

124133
// Build the generic arguments for the output type, where the current parameter
125-
// is replaced with a new type variable
126-
let new_type_var = self.base.namespace.unique_ident(param_ident.to_string());
134+
// is replaced with a new type variable. Even though the `GenericsNamespace`
135+
let new_type_var = self
136+
.base
137+
.namespace
138+
// Add `New` prefix to make the type variable more readable in the docs and IDE hints
139+
.unique_ident(format!("New{param_ident}"));
127140

128141
// Copy the bounds from the original type parameter to the new one
129142
let bounds = &type_param.bounds;
@@ -168,7 +181,10 @@ impl<'a> GenericSettersCtx<'a> {
168181

169182
// Add runtime assert that this field is None
170183
let field_ident = &member.name.orig;
171-
let message = format!("BUG: field `{field_ident}` should be None when converting generic parameter `{param_ident}`");
184+
let message = format!(
185+
"BUG: field `{field_ident}` should be None \
186+
when converting generic parameter `{param_ident}`"
187+
);
172188
runtime_asserts.push(quote! {
173189
::core::assert!(named.#index.is_none(), #message);
174190
});
@@ -211,11 +227,7 @@ impl<'a> GenericSettersCtx<'a> {
211227
clause.predicates.push(syn::parse_quote!(#bound));
212228
}
213229

214-
if clause.predicates.is_empty() {
215-
None
216-
} else {
217-
Some(clause)
218-
}
230+
(!clause.predicates.is_empty()).then(|| clause)
219231
};
220232

221233
quote! {
@@ -280,82 +292,34 @@ impl<'a> GenericSettersCtx<'a> {
280292
}
281293
}
282294

283-
fn find_type_params_in_bounds<'b>(
284-
bounds: &Punctuated<syn::TypeParamBound, syn::token::Plus>,
285-
type_params: &'b [&'b syn::Ident],
286-
) -> Vec<&'b syn::Ident> {
287-
use syn::visit::Visit;
295+
struct TypeParamFinder<'ty, 'ast> {
296+
type_params: &'ty [&'ty syn::Ident],
288297

289-
struct TypeParamFinder<'a> {
290-
type_params: &'a [&'a syn::Ident],
291-
found: std::collections::HashSet<&'a syn::Ident>,
292-
}
298+
// Use a `BTreeSet` for deterministic ordering
299+
found: BTreeSet<&'ast syn::Ident>,
300+
}
293301

294-
impl<'ast> Visit<'ast> for TypeParamFinder<'_> {
295-
fn visit_path(&mut self, path: &'ast syn::Path) {
296-
// Check if this path is one of our type parameters
297-
for &param in self.type_params {
298-
if path.is_ident(param) {
299-
self.found.insert(param);
300-
}
301-
}
302-
// Continue visiting nested paths
303-
syn::visit::visit_path(self, path);
302+
impl<'ty> TypeParamFinder<'ty, '_> {
303+
fn new(type_params: &'ty [&'ty syn::Ident]) -> Self {
304+
Self {
305+
type_params,
306+
found: BTreeSet::new(),
304307
}
305308
}
306-
307-
let mut finder = TypeParamFinder {
308-
type_params,
309-
found: std::collections::HashSet::new(),
310-
};
311-
312-
for bound in bounds {
313-
finder.visit_type_param_bound(bound);
314-
}
315-
316-
// Preserve the original order of type parameters for deterministic output
317-
type_params
318-
.iter()
319-
.filter(|param| finder.found.contains(*param))
320-
.copied()
321-
.collect()
322309
}
323310

324-
fn find_type_params_in_predicate<'b>(
325-
predicate: &syn::WherePredicate,
326-
type_params: &'b [&'b syn::Ident],
327-
) -> Vec<&'b syn::Ident> {
328-
use syn::visit::Visit;
329-
330-
struct TypeParamFinder<'a> {
331-
type_params: &'a [&'a syn::Ident],
332-
found: std::collections::HashSet<&'a syn::Ident>,
333-
}
334-
335-
impl<'ast> Visit<'ast> for TypeParamFinder<'_> {
336-
fn visit_path(&mut self, path: &'ast syn::Path) {
337-
// Check if this path is one of our type parameters
338-
for &param in self.type_params {
339-
if path.is_ident(param) {
340-
self.found.insert(param);
341-
}
311+
impl<'ast> Visit<'ast> for TypeParamFinder<'_, 'ast> {
312+
fn visit_path(&mut self, path: &'ast syn::Path) {
313+
// Check if this path is one of our type parameters
314+
if let Some(param) = path.get_ident() {
315+
if self.type_params.contains(&param) {
316+
self.found.insert(param);
342317
}
343-
// Continue visiting nested paths
344-
syn::visit::visit_path(self, path);
345318
}
346-
}
347319

348-
let mut finder = TypeParamFinder {
349-
type_params,
350-
found: std::collections::HashSet::new(),
351-
};
352-
finder.visit_where_predicate(predicate);
353-
// Preserve the original order of type parameters for deterministic output
354-
type_params
355-
.iter()
356-
.filter(|param| finder.found.contains(*param))
357-
.copied()
358-
.collect()
320+
// Continue visiting nested paths
321+
syn::visit::visit_path(self, path);
322+
}
359323
}
360324

361325
fn replace_type_param_in_predicate(
@@ -406,8 +370,6 @@ fn member_uses_generic_param(member: &super::NamedMember, param_ident: &syn::Ide
406370

407371
/// Recursively check if a type uses a specific generic parameter
408372
fn type_uses_generic_param(ty: &syn::Type, param_ident: &syn::Ident) -> bool {
409-
use syn::visit::Visit;
410-
411373
struct GenericParamVisitor<'a> {
412374
param_ident: &'a syn::Ident,
413375
found: bool,

bon-macros/src/builder/builder_gen/top_level_config/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,19 @@ impl TopLevelConfig {
183183
..Self::from_list(&configs)?
184184
};
185185

186+
if let Some(generics) = &me.generics {
187+
if generics.setters.is_some() {
188+
if let Some(const_) = &me.const_ {
189+
bail!(
190+
const_,
191+
"`generics(setters(...))` cannot be used together with `const` \
192+
functions; if you have a use case for this, consider opening an \
193+
issue to discuss it!"
194+
);
195+
}
196+
}
197+
}
198+
186199
if let Some(on) = me.on.iter().skip(1).find(|on| on.required.is_present()) {
187200
bail!(
188201
&on.required.span(),

bon/tests/integration/ui/compile_fail/generics_setters/fn_pointer.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,35 @@ use bon::Builder;
55

66
#[derive(Builder)]
77
#[builder(generics(setters(name = "conv_{}")))]
8-
struct FnPointerField<T> {
8+
struct FnPointerFieldInOut<T> {
99
value: fn(T) -> T,
1010
}
1111

12+
#[derive(Builder)]
13+
#[builder(generics(setters(name = "conv_{}")))]
14+
struct FnPointerFieldIn<T> {
15+
value: fn(T),
16+
}
17+
18+
#[derive(Builder)]
19+
#[builder(generics(setters(name = "conv_{}")))]
20+
struct FnPointerFieldOut<T> {
21+
value: fn() -> T,
22+
}
23+
1224
fn main() {
13-
// Test fn(T) -> T - can't change type after setting field
14-
FnPointerField::<()>::builder()
15-
.value(|x| x)
25+
FnPointerFieldInOut::<()>::builder()
26+
.value(|()| ())
27+
.conv_t::<bool>()
28+
.build();
29+
30+
FnPointerFieldIn::<()>::builder()
31+
.value(|()| ())
32+
.conv_t::<bool>()
33+
.build();
34+
35+
FnPointerFieldOut::<()>::builder()
36+
.value(|| ())
1637
.conv_t::<bool>()
1738
.build();
1839
}
Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,50 @@
1-
error[E0277]: the member `Set<value>` was already set, but this method requires it to be unset
2-
--> tests/integration/ui/compile_fail/generics_setters/fn_pointer.rs:16:10
1+
error[E0277]: the member `Set<fn_pointer_field_in_out_builder::members::value>` was already set, but this method requires it to be unset
2+
--> tests/integration/ui/compile_fail/generics_setters/fn_pointer.rs:27:10
33
|
4-
16 | .conv_t::<bool>()
5-
| ^^^^^^ the member `Set<value>` was already set, but this method requires it to be unset
4+
27 | .conv_t::<bool>()
5+
| ^^^^^^ the member `Set<fn_pointer_field_in_out_builder::members::value>` was already set, but this method requires it to be unset
66
|
7-
= help: the trait `IsUnset` is not implemented for `Set<value>`
8-
note: required by a bound in `FnPointerFieldBuilder::<T, S>::conv_t`
7+
= help: the trait `IsUnset` is not implemented for `Set<fn_pointer_field_in_out_builder::members::value>`
8+
note: required by a bound in `FnPointerFieldInOutBuilder::<T, S>::conv_t`
99
--> tests/integration/ui/compile_fail/generics_setters/fn_pointer.rs:6:10
1010
|
1111
6 | #[derive(Builder)]
12-
| ^^^^^^^ required by this bound in `FnPointerFieldBuilder::<T, S>::conv_t`
12+
| ^^^^^^^ required by this bound in `FnPointerFieldInOutBuilder::<T, S>::conv_t`
1313
7 | #[builder(generics(setters(name = "conv_{}")))]
14-
8 | struct FnPointerField<T> {
15-
| - required by a bound in this associated function
14+
8 | struct FnPointerFieldInOut<T> {
15+
| - required by a bound in this associated function
16+
= note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info)
17+
18+
error[E0277]: the member `Set<fn_pointer_field_in_builder::members::value>` was already set, but this method requires it to be unset
19+
--> tests/integration/ui/compile_fail/generics_setters/fn_pointer.rs:32:10
20+
|
21+
32 | .conv_t::<bool>()
22+
| ^^^^^^ the member `Set<fn_pointer_field_in_builder::members::value>` was already set, but this method requires it to be unset
23+
|
24+
= help: the trait `IsUnset` is not implemented for `Set<fn_pointer_field_in_builder::members::value>`
25+
note: required by a bound in `FnPointerFieldInBuilder::<T, S>::conv_t`
26+
--> tests/integration/ui/compile_fail/generics_setters/fn_pointer.rs:12:10
27+
|
28+
12 | #[derive(Builder)]
29+
| ^^^^^^^ required by this bound in `FnPointerFieldInBuilder::<T, S>::conv_t`
30+
13 | #[builder(generics(setters(name = "conv_{}")))]
31+
14 | struct FnPointerFieldIn<T> {
32+
| - required by a bound in this associated function
33+
= note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info)
34+
35+
error[E0277]: the member `Set<fn_pointer_field_out_builder::members::value>` was already set, but this method requires it to be unset
36+
--> tests/integration/ui/compile_fail/generics_setters/fn_pointer.rs:37:10
37+
|
38+
37 | .conv_t::<bool>()
39+
| ^^^^^^ the member `Set<fn_pointer_field_out_builder::members::value>` was already set, but this method requires it to be unset
40+
|
41+
= help: the trait `IsUnset` is not implemented for `Set<fn_pointer_field_out_builder::members::value>`
42+
note: required by a bound in `FnPointerFieldOutBuilder::<T, S>::conv_t`
43+
--> tests/integration/ui/compile_fail/generics_setters/fn_pointer.rs:18:10
44+
|
45+
18 | #[derive(Builder)]
46+
| ^^^^^^^ required by this bound in `FnPointerFieldOutBuilder::<T, S>::conv_t`
47+
19 | #[builder(generics(setters(name = "conv_{}")))]
48+
20 | struct FnPointerFieldOut<T> {
49+
| - required by a bound in this associated function
1650
= note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
error: generic conversion methods cannot be generated for interdependent type parameters; the bounds on generic parameter `Iter` reference other type parameters: `Item`
22

33
Consider removing `generics(setters(...))` or restructuring your types to avoid interdependencies
4-
--> tests/integration/ui/compile_fail/generics_setters/interdependent_param_bounds.rs:7:18
4+
--> tests/integration/ui/compile_fail/generics_setters/interdependent_param_bounds.rs:7:34
55
|
66
7 | struct Sut<Iter: Iterator<Item = Item>, Item> {
7-
| ^^^^^^^^
7+
| ^^^^

scripts/test-msrv.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ with_log cd bon
3535
step echo '[workspace]' >> Cargo.toml
3636

3737
step cargo update --precise 0.21.3 -p darling
38+
step cargo update --precise 1.0.22 -p unicode-ident
3839
step cargo update --precise 1.0.15 -p itoa
3940
step cargo update --precise 1.0.101 -p proc-macro2
4041
step cargo update --precise 1.0.40 -p quote

0 commit comments

Comments
 (0)