Skip to content

Commit 35e55a6

Browse files
authored
manual_unwrap_or(_default): don't lint if not safe to move scrutinee (#15817)
When matched against `Result` with copyable `Ok` variant, uncopyable scrutinee won't actually be moved. But `manual_unwrap_or`/`manual_unwrap_or_default` will force it to move, which can cause problem if scrutinee is used later. Fixes #15807 changelog: [`manual_unwrap_or`]: don't lint if not safe to move scrutinee changelog: [`manual_unwrap_or_default`]: don't lint if not safe to move scrutinee
2 parents 7012a5d + d449806 commit 35e55a6

File tree

7 files changed

+111
-5
lines changed

7 files changed

+111
-5
lines changed

clippy_lints/src/matches/manual_unwrap_or.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::consts::ConstEvalCtxt;
2-
use clippy_utils::res::{MaybeDef, MaybeQPath};
2+
use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath};
33
use clippy_utils::source::{SpanRangeExt as _, indent_of, reindent_multiline};
44
use rustc_ast::{BindingMode, ByRef};
55
use rustc_errors::Applicability;
@@ -11,7 +11,8 @@ use rustc_span::sym;
1111

1212
use clippy_utils::diagnostics::span_lint_and_sugg;
1313
use clippy_utils::sugg::Sugg;
14-
use clippy_utils::ty::{expr_type_is_certain, implements_trait};
14+
use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_copy};
15+
use clippy_utils::usage::local_used_after_expr;
1516
use clippy_utils::{is_default_equivalent, is_lint_allowed, peel_blocks, span_contains_comment};
1617

1718
use super::{MANUAL_UNWRAP_OR, MANUAL_UNWRAP_OR_DEFAULT};
@@ -87,7 +88,9 @@ fn handle(
8788
binding_id: HirId,
8889
) {
8990
// Only deal with situations where both alternatives return the same non-adjusted type.
90-
if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none) {
91+
if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none)
92+
|| !safe_to_move_scrutinee(cx, expr, condition)
93+
{
9194
return;
9295
}
9396

@@ -185,6 +188,29 @@ fn find_type_name<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'static
185188
}
186189
}
187190

191+
/// Checks whether it is safe to move scrutinee.
192+
/// It is not safe to move if:
193+
/// 1. `scrutinee` is a `Result` that doesn't implemenet `Copy`, mainly because the `Err`
194+
/// variant is not copyable.
195+
/// 2. `expr` is a local variable that is used after the if-let-else expression.
196+
/// ```rust,ignore
197+
/// let foo: Result<usize, String> = Ok(0);
198+
/// let v = if let Ok(v) = foo { v } else { 1 };
199+
/// let bar = foo;
200+
/// ```
201+
fn safe_to_move_scrutinee(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>) -> bool {
202+
if let Some(hir_id) = scrutinee.res_local_id()
203+
&& let scrutinee_ty = cx.typeck_results().expr_ty(scrutinee)
204+
&& scrutinee_ty.is_diag_item(cx, sym::Result)
205+
&& !is_copy(cx, scrutinee_ty)
206+
&& local_used_after_expr(cx, hir_id, expr)
207+
{
208+
false
209+
} else {
210+
true
211+
}
212+
}
213+
188214
pub fn check_match<'tcx>(
189215
cx: &LateContext<'tcx>,
190216
expr: &'tcx Expr<'tcx>,

tests/ui/manual_unwrap_or.fixed

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,4 +250,18 @@ fn allowed_manual_unwrap_or_zero() -> u32 {
250250
Some(42).unwrap_or(0)
251251
}
252252

253+
fn issue_15807() {
254+
let uncopyable_res: Result<usize, String> = Ok(1);
255+
let _ = if let Ok(v) = uncopyable_res { v } else { 2 };
256+
257+
let x = uncopyable_res;
258+
let _ = x.unwrap_or(2);
259+
//~^ manual_unwrap_or
260+
261+
let copyable_res: Result<usize, ()> = Ok(1);
262+
let _ = copyable_res.unwrap_or(2);
263+
//~^ manual_unwrap_or
264+
let _ = copyable_res;
265+
}
266+
253267
fn main() {}

