]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/instant_subtraction.rs
style: inline variables in `format!`
[rust.git] / clippy_lints / src / instant_subtraction.rs
1 use clippy_utils::{
2     diagnostics::{self, span_lint_and_sugg},
3     meets_msrv, msrvs, source,
4     sugg::Sugg,
5     ty,
6 };
7 use rustc_errors::Applicability;
8 use rustc_hir::{BinOpKind, Expr, ExprKind};
9 use rustc_lint::{LateContext, LateLintPass};
10 use rustc_semver::RustcVersion;
11 use rustc_session::{declare_tool_lint, impl_lint_pass};
12 use rustc_span::{source_map::Spanned, sym};
13
14 declare_clippy_lint! {
15     /// ### What it does
16     /// Lints subtraction between `Instant::now()` and another `Instant`.
17     ///
18     /// ### Why is this bad?
19     /// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns
20     /// as `Instant` subtraction saturates.
21     ///
22     /// `prev_instant.elapsed()` also more clearly signals intention.
23     ///
24     /// ### Example
25     /// ```rust
26     /// use std::time::Instant;
27     /// let prev_instant = Instant::now();
28     /// let duration = Instant::now() - prev_instant;
29     /// ```
30     /// Use instead:
31     /// ```rust
32     /// use std::time::Instant;
33     /// let prev_instant = Instant::now();
34     /// let duration = prev_instant.elapsed();
35     /// ```
36     #[clippy::version = "1.64.0"]
37     pub MANUAL_INSTANT_ELAPSED,
38     pedantic,
39     "subtraction between `Instant::now()` and previous `Instant`"
40 }
41
42 declare_clippy_lint! {
43     /// ### What it does
44     /// Finds patterns of unchecked subtraction of [`Duration`] from [`Instant::now()`].
45     ///
46     /// ### Why is this bad?
47     /// Unchecked subtraction could cause underflow on certain platforms, leading to
48     /// unintentional panics.
49     ///
50     /// ### Example
51     /// ```rust
52     /// let time_passed = Instant::now() - Duration::from_secs(5);
53     /// ```
54     ///
55     /// Use instead:
56     /// ```rust
57     /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5));
58     /// ```
59     ///
60     /// [`Duration`]: std::time::Duration
61     /// [`Instant::now()`]: std::time::Instant::now;
62     #[clippy::version = "1.65.0"]
63     pub UNCHECKED_DURATION_SUBTRACTION,
64     suspicious,
65     "finds unchecked subtraction of a 'Duration' from an 'Instant'"
66 }
67
68 pub struct InstantSubtraction {
69     msrv: Option<RustcVersion>,
70 }
71
72 impl InstantSubtraction {
73     #[must_use]
74     pub fn new(msrv: Option<RustcVersion>) -> Self {
75         Self { msrv }
76     }
77 }
78
79 impl_lint_pass!(InstantSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_DURATION_SUBTRACTION]);
80
81 impl LateLintPass<'_> for InstantSubtraction {
82     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
83         if let ExprKind::Binary(
84             Spanned {
85                 node: BinOpKind::Sub, ..
86             },
87             lhs,
88             rhs,
89         ) = expr.kind
90         {
91             if_chain! {
92                 if is_instant_now_call(cx, lhs);
93
94                 if is_an_instant(cx, rhs);
95                 if let Some(sugg) = Sugg::hir_opt(cx, rhs);
96
97                 then {
98                     print_manual_instant_elapsed_sugg(cx, expr, sugg)
99                 } else {
100                     if_chain! {
101                         if !expr.span.from_expansion();
102                         if meets_msrv(self.msrv, msrvs::TRY_FROM);
103
104                         if is_an_instant(cx, lhs);
105                         if is_a_duration(cx, rhs);
106
107                         then {
108                             print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr)
109                         }
110                     }
111                 }
112             }
113         }
114     }
115 }
116
117 fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
118     if let ExprKind::Call(fn_expr, []) = expr_block.kind
119         && let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr)
120         && clippy_utils::match_def_path(cx, fn_id, &clippy_utils::paths::INSTANT_NOW)
121     {
122         true
123     } else {
124         false
125     }
126 }
127
128 fn is_an_instant(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
129     let expr_ty = cx.typeck_results().expr_ty(expr);
130
131     match expr_ty.kind() {
132         rustc_middle::ty::Adt(def, _) => clippy_utils::match_def_path(cx, def.did(), &clippy_utils::paths::INSTANT),
133         _ => false,
134     }
135 }
136
137 fn is_a_duration(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
138     let expr_ty = cx.typeck_results().expr_ty(expr);
139     ty::is_type_diagnostic_item(cx, expr_ty, sym::Duration)
140 }
141
142 fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) {
143     span_lint_and_sugg(
144         cx,
145         MANUAL_INSTANT_ELAPSED,
146         expr.span,
147         "manual implementation of `Instant::elapsed`",
148         "try",
149         format!("{}.elapsed()", sugg.maybe_par()),
150         Applicability::MachineApplicable,
151     );
152 }
153
154 fn print_unchecked_duration_subtraction_sugg(
155     cx: &LateContext<'_>,
156     left_expr: &Expr<'_>,
157     right_expr: &Expr<'_>,
158     expr: &Expr<'_>,
159 ) {
160     let mut applicability = Applicability::MachineApplicable;
161
162     let left_expr =
163         source::snippet_with_applicability(cx, left_expr.span, "std::time::Instant::now()", &mut applicability);
164     let right_expr = source::snippet_with_applicability(
165         cx,
166         right_expr.span,
167         "std::time::Duration::from_secs(1)",
168         &mut applicability,
169     );
170
171     diagnostics::span_lint_and_sugg(
172         cx,
173         UNCHECKED_DURATION_SUBTRACTION,
174         expr.span,
175         "unchecked subtraction of a 'Duration' from an 'Instant'",
176         "try",
177         format!("{left_expr}.checked_sub({right_expr}).unwrap();"),
178         applicability,
179     );
180 }