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};
8 /// Checks for instances of `map_err(|_| Some::Enum)`
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
24 /// impl fmt::Display for Error {
25 /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 /// Error::Indivisible => write!(f, "could not divide input by three"),
28 /// Error::Remainder(remainder) => write!(
30 /// "input is not divisible by three, remainder = {}",
37 /// impl std::error::Error for Error {}
39 /// fn divisible_by_3(input: &str) -> Result<(), Error> {
42 /// .map_err(|_| Error::Indivisible)
44 /// .and_then(|remainder| {
45 /// if remainder == 0 {
48 /// Err(Error::Remainder(remainder as u8))
56 /// use std::{fmt, num::ParseIntError};
60 /// Indivisible(ParseIntError),
64 /// impl fmt::Display for Error {
65 /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 /// Error::Indivisible(_) => write!(f, "could not divide input by three"),
68 /// Error::Remainder(remainder) => write!(
70 /// "input is not divisible by three, remainder = {}",
77 /// impl std::error::Error for Error {
78 /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
80 /// Error::Indivisible(source) => Some(source),
86 /// fn divisible_by_3(input: &str) -> Result<(), Error> {
89 /// .map_err(Error::Indivisible)
91 /// .and_then(|remainder| {
92 /// if remainder == 0 {
95 /// Err(Error::Remainder(remainder as u8))
102 "`map_err` should not ignore the original error"
105 declare_lint_pass!(MapErrIgnore => [MAP_ERR_IGNORE]);
107 impl<'tcx> LateLintPass<'tcx> for MapErrIgnore {
108 // do not try to lint if this is from a macro or desugaring
109 fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
110 if e.span.from_expansion() {
114 // check if this is a method call (e.g. x.foo())
115 if let ExprKind::MethodCall(method, _t_span, args, _) = e.kind {
116 // only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1]
117 // Enum::Variant[2]))
118 if method.ident.as_str() == "map_err" && args.len() == 2 {
119 // make sure the first argument is a closure, and grab the CaptureRef, body_id, and body_span fields
120 if let ExprKind::Closure(capture, _, body_id, body_span, _) = args[1].kind {
121 // check if this is by Reference (meaning there's no move statement)
122 if capture == CaptureBy::Ref {
123 // Get the closure body to check the parameters and values
124 let closure_body = cx.tcx.hir().body(body_id);
125 // make sure there's only one parameter (`|_|`)
126 if closure_body.params.len() == 1 {
127 // make sure that parameter is the wild token (`_`)
128 if let PatKind::Wild = closure_body.params[0].pat.kind {
129 // span the area of the closure capture and warn that the
130 // original error will be thrown away
135 "`map_err(|_|...` wildcard pattern discards the original error",
137 "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)",