Skip to content

Commit

Permalink
rebase, move lint to filter_map, apply PR suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric Loren committed Mar 30, 2021
1 parent 370c0a1 commit 983088a
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 118 deletions.
217 changes: 150 additions & 67 deletions clippy_lints/src/methods/filter_map.rs
Original file line number Diff line number Diff line change
@@ -1,87 +1,170 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::{is_trait_method, path_to_local_id, SpanlessEq};
use clippy_utils::source::{indent_of, reindent_multiline, snippet, snippet_block};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{
is_trait_method, match_qpath, match_trait_method, path_to_local_id, paths, remove_blocks, SpanlessEq,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{Expr, ExprKind, PatKind, UnOp};
use rustc_hir::def::Res;
use rustc_hir::{Expr, ExprKind, PatKind, QPath, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty::TyS;
use rustc_span::symbol::sym;
use rustc_span::source_map::Span;
use rustc_span::symbol::{sym, Symbol};
use std::borrow::Cow;

use super::MANUAL_FILTER_MAP;
use super::MANUAL_FIND_MAP;
use super::OPTION_FILTER_MAP;

fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool {
match &expr.kind {
hir::ExprKind::Path(QPath::TypeRelative(_, ref mname)) => mname.ident.name == method_name,
hir::ExprKind::Path(QPath::Resolved(_, segments)) => {
segments.segments.last().unwrap().ident.name == method_name
},
hir::ExprKind::Closure(_, _, c, _, _) => {
let body = cx.tcx.hir().body(*c);
let closure_expr = remove_blocks(&body.value);
let arg_id = body.params[0].pat.hir_id;
match closure_expr.kind {
hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, _, ref args, _) => {
if_chain! {
if ident.name == method_name;
if let hir::ExprKind::Path(path) = &args[0].kind;
if let Res::Local(ref local) = cx.qpath_res(path, args[0].hir_id);
then {
return arg_id == *local
}
}
false
},
_ => false,
}
},
_ => false,
}
}

fn is_option_filter_map<'tcx>(
cx: &LateContext<'tcx>,
filter_arg: &'tcx hir::Expr<'_>,
map_arg: &'tcx hir::Expr<'_>,
) -> bool {
is_method(cx, map_arg, sym!(unwrap)) && is_method(cx, filter_arg, sym!(is_some))
}

/// lint use of `filter().map()` for `Iterators`
fn lint_filter_some_map_unwrap<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
filter_recv: &'tcx hir::Expr<'_>,
filter_arg: &'tcx hir::Expr<'_>,
map_arg: &'tcx hir::Expr<'_>,
target_span: Span,
filter_span: Span,
map_span: Span,
) {
let iterator = is_trait_method(cx, expr, sym::Iterator);
let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&filter_recv), sym::option_type);
if (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg) {
let msg = "`filter` for `Some` followed by `unwrap`";
let help = "consider using `flatten` instead";
let sugg = format!(
"{}",
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, target_span),)
);
span_lint_and_sugg(
cx,
OPTION_FILTER_MAP,
// expr.span.with_hi(expr.span.hi()),
filter_span.to(map_span),
msg,
help,
sugg,
Applicability::MachineApplicable,
);
}
}

/// lint use of `filter().map()` or `find().map()` for `Iterators`
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool) {
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool, target_span: Span) {
if_chain! {
if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind;
if let ExprKind::MethodCall(_, _, [_, filter_arg], filter_span) = map_recv.kind;
if is_trait_method(cx, map_recv, sym::Iterator);
if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind;
if let ExprKind::MethodCall(_, _, [filter_recv, filter_arg], filter_span) = map_recv.kind;
then {
lint_filter_some_map_unwrap(cx, expr, filter_recv, filter_arg,
map_arg, target_span, filter_span, map_span);
if_chain! {
if is_trait_method(cx, map_recv, sym::Iterator);

// filter(|x| ...is_some())...
if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind;
let filter_body = cx.tcx.hir().body(filter_body_id);
if let [filter_param] = filter_body.params;
// optional ref pattern: `filter(|&x| ..)`
let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind {
(ref_pat, true)
} else {
(filter_param.pat, false)
};
// closure ends with is_some() or is_ok()
if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind;
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def();
if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) {
Some(false)
} else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) {
Some(true)
} else {
None
};
if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" };
// filter(|x| ...is_some())...
if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind;
let filter_body = cx.tcx.hir().body(filter_body_id);
if let [filter_param] = filter_body.params;
// optional ref pattern: `filter(|&x| ..)`
let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind {
(ref_pat, true)
} else {
(filter_param.pat, false)
};
// closure ends with is_some() or is_ok()
if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind;
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def();
if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) {
Some(false)
} else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) {
Some(true)
} else {
None
};
if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" };

// ...map(|x| ...unwrap())
if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind;
let map_body = cx.tcx.hir().body(map_body_id);
if let [map_param] = map_body.params;
if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind;
// closure ends with expect() or unwrap()
if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind;
if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
// ...map(|x| ...unwrap())
if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind;
let map_body = cx.tcx.hir().body(map_body_id);
if let [map_param] = map_body.params;
if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind;
// closure ends with expect() or unwrap()
if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind;
if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);

