@@ -8,7 +8,7 @@ use clippy_utils::{
88 is_default_equivalent, is_expr_used_or_unified, is_res_lang_ctor, path_res, peel_ref_operators, std_or_core,
99} ;
1010use rustc_errors:: Applicability ;
11- use rustc_hir:: LangItem :: OptionNone ;
11+ use rustc_hir:: LangItem :: { OptionNone , OptionSome } ;
1212use rustc_hir:: { Expr , ExprKind } ;
1313use rustc_lint:: { LateContext , LateLintPass } ;
1414use rustc_session:: impl_lint_pass;
@@ -43,6 +43,31 @@ declare_clippy_lint! {
4343 "replacing an `Option` with `None` instead of `take()`"
4444}
4545
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+
4671declare_clippy_lint ! {
4772 /// ### What it does
4873 /// Checks for `mem::replace(&mut _, mem::uninitialized())`
@@ -101,28 +126,67 @@ declare_clippy_lint! {
101126}
102127
103128impl_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+ }
105157
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+ }
126190}
127191
128192fn 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<'
181245 }
182246}
183247
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+ {
192263 span_lint_and_then (
193264 cx,
194265 MEM_REPLACE_WITH_DEFAULT ,
195- expr_span ,
266+ expr . span ,
196267 format ! (
197268 "replacing a value of type `T` with `T::default()` is better expressed using `{top_crate}::mem::take`"
198269 ) ,
199270 |diag| {
200- if !expr_span . from_expansion ( ) {
271+ if !expr . span . from_expansion ( ) {
201272 let suggestion = format ! ( "{top_crate}::mem::take({})" , snippet( cx, dest. span, "" ) ) ;
202273
203274 diag. span_suggestion (
204- expr_span ,
275+ expr . span ,
205276 "consider using" ,
206277 suggestion,
207278 Applicability :: MachineApplicable ,
208279 ) ;
209280 }
210281 } ,
211282 ) ;
283+ true
284+ } else {
285+ false
212286 }
213287}
214288
@@ -233,12 +307,12 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
233307 && cx. tcx . is_diagnostic_item ( sym:: mem_replace, def_id)
234308 {
235309 // 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 ) ;
240315 }
241- check_replace_with_uninit ( cx, src, dest, expr. span ) ;
242316 }
243317 }
244318 extract_msrv_attr ! ( LateContext ) ;
0 commit comments