1- use clippy_utils:: { diagnostics, meets_msrv, msrvs, source, ty} ;
1+ use clippy_utils:: {
2+ diagnostics:: { self , span_lint_and_sugg} ,
3+ meets_msrv, msrvs, source, ty,
4+ } ;
25use rustc_errors:: Applicability ;
3- use rustc_hir:: * ;
6+ use rustc_hir:: { BinOpKind , Expr , ExprKind } ;
47use rustc_lint:: { LateContext , LateLintPass } ;
58use rustc_semver:: RustcVersion ;
69use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
7- use rustc_span:: symbol:: sym;
10+ use rustc_span:: { source_map:: Spanned , sym} ;
11+
12+ declare_clippy_lint ! {
13+ /// ### What it does
14+ /// Lints subtraction between `Instant::now()` and another `Instant`.
15+ ///
16+ /// ### Why is this bad?
17+ /// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns
18+ /// as `Instant` subtraction saturates.
19+ ///
20+ /// `prev_instant.elapsed()` also more clearly signals intention.
21+ ///
22+ /// ### Example
23+ /// ```rust
24+ /// use std::time::Instant;
25+ /// let prev_instant = Instant::now();
26+ /// let duration = Instant::now() - prev_instant;
27+ /// ```
28+ /// Use instead:
29+ /// ```rust
30+ /// use std::time::Instant;
31+ /// let prev_instant = Instant::now();
32+ /// let duration = prev_instant.elapsed();
33+ /// ```
34+ #[ clippy:: version = "1.64.0" ]
35+ pub MANUAL_INSTANT_ELAPSED ,
36+ pedantic,
37+ "subtraction between `Instant::now()` and previous `Instant`"
38+ }
839
940declare_clippy_lint ! {
1041 /// ### What it does
1142 /// Finds patterns of unchecked subtraction of [`Duration`] from [`Instant::now()`].
1243 ///
1344 /// ### Why is this bad?
14- /// Unchecked subtraction could cause underflow on certain platforms, leading to bugs and/or
45+ /// Unchecked subtraction could cause underflow on certain platforms, leading to
1546 /// unintentional panics.
1647 ///
1748 /// ### Example
@@ -32,21 +63,39 @@ declare_clippy_lint! {
3263 "finds unchecked subtraction of a 'Duration' from an 'Instant'"
3364}
3465
35- pub struct UncheckedDurationSubtraction {
66+ pub struct InstantSubtraction {
3667 msrv : Option < RustcVersion > ,
3768}
3869
39- impl UncheckedDurationSubtraction {
70+ impl InstantSubtraction {
4071 #[ must_use]
4172 pub fn new ( msrv : Option < RustcVersion > ) -> Self {
4273 Self { msrv }
4374 }
4475}
4576
46- impl_lint_pass ! ( UncheckedDurationSubtraction => [ UNCHECKED_DURATION_SUBTRACTION ] ) ;
77+ impl_lint_pass ! ( InstantSubtraction => [ MANUAL_INSTANT_ELAPSED , UNCHECKED_DURATION_SUBTRACTION ] ) ;
78+
79+ impl LateLintPass < ' _ > for InstantSubtraction {
80+ fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & ' _ Expr < ' _ > ) {
81+ if let ExprKind :: Binary ( Spanned { node : BinOpKind :: Sub , ..} , lhs, rhs) = expr. kind
82+ && check_instant_now_call ( cx, lhs)
83+ && let ty_resolved = cx. typeck_results ( ) . expr_ty ( rhs)
84+ && let rustc_middle:: ty:: Adt ( def, _) = ty_resolved. kind ( )
85+ && clippy_utils:: match_def_path ( cx, def. did ( ) , & clippy_utils:: paths:: INSTANT )
86+ && let Some ( sugg) = clippy_utils:: sugg:: Sugg :: hir_opt ( cx, rhs)
87+ {
88+ span_lint_and_sugg (
89+ cx,
90+ MANUAL_INSTANT_ELAPSED ,
91+ expr. span ,
92+ "manual implementation of `Instant::elapsed`" ,
93+ "try" ,
94+ format ! ( "{}.elapsed()" , sugg. maybe_par( ) ) ,
95+ Applicability :: MachineApplicable ,
96+ ) ;
97+ }
4798
48- impl < ' tcx > LateLintPass < ' tcx > for UncheckedDurationSubtraction {
49- fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
5099 if expr. span . from_expansion ( ) || !meets_msrv ( self . msrv , msrvs:: TRY_FROM ) {
51100 return ;
52101 }
@@ -67,6 +116,17 @@ impl<'tcx> LateLintPass<'tcx> for UncheckedDurationSubtraction {
67116 }
68117}
69118
119+ fn check_instant_now_call ( cx : & LateContext < ' _ > , expr_block : & ' _ Expr < ' _ > ) -> bool {
120+ if let ExprKind :: Call ( fn_expr, [ ] ) = expr_block. kind
121+ && let Some ( fn_id) = clippy_utils:: path_def_id ( cx, fn_expr)
122+ && clippy_utils:: match_def_path ( cx, fn_id, & clippy_utils:: paths:: INSTANT_NOW )
123+ {
124+ true
125+ } else {
126+ false
127+ }
128+ }
129+
70130fn is_an_instant ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
71131 let expr_ty = cx. typeck_results ( ) . expr_ty ( expr) ;
72132
0 commit comments