1
1
use clippy_config:: Conf ;
2
- use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
+ use clippy_utils:: diagnostics:: { span_lint , span_lint_and_sugg} ;
3
3
use clippy_utils:: msrvs:: { self , Msrv } ;
4
- use clippy_utils:: source:: snippet_with_context;
5
4
use clippy_utils:: sugg:: Sugg ;
6
5
use clippy_utils:: { is_path_diagnostic_item, ty} ;
7
6
use rustc_errors:: Applicability ;
8
7
use rustc_hir:: { BinOpKind , Expr , ExprKind } ;
9
8
use rustc_lint:: { LateContext , LateLintPass } ;
9
+ use rustc_middle:: ty:: Ty ;
10
10
use rustc_session:: impl_lint_pass;
11
11
use rustc_span:: source_map:: Spanned ;
12
12
use rustc_span:: sym;
@@ -41,7 +41,7 @@ declare_clippy_lint! {
41
41
42
42
declare_clippy_lint ! {
43
43
/// ### 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 .
45
45
///
46
46
/// ### Why is this bad?
47
47
/// Unchecked subtraction could cause underflow on certain platforms, leading to
@@ -51,32 +51,38 @@ declare_clippy_lint! {
51
51
/// ```no_run
52
52
/// # use std::time::{Instant, Duration};
53
53
/// 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;
54
57
/// ```
55
58
///
56
59
/// Use instead:
57
60
/// ```no_run
58
61
/// # use std::time::{Instant, Duration};
59
62
/// 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);
60
66
/// ```
61
67
#[ clippy:: version = "1.67.0" ]
62
68
pub UNCHECKED_TIME_SUBTRACTION ,
63
69
pedantic,
64
- "finds unchecked subtraction of a 'Duration' from an 'Instant'"
70
+ "finds unchecked subtraction involving 'Duration' or 'Instant'"
65
71
}
66
72
67
- pub struct InstantSubtraction {
73
+ pub struct UncheckedTimeSubtraction {
68
74
msrv : Msrv ,
69
75
}
70
76
71
- impl InstantSubtraction {
77
+ impl UncheckedTimeSubtraction {
72
78
pub fn new ( conf : & ' static Conf ) -> Self {
73
79
Self { msrv : conf. msrv }
74
80
}
75
81
}
76
82
77
- impl_lint_pass ! ( InstantSubtraction => [ MANUAL_INSTANT_ELAPSED , UNCHECKED_TIME_SUBTRACTION ] ) ;
83
+ impl_lint_pass ! ( UncheckedTimeSubtraction => [ MANUAL_INSTANT_ELAPSED , UNCHECKED_TIME_SUBTRACTION ] ) ;
78
84
79
- impl LateLintPass < ' _ > for InstantSubtraction {
85
+ impl LateLintPass < ' _ > for UncheckedTimeSubtraction {
80
86
fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & ' _ Expr < ' _ > ) {
81
87
if let ExprKind :: Binary (
82
88
Spanned {
@@ -85,21 +91,54 @@ impl LateLintPass<'_> for InstantSubtraction {
85
91
lhs,
86
92
rhs,
87
93
) = expr. kind
88
- && let typeck = cx. typeck_results ( )
89
- && ty:: is_type_diagnostic_item ( cx, typeck. expr_ty ( lhs) , sym:: Instant )
90
94
{
95
+ let typeck = cx. typeck_results ( ) ;
96
+ let lhs_ty = typeck. expr_ty ( lhs) ;
91
97
let rhs_ty = typeck. expr_ty ( rhs) ;
92
98
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 )
99
127
&& !expr. span . from_expansion ( )
100
128
&& self . msrv . meets ( cx, msrvs:: TRY_FROM )
101
129
{
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
+ }
103
142
}
104
143
}
105
144
}
@@ -115,6 +154,25 @@ fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
115
154
}
116
155
}
117
156
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
+
118
176
fn print_manual_instant_elapsed_sugg ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , sugg : Sugg < ' _ > ) {
119
177
span_lint_and_sugg (
120
178
cx,
@@ -133,19 +191,26 @@ fn print_unchecked_duration_subtraction_sugg(
133
191
right_expr : & Expr < ' _ > ,
134
192
expr : & Expr < ' _ > ,
135
193
) {
136
- let mut applicability = Applicability :: MachineApplicable ;
194
+ let typeck = cx. typeck_results ( ) ;
195
+ let left_ty = typeck. expr_ty ( left_expr) ;
137
196
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) ;
141
206
142
207
span_lint_and_sugg (
143
208
cx,
144
209
UNCHECKED_TIME_SUBTRACTION ,
145
210
expr. span ,
146
- "unchecked subtraction of a 'Duration' from an 'Instant'" ,
211
+ lint_msg ,
147
212
"try" ,
148
- format ! ( "{left_expr }.checked_sub({right_expr }).unwrap()" ) ,
213
+ format ! ( "{}.checked_sub({}).unwrap()" , left_sugg . maybe_paren ( ) , right_sugg ) ,
149
214
applicability,
150
215
) ;
151
216
}
0 commit comments