wowlab_tidy/languages/rust/rules/style/
where_clauses.rs1use ra_ap_syntax::{
2 AstNode,
3 ast::{self, HasTypeBounds},
4};
5
6use crate::{AstCtx, Example, Violation};
7
8#[rustfmt::skip]
9const EXAMPLES: &[Example] = &[
10 Example {
11 label: "function where clause",
12 code: "fn render<T>(value: T) where T: std::fmt::Display {}",
13 pass: true,
14 },
15 Example {
16 label: "inline function bound",
17 code: "fn render<T: std::fmt::Display>(value: T) {}",
18 pass: false,
19 },
20 Example {
21 label: "inline struct bound",
22 code: "struct Wrapper<T: Clone> { value: T }",
23 pass: false,
24 },
25 Example {
26 label: "lifetime bounds are exempt",
27 code: "struct Borrowed<'a: 'static> { value: &'a str }",
28 pass: true,
29 },
30 Example {
31 label: "const generics are exempt",
32 code: "struct Buffer<const N: usize> { bytes: [u8; N] }",
33 pass: true,
34 },
35 Example {
36 label: "default type params are exempt",
37 code: "struct Wrapper<T: Clone = String> { value: T }",
38 pass: true,
39 },
40];
41
42crate::ast_rule!(
43 where_clauses,
44 "Require type-parameter trait bounds to use where clauses.",
45 "Where clauses keep function and type declarations readable as constraints grow.",
46 Low,
47);
48
49fn check_where_clauses(ctx: &AstCtx<'_>) -> Vec<Violation> {
50 let bounded_params = ctx
51 .nodes::<ast::TypeParam>()
52 .filter(|param| param.default_type().is_none())
53 .filter(|param| {
54 param
55 .type_bound_list()
56 .is_some_and(|bounds| bounds.bounds().next().is_some())
57 });
58
59 bounded_params
60 .filter(|param| {
61 param.syntax().ancestors().any(|ancestor| {
62 ast::Fn::can_cast(ancestor.kind())
63 || ast::Impl::can_cast(ancestor.kind())
64 || ast::Struct::can_cast(ancestor.kind())
65 || ast::Enum::can_cast(ancestor.kind())
66 })
67 })
68 .map(|param| {
69 ctx.violation(
70 ¶m,
71 "trait bounds in generic parameter lists must move to a where clause",
72 )
73 })
74 .collect()
75}
76
77crate::tidy_ast_test!(check_where_clauses, {
78 crate::example_tests!(EXAMPLES, check_where_clauses);
79});