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}
;
13 declare_clippy_lint
! {
14 /// **What it does:** Checks for explicit `deref()` or `deref_mut()` method calls.
16 /// **Why is this bad?** Dereferencing by `&*x` or `&mut *x` is clearer and more concise,
17 /// when not part of a method chain.
21 /// use std::ops::Deref;
22 /// let a: &mut String = &mut String::from("foo");
23 /// let b: &str = a.deref();
25 /// Could be written as:
27 /// let a: &mut String = &mut String::from("foo");
31 /// This lint excludes
33 /// let _ = d.unwrap().deref();
35 pub EXPLICIT_DEREF_METHODS
,
37 "Explicit use of deref or deref_mut method while not in a method chain."
40 impl_lint_pass
!(Dereferencing
=> [
41 EXPLICIT_DEREF_METHODS
,
45 pub struct Dereferencing
{
46 state
: Option
<(State
, StateData
)>,
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
>,
55 /// Span of the top level expression
57 /// The required mutability
58 target_mut
: Mutability
,
62 // Any number of deref method calls.
64 // The number of calls in a sequence which changed the referenced type
65 ty_changed_count
: usize,
70 // A reference operation considered by this lint pass
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() {
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
);
92 let typeck
= cx
.typeck_results();
93 let (kind
, sub_expr
) = if let Some(x
) = try_parse_ref_op(cx
.tcx
, typeck
, expr
) {
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
);
103 match (self.state
.take(), kind
) {
105 let parent
= get_parent_node(cx
.tcx
, expr
.hir_id
);
106 let expr_ty
= typeck
.expr_ty(expr
);
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
) =>
115 ty_changed_count
: if deref_method_same_type(expr_ty
, typeck
.expr_ty(sub_expr
)) {
120 is_final_ufcs
: matches
!(expr
.kind
, ExprKind
::Call(..)),
131 (Some((State
::DerefMethod { ty_changed_count, .. }
, data
)), RefOp
::Method(_
)) => {
134 ty_changed_count
: if deref_method_same_type(typeck
.expr_ty(expr
), typeck
.expr_ty(sub_expr
)) {
139 is_final_ufcs
: matches
!(expr
.kind
, ExprKind
::Call(..)),
145 (Some((state
, data
)), _
) => report(cx
, expr
, state
, data
),
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
),
159 kind
: ExprKind
::Path(path
),
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
));
168 ExprKind
::AddrOf(BorrowKind
::Ref
, _
, sub_expr
) => return Some((RefOp
::AddrOf
, sub_expr
)),
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
))
180 // Checks whether the type for a deref call actually changed the type, not just the mutability of
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
),
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
193 // Checks whether the parent node is a suitable context for switching from a deref method to the
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
,
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,
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,
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,
219 | ExprKind
::ConstBlock(..)
222 | ExprKind
::MethodCall(..)
224 | ExprKind
::Binary(..)
225 | ExprKind
::Unary(..)
229 | ExprKind
::DropTemps(..)
232 | ExprKind
::Match(..)
233 | ExprKind
::Closure(..)
234 | ExprKind
::Block(..)
235 | ExprKind
::Assign(..)
236 | ExprKind
::AssignOp(..)
238 | ExprKind
::AddrOf(..)
239 | ExprKind
::Break(..)
240 | ExprKind
::Continue(..)
242 | ExprKind
::InlineAsm(..)
243 | ExprKind
::LlvmInlineAsm(..)
244 | ExprKind
::Struct(..)
245 | ExprKind
::Repeat(..)
246 | ExprKind
::Yield(..) => true,
250 #[allow(clippy::needless_pass_by_value)]
251 fn report(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>, state
: State
, data
: StateData
) {
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)
266 "*".repeat(ty_changed_count
)
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
)) {
275 } else if data
.target_mut
== Mutability
::Mut
{
281 let expr_str
= if !expr_is_macro_call
&& is_final_ufcs
&& expr
.precedence().order() < PREC_PREFIX
{
282 format
!("({})", expr_str
)
284 expr_str
.into_owned()
289 EXPLICIT_DEREF_METHODS
,
291 match data
.target_mut
{
292 Mutability
::Not
=> "explicit `deref` method call",
293 Mutability
::Mut
=> "explicit `deref_mut` method call",
296 format
!("{}{}{}", addr_of_str
, deref_str
, expr_str
),