Skip to main content

wowlab_tidy/languages/rust/rules/style/
conversion_self_convention.rs

1use 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});