Skip to main content

wowlab_tidy/languages/rust/rules/interop/
range_over_rangebounds.rs

1use ra_ap_syntax::ast;
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7    Example {
8        label: "pub fn taking Range",
9        code: "pub fn f(r: Range<usize>) {}",
10        pass: false,
11    },
12    Example {
13        label: "pub fn taking std::ops::Range",
14        code: "pub fn f(r: std::ops::Range<usize>) {}",
15        pass: false,
16    },
17    Example {
18        label: "pub method taking Range",
19        code: "pub struct S;\nimpl S {\n    pub fn f(&self, r: Range<u32>) {}\n}",
20        pass: false,
21    },
22    Example {
23        label: "private fn taking Range",
24        code: "fn f(r: Range<usize>) {}",
25        pass: true,
26    },
27    Example {
28        label: "impl RangeBounds parameter",
29        code: "pub fn f(r: impl std::ops::RangeBounds<usize>) {}",
30        pass: true,
31    },
32    Example {
33        label: "Range behind a reference",
34        code: "pub fn f(r: &Range<usize>) {}",
35        pass: true,
36    },
37    Example {
38        label: "RangeInclusive is not flagged",
39        code: "pub fn f(r: RangeInclusive<usize>) {}",
40        pass: true,
41    },
42    Example {
43        label: "Range in test module",
44        code: "#[cfg(test)]\nmod tests {\n    pub fn f(r: Range<usize>) {}\n}",
45        pass: true,
46    },
47];
48
49crate::ast_rule!(
50    range_over_rangebounds,
51    "Flag `pub` fn parameters typed `Range<T>` — accept `impl RangeBounds<T>` instead.",
52    "Range<T> forces callers to supply half-open bounds; impl RangeBounds<T> also accepts `1..`, `..=n`, and `..` (M-IMPL-RANGEBOUNDS).",
53    Low,
54);
55
56fn check_range_over_rangebounds(ctx: &AstCtx<'_>) -> Vec<Violation> {
57    ctx.nodes::<ast::Fn>()
58        .filter(|function| {
59            super::support::is_item_or_impl_fn(function)
60                && !ctx.is_in_test(function)
61                && super::support::is_fully_public(function)
62        })
63        .flat_map(|function| {
64            function
65                .param_list()
66                .into_iter()
67                .flat_map(|params| params.params())
68                .filter_map(|param| {
69                    let ty = param.ty()?;
70
71                    is_range_path(&ty).then(|| {
72                        ctx.violation(
73                            &ty,
74                            "public fn parameter typed `Range<T>` — accept `impl RangeBounds<T>` for caller flexibility",
75                        )
76                    })
77                })
78                .collect::<Vec<_>>()
79        })
80        .collect()
81}
82
83fn is_range_path(ty: &ast::Type) -> bool {
84    let ast::Type::PathType(path) = ty else {
85        return false;
86    };
87    let Some(path) = path.path() else {
88        return false;
89    };
90
91    super::support::path_matches(path.clone(), &["Range"])
92        || super::support::path_matches(path, &["std", "ops", "Range"])
93}
94
95crate::tidy_ast_test!(check_range_over_rangebounds, {
96    crate::example_tests!(EXAMPLES, check_range_over_rangebounds);
97});