tests/ui/manual_unwrap_or.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,4 +329,18 @@ fn allowed_manual_unwrap_or_zero() -> u32 {
329329
}
330330
}
331331

332+
fn issue_15807() {
333+
let uncopyable_res: Result<usize, String> = Ok(1);
334+
let _ = if let Ok(v) = uncopyable_res { v } else { 2 };
335+
336+
let x = uncopyable_res;
337+
let _ = if let Ok(v) = x { v } else { 2 };
338+
//~^ manual_unwrap_or
339+
340+
let copyable_res: Result<usize, ()> = Ok(1);
341+
let _ = if let Ok(v) = copyable_res { v } else { 2 };
342+
//~^ manual_unwrap_or
343+
let _ = copyable_res;
344+
}
345+
332346
fn main() {}

tests/ui/manual_unwrap_or.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,5 +201,17 @@ LL | | 0
201201
LL | | }
202202
| |_____^ help: replace with: `Some(42).unwrap_or(0)`
203203

204-
error: aborting due to 18 previous errors
204+
error: this pattern reimplements `Result::unwrap_or`
205+
--> tests/ui/manual_unwrap_or.rs:337:13
206+
|
207+
LL | let _ = if let Ok(v) = x { v } else { 2 };
208+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `x.unwrap_or(2)`
209+
210+
error: this pattern reimplements `Result::unwrap_or`
211+
--> tests/ui/manual_unwrap_or.rs:341:13
212+
|
213+
LL | let _ = if let Ok(v) = copyable_res { v } else { 2 };
214+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `copyable_res.unwrap_or(2)`
215+
216+
error: aborting due to 20 previous errors
205217

tests/ui/manual_unwrap_or_default.fixed

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,17 @@ mod issue14716 {
128128
};
129129
}
130130
}
131+
132+
fn issue_15807() {
133+
let uncopyable_res: Result<usize, String> = Ok(1);
134+
let _ = if let Ok(v) = uncopyable_res { v } else { 0 };
135+
136+
let x = uncopyable_res;
137+
let _ = x.unwrap_or_default();
138+
//~^ manual_unwrap_or_default
139+
140+
let copyable_res: Result<usize, ()> = Ok(1);
141+
let _ = copyable_res.unwrap_or_default();
142+
//~^ manual_unwrap_or_default
143+
let _ = copyable_res;
144+
}

tests/ui/manual_unwrap_or_default.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,17 @@ mod issue14716 {
169169
};
170170
}
171171
}
172+
173+
fn issue_15807() {
174+
let uncopyable_res: Result<usize, String> = Ok(1);
175+
let _ = if let Ok(v) = uncopyable_res { v } else { 0 };
176+
177+
let x = uncopyable_res;
178+
let _ = if let Ok(v) = x { v } else { 0 };
179+
//~^ manual_unwrap_or_default
180+
181+
let copyable_res: Result<usize, ()> = Ok(1);
182+
let _ = if let Ok(v) = copyable_res { v } else { 0 };
183+
//~^ manual_unwrap_or_default
184+
let _ = copyable_res;
185+
}

tests/ui/manual_unwrap_or_default.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,17 @@ LL | | 0
9797
LL | | }
9898
| |_____^ help: replace it with: `Some(42).unwrap_or_default()`
9999

100-
error: aborting due to 9 previous errors
100+
error: if let can be simplified with `.unwrap_or_default()`
101+
--> tests/ui/manual_unwrap_or_default.rs:178:13
102+
|
103+
LL | let _ = if let Ok(v) = x { v } else { 0 };
104+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x.unwrap_or_default()`
105+
106+
error: if let can be simplified with `.unwrap_or_default()`
107+
--> tests/ui/manual_unwrap_or_default.rs:182:13
108+
|
109+
LL | let _ = if let Ok(v) = copyable_res { v } else { 0 };
110+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `copyable_res.unwrap_or_default()`
111+
112+
error: aborting due to 11 previous errors
101113

0 commit comments

Comments
 (0)