]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/misc.rs
ea245edd77040688400d3810e3e10b668fcac282
[rust.git] / src / tools / clippy / clippy_lints / src / misc.rs
1 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then};
2 use clippy_utils::source::{snippet, snippet_opt};
3 use if_chain::if_chain;
4 use rustc_ast::ast::LitKind;
5 use rustc_errors::Applicability;
6 use rustc_hir::intravisit::FnKind;
7 use rustc_hir::{
8     self as hir, def, BinOpKind, BindingAnnotation, Body, ByRef, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind,
9     Stmt, StmtKind, TyKind,
10 };
11 use rustc_lint::{LateContext, LateLintPass};
12 use rustc_middle::lint::in_external_macro;
13 use rustc_session::{declare_lint_pass, declare_tool_lint};
14 use rustc_span::hygiene::DesugaringKind;
15 use rustc_span::source_map::{ExpnKind, Span};
16
17 use clippy_utils::sugg::Sugg;
18 use clippy_utils::{get_parent_expr, in_constant, iter_input_pats, last_path_segment, SpanlessEq};
19
20 declare_clippy_lint! {
21     /// ### What it does
22     /// Checks for function arguments and let bindings denoted as
23     /// `ref`.
24     ///
25     /// ### Why is this bad?
26     /// The `ref` declaration makes the function take an owned
27     /// value, but turns the argument into a reference (which means that the value
28     /// is destroyed when exiting the function). This adds not much value: either
29     /// take a reference type, or take an owned value and create references in the
30     /// body.
31     ///
32     /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The
33     /// type of `x` is more obvious with the former.
34     ///
35     /// ### Known problems
36     /// If the argument is dereferenced within the function,
37     /// removing the `ref` will lead to errors. This can be fixed by removing the
38     /// dereferences, e.g., changing `*x` to `x` within the function.
39     ///
40     /// ### Example
41     /// ```rust
42     /// fn foo(ref _x: u8) {}
43     /// ```
44     ///
45     /// Use instead:
46     /// ```rust
47     /// fn foo(_x: &u8) {}
48     /// ```
49     #[clippy::version = "pre 1.29.0"]
50     pub TOPLEVEL_REF_ARG,
51     style,
52     "an entire binding declared as `ref`, in a function argument or a `let` statement"
53 }
54 declare_clippy_lint! {
55     /// ### What it does
56     /// Checks for the use of bindings with a single leading
57     /// underscore.
58     ///
59     /// ### Why is this bad?
60     /// A single leading underscore is usually used to indicate
61     /// that a binding will not be used. Using such a binding breaks this
62     /// expectation.
63     ///
64     /// ### Known problems
65     /// The lint does not work properly with desugaring and
66     /// macro, it has been allowed in the mean time.
67     ///
68     /// ### Example
69     /// ```rust
70     /// let _x = 0;
71     /// let y = _x + 1; // Here we are using `_x`, even though it has a leading
72     ///                 // underscore. We should rename `_x` to `x`
73     /// ```
74     #[clippy::version = "pre 1.29.0"]
75     pub USED_UNDERSCORE_BINDING,
76     pedantic,
77     "using a binding which is prefixed with an underscore"
78 }
79
80 declare_clippy_lint! {
81     /// ### What it does
82     /// Checks for the use of short circuit boolean conditions as
83     /// a
84     /// statement.
85     ///
86     /// ### Why is this bad?
87     /// Using a short circuit boolean condition as a statement
88     /// may hide the fact that the second part is executed or not depending on the
89     /// outcome of the first part.
90     ///
91     /// ### Example
92     /// ```rust,ignore
93     /// f() && g(); // We should write `if f() { g(); }`.
94     /// ```
95     #[clippy::version = "pre 1.29.0"]
96     pub SHORT_CIRCUIT_STATEMENT,
97     complexity,
98     "using a short circuit boolean condition as a statement"
99 }
100
101 declare_clippy_lint! {
102     /// ### What it does
103     /// Catch casts from `0` to some pointer type
104     ///
105     /// ### Why is this bad?
106     /// This generally means `null` and is better expressed as
107     /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}.
108     ///
109     /// ### Example
110     /// ```rust
111     /// let a = 0 as *const u32;
112     /// ```
113     ///
114     /// Use instead:
115     /// ```rust
116     /// let a = std::ptr::null::<u32>();
117     /// ```
118     #[clippy::version = "pre 1.29.0"]
119     pub ZERO_PTR,
120     style,
121     "using `0 as *{const, mut} T`"
122 }
123
124 declare_lint_pass!(MiscLints => [
125     TOPLEVEL_REF_ARG,
126     USED_UNDERSCORE_BINDING,
127     SHORT_CIRCUIT_STATEMENT,
128     ZERO_PTR,
129 ]);
130
131 impl<'tcx> LateLintPass<'tcx> for MiscLints {
132     fn check_fn(
133         &mut self,
134         cx: &LateContext<'tcx>,
135         k: FnKind<'tcx>,
136         decl: &'tcx FnDecl<'_>,
137         body: &'tcx Body<'_>,
138         span: Span,
139         _: HirId,
140     ) {
141         if let FnKind::Closure = k {
142             // Does not apply to closures
143             return;
144         }
145         if in_external_macro(cx.tcx.sess, span) {
146             return;
147         }
148         for arg in iter_input_pats(decl, body) {
149             if let PatKind::Binding(BindingAnnotation(ByRef::Yes, _), ..) = arg.pat.kind {
150                 span_lint(
151                     cx,
152                     TOPLEVEL_REF_ARG,
153                     arg.pat.span,
154                     "`ref` directly on a function argument is ignored. \
155                     Consider using a reference type instead",
156                 );
157             }
158         }
159     }
160
161     fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
162         if_chain! {
163             if !in_external_macro(cx.tcx.sess, stmt.span);
164             if let StmtKind::Local(local) = stmt.kind;
165             if let PatKind::Binding(BindingAnnotation(ByRef::Yes, mutabl), .., name, None) = local.pat.kind;
166             if let Some(init) = local.init;
167             then {
168                 // use the macro callsite when the init span (but not the whole local span)
169                 // comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];`
170                 let sugg_init = if init.span.from_expansion() && !local.span.from_expansion() {
171                     Sugg::hir_with_macro_callsite(cx, init, "..")
172                 } else {
173                     Sugg::hir(cx, init, "..")
174                 };
175                 let (mutopt, initref) = if mutabl == Mutability::Mut {
176                     ("mut ", sugg_init.mut_addr())
177                 } else {
178                     ("", sugg_init.addr())
179                 };
180                 let tyopt = if let Some(ty) = local.ty {
181                     format!(": &{mutopt}{ty}", mutopt=mutopt, ty=snippet(cx, ty.span, ".."))
182                 } else {
183                     String::new()
184                 };
185                 span_lint_hir_and_then(
186                     cx,
187                     TOPLEVEL_REF_ARG,
188                     init.hir_id,
189                     local.pat.span,
190                     "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead",
191                     |diag| {
192                         diag.span_suggestion(
193                             stmt.span,
194                             "try",
195                             format!(
196                                 "let {name}{tyopt} = {initref};",
197                                 name=snippet(cx, name.span, ".."),
198                                 tyopt=tyopt,
199                                 initref=initref,
200                             ),
201                             Applicability::MachineApplicable,
202                         );
203                     }
204                 );
205             }
206         };
207         if_chain! {
208             if let StmtKind::Semi(expr) = stmt.kind;
209             if let ExprKind::Binary(ref binop, a, b) = expr.kind;
210             if binop.node == BinOpKind::And || binop.node == BinOpKind::Or;
211             if let Some(sugg) = Sugg::hir_opt(cx, a);
212             then {
213                 span_lint_hir_and_then(
214                     cx,
215                     SHORT_CIRCUIT_STATEMENT,
216                     expr.hir_id,
217                     stmt.span,
218                     "boolean short circuit operator in statement may be clearer using an explicit test",
219                     |diag| {
220                         let sugg = if binop.node == BinOpKind::Or { !sugg } else { sugg };
221                         diag.span_suggestion(
222                             stmt.span,
223                             "replace it with",
224                             format!(
225                                 "if {} {{ {}; }}",
226                                 sugg,
227                                 &snippet(cx, b.span, ".."),
228                             ),
229                             Applicability::MachineApplicable, // snippet
230                         );
231                     });
232             }
233         };
234     }
235
236     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
237         if let ExprKind::Cast(e, ty) = expr.kind {
238             check_cast(cx, expr.span, e, ty);
239             return;
240         }
241         if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
242             // Don't lint things expanded by #[derive(...)], etc or `await` desugaring
243             return;
244         }
245         let sym;
246         let binding = match expr.kind {
247             ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => {
248                 let binding = last_path_segment(qpath).ident.as_str();
249                 if binding.starts_with('_') &&
250                     !binding.starts_with("__") &&
251                     binding != "_result" && // FIXME: #944
252                     is_used(cx, expr) &&
253                     // don't lint if the declaration is in a macro
254                     non_macro_local(cx, cx.qpath_res(qpath, expr.hir_id))
255                 {
256                     Some(binding)
257                 } else {
258                     None
259                 }
260             },
261             ExprKind::Field(_, ident) => {
262                 sym = ident.name;
263                 let name = sym.as_str();
264                 if name.starts_with('_') && !name.starts_with("__") {
265                     Some(name)
266                 } else {
267                     None
268                 }
269             },
270             _ => None,
271         };
272         if let Some(binding) = binding {
273             span_lint(
274                 cx,
275                 USED_UNDERSCORE_BINDING,
276                 expr.span,
277                 &format!(
278                     "used binding `{}` which is prefixed with an underscore. A leading \
279                      underscore signals that a binding will not be used",
280                     binding
281                 ),
282             );
283         }
284     }
285 }
286
287 /// Heuristic to see if an expression is used. Should be compatible with
288 /// `unused_variables`'s idea
289 /// of what it means for an expression to be "used".
290 fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
291     get_parent_expr(cx, expr).map_or(true, |parent| match parent.kind {
292         ExprKind::Assign(_, rhs, _) | ExprKind::AssignOp(_, _, rhs) => SpanlessEq::new(cx).eq_expr(rhs, expr),
293         _ => is_used(cx, parent),
294     })
295 }
296
297 /// Tests whether an expression is in a macro expansion (e.g., something
298 /// generated by `#[derive(...)]` or the like).
299 fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
300     use rustc_span::hygiene::MacroKind;
301     if expr.span.from_expansion() {
302         let data = expr.span.ctxt().outer_expn_data();
303         matches!(data.kind, ExpnKind::Macro(MacroKind::Attr | MacroKind::Derive, _))
304     } else {
305         false
306     }
307 }
308
309 /// Tests whether `res` is a variable defined outside a macro.
310 fn non_macro_local(cx: &LateContext<'_>, res: def::Res) -> bool {
311     if let def::Res::Local(id) = res {
312         !cx.tcx.hir().span(id).from_expansion()
313     } else {
314         false
315     }
316 }
317
318 fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) {
319     if_chain! {
320         if let TyKind::Ptr(ref mut_ty) = ty.kind;
321         if let ExprKind::Lit(ref lit) = e.kind;
322         if let LitKind::Int(0, _) = lit.node;
323         if !in_constant(cx, e.hir_id);
324         then {
325             let (msg, sugg_fn) = match mut_ty.mutbl {
326                 Mutability::Mut => ("`0 as *mut _` detected", "std::ptr::null_mut"),
327                 Mutability::Not => ("`0 as *const _` detected", "std::ptr::null"),
328             };
329
330             let (sugg, appl) = if let TyKind::Infer = mut_ty.ty.kind {
331                 (format!("{}()", sugg_fn), Applicability::MachineApplicable)
332             } else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) {
333                 (format!("{}::<{}>()", sugg_fn, mut_ty_snip), Applicability::MachineApplicable)
334             } else {
335                 // `MaybeIncorrect` as type inference may not work with the suggested code
336                 (format!("{}()", sugg_fn), Applicability::MaybeIncorrect)
337             };
338             span_lint_and_sugg(cx, ZERO_PTR, span, msg, "try", sugg, appl);
339         }
340     }
341 }