1 use clippy_utils
::diagnostics
::span_lint_and_then
;
2 use clippy_utils
::source
::{snippet, snippet_with_applicability, snippet_with_context}
;
3 use clippy_utils
::ty
::is_type_diagnostic_item
;
4 use clippy_utils
::{iter_input_pats, method_chain_args}
;
5 use if_chain
::if_chain
;
6 use rustc_errors
::Applicability
;
8 use rustc_lint
::{LateContext, LateLintPass}
;
9 use rustc_middle
::ty
::{self, Ty}
;
10 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
11 use rustc_span
::source_map
::Span
;
14 declare_clippy_lint
! {
16 /// Checks for usage of `option.map(f)` where f is a function
17 /// or closure that returns the unit type `()`.
19 /// ### Why is this bad?
20 /// Readability, this can be written more clearly with
21 /// an if let statement
25 /// # fn do_stuff() -> Option<String> { Some(String::new()) }
26 /// # fn log_err_msg(foo: String) -> Option<String> { Some(foo) }
27 /// # fn format_msg(foo: String) -> String { String::new() }
28 /// let x: Option<String> = do_stuff();
29 /// x.map(log_err_msg);
30 /// # let x: Option<String> = do_stuff();
31 /// x.map(|msg| log_err_msg(format_msg(msg)));
34 /// The correct use would be:
37 /// # fn do_stuff() -> Option<String> { Some(String::new()) }
38 /// # fn log_err_msg(foo: String) -> Option<String> { Some(foo) }
39 /// # fn format_msg(foo: String) -> String { String::new() }
40 /// let x: Option<String> = do_stuff();
41 /// if let Some(msg) = x {
45 /// # let x: Option<String> = do_stuff();
46 /// if let Some(msg) = x {
47 /// log_err_msg(format_msg(msg));
50 #[clippy::version = "pre 1.29.0"]
51 pub OPTION_MAP_UNIT_FN
,
53 "using `option.map(f)`, where `f` is a function or closure that returns `()`"
56 declare_clippy_lint
! {
58 /// Checks for usage of `result.map(f)` where f is a function
59 /// or closure that returns the unit type `()`.
61 /// ### Why is this bad?
62 /// Readability, this can be written more clearly with
63 /// an if let statement
67 /// # fn do_stuff() -> Result<String, String> { Ok(String::new()) }
68 /// # fn log_err_msg(foo: String) -> Result<String, String> { Ok(foo) }
69 /// # fn format_msg(foo: String) -> String { String::new() }
70 /// let x: Result<String, String> = do_stuff();
71 /// x.map(log_err_msg);
72 /// # let x: Result<String, String> = do_stuff();
73 /// x.map(|msg| log_err_msg(format_msg(msg)));
76 /// The correct use would be:
79 /// # fn do_stuff() -> Result<String, String> { Ok(String::new()) }
80 /// # fn log_err_msg(foo: String) -> Result<String, String> { Ok(foo) }
81 /// # fn format_msg(foo: String) -> String { String::new() }
82 /// let x: Result<String, String> = do_stuff();
83 /// if let Ok(msg) = x {
86 /// # let x: Result<String, String> = do_stuff();
87 /// if let Ok(msg) = x {
88 /// log_err_msg(format_msg(msg));
91 #[clippy::version = "pre 1.29.0"]
92 pub RESULT_MAP_UNIT_FN
,
94 "using `result.map(f)`, where `f` is a function or closure that returns `()`"
97 declare_lint_pass
!(MapUnit
=> [OPTION_MAP_UNIT_FN
, RESULT_MAP_UNIT_FN
]);
99 fn is_unit_type(ty
: Ty
<'_
>) -> bool
{
100 ty
.is_unit() || ty
.is_never()
103 fn is_unit_function(cx
: &LateContext
<'_
>, expr
: &hir
::Expr
<'_
>) -> bool
{
104 let ty
= cx
.typeck_results().expr_ty(expr
);
106 if let ty
::FnDef(id
, _
) = *ty
.kind() {
107 if let Some(fn_type
) = cx
.tcx
.fn_sig(id
).no_bound_vars() {
108 return is_unit_type(fn_type
.output());
114 fn is_unit_expression(cx
: &LateContext
<'_
>, expr
: &hir
::Expr
<'_
>) -> bool
{
115 is_unit_type(cx
.typeck_results().expr_ty(expr
))
118 /// The expression inside a closure may or may not have surrounding braces and
119 /// semicolons, which causes problems when generating a suggestion. Given an
120 /// expression that evaluates to '()' or '!', recursively remove useless braces
121 /// and semi-colons until is suitable for including in the suggestion template
122 fn reduce_unit_expression
<'a
>(cx
: &LateContext
<'_
>, expr
: &'a hir
::Expr
<'_
>) -> Option
<Span
> {
123 if !is_unit_expression(cx
, expr
) {
128 hir
::ExprKind
::Call(_
, _
) | hir
::ExprKind
::MethodCall(..) => {
129 // Calls can't be reduced any more
132 hir
::ExprKind
::Block(block
, _
) => {
133 match (block
.stmts
, block
.expr
.as_ref()) {
134 (&[], Some(inner_expr
)) => {
135 // If block only contains an expression,
136 // reduce `{ X }` to `X`
137 reduce_unit_expression(cx
, inner_expr
)
139 (&[ref inner_stmt
], None
) => {
140 // If block only contains statements,
141 // reduce `{ X; }` to `X` or `X;`
142 match inner_stmt
.kind
{
143 hir
::StmtKind
::Local(local
) => Some(local
.span
),
144 hir
::StmtKind
::Expr(e
) => Some(e
.span
),
145 hir
::StmtKind
::Semi(..) => Some(inner_stmt
.span
),
146 hir
::StmtKind
::Item(..) => None
,
150 // For closures that contain multiple statements
151 // it's difficult to get a correct suggestion span
152 // for all cases (multi-line closures specifically)
154 // We do not attempt to build a suggestion for those right now.
163 fn unit_closure
<'tcx
>(
164 cx
: &LateContext
<'tcx
>,
165 expr
: &hir
::Expr
<'_
>,
166 ) -> Option
<(&'tcx hir
::Param
<'tcx
>, &'tcx hir
::Expr
<'tcx
>)> {
168 if let hir
::ExprKind
::Closure(&hir
::Closure { fn_decl, body, .. }
) = expr
.kind
;
169 let body
= cx
.tcx
.hir().body(body
);
170 let body_expr
= &body
.value
;
171 if fn_decl
.inputs
.len() == 1;
172 if is_unit_expression(cx
, body_expr
);
173 if let Some(binding
) = iter_input_pats(fn_decl
, body
).next();
175 return Some((binding
, body_expr
));
181 /// Builds a name for the let binding variable (`var_arg`)
183 /// `x.field` => `x_field`
186 /// Anything else will return `a`.
187 fn let_binding_name(cx
: &LateContext
<'_
>, var_arg
: &hir
::Expr
<'_
>) -> String
{
188 match &var_arg
.kind
{
189 hir
::ExprKind
::Field(_
, _
) => snippet(cx
, var_arg
.span
, "_").replace('
.'
, "_"),
190 hir
::ExprKind
::Path(_
) => format
!("_{}", snippet(cx
, var_arg
.span
, "")),
191 _
=> "a".to_string(),
196 fn suggestion_msg(function_type
: &str, map_type
: &str) -> String
{
198 "called `map(f)` on an `{0}` value where `f` is a {1} that returns the unit type `()`",
199 map_type
, function_type
204 cx
: &LateContext
<'_
>,
205 stmt
: &hir
::Stmt
<'_
>,
206 expr
: &hir
::Expr
<'_
>,
207 map_args
: (&hir
::Expr
<'_
>, &[hir
::Expr
<'_
>]),
209 let var_arg
= &map_args
.0;
211 let (map_type
, variant
, lint
) = if is_type_diagnostic_item(cx
, cx
.typeck_results().expr_ty(var_arg
), sym
::Option
) {
212 ("Option", "Some", OPTION_MAP_UNIT_FN
)
213 } else if is_type_diagnostic_item(cx
, cx
.typeck_results().expr_ty(var_arg
), sym
::Result
) {
214 ("Result", "Ok", RESULT_MAP_UNIT_FN
)
218 let fn_arg
= &map_args
.1[0];
220 if is_unit_function(cx
, fn_arg
) {
221 let mut applicability
= Applicability
::MachineApplicable
;
222 let msg
= suggestion_msg("function", map_type
);
223 let suggestion
= format
!(
224 "if let {0}({binding}) = {1} {{ {2}({binding}) }}",
226 snippet_with_applicability(cx
, var_arg
.span
, "_", &mut applicability
),
227 snippet_with_applicability(cx
, fn_arg
.span
, "_", &mut applicability
),
228 binding
= let_binding_name(cx
, var_arg
)
231 span_lint_and_then(cx
, lint
, expr
.span
, &msg
, |diag
| {
232 diag
.span_suggestion(stmt
.span
, "try this", suggestion
, applicability
);
234 } else if let Some((binding
, closure_expr
)) = unit_closure(cx
, fn_arg
) {
235 let msg
= suggestion_msg("closure", map_type
);
237 span_lint_and_then(cx
, lint
, expr
.span
, &msg
, |diag
| {
238 if let Some(reduced_expr_span
) = reduce_unit_expression(cx
, closure_expr
) {
239 let mut applicability
= Applicability
::MachineApplicable
;
240 let suggestion
= format
!(
241 "if let {0}({1}) = {2} {{ {3} }}",
243 snippet_with_applicability(cx
, binding
.pat
.span
, "_", &mut applicability
),
244 snippet_with_applicability(cx
, var_arg
.span
, "_", &mut applicability
),
245 snippet_with_context(cx
, reduced_expr_span
, var_arg
.span
.ctxt(), "_", &mut applicability
).0,
247 diag
.span_suggestion(stmt
.span
, "try this", suggestion
, applicability
);
249 let suggestion
= format
!(
250 "if let {0}({1}) = {2} {{ ... }}",
252 snippet(cx
, binding
.pat
.span
, "_"),
253 snippet(cx
, var_arg
.span
, "_"),
255 diag
.span_suggestion(stmt
.span
, "try this", suggestion
, Applicability
::HasPlaceholders
);
261 impl<'tcx
> LateLintPass
<'tcx
> for MapUnit
{
262 fn check_stmt(&mut self, cx
: &LateContext
<'_
>, stmt
: &hir
::Stmt
<'_
>) {
263 if stmt
.span
.from_expansion() {
267 if let hir
::StmtKind
::Semi(expr
) = stmt
.kind
{
268 if let Some(arglists
) = method_chain_args(expr
, &["map"]) {
269 lint_map_unit_fn(cx
, stmt
, expr
, arglists
[0]);