Skip to content

Commit 761ecf0

Browse files
committed
fix: map_unwrap_or suggests wrongly for empty slice
1 parent 411992a commit 761ecf0

File tree

5 files changed

+166
-151
lines changed

5 files changed

+166
-151
lines changed

clippy_lints/src/derivable_impls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ fn check_struct<'tcx>(
105105
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind
106106
&& let Some(PathSegment { args, .. }) = p.segments.last()
107107
{
108-
let args = args.map(|a| a.args).unwrap_or(&[]);
108+
let args = args.map(|a| a.args).unwrap_or_default();
109109

110110
// ty_args contains the generic parameters of the type declaration, while args contains the
111111
// arguments used at instantiation time. If both len are not equal, it means that some

clippy_lints/src/methods/map_unwrap_or.rs

Lines changed: 107 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::msrvs::{self, Msrv};
33
use clippy_utils::source::snippet_with_applicability;
4-
use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
4+
use clippy_utils::ty::{get_type_diagnostic_name, is_copy};
55
use rustc_data_structures::fx::FxHashSet;
66
use rustc_errors::Applicability;
77
use rustc_hir::def::Res;
@@ -27,116 +27,120 @@ pub(super) fn check<'tcx>(
2727
msrv: Msrv,
2828
) {
2929
let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs();
30-
let is_option = is_type_diagnostic_item(cx, recv_ty, sym::Option);
31-
let is_result = is_type_diagnostic_item(cx, recv_ty, sym::Result);
30+
let is_option = match get_type_diagnostic_name(cx, recv_ty) {
31+
Some(sym::Option) => true,
32+
Some(sym::Result) if msrv.meets(cx, msrvs::RESULT_MAP_OR) => false,
33+
_ => return,
34+
};
35+
36+
let unwrap_arg_ty = cx.typeck_results().expr_ty(unwrap_arg);
37+
if !is_copy(cx, unwrap_arg_ty) {
38+
// Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
39+
// borrowck errors, see #10579 for one such instance.
40+
// In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
41+
// ```
42+
// let x = vec![1, 2];
43+
// x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
44+
// ```
45+
// This compiles, but changing it to `map_or` will produce a compile error:
46+
// ```
47+
// let x = vec![1, 2];
48+
// x.get(0..1).map_or(x, |s| s.to_vec())
49+
// ^ moving `x` here
50+
// ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
51+
// ```
52+
// So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
53+
// before the call to `unwrap_or`.
54+
55+
let mut unwrap_visitor = UnwrapVisitor {
56+
cx,
57+
identifiers: FxHashSet::default(),
58+
};
59+
unwrap_visitor.visit_expr(unwrap_arg);
3260

33-
if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR) {
34-
return;
35-
}
61+
let mut reference_visitor = ReferenceVisitor {
62+
cx,
63+
identifiers: unwrap_visitor.identifiers,
64+
unwrap_or_span: unwrap_arg.span,
65+
};
3666

37-
// lint if the caller of `map()` is an `Option`
38-
if is_option || is_result {
39-
if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) {
40-
// Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
41-
// borrowck errors, see #10579 for one such instance.
42-
// In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
43-
// ```
44-
// let x = vec![1, 2];
45-
// x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
46-
// ```
47-
// This compiles, but changing it to `map_or` will produce a compile error:
48-
// ```
49-
// let x = vec![1, 2];
50-
// x.get(0..1).map_or(x, |s| s.to_vec())
51-
// ^ moving `x` here
52-
// ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
53-
// ```
54-
// So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
55-
// before the call to `unwrap_or`.
56-
57-
let mut unwrap_visitor = UnwrapVisitor {
58-
cx,
59-
identifiers: FxHashSet::default(),
60-
};
61-
unwrap_visitor.visit_expr(unwrap_arg);
62-
63-
let mut reference_visitor = ReferenceVisitor {
64-
cx,
65-
identifiers: unwrap_visitor.identifiers,
66-
unwrap_or_span: unwrap_arg.span,
67-
};
68-
69-
let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
70-
71-
// Visit the body, and return if we've found a reference
72-
if reference_visitor.visit_body(body).is_break() {
73-
return;
74-
}
75-
}
67+
let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
7668

77-
if !unwrap_arg.span.eq_ctxt(map_span) {
69+
// Visit the body, and return if we've found a reference
70+
if reference_visitor.visit_body(body).is_break() {
7871
return;
7972
}
73+
}
74+
75+
if !unwrap_arg.span.eq_ctxt(map_span) {
76+
return;
77+
}
8078

81-
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
82-
let suggest_is_some_and = matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
79+
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
80+
let suggest_is_some_and = matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
8381
if matches!(lit.node, rustc_ast::LitKind::Bool(false)))
84-
&& msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND);
85-
86-
let mut applicability = Applicability::MachineApplicable;
87-
// get snippet for unwrap_or()
88-
let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
89-
// lint message
90-
// comparing the snippet from source to raw text ("None") below is safe
91-
// because we already have checked the type.
92-
let unwrap_snippet_none = is_option && unwrap_snippet == "None";
93-
let arg = if unwrap_snippet_none {
94-
"None"
95-
} else if suggest_is_some_and {
96-
"false"
97-
} else {
98-
"<a>"
99-
};
100-
let suggest = if unwrap_snippet_none {
101-
"and_then(<f>)"
102-
} else if suggest_is_some_and {
103-
if is_result {
104-
"is_ok_and(<f>)"
105-
} else {
106-
"is_some_and(<f>)"
107-
}
82+
&& msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND);
83+
84+
let mut applicability = Applicability::MachineApplicable;
85+
// get snippet for unwrap_or()
86+
let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
87+
// lint message
88+
// comparing the snippet from source to raw text ("None") below is safe
89+
// because we already have checked the type.
90+
let unwrap_snippet_none = is_option && unwrap_snippet == "None";
91+
let arg = if unwrap_snippet_none {
92+
"None"
93+
} else if suggest_is_some_and {
94+
"false"
95+
} else {
96+
"<a>"
97+
};
98+
let suggest = if unwrap_snippet_none {
99+
"and_then(<f>)"
100+
} else if suggest_is_some_and {
101+
if is_option {
102+
"is_some_and(<f>)"
108103
} else {
109-
"map_or(<a>, <f>)"
110-
};
111-
let msg = format!(
112-
"called `map(<f>).unwrap_or({arg})` on an `{}` value",
113-
if is_option { "Option" } else { "Result" }
114-
);
115-
116-
span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| {
117-
let map_arg_span = map_arg.span;
118-
119-
let mut suggestion = vec![
120-
(
121-
map_span,
122-
String::from(if unwrap_snippet_none {
123-
"and_then"
124-
} else if suggest_is_some_and {
125-
if is_result { "is_ok_and" } else { "is_some_and" }
126-
} else {
127-
"map_or"
128-
}),
129-
),
130-
(expr.span.with_lo(unwrap_recv.span.hi()), String::new()),
131-
];
132-
133-
if !unwrap_snippet_none && !suggest_is_some_and {
134-
suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, ")));
135-
}
136-
137-
diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability);
138-
});
139-
}
104+
"is_ok_and(<f>)"
105+
}
106+
} else {
107+
"map_or(<a>, <f>)"
108+
};
109+
let msg = format!(
110+
"called `map(<f>).unwrap_or({arg})` on an `{}` value",
111+
if is_option { "Option" } else { "Result" }
112+
);
113+
114+
span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| {
115+
let map_arg_span = map_arg.span;
116+
117+
let mut suggestion = vec![
118+
(
119+
map_span,
120+
String::from(if unwrap_snippet_none {
121+
"and_then"
122+
} else if suggest_is_some_and {
123+
if is_option { "is_some_and" } else { "is_ok_and" }
124+
} else {
125+
let unwrap_arg_ty = unwrap_arg_ty.peel_refs();
126+
if unwrap_arg_ty.is_array()
127+
&& let unwrap_arg_ty_adj = cx.typeck_results().expr_ty_adjusted(unwrap_arg).peel_refs()
128+
&& unwrap_arg_ty_adj.is_slice()
129+
{
130+
return;
131+
}
132+
"map_or"
133+
}),
134+
),
135+
(expr.span.with_lo(unwrap_recv.span.hi()), String::new()),
136+
];
137+
138+
if !unwrap_snippet_none && !suggest_is_some_and {
139+
suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, ")));
140+
}
141+
142+
diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability);
143+
});
140144
}
141145

