1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::source::snippet;
3 use clippy_utils::ty::is_type_diagnostic_item;
4 use clippy_utils::{is_res_lang_ctor, path_def_id, path_res};
5 use rustc_errors::Applicability;
7 use rustc_hir::LangItem::{OptionNone, OptionSome};
8 use rustc_lint::LateContext;
9 use rustc_middle::ty::DefIdTree;
10 use rustc_span::symbol::sym;
12 use super::OPTION_MAP_OR_NONE;
13 use super::RESULT_MAP_OR_INTO_OPTION;
15 // The expression inside a closure may or may not have surrounding braces
16 // which causes problems when generating a suggestion.
17 fn reduce_unit_expression<'a>(expr: &'a hir::Expr<'_>) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
19 hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)),
20 hir::ExprKind::Block(block, _) => {
21 match (block.stmts, block.expr) {
22 (&[], Some(inner_expr)) => {
23 // If block only contains an expression,
24 // reduce `|x| { x + 1 }` to `|x| x + 1`
25 reduce_unit_expression(inner_expr)
34 /// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
35 pub(super) fn check<'tcx>(
36 cx: &LateContext<'tcx>,
37 expr: &'tcx hir::Expr<'_>,
38 recv: &'tcx hir::Expr<'_>,
39 def_arg: &'tcx hir::Expr<'_>,
40 map_arg: &'tcx hir::Expr<'_>,
42 let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
43 let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
45 // There are two variants of this `map_or` lint:
46 // (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>`
47 // (2) using `map_or` as a combinator instead of `and_then`
49 // (For this lint) we don't care if any other type calls `map_or`
50 if !is_option && !is_result {
54 if !is_res_lang_ctor(cx, path_res(cx, def_arg), OptionNone) {
59 let f_arg_is_some = is_res_lang_ctor(cx, path_res(cx, map_arg), OptionSome);
62 let self_snippet = snippet(cx, recv.span, "..");
64 if let hir::ExprKind::Closure(&hir::Closure { body, fn_decl_span, .. }) = map_arg.kind;
65 let arg_snippet = snippet(cx, fn_decl_span, "..");
66 let body = cx.tcx.hir().body(body);
67 if let Some((func, [arg_char])) = reduce_unit_expression(body.value);
68 if let Some(id) = path_def_id(cx, func).map(|ctor_id| cx.tcx.parent(ctor_id));
69 if Some(id) == cx.tcx.lang_items().option_some_variant();
71 let func_snippet = snippet(cx, arg_char.span, "..");
72 let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
74 return span_lint_and_sugg(
79 "try using `map` instead",
80 format!("{self_snippet}.map({arg_snippet} {func_snippet})"),
81 Applicability::MachineApplicable,
86 let func_snippet = snippet(cx, map_arg.span, "..");
87 let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
88 `and_then(..)` instead";
94 "try using `and_then` instead",
95 format!("{self_snippet}.and_then({func_snippet})"),
96 Applicability::MachineApplicable,
98 } else if f_arg_is_some {
99 let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \
101 let self_snippet = snippet(cx, recv.span, "..");
104 RESULT_MAP_OR_INTO_OPTION,
107 "try using `ok` instead",
108 format!("{self_snippet}.ok()"),
109 Applicability::MachineApplicable,