use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{path_res, peel_blocks}; use rustc_hir::def::Res; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, ExprKind, FnDecl, FnRetTy}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::{Span, sym}; declare_clippy_lint! { /// ### What it does /// Checks for functions with method calls to `.map(_)` on an arg /// of type `Option` as the outermost expression. /// /// ### Why is this bad? /// Taking and returning an `Option` may require additional /// `Some(_)` and `unwrap` if all you have is a `T`. /// /// ### Example /// ```no_run /// fn double(param: Option) -> Option { /// param.map(|x| x * 2) /// } /// ``` /// Use instead: /// ```no_run /// fn double(param: u32) -> u32 { /// param * 2 /// } /// ``` #[clippy::version = "1.86.0"] pub SINGLE_OPTION_MAP, nursery, "Checks for functions with method calls to `.map(_)` on an arg of type `Option` as the outermost expression." } declare_lint_pass!(SingleOptionMap => [SINGLE_OPTION_MAP]); impl<'tcx> LateLintPass<'tcx> for SingleOptionMap { fn check_fn( &mut self, cx: &LateContext<'tcx>, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'tcx>, body: &'tcx Body<'tcx>, span: Span, _fn_def: LocalDefId, ) { if let FnRetTy::Return(_ret) = decl.output && matches!(kind, FnKind::ItemFn(_, _, _) | FnKind::Method(_, _)) { let func_body = peel_blocks(body.value); if let ExprKind::MethodCall(method_name, callee, args, _span) = func_body.kind && method_name.ident.name == sym::map && let callee_type = cx.typeck_results().expr_ty(callee) && is_type_diagnostic_item(cx, callee_type, sym::Option) && let ExprKind::Path(_path) = callee.kind && let Res::Local(_id) = path_res(cx, callee) && matches!(path_res(cx, callee), Res::Local(_id)) && !matches!(args[0].kind, ExprKind::Path(_)) { if let ExprKind::Closure(closure) = args[0].kind { let Body { params: [..], value } = cx.tcx.hir_body(closure.body); if let ExprKind::Call(func, f_args) = value.kind && matches!(func.kind, ExprKind::Path(_)) && f_args.iter().all(|arg| matches!(arg.kind, ExprKind::Path(_))) { return; } else if let ExprKind::MethodCall(_segment, receiver, method_args, _span) = value.kind && matches!(receiver.kind, ExprKind::Path(_)) && method_args.iter().all(|arg| matches!(arg.kind, ExprKind::Path(_))) && method_args.iter().all(|arg| matches!(path_res(cx, arg), Res::Local(_))) { return; } } span_lint_and_help( cx, SINGLE_OPTION_MAP, span, "`fn` that only maps over argument", None, "move the `.map` to the caller or to an `_opt` function", ); } } } }