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