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