1 use clippy_utils
::diagnostics
::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}
;
2 use clippy_utils
::source
::{snippet, snippet_opt}
;
3 use clippy_utils
::ty
::implements_trait
;
4 use if_chain
::if_chain
;
5 use rustc_ast
::ast
::LitKind
;
6 use rustc_errors
::Applicability
;
7 use rustc_hir
::intravisit
::FnKind
;
9 self as hir
, def
, BinOpKind
, BindingAnnotation
, Body
, Expr
, ExprKind
, FnDecl
, HirId
, Mutability
, PatKind
, Stmt
,
10 StmtKind
, TyKind
, UnOp
,
12 use rustc_lint
::{LateContext, LateLintPass}
;
13 use rustc_middle
::lint
::in_external_macro
;
14 use rustc_middle
::ty
::{self, Ty}
;
15 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
16 use rustc_span
::hygiene
::DesugaringKind
;
17 use rustc_span
::source_map
::{ExpnKind, Span}
;
18 use rustc_span
::symbol
::sym
;
20 use clippy_utils
::consts
::{constant, Constant}
;
21 use clippy_utils
::sugg
::Sugg
;
23 get_item_name
, get_parent_expr
, in_constant
, is_diag_trait_item
, is_integer_const
, iter_input_pats
,
24 last_path_segment
, match_any_def_paths
, path_def_id
, paths
, unsext
, SpanlessEq
,
27 declare_clippy_lint
! {
29 /// Checks for function arguments and let bindings denoted as
32 /// ### Why is this bad?
33 /// The `ref` declaration makes the function take an owned
34 /// value, but turns the argument into a reference (which means that the value
35 /// is destroyed when exiting the function). This adds not much value: either
36 /// take a reference type, or take an owned value and create references in the
39 /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The
40 /// type of `x` is more obvious with the former.
42 /// ### Known problems
43 /// If the argument is dereferenced within the function,
44 /// removing the `ref` will lead to errors. This can be fixed by removing the
45 /// dereferences, e.g., changing `*x` to `x` within the function.
50 /// fn foo(ref x: u8) -> bool {
55 /// fn foo(x: &u8) -> bool {
59 #[clippy::version = "pre 1.29.0"]
62 "an entire binding declared as `ref`, in a function argument or a `let` statement"
65 declare_clippy_lint
! {
67 /// Checks for comparisons to NaN.
69 /// ### Why is this bad?
70 /// NaN does not compare meaningfully to anything – not
71 /// even itself – so those comparisons are simply wrong.
78 /// if x == f32::NAN { }
83 #[clippy::version = "pre 1.29.0"]
86 "comparisons to `NAN`, which will always return false, probably not intended"
89 declare_clippy_lint
! {
91 /// Checks for (in-)equality comparisons on floating-point
92 /// values (apart from zero), except in functions called `*eq*` (which probably
93 /// implement equality for a type involving floats).
95 /// ### Why is this bad?
96 /// Floating point calculations are usually imprecise, so
97 /// asking if two values are *exactly* equal is asking for trouble. For a good
98 /// guide on what to do, see [the floating point
99 /// guide](http://www.floating-point-gui.de/errors/comparison).
103 /// let x = 1.2331f64;
104 /// let y = 1.2332f64;
107 /// if y == 1.23f64 { }
108 /// if y != x {} // where both are floats
111 /// let error_margin = f64::EPSILON; // Use an epsilon for comparison
112 /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
113 /// // let error_margin = std::f64::EPSILON;
114 /// if (y - 1.23f64).abs() < error_margin { }
115 /// if (y - x).abs() > error_margin { }
117 #[clippy::version = "pre 1.29.0"]
120 "using `==` or `!=` on float values instead of comparing difference with an epsilon"
123 declare_clippy_lint
! {
125 /// Checks for conversions to owned values just for the sake
128 /// ### Why is this bad?
129 /// The comparison can operate on a reference, so creating
130 /// an owned value effectively throws it away directly afterwards, which is
131 /// needlessly consuming code and heap space.
136 /// # let y = String::from("foo");
137 /// if x.to_owned() == y {}
139 /// Could be written as
142 /// # let y = String::from("foo");
145 #[clippy::version = "pre 1.29.0"]
148 "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`"
151 declare_clippy_lint
! {
153 /// Checks for getting the remainder of a division by one or minus
156 /// ### Why is this bad?
157 /// The result for a divisor of one can only ever be zero; for
158 /// minus one it can cause panic/overflow (if the left operand is the minimal value of
159 /// the respective integer type) or results in zero. No one will write such code
160 /// deliberately, unless trying to win an Underhanded Rust Contest. Even for that
161 /// contest, it's probably a bad idea. Use something more underhanded.
169 #[clippy::version = "pre 1.29.0"]
172 "taking a number modulo +/-1, which can either panic/overflow or always returns 0"
175 declare_clippy_lint
! {
177 /// Checks for the use of bindings with a single leading
180 /// ### Why is this bad?
181 /// A single leading underscore is usually used to indicate
182 /// that a binding will not be used. Using such a binding breaks this
185 /// ### Known problems
186 /// The lint does not work properly with desugaring and
187 /// macro, it has been allowed in the mean time.
192 /// let y = _x + 1; // Here we are using `_x`, even though it has a leading
193 /// // underscore. We should rename `_x` to `x`
195 #[clippy::version = "pre 1.29.0"]
196 pub USED_UNDERSCORE_BINDING
,
198 "using a binding which is prefixed with an underscore"
201 declare_clippy_lint
! {
203 /// Checks for the use of short circuit boolean conditions as
207 /// ### Why is this bad?
208 /// Using a short circuit boolean condition as a statement
209 /// may hide the fact that the second part is executed or not depending on the
210 /// outcome of the first part.
214 /// f() && g(); // We should write `if f() { g(); }`.
216 #[clippy::version = "pre 1.29.0"]
217 pub SHORT_CIRCUIT_STATEMENT
,
219 "using a short circuit boolean condition as a statement"
222 declare_clippy_lint
! {
224 /// Catch casts from `0` to some pointer type
226 /// ### Why is this bad?
227 /// This generally means `null` and is better expressed as
228 /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}.
233 /// let a = 0 as *const u32;
236 /// let a = std::ptr::null::<u32>();
238 #[clippy::version = "pre 1.29.0"]
241 "using `0 as *{const, mut} T`"
244 declare_clippy_lint
! {
246 /// Checks for (in-)equality comparisons on floating-point
247 /// value and constant, except in functions called `*eq*` (which probably
248 /// implement equality for a type involving floats).
250 /// ### Why is this bad?
251 /// Floating point calculations are usually imprecise, so
252 /// asking if two values are *exactly* equal is asking for trouble. For a good
253 /// guide on what to do, see [the floating point
254 /// guide](http://www.floating-point-gui.de/errors/comparison).
258 /// let x: f64 = 1.0;
259 /// const ONE: f64 = 1.00;
262 /// if x == ONE { } // where both are floats
265 /// let error_margin = f64::EPSILON; // Use an epsilon for comparison
266 /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
267 /// // let error_margin = std::f64::EPSILON;
268 /// if (x - ONE).abs() < error_margin { }
270 #[clippy::version = "pre 1.29.0"]
273 "using `==` or `!=` on float constants instead of comparing difference with an epsilon"
276 declare_lint_pass
!(MiscLints
=> [
282 USED_UNDERSCORE_BINDING
,
283 SHORT_CIRCUIT_STATEMENT
,
288 impl<'tcx
> LateLintPass
<'tcx
> for MiscLints
{
291 cx
: &LateContext
<'tcx
>,
293 decl
: &'tcx FnDecl
<'_
>,
294 body
: &'tcx Body
<'_
>,
298 if let FnKind
::Closure
= k
{
299 // Does not apply to closures
302 if in_external_macro(cx
.tcx
.sess
, span
) {
305 for arg
in iter_input_pats(decl
, body
) {
306 if let PatKind
::Binding(BindingAnnotation
::Ref
| BindingAnnotation
::RefMut
, ..) = arg
.pat
.kind
{
311 "`ref` directly on a function argument is ignored. \
312 Consider using a reference type instead",
318 fn check_stmt(&mut self, cx
: &LateContext
<'tcx
>, stmt
: &'tcx Stmt
<'_
>) {
320 if !in_external_macro(cx
.tcx
.sess
, stmt
.span
);
321 if let StmtKind
::Local(local
) = stmt
.kind
;
322 if let PatKind
::Binding(an
, .., name
, None
) = local
.pat
.kind
;
323 if let Some(init
) = local
.init
;
324 if an
== BindingAnnotation
::Ref
|| an
== BindingAnnotation
::RefMut
;
326 // use the macro callsite when the init span (but not the whole local span)
327 // comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];`
328 let sugg_init
= if init
.span
.from_expansion() && !local
.span
.from_expansion() {
329 Sugg
::hir_with_macro_callsite(cx
, init
, "..")
331 Sugg
::hir(cx
, init
, "..")
333 let (mutopt
, initref
) = if an
== BindingAnnotation
::RefMut
{
334 ("mut ", sugg_init
.mut_addr())
336 ("", sugg_init
.addr())
338 let tyopt
= if let Some(ty
) = local
.ty
{
339 format
!(": &{mutopt}{ty}", mutopt
=mutopt
, ty
=snippet(cx
, ty
.span
, ".."))
343 span_lint_hir_and_then(
348 "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead",
350 diag
.span_suggestion(
354 "let {name}{tyopt} = {initref};",
355 name
=snippet(cx
, name
.span
, ".."),
359 Applicability
::MachineApplicable
,
366 if let StmtKind
::Semi(expr
) = stmt
.kind
;
367 if let ExprKind
::Binary(ref binop
, a
, b
) = expr
.kind
;
368 if binop
.node
== BinOpKind
::And
|| binop
.node
== BinOpKind
::Or
;
369 if let Some(sugg
) = Sugg
::hir_opt(cx
, a
);
371 span_lint_hir_and_then(
373 SHORT_CIRCUIT_STATEMENT
,
376 "boolean short circuit operator in statement may be clearer using an explicit test",
378 let sugg
= if binop
.node
== BinOpKind
::Or { !sugg }
else { sugg }
;
379 diag
.span_suggestion(
385 &snippet(cx
, b
.span
, ".."),
387 Applicability
::MachineApplicable
, // snippet
394 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
396 ExprKind
::Cast(e
, ty
) => {
397 check_cast(cx
, expr
.span
, e
, ty
);
400 ExprKind
::Binary(ref cmp
, left
, right
) => {
401 check_binary(cx
, expr
, cmp
, left
, right
);
406 if in_attributes_expansion(expr
) || expr
.span
.is_desugaring(DesugaringKind
::Await
) {
407 // Don't lint things expanded by #[derive(...)], etc or `await` desugaring
411 let binding
= match expr
.kind
{
412 ExprKind
::Path(ref qpath
) if !matches
!(qpath
, hir
::QPath
::LangItem(..)) => {
413 let binding
= last_path_segment(qpath
).ident
.as_str();
414 if binding
.starts_with('_'
) &&
415 !binding
.starts_with("__") &&
416 binding
!= "_result" && // FIXME: #944
418 // don't lint if the declaration is in a macro
419 non_macro_local(cx
, cx
.qpath_res(qpath
, expr
.hir_id
))
426 ExprKind
::Field(_
, ident
) => {
428 let name
= sym
.as_str();
429 if name
.starts_with('_'
) && !name
.starts_with("__") {
437 if let Some(binding
) = binding
{
440 USED_UNDERSCORE_BINDING
,
443 "used binding `{}` which is prefixed with an underscore. A leading \
444 underscore signals that a binding will not be used",
452 fn get_lint_and_message(
453 is_comparing_constants
: bool
,
454 is_comparing_arrays
: bool
,
455 ) -> (&'
static rustc_lint
::Lint
, &'
static str) {
456 if is_comparing_constants
{
459 if is_comparing_arrays
{
460 "strict comparison of `f32` or `f64` constant arrays"
462 "strict comparison of `f32` or `f64` constant"
468 if is_comparing_arrays
{
469 "strict comparison of `f32` or `f64` arrays"
471 "strict comparison of `f32` or `f64`"
477 fn check_nan(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, cmp_expr
: &Expr
<'_
>) {
479 if !in_constant(cx
, cmp_expr
.hir_id
);
480 if let Some((value
, _
)) = constant(cx
, cx
.typeck_results(), expr
);
482 Constant
::F32(num
) => num
.is_nan(),
483 Constant
::F64(num
) => num
.is_nan(),
491 "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead",
497 fn is_named_constant
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) -> bool
{
498 if let Some((_
, res
)) = constant(cx
, cx
.typeck_results(), expr
) {
505 fn is_allowed
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) -> bool
{
506 match constant(cx
, cx
.typeck_results(), expr
) {
507 Some((Constant
::F32(f
), _
)) => f
== 0.0 || f
.is_infinite(),
508 Some((Constant
::F64(f
), _
)) => f
== 0.0 || f
.is_infinite(),
509 Some((Constant
::Vec(vec
), _
)) => vec
.iter().all(|f
| match f
{
510 Constant
::F32(f
) => *f
== 0.0 || (*f
).is_infinite(),
511 Constant
::F64(f
) => *f
== 0.0 || (*f
).is_infinite(),
518 // Return true if `expr` is the result of `signum()` invoked on a float value.
519 fn is_signum(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
520 // The negation of a signum is still a signum
521 if let ExprKind
::Unary(UnOp
::Neg
, child_expr
) = expr
.kind
{
522 return is_signum(cx
, child_expr
);
526 if let ExprKind
::MethodCall(method_name
, [ref self_arg
, ..], _
) = expr
.kind
;
527 if sym
!(signum
) == method_name
.ident
.name
;
528 // Check that the receiver of the signum() is a float (expressions[0] is the receiver of
531 return is_float(cx
, self_arg
);
537 fn is_float(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
538 let value
= &cx
.typeck_results().expr_ty(expr
).peel_refs().kind();
540 if let ty
::Array(arr_ty
, _
) = value
{
541 return matches
!(arr_ty
.kind(), ty
::Float(_
));
544 matches
!(value
, ty
::Float(_
))
547 fn is_array(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
548 matches
!(&cx
.typeck_results().expr_ty(expr
).peel_refs().kind(), ty
::Array(_
, _
))
551 #[allow(clippy::too_many_lines)]
552 fn check_to_owned(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, other
: &Expr
<'_
>, left
: bool
) {
560 fn is_implemented(&self) -> bool
{
561 self.ty_eq_other
|| self.other_eq_ty
565 fn symmetric_partial_eq
<'tcx
>(cx
: &LateContext
<'tcx
>, ty
: Ty
<'tcx
>, other
: Ty
<'tcx
>) -> Option
<EqImpl
> {
566 cx
.tcx
.lang_items().eq_trait().map(|def_id
| EqImpl
{
567 ty_eq_other
: implements_trait(cx
, ty
, def_id
, &[other
.into()]),
568 other_eq_ty
: implements_trait(cx
, other
, def_id
, &[ty
.into()]),
572 let (arg_ty
, snip
) = match expr
.kind
{
573 ExprKind
::MethodCall(.., args
, _
) if args
.len() == 1 => {
575 if let Some(expr_def_id
) = cx
.typeck_results().type_dependent_def_id(expr
.hir_id
);
576 if is_diag_trait_item(cx
, expr_def_id
, sym
::ToString
)
577 || is_diag_trait_item(cx
, expr_def_id
, sym
::ToOwned
);
579 (cx
.typeck_results().expr_ty(&args
[0]), snippet(cx
, args
[0].span
, ".."))
585 ExprKind
::Call(path
, [arg
]) => {
586 if path_def_id(cx
, path
)
587 .and_then(|id
| match_any_def_paths(cx
, id
, &[&paths
::FROM_STR_METHOD
, &paths
::FROM_FROM
]))
590 (cx
.typeck_results().expr_ty(arg
), snippet(cx
, arg
.span
, ".."))
598 let other_ty
= cx
.typeck_results().expr_ty(other
);
600 let without_deref
= symmetric_partial_eq(cx
, arg_ty
, other_ty
).unwrap_or_default();
601 let with_deref
= arg_ty
603 .and_then(|tam
| symmetric_partial_eq(cx
, tam
.ty
, other_ty
))
604 .unwrap_or_default();
606 if !with_deref
.is_implemented() && !without_deref
.is_implemented() {
610 let other_gets_derefed
= matches
!(other
.kind
, ExprKind
::Unary(UnOp
::Deref
, _
));
612 let lint_span
= if other_gets_derefed
{
613 expr
.span
.to(other
.span
)
622 "this creates an owned instance just for comparison",
624 // This also catches `PartialEq` implementations that call `to_owned`.
625 if other_gets_derefed
{
626 diag
.span_label(lint_span
, "try implementing the comparison without allocating");
632 if with_deref
.is_implemented() {
633 expr_snip
= format
!("*{}", snip
);
634 eq_impl
= with_deref
;
636 expr_snip
= snip
.to_string();
637 eq_impl
= without_deref
;
642 if (eq_impl
.ty_eq_other
&& left
) || (eq_impl
.other_eq_ty
&& !left
) {
646 span
= expr
.span
.to(other
.span
);
648 let cmp_span
= if other
.span
< expr
.span
{
649 other
.span
.between(expr
.span
)
651 expr
.span
.between(other
.span
)
653 if eq_impl
.ty_eq_other
{
657 snippet(cx
, cmp_span
, ".."),
658 snippet(cx
, other
.span
, "..")
663 snippet(cx
, other
.span
, ".."),
664 snippet(cx
, cmp_span
, ".."),
670 diag
.span_suggestion(
674 Applicability
::MachineApplicable
, // snippet
680 /// Heuristic to see if an expression is used. Should be compatible with
681 /// `unused_variables`'s idea
682 /// of what it means for an expression to be "used".
683 fn is_used(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
684 get_parent_expr(cx
, expr
).map_or(true, |parent
| match parent
.kind
{
685 ExprKind
::Assign(_
, rhs
, _
) | ExprKind
::AssignOp(_
, _
, rhs
) => SpanlessEq
::new(cx
).eq_expr(rhs
, expr
),
686 _
=> is_used(cx
, parent
),
690 /// Tests whether an expression is in a macro expansion (e.g., something
691 /// generated by `#[derive(...)]` or the like).
692 fn in_attributes_expansion(expr
: &Expr
<'_
>) -> bool
{
693 use rustc_span
::hygiene
::MacroKind
;
694 if expr
.span
.from_expansion() {
695 let data
= expr
.span
.ctxt().outer_expn_data();
696 matches
!(data
.kind
, ExpnKind
::Macro(MacroKind
::Attr
, _
))
702 /// Tests whether `res` is a variable defined outside a macro.
703 fn non_macro_local(cx
: &LateContext
<'_
>, res
: def
::Res
) -> bool
{
704 if let def
::Res
::Local(id
) = res
{
705 !cx
.tcx
.hir().span(id
).from_expansion()
711 fn check_cast(cx
: &LateContext
<'_
>, span
: Span
, e
: &Expr
<'_
>, ty
: &hir
::Ty
<'_
>) {
713 if let TyKind
::Ptr(ref mut_ty
) = ty
.kind
;
714 if let ExprKind
::Lit(ref lit
) = e
.kind
;
715 if let LitKind
::Int(0, _
) = lit
.node
;
716 if !in_constant(cx
, e
.hir_id
);
718 let (msg
, sugg_fn
) = match mut_ty
.mutbl
{
719 Mutability
::Mut
=> ("`0 as *mut _` detected", "std::ptr::null_mut"),
720 Mutability
::Not
=> ("`0 as *const _` detected", "std::ptr::null"),
723 let (sugg
, appl
) = if let TyKind
::Infer
= mut_ty
.ty
.kind
{
724 (format
!("{}()", sugg_fn
), Applicability
::MachineApplicable
)
725 } else if let Some(mut_ty_snip
) = snippet_opt(cx
, mut_ty
.ty
.span
) {
726 (format
!("{}::<{}>()", sugg_fn
, mut_ty_snip
), Applicability
::MachineApplicable
)
728 // `MaybeIncorrect` as type inference may not work with the suggested code
729 (format
!("{}()", sugg_fn
), Applicability
::MaybeIncorrect
)
731 span_lint_and_sugg(cx
, ZERO_PTR
, span
, msg
, "try", sugg
, appl
);
737 cx
: &LateContext
<'a
>,
739 cmp
: &rustc_span
::source_map
::Spanned
<rustc_hir
::BinOpKind
>,
744 if op
.is_comparison() {
745 check_nan(cx
, left
, expr
);
746 check_nan(cx
, right
, expr
);
747 check_to_owned(cx
, left
, right
, true);
748 check_to_owned(cx
, right
, left
, false);
750 if (op
== BinOpKind
::Eq
|| op
== BinOpKind
::Ne
) && (is_float(cx
, left
) || is_float(cx
, right
)) {
751 if is_allowed(cx
, left
) || is_allowed(cx
, right
) {
755 // Allow comparing the results of signum()
756 if is_signum(cx
, left
) && is_signum(cx
, right
) {
760 if let Some(name
) = get_item_name(cx
, expr
) {
761 let name
= name
.as_str();
762 if name
== "eq" || name
== "ne" || name
== "is_nan" || name
.starts_with("eq_") || name
.ends_with("_eq") {
766 let is_comparing_arrays
= is_array(cx
, left
) || is_array(cx
, right
);
767 let (lint
, msg
) = get_lint_and_message(
768 is_named_constant(cx
, left
) || is_named_constant(cx
, right
),
771 span_lint_and_then(cx
, lint
, expr
.span
, msg
, |diag
| {
772 let lhs
= Sugg
::hir(cx
, left
, "..");
773 let rhs
= Sugg
::hir(cx
, right
, "..");
775 if !is_comparing_arrays
{
776 diag
.span_suggestion(
778 "consider comparing them within some margin of error",
780 "({}).abs() {} error_margin",
782 if op
== BinOpKind
::Eq { '<' }
else { '>' }
784 Applicability
::HasPlaceholders
, // snippet
787 diag
.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
789 } else if op
== BinOpKind
::Rem
{
790 if is_integer_const(cx
, right
, 1) {
791 span_lint(cx
, MODULO_ONE
, expr
.span
, "any number modulo 1 will be 0");
794 if let ty
::Int(ity
) = cx
.typeck_results().expr_ty(right
).kind() {
795 if is_integer_const(cx
, right
, unsext(cx
.tcx
, -1, *ity
)) {
800 "any number modulo -1 will panic/overflow or result in 0",