wowlab_tidy/languages/rust/rules/style/
conversion_self_convention.rs1use ra_ap_syntax::ast::{self, HasName};
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "as_ takes self by value",
9 code: "struct S;\nimpl S {\n fn as_str(self) {}\n}",
10 pass: false,
11 },
12 Example {
13 label: "to_ takes self by value",
14 code: "struct S;\nimpl S {\n fn to_vec(self) {}\n}",
15 pass: false,
16 },
17 Example {
18 label: "into_ borrows self",
19 code: "struct S;\nimpl S {\n fn into_parts(&self) {}\n}",
20 pass: false,
21 },
22 Example {
23 label: "into_ borrows self mutably",
24 code: "struct S;\nimpl S {\n fn into_inner(&mut self) {}\n}",
25 pass: false,
26 },
27 Example {
28 label: "wrong convention on trait method",
29 code: "trait T {\n fn as_bytes(self);\n}",
30 pass: false,
31 },
32 Example {
33 label: "as_ borrows",
34 code: "struct S;\nimpl S {\n fn as_str(&self) {}\n}",
35 pass: true,
36 },
37 Example {
38 label: "as_ borrows mutably",
39 code: "struct S;\nimpl S {\n fn as_mut_slice(&mut self) {}\n}",
40 pass: true,
41 },
42 Example {
43 label: "to_ borrows",
44 code: "struct S;\nimpl S {\n fn to_vec(&self) {}\n}",
45 pass: true,
46 },
47 Example {
48 label: "into_ consumes",
49 code: "struct S;\nimpl S {\n fn into_parts(self) {}\n}",
50 pass: true,
51 },
52 Example {
53 label: "no receiver is exempt",
54 code: "struct S;\nimpl S {\n fn into_config() -> S { S }\n}",
55 pass: true,
56 },
57 Example {
58 label: "non-conversion method",
59 code: "struct S;\nimpl S {\n fn assemble(self) {}\n}",
60 pass: true,
61 },
62 Example {
63 label: "wrong convention in test module",
64 code: "#[cfg(test)]\nmod tests {\n struct S;\n impl S {\n fn as_str(self) {}\n }\n}",
65 pass: true,
66 },
67];
68
69crate::ast_rule!(
70 conversion_self_convention,
71 "Enforce C-CONV receivers: `as_`/`to_` methods borrow (`&self`), `into_` methods consume (`self`).",
72 "The `as_`/`to_`/`into_` prefixes promise a cost and ownership contract; a consuming `to_` or borrowing `into_` misleads every caller.",
73 Medium,
74);
75
76fn check_conversion_self_convention(ctx: &AstCtx<'_>) -> Vec<Violation> {
77 ctx.nodes::<ast::Fn>()
78 .filter(|function| !ctx.is_in_test(function))
79 .filter_map(|function| {
80 let receiver = function.param_list()?.self_param()?;
81
82 if receiver.colon_token().is_some() {
83 return None;
84 }
85
86 let name = function.name()?;
87 let name_text = name.text();
88 let borrows = receiver.amp_token().is_some();
89 let message = if (name_text.starts_with("as_") || name_text.starts_with("to_"))
90 && !borrows
91 {
92 format!(
93 "conversion `{name_text}` consumes `self` — `as_`/`to_` methods take `&self`; use `into_` for consuming conversions (C-CONV)"
94 )
95 } else if name_text.starts_with("into_") && borrows {
96 format!(
97 "conversion `{name_text}` borrows `self` — `into_` methods consume `self` by value (C-CONV)"
98 )
99 } else {
100 return None;
101 };
102
103 Some(ctx.violation(&name, message))
104 })
105 .collect()
106}
107
108crate::tidy_ast_test!(check_conversion_self_convention, {
109 crate::example_tests!(EXAMPLES, check_conversion_self_convention);
110});