let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
// in `filter(|x| ..)`, replace `*x` with `x`
let a_path = if_chain! {
if !is_filter_param_ref;
if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind;
then { expr_path } else { a }
};
// let the filter closure arg and the map closure arg be equal
if_chain! {
if path_to_local_id(a_path, filter_param_id);
if path_to_local_id(b, map_param_id);
if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b));
then {
return true;
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
// in `filter(|x| ..)`, replace `*x` with `x`
let a_path = if_chain! {
if !is_filter_param_ref;
if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind;
then { expr_path } else { a }
};
// let the filter closure arg and the map closure arg be equal
if_chain! {
if path_to_local_id(a_path, filter_param_id);
if path_to_local_id(b, map_param_id);
if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b));
then {
return true;
}
}
}
false
};
if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg);
then {
let span = filter_span.to(map_span);
let (filter_name, lint) = if is_find {
("find", MANUAL_FIND_MAP)
} else {
("filter", MANUAL_FILTER_MAP)
false
};
let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name);
let to_opt = if is_result { ".ok()" } else { "" };
let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident,
snippet(cx, map_arg.span, ".."), to_opt);
span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg);
then {
let span = filter_span.to(map_span);
let (filter_name, lint) = if is_find {
("find", MANUAL_FIND_MAP)
} else {
("filter", MANUAL_FILTER_MAP)
};
let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name);
let to_opt = if is_result { ".ok()" } else { "" };
let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident,
snippet(cx, map_arg.span, ".."), to_opt);
span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
}
}
}
}
}
67 changes: 60 additions & 7 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,14 @@ use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait,
use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, method_calls, paths, return_ty};
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_hir::{Expr, ExprKind, PatKind, PrimTy, QPath, TraitItem, TraitItemKind, UnOp};
use rustc_hir::def::Res;
use rustc_hir::{Expr, ExprKind, PatKind, TraitItem, TraitItemKind, UnOp};
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
use rustc_hir::{PrimTy, QPath, TraitItem, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, TraitRef, Ty, TyS};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
use rustc_span::symbol::{sym, Symbol, SymbolStr};
use rustc_span::symbol::{sym, SymbolStr};
use rustc_typeck::hir_ty_to_ty;

declare_clippy_lint! {
Expand Down Expand Up @@ -1745,10 +1743,10 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
["next", "filter"] => filter_next::check(cx, expr, arg_lists[1]),
["next", "skip_while"] => skip_while_next::check(cx, expr, arg_lists[1]),
["next", "iter"] => iter_next_slice::check(cx, expr, arg_lists[1]),
["map", "filter"] => filter_map::check(cx, expr, false),
["map", "filter"] => filter_map::check(cx, expr, false, method_spans[0]),
["map", "filter_map"] => filter_map_map::check(cx, expr),
["next", "filter_map"] => filter_map_next::check(cx, expr, arg_lists[1], self.msrv.as_ref()),
["map", "find"] => filter_map::check(cx, expr, true),
["map", "find"] => filter_map::check(cx, expr, true, method_spans[0]),
["flat_map", "filter"] => filter_flat_map::check(cx, expr),
["flat_map", "filter_map"] => filter_map_flat_map::check(cx, expr),
["flat_map", ..] => flat_map_identity::check(cx, expr, arg_lists[0], method_spans[0]),
Expand Down Expand Up @@ -2042,7 +2040,62 @@ fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExpr
}
}
};
}

lint_with_both_lhs_and_rhs!(chars_next_cmp::check, cx, info);
lint_with_both_lhs_and_rhs!(chars_last_cmp::check, cx, info);
lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info);
lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info);
}

const FN_HEADER: hir::FnHeader = hir::FnHeader {
unsafety: hir::Unsafety::Normal,
constness: hir::Constness::NotConst,
asyncness: hir::IsAsync::NotAsync,
abi: rustc_target::spec::abi::Abi::Rust,
};

struct ShouldImplTraitCase {
trait_name: &'static str,
method_name: &'static str,
param_count: usize,
fn_header: hir::FnHeader,
// implicit self kind expected (none, self, &self, ...)
self_kind: SelfKind,
// checks against the output type
output_type: OutType,
// certain methods with explicit lifetimes can't implement the equivalent trait method
lint_explicit_lifetime: bool,
}
impl ShouldImplTraitCase {
const fn new(
trait_name: &'static str,
method_name: &'static str,
param_count: usize,
fn_header: hir::FnHeader,
self_kind: SelfKind,
output_type: OutType,
lint_explicit_lifetime: bool,
) -> ShouldImplTraitCase {
ShouldImplTraitCase {
trait_name,
method_name,
param_count,
fn_header,
self_kind,
output_type,
lint_explicit_lifetime,
}
}

fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
self.lint_explicit_lifetime
|| !impl_item.generics.params.iter().any(|p| {
matches!(
p.kind,
hir::GenericParamKind::Lifetime {
kind: hir::LifetimeParamKind::Explicit
}
)
})
}
Expand Down
2 changes: 0 additions & 2 deletions clippy_utils/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
pub const OPS_MODULE: [&str; 2] = ["core", "ops"];
pub const OPTION: [&str; 3] = ["core", "option", "Option"];
pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
pub const OPTION_IS_SOME: [&str; 4] = ["core", "option", "Option", "is_some"];
pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
pub const OPTION_UNWRAP: [&str; 4] = ["core", "option", "Option", "unwrap"];
pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
Expand Down
6 changes: 4 additions & 2 deletions tests/ui/option_filter_map.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ fn main() {
let _ = vec![Some(1)].into_iter().flatten();
let _ = vec![1]
.into_iter()
.map(odds_out).flatten();
.map(odds_out)
.flatten();
let _ = vec![1]
.into_iter()
.map(odds_out).flatten();
.map(odds_out)
.flatten();
}
Loading

0 comments on commit 983088a

Please sign in to comment.