Skip to content

Commit 1e16651

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

File tree

5 files changed

+168
-152
lines changed

5 files changed

+168
-152
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: 109 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
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};
5+
use clippy_utils::{is_res_lang_ctor, path_res};
56
use rustc_data_structures::fx::FxHashSet;
67
use rustc_errors::Applicability;
78
use rustc_hir::def::Res;
89
use rustc_hir::intravisit::{Visitor, walk_path};
9-
use rustc_hir::{ExprKind, HirId, Node, PatKind, Path, QPath};
10+
use rustc_hir::{ExprKind, HirId, LangItem, Node, PatKind, Path, QPath};
1011
use rustc_lint::LateContext;
1112
use rustc_middle::hir::nested_filter;
1213
use rustc_span::{Span, sym};
@@ -27,116 +28,120 @@ pub(super) fn check<'tcx>(
2728
msrv: Msrv,
2829
) {
2930
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);
31+
let is_option = match get_type_diagnostic_name(cx, recv_ty) {
32+
Some(sym::Option) => true,
33+
Some(sym::Result) if msrv.meets(cx, msrvs::RESULT_MAP_OR) => false,
34+
_ => return,
35+
};
36+
37+
let unwrap_arg_ty = cx.typeck_results().expr_ty(unwrap_arg);
38+
if !is_copy(cx, unwrap_arg_ty) {
39+
// Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
40+
// borrowck errors, see #10579 for one such instance.
41+
// In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
42+
// ```
43+
// let x = vec![1, 2];
44+
// x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
45+
// ```
46+
// This compiles, but changing it to `map_or` will produce a compile error:
47+
// ```
48+
// let x = vec![1, 2];
49+
// x.get(0..1).map_or(x, |s| s.to_vec())
50+
// ^ moving `x` here
51+
// ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
52+
// ```
53+
// So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
54+
// before the call to `unwrap_or`.
55+
56+
let mut unwrap_visitor = UnwrapVisitor {
57+
cx,
58+
identifiers: FxHashSet::default(),
59+
};
60+
unwrap_visitor.visit_expr(unwrap_arg);
3261

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

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-
}
68+
let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
7669

77-
if !unwrap_arg.span.eq_ctxt(map_span) {
70+
// Visit the body, and return if we've found a reference
71+
if reference_visitor.visit_body(body).is_break() {
7872
return;
7973
}
74+
}
75+
76+
if !unwrap_arg.span.eq_ctxt(map_span) {
77+
return;
78+
}
8079

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

142147
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)