wowlab_tidy/languages/rust/rules/interop/
owned_ref_param.rs1use ra_ap_syntax::ast;
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "&String parameter",
9 code: "fn f(x: &String) {}",
10 pass: false,
11 },
12 Example {
13 label: "&PathBuf parameter",
14 code: "fn f(x: &PathBuf) {}",
15 pass: false,
16 },
17 Example {
18 label: "&Vec parameter",
19 code: "fn f(x: &Vec<u32>) {}",
20 pass: false,
21 },
22 Example {
23 label: "fully qualified &OsString parameter",
24 code: "fn f(x: &std::ffi::OsString) {}",
25 pass: false,
26 },
27 Example {
28 label: "&String in impl method",
29 code: "struct S;\nimpl S {\n fn f(&self, x: &String) {}\n}",
30 pass: false,
31 },
32 Example {
33 label: "&str parameter",
34 code: "fn f(x: &str) {}",
35 pass: true,
36 },
37 Example {
38 label: "&mut String needs the owned type",
39 code: "fn f(x: &mut String) {}",
40 pass: true,
41 },
42 Example {
43 label: "owned String parameter",
44 code: "fn f(x: String) {}",
45 pass: true,
46 },
47 Example {
48 label: "slice parameter",
49 code: "fn f(x: &[u32]) {}",
50 pass: true,
51 },
52 Example {
53 label: "&String in test module",
54 code: "#[cfg(test)]\nmod tests {\n fn f(x: &String) {}\n}",
55 pass: true,
56 },
57];
58
59crate::ast_rule!(
60 owned_ref_param,
61 "Flag fn parameters typed `&String`, `&PathBuf`, `&Vec<T>`, `&OsString`.",
62 "A shared reference to an owned container forces callers to materialize the owned type; borrowed forms accept more argument types for free (M-IMPL-ASREF).",
63 Medium,
64);
65
66const OWNED_REF_SUGGESTIONS: &[(&str, &str)] = &[
67 ("String", "`&str` or `impl AsRef<str>`"),
68 ("PathBuf", "`&Path` or `impl AsRef<Path>`"),
69 ("Vec", "`&[T]` or `impl AsRef<[T]>`"),
70 ("OsString", "`&OsStr` or `impl AsRef<OsStr>`"),
71];
72
73fn check_owned_ref_param(ctx: &AstCtx<'_>) -> Vec<Violation> {
74 ctx.nodes::<ast::Fn>()
75 .filter(|function| {
76 super::support::is_item_or_impl_fn(function) && !ctx.is_in_test(function)
77 })
78 .flat_map(|function| {
79 function
80 .param_list()
81 .into_iter()
82 .flat_map(|params| params.params())
83 .filter_map(|param| {
84 let ty = param.ty()?;
85
86 owned_ref_target(ty.clone()).map(|(name, suggestion)| {
87 ctx.violation(
88 &ty,
89 format!("parameter typed `&{name}` — accept {suggestion} instead"),
90 )
91 })
92 })
93 .collect::<Vec<_>>()
94 })
95 .collect()
96}
97
98fn owned_ref_target(ty: ast::Type) -> Option<(&'static str, &'static str)> {
99 let ast::Type::RefType(reference) = ty else {
100 return None;
101 };
102
103 if reference.mut_token().is_some() {
104 return None;
105 }
106
107 let ast::Type::PathType(path) = reference.ty()? else {
108 return None;
109 };
110 let last = path.path()?.segment()?.name_ref()?;
111
112 OWNED_REF_SUGGESTIONS
113 .iter()
114 .copied()
115 .find(|(name, _)| last.text() == *name)
116}
117
118crate::tidy_ast_test!(check_owned_ref_param, {
119 crate::example_tests!(EXAMPLES, check_owned_ref_param);
120});