]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/dereference.rs
Auto merge of #6828 - mgacek8:issue_6758_enhance_wrong_self_convention, r=flip1995
[rust.git] / clippy_lints / src / dereference.rs
1 use crate::utils::{get_parent_node, in_macro, is_allowed, snippet_with_context, span_lint_and_sugg};
2 use clippy_utils::ty::peel_mid_ty_refs;
3 use rustc_ast::util::parser::PREC_PREFIX;
4 use rustc_errors::Applicability;
5 use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, MatchSource, Mutability, Node, UnOp};
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_middle::ty::{self, Ty, TyCtxt, TyS, TypeckResults};
8 use rustc_session::{declare_tool_lint, impl_lint_pass};
9 use rustc_span::{symbol::sym, Span};
10
11 declare_clippy_lint! {
12     /// **What it does:** Checks for explicit `deref()` or `deref_mut()` method calls.
13     ///
14     /// **Why is this bad?** Dereferencing by `&*x` or `&mut *x` is clearer and more concise,
15     /// when not part of a method chain.
16     ///
17     /// **Example:**
18     /// ```rust
19     /// use std::ops::Deref;
20     /// let a: &mut String = &mut String::from("foo");
21     /// let b: &str = a.deref();
22     /// ```
23     /// Could be written as:
24     /// ```rust
25     /// let a: &mut String = &mut String::from("foo");
26     /// let b = &*a;
27     /// ```
28     ///
29     /// This lint excludes
30     /// ```rust,ignore
31     /// let _ = d.unwrap().deref();
32     /// ```
33     pub EXPLICIT_DEREF_METHODS,
34     pedantic,
35     "Explicit use of deref or deref_mut method while not in a method chain."
36 }
37
38 impl_lint_pass!(Dereferencing => [
39     EXPLICIT_DEREF_METHODS,
40 ]);
41
42 #[derive(Default)]
43 pub struct Dereferencing {
44     state: Option<(State, StateData)>,
45
46     // While parsing a `deref` method call in ufcs form, the path to the function is itself an
47     // expression. This is to store the id of that expression so it can be skipped when
48     // `check_expr` is called for it.
49     skip_expr: Option<HirId>,
50 }
51
52 struct StateData {
53     /// Span of the top level expression
54     span: Span,
55     /// The required mutability
56     target_mut: Mutability,
57 }
58
59 enum State {
60     // Any number of deref method calls.
61     DerefMethod {
62         // The number of calls in a sequence which changed the referenced type
63         ty_changed_count: usize,
64         is_final_ufcs: bool,
65     },
66 }
67
68 // A reference operation considered by this lint pass
69 enum RefOp {
70     Method(Mutability),
71     Deref,
72     AddrOf,
73 }
74
75 impl<'tcx> LateLintPass<'tcx> for Dereferencing {
76     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
77         // Skip path expressions from deref calls. e.g. `Deref::deref(e)`
78         if Some(expr.hir_id) == self.skip_expr.take() {
79             return;
80         }
81
82         // Stop processing sub expressions when a macro call is seen
83         if in_macro(expr.span) {
84             if let Some((state, data)) = self.state.take() {
85                 report(cx, expr, state, data);
86             }
87             return;
88         }
89
90         let typeck = cx.typeck_results();
91         let (kind, sub_expr) = if let Some(x) = try_parse_ref_op(cx.tcx, typeck, expr) {
92             x
93         } else {
94             // The whole chain of reference operations has been seen
95             if let Some((state, data)) = self.state.take() {
96                 report(cx, expr, state, data);
97             }
98             return;
99         };
100
101         match (self.state.take(), kind) {
102             (None, kind) => {
103                 let parent = get_parent_node(cx.tcx, expr.hir_id);
104                 let expr_ty = typeck.expr_ty(expr);
105
106                 match kind {
107                     RefOp::Method(target_mut)
108                         if !is_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id)
109                             && is_linted_explicit_deref_position(parent, expr.hir_id, expr.span) =>
110                     {
111                         self.state = Some((
112                             State::DerefMethod {
113                                 ty_changed_count: if deref_method_same_type(expr_ty, typeck.expr_ty(sub_expr)) {
114                                     0
115                                 } else {
116                                     1
117                                 },
118                                 is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)),
119                             },
120                             StateData {
121                                 span: expr.span,
122                                 target_mut,
123                             },
124                         ));
125                     }
126                     _ => (),
127                 }
128             },
129             (Some((State::DerefMethod { ty_changed_count, .. }, data)), RefOp::Method(_)) => {
130                 self.state = Some((
131                     State::DerefMethod {
132                         ty_changed_count: if deref_method_same_type(typeck.expr_ty(expr), typeck.expr_ty(sub_expr)) {
133                             ty_changed_count
134                         } else {
135                             ty_changed_count + 1
136                         },
137                         is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)),
138                     },
139                     data,
140                 ));
141             },
142
143             (Some((state, data)), _) => report(cx, expr, state, data),
144         }
145     }
146 }
147
148 fn try_parse_ref_op(
149     tcx: TyCtxt<'tcx>,
150     typeck: &'tcx TypeckResults<'_>,
151     expr: &'tcx Expr<'_>,
152 ) -> Option<(RefOp, &'tcx Expr<'tcx>)> {
153     let (def_id, arg) = match expr.kind {
154         ExprKind::MethodCall(_, _, [arg], _) => (typeck.type_dependent_def_id(expr.hir_id)?, arg),
155         ExprKind::Call(
156             Expr {
157                 kind: ExprKind::Path(path),
158                 hir_id,
159                 ..
160             },
161             [arg],
162         ) => (typeck.qpath_res(path, *hir_id).opt_def_id()?, arg),
163         ExprKind::Unary(UnOp::Deref, sub_expr) if !typeck.expr_ty(sub_expr).is_unsafe_ptr() => {
164             return Some((RefOp::Deref, sub_expr));
165         },
166         ExprKind::AddrOf(BorrowKind::Ref, _, sub_expr) => return Some((RefOp::AddrOf, sub_expr)),
167         _ => return None,
168     };
169     if tcx.is_diagnostic_item(sym::deref_method, def_id) {
170         Some((RefOp::Method(Mutability::Not), arg))
171     } else if tcx.trait_of_item(def_id)? == tcx.lang_items().deref_mut_trait()? {
172         Some((RefOp::Method(Mutability::Mut), arg))
173     } else {
174         None
175     }
176 }
177
178 // Checks whether the type for a deref call actually changed the type, not just the mutability of
179 // the reference.
180 fn deref_method_same_type(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
181     match (result_ty.kind(), arg_ty.kind()) {
182         (ty::Ref(_, result_ty, _), ty::Ref(_, arg_ty, _)) => TyS::same_type(result_ty, arg_ty),
183
184         // The result type for a deref method is always a reference
185         // Not matching the previous pattern means the argument type is not a reference
186         // This means that the type did change
187         _ => false,
188     }
189 }
190
191 // Checks whether the parent node is a suitable context for switching from a deref method to the
192 // deref operator.
193 fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId, child_span: Span) -> bool {
194     let parent = match parent {
195         Some(Node::Expr(e)) if e.span.ctxt() == child_span.ctxt() => e,
196         _ => return true,
197     };
198     match parent.kind {
199         // Leave deref calls in the middle of a method chain.
200         // e.g. x.deref().foo()
201         ExprKind::MethodCall(_, _, [self_arg, ..], _) if self_arg.hir_id == child_id => false,
202
203         // Leave deref calls resulting in a called function
204         // e.g. (x.deref())()
205         ExprKind::Call(func_expr, _) if func_expr.hir_id == child_id => false,
206
207         // Makes an ugly suggestion
208         // e.g. *x.deref() => *&*x
209         ExprKind::Unary(UnOp::Deref, _)
210         // Postfix expressions would require parens
211         | ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
212         | ExprKind::Field(..)
213         | ExprKind::Index(..)
214         | ExprKind::Err => false,
215
216         ExprKind::Box(..)
217         | ExprKind::ConstBlock(..)
218         | ExprKind::Array(_)
219         | ExprKind::Call(..)
220         | ExprKind::MethodCall(..)
221         | ExprKind::Tup(..)
222         | ExprKind::Binary(..)
223         | ExprKind::Unary(..)
224         | ExprKind::Lit(..)
225         | ExprKind::Cast(..)
226         | ExprKind::Type(..)
227         | ExprKind::DropTemps(..)
228         | ExprKind::If(..)
229         | ExprKind::Loop(..)
230         | ExprKind::Match(..)
231         | ExprKind::Closure(..)
232         | ExprKind::Block(..)
233         | ExprKind::Assign(..)
234         | ExprKind::AssignOp(..)
235         | ExprKind::Path(..)
236         | ExprKind::AddrOf(..)
237         | ExprKind::Break(..)
238         | ExprKind::Continue(..)
239         | ExprKind::Ret(..)
240         | ExprKind::InlineAsm(..)
241         | ExprKind::LlvmInlineAsm(..)
242         | ExprKind::Struct(..)
243         | ExprKind::Repeat(..)
244         | ExprKind::Yield(..) => true,
245     }
246 }
247
248 #[allow(clippy::needless_pass_by_value)]
249 fn report(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) {
250     match state {
251         State::DerefMethod {
252             ty_changed_count,
253             is_final_ufcs,
254         } => {
255             let mut app = Applicability::MachineApplicable;
256             let (expr_str, expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
257             let ty = cx.typeck_results().expr_ty(expr);
258             let (_, ref_count) = peel_mid_ty_refs(ty);
259             let deref_str = if ty_changed_count >= ref_count && ref_count != 0 {
260                 // a deref call changing &T -> &U requires two deref operators the first time
261                 // this occurs. One to remove the reference, a second to call the deref impl.
262                 "*".repeat(ty_changed_count + 1)
263             } else {
264                 "*".repeat(ty_changed_count)
265             };
266             let addr_of_str = if ty_changed_count < ref_count {
267                 // Check if a reborrow from &mut T -> &T is required.
268                 if data.target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
269                     "&*"
270                 } else {
271                     ""
272                 }
273             } else if data.target_mut == Mutability::Mut {
274                 "&mut "
275             } else {
276                 "&"
277             };
278
279             let expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence().order() < PREC_PREFIX {
280                 format!("({})", expr_str)
281             } else {
282                 expr_str.into_owned()
283             };
284
285             span_lint_and_sugg(
286                 cx,
287                 EXPLICIT_DEREF_METHODS,
288                 data.span,
289                 match data.target_mut {
290                     Mutability::Not => "explicit `deref` method call",
291                     Mutability::Mut => "explicit `deref_mut` method call",
292                 },
293                 "try this",
294                 format!("{}{}{}", addr_of_str, deref_str, expr_str),
295                 app,
296             );
297         },
298     }
299 }