-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathsingle_option_map.rs
91 lines (87 loc) · 3.43 KB
/
single_option_map.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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<T>` may require additional
/// `Some(_)` and `unwrap` if all you have is a `T`.
///
/// ### Example
/// ```no_run
/// fn double(param: Option<u32>) -> Option<u32> {
/// 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",
);
}
}
}
}