142146
struct UnwrapVisitor<'a, 'tcx> {

clippy_lints/src/methods/map_unwrap_or_else.rs

Lines changed: 43 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
22
use clippy_utils::msrvs::{self, Msrv};
33
use clippy_utils::source::snippet;
4-
use clippy_utils::ty::is_type_diagnostic_item;
4+
use clippy_utils::ty::get_type_diagnostic_name;
55
use clippy_utils::usage::mutated_variables;
66
use rustc_errors::Applicability;
77
use rustc_hir as hir;
@@ -22,55 +22,52 @@ pub(super) fn check<'tcx>(
2222
msrv: Msrv,
2323
) -> bool {
2424
let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs();
25-
let is_option = is_type_diagnostic_item(cx, recv_ty, sym::Option);
26-
let is_result = is_type_diagnostic_item(cx, recv_ty, sym::Result);
25+
let is_option = match get_type_diagnostic_name(cx, recv_ty) {
26+
Some(sym::Option) => true,
27+
Some(sym::Result) if msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) => false,
28+
_ => return false,
29+
};
2730

28-
if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) {
29-
return false;
30-
}
31-
32-
if is_option || is_result {
33-
// Don't make a suggestion that may fail to compile due to mutably borrowing
34-
// the same variable twice.
35-
let map_mutated_vars = mutated_variables(recv, cx);
36-
let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx);
37-
if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
38-
if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
39-
return false;
40-
}
41-
} else {
31+
// Don't make a suggestion that may fail to compile due to mutably borrowing
32+
// the same variable twice.
33+
let map_mutated_vars = mutated_variables(recv, cx);
34+
let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx);
35+
if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
36+
if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
4237
return false;
4338
}
39+
} else {
40+
return false;
41+
}
4442

