]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/map_err_ignore.rs
Added tests for map_err, ignored map_err lint on drop_ref tests
[rust.git] / clippy_lints / src / map_err_ignore.rs
1 use crate::utils::span_lint_and_sugg;
2 use rustc_errors::Applicability;
3 use rustc_hir::{CaptureBy, Expr, ExprKind, PatKind, QPath};
4 use rustc_lint::{LateContext, LateLintPass};
5 use rustc_session::{declare_lint_pass, declare_tool_lint};
6
7 declare_clippy_lint! {
8     /// **What it does:** Checks for instances of `map_err(|_| Some::Enum)`
9     ///
10     /// **Why is this bad?** This map_err throws away the original error rather than allowing the enum to bubble the original error
11     ///
12     /// **Known problems:** None.
13     ///
14     /// **Example:**
15     ///
16     /// ```rust
17     /// enum Errors {
18     ///    Ignore
19     ///}
20     ///fn main() -> Result<(), Errors> {
21     ///
22     ///    let x = u32::try_from(-123_i32);
23     ///
24     ///    println!("{:?}", x.map_err(|_| Errors::Ignore));
25     ///
26     ///    Ok(())
27     ///}
28     /// ```
29     /// Use instead:
30     /// ```rust
31     /// enum Errors {
32     ///    WithContext(TryFromIntError)
33     ///}
34     ///fn main() -> Result<(), Errors> {
35     ///
36     ///    let x = u32::try_from(-123_i32);
37     ///
38     ///    println!("{:?}", x.map_err(|e| Errors::WithContext(e)));
39     ///
40     ///    Ok(())
41     ///}
42     /// ```
43     pub MAP_ERR_IGNORE,
44     style,
45     "`map_err` should not ignore the original error"
46 }
47
48 declare_lint_pass!(MapErrIgnore => [MAP_ERR_IGNORE]);
49
50 impl<'tcx> LateLintPass<'tcx> for MapErrIgnore {
51     // do not try to lint if this is from a macro or desugaring
52     fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
53         if e.span.from_expansion() {
54             return;
55         }
56
57         // check if this is a method call (e.g. x.foo())
58         if let ExprKind::MethodCall(ref method, _t_span, ref args, _) = e.kind {
59             // only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1]
60             // Enum::Variant[2]))
61             if method.ident.as_str() == "map_err" && args.len() == 2 {
62                 // make sure the first argument is a closure, and grab the CaptureRef, body_id, and body_span fields
63                 if let ExprKind::Closure(capture, _, body_id, body_span, _) = args[1].kind {
64                     // check if this is by Reference (meaning there's no move statement)
65                     if capture == CaptureBy::Ref {
66                         // Get the closure body to check the parameters and values
67                         let closure_body = cx.tcx.hir().body(body_id);
68                         // make sure there's only one parameter (`|_|`)
69                         if closure_body.params.len() == 1 {
70                             // make sure that parameter is the wild token (`_`)
71                             if let PatKind::Wild = closure_body.params[0].pat.kind {
72                                 // Check the value of the closure to see if we can build the enum we are throwing away
73                                 // the error for make sure this is a Path
74                                 if let ExprKind::Path(q_path) = &closure_body.value.kind {
75                                     // this should be a resolved path, only keep the path field
76                                     if let QPath::Resolved(_, path) = q_path {
77                                         // finally get the idents for each path segment collect them as a string and
78                                         // join them with the path separator ("::"")
79                                         let closure_fold: String = path
80                                             .segments
81                                             .iter()
82                                             .map(|x| x.ident.as_str().to_string())
83                                             .collect::<Vec<String>>()
84                                             .join("::");
85                                         //Span the body of the closure (the |...| bit) and suggest the fix by taking
86                                         // the error and encapsulating it in the enum
87                                         span_lint_and_sugg(
88                                             cx,
89                                             MAP_ERR_IGNORE,
90                                             body_span,
91                                             "`map_err` has thrown away the original error",
92                                             "Allow the error enum to encapsulate the original error",
93                                             format!("|e| {}(e)", closure_fold),
94                                             Applicability::HasPlaceholders,
95                                         );
96                                     }
97                                 } else {
98                                     //If we cannot build the enum in a human readable way just suggest not throwing way
99                                     // the error
100                                     span_lint_and_sugg(
101                                         cx,
102                                         MAP_ERR_IGNORE,
103                                         body_span,
104                                         "`map_err` has thrown away the original error",
105                                         "Allow the error enum to encapsulate the original error",
106                                         "|e|".to_string(),
107                                         Applicability::HasPlaceholders,
108                                     );
109                                 }
110                             }
111                         }
112                     }
113                 }
114             }
115         }
116     }
117 }