@@ -8,7 +8,7 @@ use clippy_utils::{
8
8
is_default_equivalent, is_expr_used_or_unified, is_res_lang_ctor, path_res, peel_ref_operators, std_or_core,
9
9
} ;
10
10
use rustc_errors:: Applicability ;
11
- use rustc_hir:: LangItem :: OptionNone ;
11
+ use rustc_hir:: LangItem :: { OptionNone , OptionSome } ;
12
12
use rustc_hir:: { Expr , ExprKind } ;
13
13
use rustc_lint:: { LateContext , LateLintPass } ;
14
14
use rustc_session:: impl_lint_pass;
@@ -43,6 +43,31 @@ declare_clippy_lint! {
43
43
"replacing an `Option` with `None` instead of `take()`"
44
44
}
45
45
46
+ declare_clippy_lint ! {
47
+ /// ### What it does
48
+ /// Checks for `mem::replace()` on an `Option` with `Some(…)`.
49
+ ///
50
+ /// ### Why is this bad?
51
+ /// `Option` already has the method `replace()` for
52
+ /// taking its current value (Some(…) or None) and replacing it with
53
+ /// `Some(…)`.
54
+ ///
55
+ /// ### Example
56
+ /// ```no_run
57
+ /// let mut an_option = Some(0);
58
+ /// let replaced = std::mem::replace(&mut an_option, Some(1));
59
+ /// ```
60
+ /// Is better expressed with:
61
+ /// ```no_run
62
+ /// let mut an_option = Some(0);
63
+ /// let taken = an_option.replace(1);
64
+ /// ```
65
+ #[ clippy:: version = "1.86.0" ]
66
+ pub MEM_REPLACE_OPTION_WITH_SOME ,
67
+ style,
68
+ "replacing an `Option` with `Some` instead of `replace()`"
69
+ }
70
+
46
71
declare_clippy_lint ! {
47
72
/// ### What it does
48
73
/// Checks for `mem::replace(&mut _, mem::uninitialized())`
@@ -101,28 +126,67 @@ declare_clippy_lint! {
101
126
}
102
127
103
128
impl_lint_pass ! ( MemReplace =>
104
- [ MEM_REPLACE_OPTION_WITH_NONE , MEM_REPLACE_WITH_UNINIT , MEM_REPLACE_WITH_DEFAULT ] ) ;
129
+ [ MEM_REPLACE_OPTION_WITH_NONE , MEM_REPLACE_OPTION_WITH_SOME , MEM_REPLACE_WITH_UNINIT , MEM_REPLACE_WITH_DEFAULT ] ) ;
130
+
131
+ fn check_replace_option_with_none ( cx : & LateContext < ' _ > , src : & Expr < ' _ > , dest : & Expr < ' _ > , expr_span : Span ) -> bool {
132
+ if is_res_lang_ctor ( cx, path_res ( cx, src) , OptionNone ) {
133
+ // Since this is a late pass (already type-checked),
134
+ // and we already know that the second argument is an
135
+ // `Option`, we do not need to check the first
136
+ // argument's type. All that's left is to get
137
+ // the replacee's expr after peeling off the `&mut`
138
+ let sugg_expr = peel_ref_operators ( cx, dest) ;
139
+ let mut applicability = Applicability :: MachineApplicable ;
140
+ span_lint_and_sugg (
141
+ cx,
142
+ MEM_REPLACE_OPTION_WITH_NONE ,
143
+ expr_span,
144
+ "replacing an `Option` with `None`" ,
145
+ "consider `Option::take()` instead" ,
146
+ format ! (
147
+ "{}.take()" ,
148
+ Sugg :: hir_with_context( cx, sugg_expr, expr_span. ctxt( ) , "" , & mut applicability) . maybe_par( )
149
+ ) ,
150
+ applicability,
151
+ ) ;
152
+ true
153
+ } else {
154
+ false
155
+ }
156
+ }
105
157
106
- fn check_replace_option_with_none ( cx : & LateContext < ' _ > , dest : & Expr < ' _ > , expr_span : Span ) {
107
- // Since this is a late pass (already type-checked),
108
- // and we already know that the second argument is an
109
- // `Option`, we do not need to check the first
110
- // argument's type. All that's left is to get
111
- // the replacee's expr after peeling off the `&mut`
112
- let sugg_expr = peel_ref_operators ( cx, dest) ;
113
- let mut applicability = Applicability :: MachineApplicable ;
114
- span_lint_and_sugg (
115
- cx,
116
- MEM_REPLACE_OPTION_WITH_NONE ,
117
- expr_span,
118
- "replacing an `Option` with `None`" ,
119
- "consider `Option::take()` instead" ,
120
- format ! (
121
- "{}.take()" ,
122
- Sugg :: hir_with_context( cx, sugg_expr, expr_span. ctxt( ) , "" , & mut applicability) . maybe_par( )
123
- ) ,
124
- applicability,
125
- ) ;
158
+ fn check_replace_option_with_some (
159
+ cx : & LateContext < ' _ > ,
160
+ src : & Expr < ' _ > ,
161
+ dest : & Expr < ' _ > ,
162
+ expr_span : Span ,
163
+ msrv : & Msrv ,
164
+ ) -> bool {
165
+ if msrv. meets ( msrvs:: OPTION_REPLACE )
166
+ && let ExprKind :: Call ( src_func, [ src_arg] ) = src. kind
167
+ && is_res_lang_ctor ( cx, path_res ( cx, src_func) , OptionSome )
168
+ {
169
+ // We do not have to check for a `const` context here, because `core::mem::replace()` and
170
+ // `Option::replace()` have been const-stabilized simultaneously in version 1.83.0.
171
+ let sugg_expr = peel_ref_operators ( cx, dest) ;
172
+ let mut applicability = Applicability :: MachineApplicable ;
173
+ span_lint_and_sugg (
174
+ cx,
175
+ MEM_REPLACE_OPTION_WITH_SOME ,
176
+ expr_span,
177
+ "replacing an `Option` with `Some(..)`" ,
178
+ "consider `Option::replace()` instead" ,
179
+ format ! (
180
+ "{}.replace({})" ,
181
+ Sugg :: hir_with_context( cx, sugg_expr, expr_span. ctxt( ) , "_" , & mut applicability) . maybe_par( ) ,
182
+ snippet_with_applicability( cx, src_arg. span, "_" , & mut applicability)
183
+ ) ,
184
+ applicability,
185
+ ) ;
186
+ true
187
+ } else {
188
+ false
189
+ }
126
190
}
127
191
128
192
fn check_replace_with_uninit ( cx : & LateContext < ' _ > , src : & Expr < ' _ > , dest : & Expr < ' _ > , expr_span : Span ) {
@@ -181,34 +245,44 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
181
245
}
182
246
}
183
247
184
- fn check_replace_with_default ( cx : & LateContext < ' _ > , src : & Expr < ' _ > , dest : & Expr < ' _ > , expr_span : Span ) {
185
- // disable lint for primitives
186
- let expr_type = cx. typeck_results ( ) . expr_ty_adjusted ( src) ;
187
- if is_non_aggregate_primitive_type ( expr_type) {
188
- return ;
189
- }
190
- if is_default_equivalent ( cx, src) && !expr_span. in_external_macro ( cx. tcx . sess . source_map ( ) ) {
191
- let Some ( top_crate) = std_or_core ( cx) else { return } ;
248
+ fn check_replace_with_default (
249
+ cx : & LateContext < ' _ > ,
250
+ src : & Expr < ' _ > ,
251
+ dest : & Expr < ' _ > ,
252
+ expr : & Expr < ' _ > ,
253
+ msrv : & Msrv ,
254
+ ) -> bool {
255
+ if msrv. meets ( msrvs:: MEM_TAKE ) && is_expr_used_or_unified ( cx. tcx , expr)
256
+ // disable lint for primitives
257
+ && let expr_type = cx. typeck_results ( ) . expr_ty_adjusted ( src)
258
+ && !is_non_aggregate_primitive_type ( expr_type)
259
+ && is_default_equivalent ( cx, src)
260
+ && !expr. span . in_external_macro ( cx. tcx . sess . source_map ( ) )
261
+ && let Some ( top_crate) = std_or_core ( cx)
262
+ {
192
263
span_lint_and_then (
193
264
cx,
194
265
MEM_REPLACE_WITH_DEFAULT ,
195
- expr_span ,
266
+ expr . span ,
196
267
format ! (
197
268
"replacing a value of type `T` with `T::default()` is better expressed using `{top_crate}::mem::take`"
198
269
) ,
199
270
|diag| {
200
- if !expr_span . from_expansion ( ) {
271
+ if !expr . span . from_expansion ( ) {
201
272
let suggestion = format ! ( "{top_crate}::mem::take({})" , snippet( cx, dest. span, "" ) ) ;
202
273
203
274
diag. span_suggestion (
204
- expr_span ,
275
+ expr . span ,
205
276
"consider using" ,
206
277
suggestion,
207
278
Applicability :: MachineApplicable ,
208
279
) ;
209
280
}
210
281
} ,
211
282
) ;
283
+ true
284
+ } else {
285
+ false
212
286
}
213
287
}
214
288
@@ -233,12 +307,12 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
233
307
&& cx. tcx . is_diagnostic_item ( sym:: mem_replace, def_id)
234
308
{
235
309
// Check that second argument is `Option::None`
236
- if is_res_lang_ctor ( cx, path_res ( cx, src) , OptionNone ) {
237
- check_replace_option_with_none ( cx, dest, expr. span ) ;
238
- } else if self . msrv . meets ( msrvs:: MEM_TAKE ) && is_expr_used_or_unified ( cx. tcx , expr) {
239
- check_replace_with_default ( cx, src, dest, expr. span ) ;
310
+ if !check_replace_option_with_none ( cx, src, dest, expr. span )
311
+ && !check_replace_option_with_some ( cx, src, dest, expr. span , & self . msrv )
312
+ && !check_replace_with_default ( cx, src, dest, expr, & self . msrv )
313
+ {
314
+ check_replace_with_uninit ( cx, src, dest, expr. span ) ;
240
315
}
241
- check_replace_with_uninit ( cx, src, dest, expr. span ) ;
242
316
}
243
317
}
244
318
extract_msrv_attr ! ( LateContext ) ;
0 commit comments