45-
// lint message
46-
let msg = if is_option {
47-
"called `map(<f>).unwrap_or_else(<g>)` on an `Option` value"
48-
} else {
49-
"called `map(<f>).unwrap_or_else(<g>)` on a `Result` value"
50-
};
51-
// get snippets for args to map() and unwrap_or_else()
52-
let map_snippet = snippet(cx, map_arg.span, "..");
53-
let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
54-
// lint, with note if neither arg is > 1 line and both map() and
55-
// unwrap_or_else() have the same span
56-
let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
57-
let same_span = map_arg.span.eq_ctxt(unwrap_arg.span);
58-
if same_span && !multiline {
59-
let var_snippet = snippet(cx, recv.span, "..");
60-
span_lint_and_sugg(
61-
cx,
62-
MAP_UNWRAP_OR,
63-
expr.span,
64-
msg,
65-
"try",
66-
format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"),
67-
Applicability::MachineApplicable,
68-
);
69-
return true;
70-
} else if same_span && multiline {
71-
span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
72-
return true;
73-
}
43+
// lint message
44+
let msg = if is_option {
45+
"called `map(<f>).unwrap_or_else(<g>)` on an `Option` value"
46+
} else {
47+
"called `map(<f>).unwrap_or_else(<g>)` on a `Result` value"
48+
};
49+
// get snippets for args to map() and unwrap_or_else()
50+
let map_snippet = snippet(cx, map_arg.span, "..");
51+
let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
52+
// lint, with note if neither arg is > 1 line and both map() and
53+
// unwrap_or_else() have the same span
54+
let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
55+
let same_span = map_arg.span.eq_ctxt(unwrap_arg.span);
56+
if same_span && !multiline {
57+
let var_snippet = snippet(cx, recv.span, "..");
58+
span_lint_and_sugg(
59+
cx,
60+
MAP_UNWRAP_OR,
61+
expr.span,
62+
msg,
63+
"try",
64+
format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"),
65+
Applicability::MachineApplicable,
66+
);
67+
return true;
68+
} else if same_span && multiline {
69+
span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
70+
return true;
7471
}
7572

7673
false

tests/ui/map_unwrap_or.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,11 @@ mod issue_10579 {
156156
println!("{y:?}");
157157
}
158158
}
159+
160+
fn issue15752() {
161+
struct Foo<'a>(&'a [u32]);
162+
163+
let x = Some(Foo(&[1, 2, 3]));
164+
x.map(|y| y.0).unwrap_or(&[]);
165+
//~^ map_unwrap_or
166+
}

tests/ui/map_unwrap_or.stderr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,5 +199,11 @@ LL - let _ = opt.map(|x| x > 5).unwrap_or(false);
199199
LL + let _ = opt.is_some_and(|x| x > 5);
200200
|
201201

202-
error: aborting due to 15 previous errors
202+
error: called `map(<f>).unwrap_or(<a>)` on an `Option` value
203+
--> tests/ui/map_unwrap_or.rs:164:5
204+
|
205+
LL | x.map(|y| y.0).unwrap_or(&[]);
206+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
207+
208+
error: aborting due to 16 previous errors
203209

0 commit comments

Comments
 (0)