Skip to content

Commit aff6178

Browse files
committed
manual_unwrap_or(_default): don't lint if not safe to move scrutinee
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. 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 Signed-off-by: Zihan <[email protected]>
1 parent 8229701 commit aff6178

File tree

7 files changed

+115
-5
lines changed

7 files changed

+115
-5
lines changed

clippy_lints/src/matches/manual_unwrap_or.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ use rustc_span::sym;
1010

1111
use clippy_utils::diagnostics::span_lint_and_sugg;
1212
use clippy_utils::sugg::Sugg;
13-
use clippy_utils::ty::{expr_type_is_certain, get_type_diagnostic_name, implements_trait};
14-
use clippy_utils::{is_default_equivalent, is_lint_allowed, path_res, peel_blocks, span_contains_comment};
13+
use clippy_utils::ty::{
14+
expr_type_is_certain, get_type_diagnostic_name, implements_trait, is_copy, is_type_diagnostic_item,
15+
};
16+
use clippy_utils::usage::local_used_after_expr;
17+
use clippy_utils::{
18+
is_default_equivalent, is_lint_allowed, path_res, path_to_local, peel_blocks, span_contains_comment,
19+
};
1520

1621
use super::{MANUAL_UNWRAP_OR, MANUAL_UNWRAP_OR_DEFAULT};
1722

@@ -84,7 +89,9 @@ fn handle(
8489
binding_id: HirId,
8590
) {
8691
// Only deal with situations where both alternatives return the same non-adjusted type.
87-
if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none) {
92+
if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none)
93+
|| !safe_to_move_scrutinee(cx, expr, condition)
94+
{
8895
return;
8996
}
9097

@@ -181,6 +188,29 @@ fn find_type_name<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'static
181188
}
182189
}
183190

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) = path_to_local(scrutinee)
203+
&& let scrutinee_ty = cx.typeck_results().expr_ty(scrutinee)
204+
&& is_type_diagnostic_item(cx, scrutinee_ty, 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+
184214
pub fn check_match<'tcx>(
185215
cx: &LateContext<'tcx>,
186216
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
@@ -119,3 +119,17 @@ mod issue14716 {
119119
};
120120
}
121121
}
122+
123+
fn issue_15807() {
124+
let uncopyable_res: Result<usize, String> = Ok(1);
125+
let _ = if let Ok(v) = uncopyable_res { v } else { 0 };
126+
127+
let x = uncopyable_res;
128+
let _ = x.unwrap_or_default();
129+
//~^ manual_unwrap_or_default
130+
131+
let copyable_res: Result<usize, ()> = Ok(1);
132+
let _ = copyable_res.unwrap_or_default();
133+
//~^ manual_unwrap_or_default
134+
let _ = copyable_res;
135+
}

tests/ui/manual_unwrap_or_default.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,17 @@ mod issue14716 {
160160
};
161161
}
162162
}
163+
164+
fn issue_15807() {
165+
let uncopyable_res: Result<usize, String> = Ok(1);
166+
let _ = if let Ok(v) = uncopyable_res { v } else { 0 };
167+
168+
let x = uncopyable_res;
169+
let _ = if let Ok(v) = x { v } else { 0 };
170+
//~^ manual_unwrap_or_default
171+
172+
let copyable_res: Result<usize, ()> = Ok(1);
173+
let _ = if let Ok(v) = copyable_res { v } else { 0 };
174+
//~^ manual_unwrap_or_default
175+
let _ = copyable_res;
176+
}

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:169: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:173: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)