wowlab_tidy/languages/rust/rules/interop/
range_over_rangebounds.rs1use 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});