]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/instant_subtraction.rs
Merge commit 'f4850f7292efa33759b4f7f9b7621268979e9914' into clippyup
[rust.git] / src / tools / clippy / 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.65.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     /// Lints subtraction between an [`Instant`] and a [`Duration`].
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     /// # use std::time::{Instant, Duration};
53     /// let time_passed = Instant::now() - Duration::from_secs(5);
54     /// ```
55     ///
56     /// Use instead:
57     /// ```rust
58     /// # use std::time::{Instant, Duration};
59     /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5));
60     /// ```
61     ///
62     /// [`Duration`]: std::time::Duration
63     /// [`Instant::now()`]: std::time::Instant::now;
64     #[clippy::version = "1.65.0"]
65     pub UNCHECKED_DURATION_SUBTRACTION,
66     suspicious,
67     "finds unchecked subtraction of a 'Duration' from an 'Instant'"
68 }
69
70 pub struct InstantSubtraction {
71     msrv: Option<RustcVersion>,
72 }
73
74 impl InstantSubtraction {
75     #[must_use]
76     pub fn new(msrv: Option<RustcVersion>) -> Self {
77         Self { msrv }
78     }
79 }
80
81 impl_lint_pass!(InstantSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_DURATION_SUBTRACTION]);
82
83 impl LateLintPass<'_> for InstantSubtraction {
84     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
85         if let ExprKind::Binary(
86             Spanned {
87                 node: BinOpKind::Sub, ..
88             },
89             lhs,
90             rhs,
91         ) = expr.kind
92         {
93             if_chain! {
94                 if is_instant_now_call(cx, lhs);
95
96                 if is_an_instant(cx, rhs);
97                 if let Some(sugg) = Sugg::hir_opt(cx, rhs);
98
99                 then {
100                     print_manual_instant_elapsed_sugg(cx, expr, sugg)
101                 } else {
102                     if_chain! {
103                         if !expr.span.from_expansion();
104                         if meets_msrv(self.msrv, msrvs::TRY_FROM);
105
106                         if is_an_instant(cx, lhs);
107                         if is_a_duration(cx, rhs);
108
109                         then {
110                             print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr)
111                         }
112                     }
113                 }
114             }
115         }
116     }
117
118     extract_msrv_attr!(LateContext);
119 }
120
121 fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
122     if let ExprKind::Call(fn_expr, []) = expr_block.kind
123         && let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr)
124         && clippy_utils::match_def_path(cx, fn_id, &clippy_utils::paths::INSTANT_NOW)
125     {
126         true
127     } else {
128         false
129     }
130 }
131
132 fn is_an_instant(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
133     let expr_ty = cx.typeck_results().expr_ty(expr);
134
135     match expr_ty.kind() {
136         rustc_middle::ty::Adt(def, _) => clippy_utils::match_def_path(cx, def.did(), &clippy_utils::paths::INSTANT),
137         _ => false,
138     }
139 }
140
141 fn is_a_duration(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
142     let expr_ty = cx.typeck_results().expr_ty(expr);
143     ty::is_type_diagnostic_item(cx, expr_ty, sym::Duration)
144 }
145
146 fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) {
147     span_lint_and_sugg(
148         cx,
149         MANUAL_INSTANT_ELAPSED,
150         expr.span,
151         "manual implementation of `Instant::elapsed`",
152         "try",
153         format!("{}.elapsed()", sugg.maybe_par()),
154         Applicability::MachineApplicable,
155     );
156 }
157
158 fn print_unchecked_duration_subtraction_sugg(
159     cx: &LateContext<'_>,
160     left_expr: &Expr<'_>,
161     right_expr: &Expr<'_>,
162     expr: &Expr<'_>,
163 ) {
164     let mut applicability = Applicability::MachineApplicable;
165
166     let left_expr =
167         source::snippet_with_applicability(cx, left_expr.span, "std::time::Instant::now()", &mut applicability);
168     let right_expr = source::snippet_with_applicability(
169         cx,
170         right_expr.span,
171         "std::time::Duration::from_secs(1)",
172         &mut applicability,
173     );
174
175     diagnostics::span_lint_and_sugg(
176         cx,
177         UNCHECKED_DURATION_SUBTRACTION,
178         expr.span,
179         "unchecked subtraction of a 'Duration' from an 'Instant'",
180         "try",
181         format!("{left_expr}.checked_sub({right_expr}).unwrap()"),
182         applicability,
183     );
184 }