]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/map_err_ignore.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / map_err_ignore.rs
1 use clippy_utils::diagnostics::span_lint_and_help;
2 use rustc_hir::{CaptureBy, Expr, ExprKind, PatKind};
3 use rustc_lint::{LateContext, LateLintPass};
4 use rustc_session::{declare_lint_pass, declare_tool_lint};
5
6 declare_clippy_lint! {
7 /// ### What it does
8 /// Checks for instances of `map_err(|_| Some::Enum)`
9 ///
10 /// ### Why is this bad?
11 /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error
12 ///
13 /// ### Example
14 /// Before:
15 /// ```rust
16 /// use std::fmt;
17 ///
18 /// #[derive(Debug)]
19 /// enum Error {
20 /// Indivisible,
21 /// Remainder(u8),
22 /// }
23 ///
24 /// impl fmt::Display for Error {
25 /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 /// match self {
27 /// Error::Indivisible => write!(f, "could not divide input by three"),
28 /// Error::Remainder(remainder) => write!(
29 /// f,
30 /// "input is not divisible by three, remainder = {}",
31 /// remainder
32 /// ),
33 /// }
34 /// }
35 /// }
36 ///
37 /// impl std::error::Error for Error {}
38 ///
39 /// fn divisible_by_3(input: &str) -> Result<(), Error> {
40 /// input
41 /// .parse::<i32>()
42 /// .map_err(|_| Error::Indivisible)
43 /// .map(|v| v % 3)
44 /// .and_then(|remainder| {
45 /// if remainder == 0 {
46 /// Ok(())
47 /// } else {
48 /// Err(Error::Remainder(remainder as u8))
49 /// }
50 /// })
51 /// }
52 /// ```
53 ///
54 /// After:
55 /// ```rust
56 /// use std::{fmt, num::ParseIntError};
57 ///
58 /// #[derive(Debug)]
59 /// enum Error {
60 /// Indivisible(ParseIntError),
61 /// Remainder(u8),
62 /// }
63 ///
64 /// impl fmt::Display for Error {
65 /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 /// match self {
67 /// Error::Indivisible(_) => write!(f, "could not divide input by three"),
68 /// Error::Remainder(remainder) => write!(
69 /// f,
70 /// "input is not divisible by three, remainder = {}",
71 /// remainder
72 /// ),
73 /// }
74 /// }
75 /// }
76 ///
77 /// impl std::error::Error for Error {
78 /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
79 /// match self {
80 /// Error::Indivisible(source) => Some(source),
81 /// _ => None,
82 /// }
83 /// }
84 /// }
85 ///
86 /// fn divisible_by_3(input: &str) -> Result<(), Error> {
87 /// input
88 /// .parse::<i32>()
89 /// .map_err(Error::Indivisible)
90 /// .map(|v| v % 3)
91 /// .and_then(|remainder| {
92 /// if remainder == 0 {
93 /// Ok(())
94 /// } else {
95 /// Err(Error::Remainder(remainder as u8))
96 /// }
97 /// })
98 /// }
99 /// ```
100 #[clippy::version = "1.48.0"]
101 pub MAP_ERR_IGNORE,
102 restriction,
103 "`map_err` should not ignore the original error"
104 }
105
106 declare_lint_pass!(MapErrIgnore => [MAP_ERR_IGNORE]);
107
108 impl<'tcx> LateLintPass<'tcx> for MapErrIgnore {
109 // do not try to lint if this is from a macro or desugaring
110 fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
111 if e.span.from_expansion() {
112 return;
113 }
114
115 // check if this is a method call (e.g. x.foo())
116 if let ExprKind::MethodCall(method, args, _) = e.kind {
117 // only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1]
118 // Enum::Variant[2]))
119 if method.ident.as_str() == "map_err" && args.len() == 2 {
120 // make sure the first argument is a closure, and grab the CaptureRef, BodyId, and fn_decl_span
121 // fields
122 if let ExprKind::Closure {
123 capture_clause,
124 body,
125 fn_decl_span,
126 ..
127 } = args[1].kind
128 {
129 // check if this is by Reference (meaning there's no move statement)
130 if capture_clause == CaptureBy::Ref {
131 // Get the closure body to check the parameters and values
132 let closure_body = cx.tcx.hir().body(body);
133 // make sure there's only one parameter (`|_|`)
134 if closure_body.params.len() == 1 {
135 // make sure that parameter is the wild token (`_`)
136 if let PatKind::Wild = closure_body.params[0].pat.kind {
137 // span the area of the closure capture and warn that the
138 // original error will be thrown away
139 span_lint_and_help(
140 cx,
141 MAP_ERR_IGNORE,
142 fn_decl_span,
143 "`map_err(|_|...` wildcard pattern discards the original error",
144 None,
145 "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)",
146 );
147 }
148 }
149 }
150 }
151 }
152 }
153 }
154 }