]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/wrong_self_convention.rs
Merge commit '98e2b9f25b6db4b2680a3d388456d9f95cb28344' into clippyup
[rust.git] / clippy_lints / src / methods / wrong_self_convention.rs
1 use crate::methods::SelfKind;
2 use clippy_utils::diagnostics::span_lint_and_help;
3 use clippy_utils::ty::is_copy;
4 use rustc_lint::LateContext;
5 use rustc_middle::ty::TyS;
6 use rustc_span::source_map::Span;
7 use std::fmt;
8
9 use super::WRONG_PUB_SELF_CONVENTION;
10 use super::WRONG_SELF_CONVENTION;
11
12 #[rustfmt::skip]
13 const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [
14     (&[Convention::Eq("new")], &[SelfKind::No]),
15     (&[Convention::StartsWith("as_")], &[SelfKind::Ref, SelfKind::RefMut]),
16     (&[Convention::StartsWith("from_")], &[SelfKind::No]),
17     (&[Convention::StartsWith("into_")], &[SelfKind::Value]),
18     (&[Convention::StartsWith("is_")], &[SelfKind::Ref, SelfKind::No]),
19     (&[Convention::Eq("to_mut")], &[SelfKind::RefMut]),
20     (&[Convention::StartsWith("to_"), Convention::EndsWith("_mut")], &[SelfKind::RefMut]),
21
22     // Conversion using `to_` can use borrowed (non-Copy types) or owned (Copy types).
23     // Source: https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
24     (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(false), 
25     Convention::IsTraitItem(false)], &[SelfKind::Ref]),
26     (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(true), 
27     Convention::IsTraitItem(false), Convention::ImplementsTrait(false)], &[SelfKind::Value]),
28 ];
29
30 enum Convention {
31     Eq(&'static str),
32     StartsWith(&'static str),
33     EndsWith(&'static str),
34     NotEndsWith(&'static str),
35     IsSelfTypeCopy(bool),
36     ImplementsTrait(bool),
37     IsTraitItem(bool),
38 }
39
40 impl Convention {
41     #[must_use]
42     fn check<'tcx>(
43         &self,
44         cx: &LateContext<'tcx>,
45         self_ty: &'tcx TyS<'tcx>,
46         other: &str,
47         implements_trait: bool,
48         is_trait_item: bool,
49     ) -> bool {
50         match *self {
51             Self::Eq(this) => this == other,
52             Self::StartsWith(this) => other.starts_with(this) && this != other,
53             Self::EndsWith(this) => other.ends_with(this) && this != other,
54             Self::NotEndsWith(this) => !Self::EndsWith(this).check(cx, self_ty, other, implements_trait, is_trait_item),
55             Self::IsSelfTypeCopy(is_true) => is_true == is_copy(cx, self_ty),
56             Self::ImplementsTrait(is_true) => is_true == implements_trait,
57             Self::IsTraitItem(is_true) => is_true == is_trait_item,
58         }
59     }
60 }
61
62 impl fmt::Display for Convention {
63     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
64         match *self {
65             Self::Eq(this) => format!("`{}`", this).fmt(f),
66             Self::StartsWith(this) => format!("`{}*`", this).fmt(f),
67             Self::EndsWith(this) => format!("`*{}`", this).fmt(f),
68             Self::NotEndsWith(this) => format!("`~{}`", this).fmt(f),
69             Self::IsSelfTypeCopy(is_true) => {
70                 format!("`self` type is{} `Copy`", if is_true { "" } else { " not" }).fmt(f)
71             },
72             Self::ImplementsTrait(is_true) => {
73                 let (negation, s_suffix) = if is_true { ("", "s") } else { (" does not", "") };
74                 format!("method{} implement{} a trait", negation, s_suffix).fmt(f)
75             },
76             Self::IsTraitItem(is_true) => {
77                 let suffix = if is_true { " is" } else { " is not" };
78                 format!("method{} a trait item", suffix).fmt(f)
79             },
80         }
81     }
82 }
83
84 #[allow(clippy::too_many_arguments)]
85 pub(super) fn check<'tcx>(
86     cx: &LateContext<'tcx>,
87     item_name: &str,
88     is_pub: bool,
89     self_ty: &'tcx TyS<'tcx>,
90     first_arg_ty: &'tcx TyS<'tcx>,
91     first_arg_span: Span,
92     implements_trait: bool,
93     is_trait_item: bool,
94 ) {
95     let lint = if is_pub {
96         WRONG_PUB_SELF_CONVENTION
97     } else {
98         WRONG_SELF_CONVENTION
99     };
100     if let Some((conventions, self_kinds)) = &CONVENTIONS.iter().find(|(convs, _)| {
101         convs
102             .iter()
103             .all(|conv| conv.check(cx, self_ty, item_name, implements_trait, is_trait_item))
104     }) {
105         // don't lint if it implements a trait but not willing to check `Copy` types conventions (see #7032)
106         if implements_trait
107             && !conventions
108                 .iter()
109                 .any(|conv| matches!(conv, Convention::IsSelfTypeCopy(_)))
110         {
111             return;
112         }
113         if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) {
114             let suggestion = {
115                 if conventions.len() > 1 {
116                     // Don't mention `NotEndsWith` when there is also `StartsWith` convention present
117                     let cut_ends_with_conv = conventions.iter().any(|conv| matches!(conv, Convention::StartsWith(_)))
118                         && conventions
119                             .iter()
120                             .any(|conv| matches!(conv, Convention::NotEndsWith(_)));
121
122                     let s = conventions
123                         .iter()
124                         .filter_map(|conv| {
125                             if (cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_)))
126                                 || matches!(conv, Convention::ImplementsTrait(_))
127                                 || matches!(conv, Convention::IsTraitItem(_))
128                             {
129                                 None
130                             } else {
131                                 Some(conv.to_string())
132                             }
133                         })
134                         .collect::<Vec<_>>()
135                         .join(" and ");
136
137                     format!("methods with the following characteristics: ({})", &s)
138                 } else {
139                     format!("methods called {}", &conventions[0])
140                 }
141             };
142
143             span_lint_and_help(
144                 cx,
145                 lint,
146                 first_arg_span,
147                 &format!(
148                     "{} usually take {}",
149                     suggestion,
150                     &self_kinds
151                         .iter()
152                         .map(|k| k.description())
153                         .collect::<Vec<_>>()
154                         .join(" or ")
155                 ),
156                 None,
157                 "consider choosing a less ambiguous name",
158             );
159         }
160     }
161 }