11use clippy_config:: Conf ;
2- use clippy_utils:: diagnostics:: span_lint_and_sugg;
2+ use clippy_utils:: diagnostics:: { span_lint , span_lint_and_sugg} ;
33use clippy_utils:: msrvs:: { self , Msrv } ;
4- use clippy_utils:: source:: snippet_with_context;
54use clippy_utils:: sugg:: Sugg ;
65use clippy_utils:: { is_path_diagnostic_item, ty} ;
76use rustc_errors:: Applicability ;
87use rustc_hir:: { BinOpKind , Expr , ExprKind } ;
98use rustc_lint:: { LateContext , LateLintPass } ;
9+ use rustc_middle:: ty:: Ty ;
1010use rustc_session:: impl_lint_pass;
1111use rustc_span:: source_map:: Spanned ;
1212use rustc_span:: sym;
@@ -41,7 +41,7 @@ declare_clippy_lint! {
4141
4242declare_clippy_lint ! {
4343 /// ### What it does
44- /// Lints subtraction between an `Instant` and a `Duration`.
44+ /// Lints subtraction between an `Instant` and a `Duration`, or between two `Duration` values .
4545 ///
4646 /// ### Why is this bad?
4747 /// Unchecked subtraction could cause underflow on certain platforms, leading to
@@ -51,32 +51,38 @@ declare_clippy_lint! {
5151 /// ```no_run
5252 /// # use std::time::{Instant, Duration};
5353 /// let time_passed = Instant::now() - Duration::from_secs(5);
54+ /// let dur1 = Duration::from_secs(3);
55+ /// let dur2 = Duration::from_secs(5);
56+ /// let diff = dur1 - dur2;
5457 /// ```
5558 ///
5659 /// Use instead:
5760 /// ```no_run
5861 /// # use std::time::{Instant, Duration};
5962 /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5));
63+ /// let dur1 = Duration::from_secs(3);
64+ /// let dur2 = Duration::from_secs(5);
65+ /// let diff = dur1.checked_sub(dur2);
6066 /// ```
6167 #[ clippy:: version = "1.67.0" ]
6268 pub UNCHECKED_TIME_SUBTRACTION ,
6369 pedantic,
64- "finds unchecked subtraction of a 'Duration' from an 'Instant'"
70+ "finds unchecked subtraction involving 'Duration' or 'Instant'"
6571}
6672
67- pub struct InstantSubtraction {
73+ pub struct UncheckedTimeSubtraction {
6874 msrv : Msrv ,
6975}
7076
71- impl InstantSubtraction {
77+ impl UncheckedTimeSubtraction {
7278 pub fn new ( conf : & ' static Conf ) -> Self {
7379 Self { msrv : conf. msrv }
7480 }
7581}
7682
77- impl_lint_pass ! ( InstantSubtraction => [ MANUAL_INSTANT_ELAPSED , UNCHECKED_TIME_SUBTRACTION ] ) ;
83+ impl_lint_pass ! ( UncheckedTimeSubtraction => [ MANUAL_INSTANT_ELAPSED , UNCHECKED_TIME_SUBTRACTION ] ) ;
7884
79- impl LateLintPass < ' _ > for InstantSubtraction {
85+ impl LateLintPass < ' _ > for UncheckedTimeSubtraction {
8086 fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & ' _ Expr < ' _ > ) {
8187 if let ExprKind :: Binary (
8288 Spanned {
@@ -85,21 +91,54 @@ impl LateLintPass<'_> for InstantSubtraction {
8591 lhs,
8692 rhs,
8793 ) = expr. kind
88- && let typeck = cx. typeck_results ( )
89- && ty:: is_type_diagnostic_item ( cx, typeck. expr_ty ( lhs) , sym:: Instant )
9094 {
95+ let typeck = cx. typeck_results ( ) ;
96+ let lhs_ty = typeck. expr_ty ( lhs) ;
9197 let rhs_ty = typeck. expr_ty ( rhs) ;
9298
93- if is_instant_now_call ( cx, lhs)
94- && ty:: is_type_diagnostic_item ( cx, rhs_ty, sym:: Instant )
95- && let Some ( sugg) = Sugg :: hir_opt ( cx, rhs)
96- {
97- print_manual_instant_elapsed_sugg ( cx, expr, sugg) ;
98- } else if ty:: is_type_diagnostic_item ( cx, rhs_ty, sym:: Duration )
99+ if ty:: is_type_diagnostic_item ( cx, lhs_ty, sym:: Instant ) {
100+ // Instant::now() - instant
101+ if is_instant_now_call ( cx, lhs)
102+ && ty:: is_type_diagnostic_item ( cx, rhs_ty, sym:: Instant )
103+ && let Some ( sugg) = Sugg :: hir_opt ( cx, rhs)
104+ {
105+ print_manual_instant_elapsed_sugg ( cx, expr, sugg) ;
106+ }
107+ // instant - duration
108+ else if ty:: is_type_diagnostic_item ( cx, rhs_ty, sym:: Duration )
109+ && !expr. span . from_expansion ( )
110+ && self . msrv . meets ( cx, msrvs:: TRY_FROM )
111+ {
112+ // For chained subtraction like (instant - dur1) - dur2, avoid suggestions
113+ if is_chained_time_subtraction ( cx, lhs) {
114+ span_lint (
115+ cx,
116+ UNCHECKED_TIME_SUBTRACTION ,
117+ expr. span ,
118+ "unchecked subtraction of a 'Duration' from an 'Instant'" ,
119+ ) ;
120+ } else {
121+ // instant - duration
122+ print_unchecked_duration_subtraction_sugg ( cx, lhs, rhs, expr) ;
123+ }
124+ }
125+ } else if ty:: is_type_diagnostic_item ( cx, lhs_ty, sym:: Duration )
126+ && ty:: is_type_diagnostic_item ( cx, rhs_ty, sym:: Duration )
99127 && !expr. span . from_expansion ( )
100128 && self . msrv . meets ( cx, msrvs:: TRY_FROM )
101129 {
102- print_unchecked_duration_subtraction_sugg ( cx, lhs, rhs, expr) ;
130+ // For chained subtraction like (dur1 - dur2) - dur3, avoid suggestions
131+ if is_chained_time_subtraction ( cx, lhs) {
132+ span_lint (
133+ cx,
134+ UNCHECKED_TIME_SUBTRACTION ,
135+ expr. span ,
136+ "unchecked subtraction between 'Duration' values" ,
137+ ) ;
138+ } else {
139+ // duration - duration
140+ print_unchecked_duration_subtraction_sugg ( cx, lhs, rhs, expr) ;
141+ }
103142 }
104143 }
105144 }
@@ -115,6 +154,25 @@ fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
115154 }
116155}
117156
157+ /// Returns true if this subtraction is part of a chain like `(a - b) - c`
158+ fn is_chained_time_subtraction ( cx : & LateContext < ' _ > , lhs : & Expr < ' _ > ) -> bool {
159+ if let ExprKind :: Binary ( op, inner_lhs, inner_rhs) = & lhs. kind
160+ && matches ! ( op. node, BinOpKind :: Sub )
161+ {
162+ let typeck = cx. typeck_results ( ) ;
163+ let left_ty = typeck. expr_ty ( inner_lhs) ;
164+ let right_ty = typeck. expr_ty ( inner_rhs) ;
165+ is_time_type ( cx, left_ty) && is_time_type ( cx, right_ty)
166+ } else {
167+ false
168+ }
169+ }
170+
171+ /// Returns true if the type is Duration or Instant
172+ fn is_time_type ( cx : & LateContext < ' _ > , ty : Ty < ' _ > ) -> bool {
173+ ty:: is_type_diagnostic_item ( cx, ty, sym:: Duration ) || ty:: is_type_diagnostic_item ( cx, ty, sym:: Instant )
174+ }
175+
118176fn print_manual_instant_elapsed_sugg ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , sugg : Sugg < ' _ > ) {
119177 span_lint_and_sugg (
120178 cx,
@@ -133,19 +191,26 @@ fn print_unchecked_duration_subtraction_sugg(
133191 right_expr : & Expr < ' _ > ,
134192 expr : & Expr < ' _ > ,
135193) {
136- let mut applicability = Applicability :: MachineApplicable ;
194+ let typeck = cx. typeck_results ( ) ;
195+ let left_ty = typeck. expr_ty ( left_expr) ;
137196
138- let ctxt = expr. span . ctxt ( ) ;
139- let left_expr = snippet_with_context ( cx, left_expr. span , ctxt, "<instant>" , & mut applicability) . 0 ;
140- let right_expr = snippet_with_context ( cx, right_expr. span , ctxt, "<duration>" , & mut applicability) . 0 ;
197+ let lint_msg = if ty:: is_type_diagnostic_item ( cx, left_ty, sym:: Instant ) {
198+ "unchecked subtraction of a 'Duration' from an 'Instant'"
199+ } else {
200+ "unchecked subtraction between 'Duration' values"
201+ } ;
202+
203+ let mut applicability = Applicability :: MachineApplicable ;
204+ let left_sugg = Sugg :: hir_with_applicability ( cx, left_expr, "<left>" , & mut applicability) ;
205+ let right_sugg = Sugg :: hir_with_applicability ( cx, right_expr, "<right>" , & mut applicability) ;
141206
142207 span_lint_and_sugg (
143208 cx,
144209 UNCHECKED_TIME_SUBTRACTION ,
145210 expr. span ,
146- "unchecked subtraction of a 'Duration' from an 'Instant'" ,
211+ lint_msg ,
147212 "try" ,
148- format ! ( "{left_expr }.checked_sub({right_expr }).unwrap()" ) ,
213+ format ! ( "{}.checked_sub({}).unwrap()" , left_sugg . maybe_paren ( ) , right_sugg ) ,
149214 applicability,
150215 ) ;
151216}
0 commit comments