Skip to content

Commit 82b8949

Browse files
committed
Add validation for interdependent generic parameter bounds
1 parent 352e1d4 commit 82b8949

File tree

4 files changed

+87
-1
lines changed

4 files changed

+87
-1
lines changed

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,32 @@ impl<'a> GenericSettersCtx<'a> {
2525
})
2626
.collect();
2727

28+
// Check for interdependent type parameters in generic bounds
29+
for param in generics.iter() {
30+
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 && params_in_bounds[0] != &type_param.ident)
35+
{
36+
let params_str = params_in_bounds
37+
.iter()
38+
.map(|p| format!("`{p}`"))
39+
.collect::<Vec<_>>()
40+
.join(", ");
41+
bail!(
42+
&type_param.bounds,
43+
"generic conversion methods cannot be generated for interdependent type parameters; \
44+
the bounds on generic parameter `{}` reference other type parameters: {}\n\
45+
\n\
46+
Consider removing `generics(setters(...))` or restructuring your types to avoid interdependencies",
47+
type_param.ident,
48+
params_str
49+
);
50+
}
51+
}
52+
}
53+
2854
// Check for interdependent type parameters in where clauses
2955
if let Some(where_clause) = &self.base.generics.where_clause {
3056
for predicate in &where_clause.predicates {
@@ -253,6 +279,47 @@ impl<'a> GenericSettersCtx<'a> {
253279
}
254280
}
255281

282+
fn find_type_params_in_bounds<'b>(
283+
bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::token::Plus>,
284+
type_params: &'b [&'b syn::Ident],
285+
) -> Vec<&'b syn::Ident> {
286+
use syn::visit::Visit;
287+
288+
struct TypeParamFinder<'a> {
289+
type_params: &'a [&'a syn::Ident],
290+
found: std::collections::HashSet<&'a syn::Ident>,
291+
}
292+
293+
impl<'ast> Visit<'ast> for TypeParamFinder<'_> {
294+
fn visit_path(&mut self, path: &'ast syn::Path) {
295+
// Check if this path is one of our type parameters
296+
for &param in self.type_params {
297+
if path.is_ident(param) {
298+
self.found.insert(param);
299+
}
300+
}
301+
// Continue visiting nested paths
302+
syn::visit::visit_path(self, path);
303+
}
304+
}
305+
306+
let mut finder = TypeParamFinder {
307+
type_params,
308+
found: std::collections::HashSet::new(),
309+
};
310+
311+
for bound in bounds {
312+
finder.visit_type_param_bound(bound);
313+
}
314+
315+
// Preserve the original order of type parameters for deterministic output
316+
type_params
317+
.iter()
318+
.filter(|param| finder.found.contains(*param))
319+
.copied()
320+
.collect()
321+
}
322+
256323
fn find_type_params_in_predicate<'b>(
257324
predicate: &syn::WherePredicate,
258325
type_params: &'b [&'b syn::Ident],

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ error: generic conversion methods cannot be generated for interdependent type pa
44
--> tests/integration/ui/compile_fail/generics_setters/interdependent_bounds.rs:10:5
55
|
66
10 | T: IntoIterator<Item = U>,
7-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
7+
| ^
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use bon::Builder;
2+
3+
// Test that conversion methods reject bounds that reference other type parameters
4+
5+
#[derive(Builder)]
6+
#[builder(generics(setters(name = "with_{}")))]
7+
struct Sut<Iter: Iterator<Item = Item>, Item> {
8+
value1: Iter,
9+
}
10+
11+
fn main() {
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
error: generic conversion methods cannot be generated for interdependent type parameters; the bounds on generic parameter `Iter` reference other type parameters: `Item`
2+
3+
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
5+
|
6+
7 | struct Sut<Iter: Iterator<Item = Item>, Item> {
7+
| ^^^^^^^^

0 commit comments

Comments
 (0)