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