2020-09-02 23:21:34 +00:00
use crate ::utils ::span_lint_and_help ;
use rustc_hir ::{ CaptureBy , Expr , ExprKind , PatKind } ;
2020-09-01 20:26:59 +00:00
use rustc_lint ::{ LateContext , LateLintPass } ;
use rustc_session ::{ declare_lint_pass , declare_tool_lint } ;
declare_clippy_lint! {
/// **What it does:** Checks for instances of `map_err(|_| Some::Enum)`
///
2020-09-08 16:05:18 +00:00
/// **Why is this bad?** This map_err throws away the original error rather than allowing the enum to contain and report the cause of the error
2020-09-01 20:26:59 +00:00
///
/// **Known problems:** None.
///
/// **Example:**
2020-09-02 23:21:34 +00:00
/// Before:
2020-09-01 20:26:59 +00:00
/// ```rust
2020-09-04 22:55:13 +00:00
/// use std::fmt;
2020-09-02 23:21:34 +00:00
///
/// #[derive(Debug)]
2020-09-04 22:55:13 +00:00
/// enum Error {
/// Indivisible,
/// Remainder(u8),
/// }
///
/// impl fmt::Display for Error {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// match self {
/// Error::Indivisible => write!(f, "could not divide input by three"),
/// Error::Remainder(remainder) => write!(
/// f,
/// "input is not divisible by three, remainder = {}",
/// remainder
/// ),
/// }
/// }
2020-09-02 23:21:34 +00:00
/// }
2020-09-01 20:59:37 +00:00
///
2020-09-04 22:55:13 +00:00
/// impl std::error::Error for Error {}
2020-09-01 20:26:59 +00:00
///
2020-09-04 22:55:13 +00:00
/// fn divisible_by_3(input: &str) -> Result<(), Error> {
/// input
/// .parse::<i32>()
/// .map_err(|_| Error::Indivisible)
/// .map(|v| v % 3)
/// .and_then(|remainder| {
/// if remainder == 0 {
/// Ok(())
/// } else {
/// Err(Error::Remainder(remainder as u8))
/// }
/// })
2020-09-02 23:21:34 +00:00
/// }
/// ```
2020-09-01 20:26:59 +00:00
///
2020-09-02 23:21:34 +00:00
/// After:
/// ```rust
2020-09-04 22:55:13 +00:00
/// use std::{fmt, num::ParseIntError};
2020-09-02 23:21:34 +00:00
///
/// #[derive(Debug)]
2020-09-04 22:55:13 +00:00
/// enum Error {
/// Indivisible(ParseIntError),
/// Remainder(u8),
2020-09-02 23:21:34 +00:00
/// }
///
2020-09-04 22:55:13 +00:00
/// impl fmt::Display for Error {
2020-09-02 23:21:34 +00:00
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2020-09-04 22:55:13 +00:00
/// match self {
/// Error::Indivisible(_) => write!(f, "could not divide input by three"),
/// Error::Remainder(remainder) => write!(
/// f,
/// "input is not divisible by three, remainder = {}",
/// remainder
/// ),
2020-09-02 23:21:34 +00:00
/// }
/// }
/// }
///
2020-09-04 22:55:13 +00:00
/// impl std::error::Error for Error {
/// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
/// match self {
/// Error::Indivisible(source) => Some(source),
/// _ => None,
/// }
2020-09-02 23:21:34 +00:00
/// }
/// }
2020-09-01 20:26:59 +00:00
///
2020-09-04 22:55:13 +00:00
/// fn divisible_by_3(input: &str) -> Result<(), Error> {
/// input
/// .parse::<i32>()
/// .map_err(Error::Indivisible)
/// .map(|v| v % 3)
/// .and_then(|remainder| {
/// if remainder == 0 {
/// Ok(())
/// } else {
/// Err(Error::Remainder(remainder as u8))
/// }
/// })
2020-09-02 23:21:34 +00:00
/// }
2020-09-01 20:26:59 +00:00
/// ```
pub MAP_ERR_IGNORE ,
2020-12-03 21:11:52 +00:00
restriction ,
2020-09-01 20:26:59 +00:00
" `map_err` should not ignore the original error "
}
declare_lint_pass! ( MapErrIgnore = > [ MAP_ERR_IGNORE ] ) ;
impl < ' tcx > LateLintPass < ' tcx > for MapErrIgnore {
2020-09-01 20:59:37 +00:00
// do not try to lint if this is from a macro or desugaring
2020-09-01 20:26:59 +00:00
fn check_expr ( & mut self , cx : & LateContext < '_ > , e : & Expr < '_ > ) {
if e . span . from_expansion ( ) {
return ;
}
// check if this is a method call (e.g. x.foo())
if let ExprKind ::MethodCall ( ref method , _t_span , ref args , _ ) = e . kind {
2020-09-01 20:59:37 +00:00
// only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1]
// Enum::Variant[2]))
2020-09-01 20:26:59 +00:00
if method . ident . as_str ( ) = = " map_err " & & args . len ( ) = = 2 {
// make sure the first argument is a closure, and grab the CaptureRef, body_id, and body_span fields
2020-09-01 20:59:37 +00:00
if let ExprKind ::Closure ( capture , _ , body_id , body_span , _ ) = args [ 1 ] . kind {
2020-09-01 20:26:59 +00:00
// check if this is by Reference (meaning there's no move statement)
2020-09-01 20:59:37 +00:00
if capture = = CaptureBy ::Ref {
// Get the closure body to check the parameters and values
2020-09-01 20:26:59 +00:00
let closure_body = cx . tcx . hir ( ) . body ( body_id ) ;
// make sure there's only one parameter (`|_|`)
2020-09-01 20:59:37 +00:00
if closure_body . params . len ( ) = = 1 {
// make sure that parameter is the wild token (`_`)
2020-09-01 20:26:59 +00:00
if let PatKind ::Wild = closure_body . params [ 0 ] . pat . kind {
2020-09-02 23:21:34 +00:00
// span the area of the closure capture and warn that the
// original error will be thrown away
span_lint_and_help (
cx ,
MAP_ERR_IGNORE ,
body_span ,
2020-09-08 16:05:18 +00:00
" `map_err(|_|...` ignores the original error " ,
2020-09-02 23:21:34 +00:00
None ,
2020-12-03 21:11:52 +00:00
" Consider wrapping the error in an enum variant for more error context, or using a named wildcard (`.map_err(|_ignored| ...`) to intentionally ignore the error " ,
2020-09-02 23:21:34 +00:00
) ;
2020-09-01 20:26:59 +00:00
}
}
2020-09-01 20:59:37 +00:00
}
2020-09-01 20:26:59 +00:00
}
}
}
}
2020-09-01 20:59:37 +00:00
}