]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/misc.rs
Auto merge of #9148 - arieluy:then_some_unwrap_or, r=Jarcho
[rust.git] / 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, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt,
9     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::Ref | BindingAnnotation::RefMut, ..) = 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(an, .., name, None) = local.pat.kind;
166             if let Some(init) = local.init;
167             if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut;
168             then {
169                 // use the macro callsite when the init span (but not the whole local span)
170                 // comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];`
171                 let sugg_init = if init.span.from_expansion() && !local.span.from_expansion() {
172                     Sugg::hir_with_macro_callsite(cx, init, "..")
173                 } else {
174                     Sugg::hir(cx, init, "..")
175                 };
176                 let (mutopt, initref) = if an == BindingAnnotation::RefMut {
177                     ("mut ", sugg_init.mut_addr())
178                 } else {
179                     ("", sugg_init.addr())
180                 };
181                 let tyopt = if let Some(ty) = local.ty {
182                     format!(": &{mutopt}{ty}", mutopt=mutopt, ty=snippet(cx, ty.span, ".."))
183                 } else {
184                     String::new()
185                 };
186                 span_lint_hir_and_then(
187                     cx,
188                     TOPLEVEL_REF_ARG,
189                     init.hir_id,
190                     local.pat.span,
191                     "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead",
192                     |diag| {
193                         diag.span_suggestion(
194                             stmt.span,
195                             "try",
196                             format!(
197                                 "let {name}{tyopt} = {initref};",
198                                 name=snippet(cx, name.span, ".."),
199                                 tyopt=tyopt,
200                                 initref=initref,
201                             ),
202                             Applicability::MachineApplicable,
203                         );
204                     }
205                 );
206             }
207         };
208         if_chain! {
209             if let StmtKind::Semi(expr) = stmt.kind;
210             if let ExprKind::Binary(ref binop, a, b) = expr.kind;
211             if binop.node == BinOpKind::And || binop.node == BinOpKind::Or;
212             if let Some(sugg) = Sugg::hir_opt(cx, a);
213             then {
214                 span_lint_hir_and_then(
215                     cx,
216                     SHORT_CIRCUIT_STATEMENT,
217                     expr.hir_id,
218                     stmt.span,
219                     "boolean short circuit operator in statement may be clearer using an explicit test",
220                     |diag| {
221                         let sugg = if binop.node == BinOpKind::Or { !sugg } else { sugg };
222                         diag.span_suggestion(
223                             stmt.span,
224                             "replace it with",
225                             format!(
226                                 "if {} {{ {}; }}",
227                                 sugg,
228                                 &snippet(cx, b.span, ".."),
229                             ),
230                             Applicability::MachineApplicable, // snippet
231                         );
232                     });
233             }
234         };
235     }
236
237     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
238         if let ExprKind::Cast(e, ty) = expr.kind {
239             check_cast(cx, expr.span, e, ty);
240             return;
241         }
242         if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
243             // Don't lint things expanded by #[derive(...)], etc or `await` desugaring
244             return;
245         }
246         let sym;
247         let binding = match expr.kind {
248             ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => {
249                 let binding = last_path_segment(qpath).ident.as_str();
250                 if binding.starts_with('_') &&
251                     !binding.starts_with("__") &&
252                     binding != "_result" && // FIXME: #944
253                     is_used(cx, expr) &&
254                     // don't lint if the declaration is in a macro
255                     non_macro_local(cx, cx.qpath_res(qpath, expr.hir_id))
256                 {
257                     Some(binding)
258                 } else {
259                     None
260                 }
261             },
262             ExprKind::Field(_, ident) => {
263                 sym = ident.name;
264                 let name = sym.as_str();
265                 if name.starts_with('_') && !name.starts_with("__") {
266                     Some(name)
267                 } else {
268                     None
269                 }
270             },
271             _ => None,
272         };
273         if let Some(binding) = binding {
274             span_lint(
275                 cx,
276                 USED_UNDERSCORE_BINDING,
277                 expr.span,
278                 &format!(
279                     "used binding `{}` which is prefixed with an underscore. A leading \
280                      underscore signals that a binding will not be used",
281                     binding
282                 ),
283             );
284         }
285     }
286 }
287
288 /// Heuristic to see if an expression is used. Should be compatible with
289 /// `unused_variables`'s idea
290 /// of what it means for an expression to be "used".
291 fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
292     get_parent_expr(cx, expr).map_or(true, |parent| match parent.kind {
293         ExprKind::Assign(_, rhs, _) | ExprKind::AssignOp(_, _, rhs) => SpanlessEq::new(cx).eq_expr(rhs, expr),
294         _ => is_used(cx, parent),
295     })
296 }
297
298 /// Tests whether an expression is in a macro expansion (e.g., something
299 /// generated by `#[derive(...)]` or the like).
300 fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
301     use rustc_span::hygiene::MacroKind;
302     if expr.span.from_expansion() {
303         let data = expr.span.ctxt().outer_expn_data();
304         matches!(data.kind, ExpnKind::Macro(MacroKind::Attr | MacroKind::Derive, _))
305     } else {
306         false
307     }
308 }
309
310 /// Tests whether `res` is a variable defined outside a macro.
311 fn non_macro_local(cx: &LateContext<'_>, res: def::Res) -> bool {
312     if let def::Res::Local(id) = res {
313         !cx.tcx.hir().span(id).from_expansion()
314     } else {
315         false
316     }
317 }
318
319 fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) {
320     if_chain! {
321         if let TyKind::Ptr(ref mut_ty) = ty.kind;
322         if let ExprKind::Lit(ref lit) = e.kind;
323         if let LitKind::Int(0, _) = lit.node;
324         if !in_constant(cx, e.hir_id);
325         then {
326             let (msg, sugg_fn) = match mut_ty.mutbl {
327                 Mutability::Mut => ("`0 as *mut _` detected", "std::ptr::null_mut"),
328                 Mutability::Not => ("`0 as *const _` detected", "std::ptr::null"),
329             };
330
331             let (sugg, appl) = if let TyKind::Infer = mut_ty.ty.kind {
332                 (format!("{}()", sugg_fn), Applicability::MachineApplicable)
333             } else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) {
334                 (format!("{}::<{}>()", sugg_fn, mut_ty_snip), Applicability::MachineApplicable)
335             } else {
336                 // `MaybeIncorrect` as type inference may not work with the suggested code
337                 (format!("{}()", sugg_fn), Applicability::MaybeIncorrect)
338             };
339             span_lint_and_sugg(cx, ZERO_PTR, span, msg, "try", sugg, appl);
340         }
341     }
342 }