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