mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-28 09:44:08 +00:00
Rollup merge of #6402 - camsteffen:collapsible-match, r=llogiq
Add Collapsible match lint changelog: Add collapsible_match lint Closes #1252 Closes #2521 This lint finds nested `match` or `if let` patterns that can be squashed together. It is designed to be very conservative to only find cases where merging the patterns would most likely reduce cognitive complexity. Example: ```rust match result { Ok(opt) => match opt { Some(x) => x, _ => return, } _ => return, } ``` to ```rust match result { Ok(Some(x)) => x, _ => return, } ``` These criteria must be met for the lint to fire: * The inner match has exactly 2 branches. * Both the outer and inner match have a "wild" branch like `_ => ..`. There is a special case for `None => ..` to also be considered "wild-like". * The contents of the wild branches are identical. * The binding which "links" the matches is never used elsewhere. Thanks to the hir, `if let`'s are easily included with this lint since they are desugared into equivalent `match`'es. I think this would fit into the style category, but I would also understand changing it to pedantic.
This commit is contained in:
commit
e2ecc4ad6e
@ -1770,6 +1770,7 @@ Released 2018-09-13
|
||||
[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
|
||||
[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity
|
||||
[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
|
||||
[`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match
|
||||
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
|
||||
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
|
||||
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
|
||||
|
172
clippy_lints/src/collapsible_match.rs
Normal file
172
clippy_lints/src/collapsible_match.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use crate::utils::visitors::LocalUsedVisitor;
|
||||
use crate::utils::{span_lint_and_then, SpanlessEq};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, QPath, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{DefIdTree, TyCtxt};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{MultiSpan, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
|
||||
/// without adding any branches.
|
||||
///
|
||||
/// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
|
||||
/// cases where merging would most likely make the code more readable.
|
||||
///
|
||||
/// **Why is this bad?** It is unnecessarily verbose and complex.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
///
|
||||
/// ```rust
|
||||
/// fn func(opt: Option<Result<u64, String>>) {
|
||||
/// let n = match opt {
|
||||
/// Some(n) => match n {
|
||||
/// Ok(n) => n,
|
||||
/// _ => return,
|
||||
/// }
|
||||
/// None => return,
|
||||
/// };
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// fn func(opt: Option<Result<u64, String>>) {
|
||||
/// let n = match opt {
|
||||
/// Some(Ok(n)) => n,
|
||||
/// _ => return,
|
||||
/// };
|
||||
/// }
|
||||
/// ```
|
||||
pub COLLAPSIBLE_MATCH,
|
||||
style,
|
||||
"Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
|
||||
}
|
||||
|
||||
declare_lint_pass!(CollapsibleMatch => [COLLAPSIBLE_MATCH]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
|
||||
if let ExprKind::Match(_expr, arms, _source) = expr.kind {
|
||||
if let Some(wild_arm) = arms.iter().rfind(|arm| arm_is_wild_like(arm, cx.tcx)) {
|
||||
for arm in arms {
|
||||
check_arm(arm, wild_arm, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_arm(arm: &Arm<'_>, wild_outer_arm: &Arm<'_>, cx: &LateContext<'_>) {
|
||||
if_chain! {
|
||||
let expr = strip_singleton_blocks(arm.body);
|
||||
if let ExprKind::Match(expr_in, arms_inner, _) = expr.kind;
|
||||
// the outer arm pattern and the inner match
|
||||
if expr_in.span.ctxt() == arm.pat.span.ctxt();
|
||||
// there must be no more than two arms in the inner match for this lint
|
||||
if arms_inner.len() == 2;
|
||||
// no if guards on the inner match
|
||||
if arms_inner.iter().all(|arm| arm.guard.is_none());
|
||||
// match expression must be a local binding
|
||||
// match <local> { .. }
|
||||
if let ExprKind::Path(QPath::Resolved(None, path)) = expr_in.kind;
|
||||
if let Res::Local(binding_id) = path.res;
|
||||
// one of the branches must be "wild-like"
|
||||
if let Some(wild_inner_arm_idx) = arms_inner.iter().rposition(|arm_inner| arm_is_wild_like(arm_inner, cx.tcx));
|
||||
let (wild_inner_arm, non_wild_inner_arm) =
|
||||
(&arms_inner[wild_inner_arm_idx], &arms_inner[1 - wild_inner_arm_idx]);
|
||||
if !pat_contains_or(non_wild_inner_arm.pat);
|
||||
// the binding must come from the pattern of the containing match arm
|
||||
// ..<local>.. => match <local> { .. }
|
||||
if let Some(binding_span) = find_pat_binding(arm.pat, binding_id);
|
||||
// the "wild-like" branches must be equal
|
||||
if SpanlessEq::new(cx).eq_expr(wild_inner_arm.body, wild_outer_arm.body);
|
||||
// the binding must not be used in the if guard
|
||||
if !matches!(arm.guard, Some(Guard::If(guard)) if LocalUsedVisitor::new(binding_id).check_expr(guard));
|
||||
// ...or anywhere in the inner match
|
||||
if !arms_inner.iter().any(|arm| LocalUsedVisitor::new(binding_id).check_arm(arm));
|
||||
then {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
COLLAPSIBLE_MATCH,
|
||||
expr.span,
|
||||
"Unnecessary nested match",
|
||||
|diag| {
|
||||
let mut help_span = MultiSpan::from_spans(vec![binding_span, non_wild_inner_arm.pat.span]);
|
||||
help_span.push_span_label(binding_span, "Replace this binding".into());
|
||||
help_span.push_span_label(non_wild_inner_arm.pat.span, "with this pattern".into());
|
||||
diag.span_help(help_span, "The outer pattern can be modified to include the inner pattern.");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
|
||||
while let ExprKind::Block(block, _) = expr.kind {
|
||||
match (block.stmts, block.expr) {
|
||||
([stmt], None) => match stmt.kind {
|
||||
StmtKind::Expr(e) | StmtKind::Semi(e) => expr = e,
|
||||
_ => break,
|
||||
},
|
||||
([], Some(e)) => expr = e,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
expr
|
||||
}
|
||||
|
||||
/// A "wild-like" pattern is wild ("_") or `None`.
|
||||
/// For this lint to apply, both the outer and inner match expressions
|
||||
/// must have "wild-like" branches that can be combined.
|
||||
fn arm_is_wild_like(arm: &Arm<'_>, tcx: TyCtxt<'_>) -> bool {
|
||||
if arm.guard.is_some() {
|
||||
return false;
|
||||
}
|
||||
match arm.pat.kind {
|
||||
PatKind::Binding(..) | PatKind::Wild => true,
|
||||
PatKind::Path(QPath::Resolved(None, path)) if is_none_ctor(path.res, tcx) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_pat_binding(pat: &Pat<'_>, hir_id: HirId) -> Option<Span> {
|
||||
let mut span = None;
|
||||
pat.walk_short(|p| match &p.kind {
|
||||
// ignore OR patterns
|
||||
PatKind::Or(_) => false,
|
||||
PatKind::Binding(_bm, _, _ident, _) => {
|
||||
let found = p.hir_id == hir_id;
|
||||
if found {
|
||||
span = Some(p.span);
|
||||
}
|
||||
!found
|
||||
},
|
||||
_ => true,
|
||||
});
|
||||
span
|
||||
}
|
||||
|
||||
fn pat_contains_or(pat: &Pat<'_>) -> bool {
|
||||
let mut result = false;
|
||||
pat.walk(|p| {
|
||||
let is_or = matches!(p.kind, PatKind::Or(_));
|
||||
result |= is_or;
|
||||
!is_or
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
fn is_none_ctor(res: Res, tcx: TyCtxt<'_>) -> bool {
|
||||
if let Some(none_id) = tcx.lang_items().option_none_variant() {
|
||||
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = res {
|
||||
if let Some(variant_id) = tcx.parent(id) {
|
||||
return variant_id == none_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
@ -280,8 +280,7 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op
|
||||
// only take assignments to fields where the left-hand side field is a field of
|
||||
// the same binding as the previous statement
|
||||
if let ExprKind::Field(ref binding, field_ident) = assign_lhs.kind;
|
||||
if let ExprKind::Path(ref qpath) = binding.kind;
|
||||
if let QPath::Resolved(_, path) = qpath;
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
|
||||
if let Some(second_binding_name) = path.segments.last();
|
||||
if second_binding_name.ident.name == binding_name;
|
||||
then {
|
||||
|
@ -41,8 +41,7 @@ declare_lint_pass!(OkIfLet => [IF_LET_SOME_RESULT]);
|
||||
impl<'tcx> LateLintPass<'tcx> for OkIfLet {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! { //begin checking variables
|
||||
if let ExprKind::Match(ref op, ref body, source) = expr.kind; //test if expr is a match
|
||||
if let MatchSource::IfLetDesugar { .. } = source; //test if it is an If Let
|
||||
if let ExprKind::Match(ref op, ref body, MatchSource::IfLetDesugar { .. }) = expr.kind; //test if expr is if let
|
||||
if let ExprKind::MethodCall(_, ok_span, ref result_types, _) = op.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
|
||||
if let PatKind::TupleStruct(QPath::Resolved(_, ref x), ref y, _) = body[0].pat.kind; //get operation
|
||||
if method_chain_args(op, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized;
|
||||
|
@ -68,8 +68,7 @@ fn expr_match(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let StmtKind::Semi(expr, ..) = &stmt.kind;
|
||||
// make sure it's a break, otherwise we want to skip
|
||||
if let ExprKind::Break(.., break_expr) = &expr.kind;
|
||||
if let Some(break_expr) = break_expr;
|
||||
if let ExprKind::Break(.., Some(break_expr)) = &expr.kind;
|
||||
then {
|
||||
lint(cx, expr.span, break_expr.span, LINT_BREAK);
|
||||
}
|
||||
|
@ -59,8 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
|
||||
if let Some(target) = subtracts_one(cx, e);
|
||||
|
||||
// Extracting out the variable name
|
||||
if let ExprKind::Path(ref assign_path) = target.kind;
|
||||
if let QPath::Resolved(_, ref ares_path) = assign_path;
|
||||
if let ExprKind::Path(QPath::Resolved(_, ref ares_path)) = target.kind;
|
||||
|
||||
then {
|
||||
// Handle symmetric conditions in the if statement
|
||||
|
@ -52,8 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
|
||||
if let ItemKind::Const(hir_ty, _) = &item.kind;
|
||||
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||
if let ty::Array(element_type, cst) = ty.kind();
|
||||
if let ConstKind::Value(val) = cst.val;
|
||||
if let ConstValue::Scalar(element_count) = val;
|
||||
if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val;
|
||||
if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx);
|
||||
if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes());
|
||||
if self.maximum_allowed_size < element_count * element_size;
|
||||
|
@ -43,8 +43,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays {
|
||||
if_chain! {
|
||||
if let ExprKind::Repeat(_, _) = expr.kind;
|
||||
if let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind();
|
||||
if let ConstKind::Value(val) = cst.val;
|
||||
if let ConstValue::Scalar(element_count) = val;
|
||||
if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val;
|
||||
if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx);
|
||||
if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes());
|
||||
if self.maximum_allowed_size < element_count * element_size;
|
||||
|
@ -1,12 +1,11 @@
|
||||
use crate::utils::visitors::LocalUsedVisitor;
|
||||
use crate::utils::{higher, qpath_res, snippet, span_lint_and_then};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit;
|
||||
use rustc_hir::BindingAnnotation;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
declare_clippy_lint! {
|
||||
@ -66,10 +65,10 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
|
||||
if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
|
||||
if let hir::StmtKind::Expr(ref if_) = expr.kind;
|
||||
if let Some((ref cond, ref then, ref else_)) = higher::if_block(&if_);
|
||||
if !used_in_expr(cx, canonical_id, cond);
|
||||
if !LocalUsedVisitor::new(canonical_id).check_expr(cond);
|
||||
if let hir::ExprKind::Block(ref then, _) = then.kind;
|
||||
if let Some(value) = check_assign(cx, canonical_id, &*then);
|
||||
if !used_in_expr(cx, canonical_id, value);
|
||||
if !LocalUsedVisitor::new(canonical_id).check_expr(value);
|
||||
then {
|
||||
let span = stmt.span.to(if_.span);
|
||||
|
||||
@ -136,32 +135,6 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
|
||||
}
|
||||
}
|
||||
|
||||
struct UsedVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
id: hir::HirId,
|
||||
used: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> intravisit::Visitor<'tcx> for UsedVisitor<'a, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Path(ref qpath) = expr.kind;
|
||||
if let Res::Local(local_id) = qpath_res(self.cx, qpath, expr.hir_id);
|
||||
if self.id == local_id;
|
||||
then {
|
||||
self.used = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||
intravisit::NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
||||
fn check_assign<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
decl: hir::HirId,
|
||||
@ -176,18 +149,10 @@ fn check_assign<'tcx>(
|
||||
if let Res::Local(local_id) = qpath_res(cx, qpath, var.hir_id);
|
||||
if decl == local_id;
|
||||
then {
|
||||
let mut v = UsedVisitor {
|
||||
cx,
|
||||
id: decl,
|
||||
used: false,
|
||||
};
|
||||
let mut v = LocalUsedVisitor::new(decl);
|
||||
|
||||
for s in block.stmts.iter().take(block.stmts.len()-1) {
|
||||
intravisit::walk_stmt(&mut v, s);
|
||||
|
||||
if v.used {
|
||||
return None;
|
||||
}
|
||||
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| v.check_stmt(stmt)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(value);
|
||||
@ -196,9 +161,3 @@ fn check_assign<'tcx>(
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn used_in_expr<'tcx>(cx: &LateContext<'tcx>, id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> bool {
|
||||
let mut v = UsedVisitor { cx, id, used: false };
|
||||
intravisit::walk_expr(&mut v, expr);
|
||||
v.used
|
||||
}
|
||||
|
@ -172,6 +172,7 @@ mod cargo_common_metadata;
|
||||
mod checked_conversions;
|
||||
mod cognitive_complexity;
|
||||
mod collapsible_if;
|
||||
mod collapsible_match;
|
||||
mod comparison_chain;
|
||||
mod copies;
|
||||
mod copy_iterator;
|
||||
@ -549,6 +550,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
&checked_conversions::CHECKED_CONVERSIONS,
|
||||
&cognitive_complexity::COGNITIVE_COMPLEXITY,
|
||||
&collapsible_if::COLLAPSIBLE_IF,
|
||||
&collapsible_match::COLLAPSIBLE_MATCH,
|
||||
&comparison_chain::COMPARISON_CHAIN,
|
||||
&copies::IFS_SAME_COND,
|
||||
&copies::IF_SAME_THEN_ELSE,
|
||||
@ -978,6 +980,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| box len_zero::LenZero);
|
||||
store.register_late_pass(|| box attrs::Attributes);
|
||||
store.register_late_pass(|| box blocks_in_if_conditions::BlocksInIfConditions);
|
||||
store.register_late_pass(|| box collapsible_match::CollapsibleMatch);
|
||||
store.register_late_pass(|| box unicode::Unicode);
|
||||
store.register_late_pass(|| box unit_return_expecting_ord::UnitReturnExpectingOrd);
|
||||
store.register_late_pass(|| box strings::StringAdd);
|
||||
@ -1359,6 +1362,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
LintId::of(&booleans::NONMINIMAL_BOOL),
|
||||
LintId::of(&bytecount::NAIVE_BYTECOUNT),
|
||||
LintId::of(&collapsible_if::COLLAPSIBLE_IF),
|
||||
LintId::of(&collapsible_match::COLLAPSIBLE_MATCH),
|
||||
LintId::of(&comparison_chain::COMPARISON_CHAIN),
|
||||
LintId::of(&copies::IFS_SAME_COND),
|
||||
LintId::of(&copies::IF_SAME_THEN_ELSE),
|
||||
@ -1625,6 +1629,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
LintId::of(&blacklisted_name::BLACKLISTED_NAME),
|
||||
LintId::of(&blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
|
||||
LintId::of(&collapsible_if::COLLAPSIBLE_IF),
|
||||
LintId::of(&collapsible_match::COLLAPSIBLE_MATCH),
|
||||
LintId::of(&comparison_chain::COMPARISON_CHAIN),
|
||||
LintId::of(&default::FIELD_REASSIGN_WITH_DEFAULT),
|
||||
LintId::of(&doc::MISSING_SAFETY_DOC),
|
||||
|
@ -2,6 +2,7 @@ use crate::consts::constant;
|
||||
use crate::utils::paths;
|
||||
use crate::utils::sugg::Sugg;
|
||||
use crate::utils::usage::{is_unused, mutated_variables};
|
||||
use crate::utils::visitors::LocalUsedVisitor;
|
||||
use crate::utils::{
|
||||
contains_name, get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait,
|
||||
indent_of, is_in_panic_handler, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item,
|
||||
@ -1919,8 +1920,7 @@ fn check_for_single_element_loop<'tcx>(
|
||||
if_chain! {
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg_expr) = arg.kind;
|
||||
if let PatKind::Binding(.., target, _) = pat.kind;
|
||||
if let ExprKind::Array(ref arg_expr_list) = arg_expr.kind;
|
||||
if let [arg_expression] = arg_expr_list;
|
||||
if let ExprKind::Array([arg_expression]) = arg_expr.kind;
|
||||
if let ExprKind::Path(ref list_item) = arg_expression.kind;
|
||||
if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name);
|
||||
if let ExprKind::Block(ref block, _) = body.kind;
|
||||
@ -2025,8 +2025,7 @@ fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId>
|
||||
let node_str = cx.tcx.hir().get(hir_id);
|
||||
if_chain! {
|
||||
if let Node::Binding(pat) = node_str;
|
||||
if let PatKind::Binding(bind_ann, ..) = pat.kind;
|
||||
if let BindingAnnotation::Mutable = bind_ann;
|
||||
if let PatKind::Binding(BindingAnnotation::Mutable, ..) = pat.kind;
|
||||
then {
|
||||
return Some(hir_id);
|
||||
}
|
||||
@ -2071,28 +2070,6 @@ fn pat_is_wild<'tcx>(pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalUsedVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
local: HirId,
|
||||
used: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for LocalUsedVisitor<'a, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if same_var(self.cx, expr, self.local) {
|
||||
self.used = true;
|
||||
} else {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
||||
struct VarVisitor<'a, 'tcx> {
|
||||
/// context reference
|
||||
cx: &'a LateContext<'tcx>,
|
||||
@ -2126,11 +2103,7 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
|
||||
then {
|
||||
let index_used_directly = same_var(self.cx, idx, self.var);
|
||||
let indexed_indirectly = {
|
||||
let mut used_visitor = LocalUsedVisitor {
|
||||
cx: self.cx,
|
||||
local: self.var,
|
||||
used: false,
|
||||
};
|
||||
let mut used_visitor = LocalUsedVisitor::new(self.var);
|
||||
walk_expr(&mut used_visitor, idx);
|
||||
used_visitor.used
|
||||
};
|
||||
|
@ -219,8 +219,7 @@ fn find_stripping<'tcx>(
|
||||
if is_ref_str(self.cx, ex);
|
||||
let unref = peel_ref(ex);
|
||||
if let ExprKind::Index(indexed, index) = &unref.kind;
|
||||
if let Some(range) = higher::range(index);
|
||||
if let higher::Range { start, end, .. } = range;
|
||||
if let Some(higher::Range { start, end, .. }) = higher::range(index);
|
||||
if let ExprKind::Path(path) = &indexed.kind;
|
||||
if qpath_res(self.cx, path, ex.hir_id) == self.target;
|
||||
then {
|
||||
|
@ -646,8 +646,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
|
||||
if_chain! {
|
||||
if !in_external_macro(cx.sess(), pat.span);
|
||||
if !in_macro(pat.span);
|
||||
if let PatKind::Struct(ref qpath, fields, true) = pat.kind;
|
||||
if let QPath::Resolved(_, ref path) = qpath;
|
||||
if let PatKind::Struct(QPath::Resolved(_, ref path), fields, true) = pat.kind;
|
||||
if let Some(def_id) = path.res.opt_def_id();
|
||||
let ty = cx.tcx.type_of(def_id);
|
||||
if let ty::Adt(def, _) = ty.kind();
|
||||
@ -956,16 +955,14 @@ fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
|
||||
if let QPath::Resolved(_, p) = path {
|
||||
missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
|
||||
}
|
||||
} else if let PatKind::TupleStruct(ref path, ref patterns, ..) = arm.pat.kind {
|
||||
if let QPath::Resolved(_, p) = path {
|
||||
// Some simple checks for exhaustive patterns.
|
||||
// There is a room for improvements to detect more cases,
|
||||
// but it can be more expensive to do so.
|
||||
let is_pattern_exhaustive =
|
||||
|pat: &&Pat<'_>| matches!(pat.kind, PatKind::Wild | PatKind::Binding(.., None));
|
||||
if patterns.iter().all(is_pattern_exhaustive) {
|
||||
missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
|
||||
}
|
||||
} else if let PatKind::TupleStruct(QPath::Resolved(_, p), ref patterns, ..) = arm.pat.kind {
|
||||
// Some simple checks for exhaustive patterns.
|
||||
// There is a room for improvements to detect more cases,
|
||||
// but it can be more expensive to do so.
|
||||
let is_pattern_exhaustive =
|
||||
|pat: &&Pat<'_>| matches!(pat.kind, PatKind::Wild | PatKind::Binding(.., None));
|
||||
if patterns.iter().all(is_pattern_exhaustive) {
|
||||
missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1440,8 +1437,7 @@ fn is_ref_some_arm(arm: &Arm<'_>) -> Option<BindingAnnotation> {
|
||||
if let ExprKind::Call(ref e, ref args) = remove_blocks(&arm.body).kind;
|
||||
if let ExprKind::Path(ref some_path) = e.kind;
|
||||
if match_qpath(some_path, &paths::OPTION_SOME) && args.len() == 1;
|
||||
if let ExprKind::Path(ref qpath) = args[0].kind;
|
||||
if let &QPath::Resolved(_, ref path2) = qpath;
|
||||
if let ExprKind::Path(QPath::Resolved(_, ref path2)) = args[0].kind;
|
||||
if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
|
||||
then {
|
||||
return Some(rb)
|
||||
|
@ -90,8 +90,7 @@ fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<M
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Call(func, args) = &expr.kind;
|
||||
if args.is_empty();
|
||||
if let hir::ExprKind::Path(path) = &func.kind;
|
||||
if let hir::QPath::TypeRelative(_, segment) = path;
|
||||
if let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind;
|
||||
then {
|
||||
match &*segment.ident.as_str() {
|
||||
"max_value" => return Some(MinMax::Max),
|
||||
|
@ -6,7 +6,6 @@ use crate::utils::sugg::Sugg;
|
||||
use crate::utils::{
|
||||
higher, is_expn_of, parent_node_is_if_expr, snippet_with_applicability, span_lint, span_lint_and_sugg,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
|
||||
@ -198,13 +197,9 @@ struct ExpressionInfoWithSpan {
|
||||
}
|
||||
|
||||
fn is_unary_not(e: &Expr<'_>) -> (bool, Span) {
|
||||
if_chain! {
|
||||
if let ExprKind::Unary(unop, operand) = e.kind;
|
||||
if let UnOp::UnNot = unop;
|
||||
then {
|
||||
return (true, operand.span);
|
||||
}
|
||||
};
|
||||
if let ExprKind::Unary(UnOp::UnNot, operand) = e.kind {
|
||||
return (true, operand.span);
|
||||
}
|
||||
(false, e.span)
|
||||
}
|
||||
|
||||
|
@ -176,8 +176,7 @@ impl QuestionMark {
|
||||
if block.stmts.len() == 1;
|
||||
if let Some(expr) = block.stmts.iter().last();
|
||||
if let StmtKind::Semi(ref expr) = expr.kind;
|
||||
if let ExprKind::Ret(ret_expr) = expr.kind;
|
||||
if let Some(ret_expr) = ret_expr;
|
||||
if let ExprKind::Ret(Some(ret_expr)) = expr.kind;
|
||||
|
||||
then {
|
||||
return Some(ret_expr);
|
||||
|
@ -222,8 +222,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
|
||||
if method_names[0] == sym!(as_bytes);
|
||||
|
||||
// Check for slicer
|
||||
if let ExprKind::Struct(ref path, _, _) = right.kind;
|
||||
if let QPath::LangItem(LangItem::Range, _) = path;
|
||||
if let ExprKind::Struct(QPath::LangItem(LangItem::Range, _), _, _) = right.kind;
|
||||
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
@ -168,8 +168,7 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
|
||||
if_chain! {
|
||||
if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate;
|
||||
if !in_macro(bound_predicate.span);
|
||||
if let TyKind::Path(ref path) = bound_predicate.bounded_ty.kind;
|
||||
if let QPath::Resolved(_, Path { ref segments, .. }) = path;
|
||||
if let TyKind::Path(QPath::Resolved(_, Path { ref segments, .. })) = bound_predicate.bounded_ty.kind;
|
||||
if let Some(segment) = segments.first();
|
||||
if let Some(trait_resolutions_direct) = map.get(&segment.ident);
|
||||
then {
|
||||
|
@ -48,8 +48,7 @@ impl<'tcx> LateLintPass<'tcx> for TransmutingNull {
|
||||
if_chain! {
|
||||
if let ExprKind::Path(ref _qpath) = args[0].kind;
|
||||
let x = const_eval_context.expr(&args[0]);
|
||||
if let Some(constant) = x;
|
||||
if let Constant::RawPtr(0) = constant;
|
||||
if let Some(Constant::RawPtr(0)) = x;
|
||||
then {
|
||||
span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG)
|
||||
}
|
||||
|
@ -738,8 +738,7 @@ fn is_any_trait(t: &hir::Ty<'_>) -> bool {
|
||||
fn get_bounds_if_impl_trait<'tcx>(cx: &LateContext<'tcx>, qpath: &QPath<'_>, id: HirId) -> Option<GenericBounds<'tcx>> {
|
||||
if_chain! {
|
||||
if let Some(did) = qpath_res(cx, qpath, id).opt_def_id();
|
||||
if let Some(node) = cx.tcx.hir().get_if_local(did);
|
||||
if let Node::GenericParam(generic_param) = node;
|
||||
if let Some(Node::GenericParam(generic_param)) = cx.tcx.hir().get_if_local(did);
|
||||
if let GenericParamKind::Type { synthetic, .. } = generic_param.kind;
|
||||
if synthetic == Some(SyntheticTyParamKind::ImplTrait);
|
||||
then {
|
||||
@ -1470,8 +1469,7 @@ fn check_loss_of_sign(cx: &LateContext<'_>, expr: &Expr<'_>, op: &Expr<'_>, cast
|
||||
// don't lint for positive constants
|
||||
let const_val = constant(cx, &cx.typeck_results(), op);
|
||||
if_chain! {
|
||||
if let Some((const_val, _)) = const_val;
|
||||
if let Constant::Int(n) = const_val;
|
||||
if let Some((Constant::Int(n), _)) = const_val;
|
||||
if let ty::Int(ity) = *cast_from.kind();
|
||||
if sext(cx.tcx, n, ity) >= 0;
|
||||
then {
|
||||
|
@ -162,8 +162,7 @@ pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<
|
||||
if let hir::Block { expr: Some(expr), .. } = &**block;
|
||||
if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind;
|
||||
if let hir::ExprKind::DropTemps(cond) = &cond.kind;
|
||||
if let [arm, ..] = &arms[..];
|
||||
if let hir::Arm { body, .. } = arm;
|
||||
if let [hir::Arm { body, .. }, ..] = &arms[..];
|
||||
then {
|
||||
return Some((cond, body));
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
match (&left.kind, &right.kind) {
|
||||
match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) {
|
||||
(&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => {
|
||||
lb == rb && l_mut == r_mut && self.eq_expr(le, re)
|
||||
},
|
||||
@ -306,6 +306,32 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Some simple reductions like `{ return }` => `return`
|
||||
fn reduce_exprkind<'hir>(kind: &'hir ExprKind<'hir>) -> &ExprKind<'hir> {
|
||||
if let ExprKind::Block(block, _) = kind {
|
||||
match (block.stmts, block.expr) {
|
||||
// `{}` => `()`
|
||||
([], None) => &ExprKind::Tup(&[]),
|
||||
([], Some(expr)) => match expr.kind {
|
||||
// `{ return .. }` => `return ..`
|
||||
ExprKind::Ret(..) => &expr.kind,
|
||||
_ => kind,
|
||||
},
|
||||
([stmt], None) => match stmt.kind {
|
||||
StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
|
||||
// `{ return ..; }` => `return ..`
|
||||
ExprKind::Ret(..) => &expr.kind,
|
||||
_ => kind,
|
||||
},
|
||||
_ => kind,
|
||||
},
|
||||
_ => kind,
|
||||
}
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
|
||||
fn swap_binop<'a>(
|
||||
binop: BinOpKind,
|
||||
lhs: &'a Expr<'a>,
|
||||
|
@ -1,5 +1,7 @@
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, HirId, QPath, Stmt};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::map::Map;
|
||||
|
||||
@ -123,3 +125,54 @@ where
|
||||
!ret_finder.failed
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LocalUsedVisitor {
|
||||
pub local_hir_id: HirId,
|
||||
pub used: bool,
|
||||
}
|
||||
|
||||
impl LocalUsedVisitor {
|
||||
pub fn new(local_hir_id: HirId) -> Self {
|
||||
Self {
|
||||
local_hir_id,
|
||||
used: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
|
||||
visit(self, t);
|
||||
std::mem::replace(&mut self.used, false)
|
||||
}
|
||||
|
||||
pub fn check_arm(&mut self, arm: &Arm<'_>) -> bool {
|
||||
self.check(arm, Self::visit_arm)
|
||||
}
|
||||
|
||||
pub fn check_expr(&mut self, expr: &Expr<'_>) -> bool {
|
||||
self.check(expr, Self::visit_expr)
|
||||
}
|
||||
|
||||
pub fn check_stmt(&mut self, stmt: &Stmt<'_>) -> bool {
|
||||
self.check(stmt, Self::visit_stmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v> Visitor<'v> for LocalUsedVisitor {
|
||||
type Map = Map<'v>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'v Expr<'v>) {
|
||||
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind {
|
||||
if let Res::Local(id) = path.res {
|
||||
if id == self.local_hir_id {
|
||||
self.used = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
239
tests/ui/collapsible_match.rs
Normal file
239
tests/ui/collapsible_match.rs
Normal file
@ -0,0 +1,239 @@
|
||||
#![warn(clippy::collapsible_match)]
|
||||
#![allow(clippy::needless_return, clippy::no_effect, clippy::single_match)]
|
||||
|
||||
fn lint_cases(opt_opt: Option<Option<u32>>, res_opt: Result<Option<u32>, String>) {
|
||||
// match without block
|
||||
match res_opt {
|
||||
Ok(val) => match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// match with block
|
||||
match res_opt {
|
||||
Ok(val) => match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// if let, if let
|
||||
if let Ok(val) = res_opt {
|
||||
if let Some(n) = val {
|
||||
take(n);
|
||||
}
|
||||
}
|
||||
|
||||
// if let else, if let else
|
||||
if let Ok(val) = res_opt {
|
||||
if let Some(n) = val {
|
||||
take(n);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// if let, match
|
||||
if let Ok(val) = res_opt {
|
||||
match val {
|
||||
Some(n) => foo(n),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// match, if let
|
||||
match res_opt {
|
||||
Ok(val) => {
|
||||
if let Some(n) = val {
|
||||
take(n);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
// if let else, match
|
||||
if let Ok(val) = res_opt {
|
||||
match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// match, if let else
|
||||
match res_opt {
|
||||
Ok(val) => {
|
||||
if let Some(n) = val {
|
||||
take(n);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// None in inner match same as outer wild branch
|
||||
match res_opt {
|
||||
Ok(val) => match val {
|
||||
Some(n) => foo(n),
|
||||
None => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// None in outer match same as inner wild branch
|
||||
match opt_opt {
|
||||
Some(val) => match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
},
|
||||
None => return,
|
||||
}
|
||||
}
|
||||
|
||||
fn negative_cases(res_opt: Result<Option<u32>, String>, res_res: Result<Result<u32, String>, String>) {
|
||||
// no wild pattern in outer match
|
||||
match res_opt {
|
||||
Ok(val) => match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
},
|
||||
Err(_) => return,
|
||||
}
|
||||
|
||||
// inner branch is not wild or None
|
||||
match res_res {
|
||||
Ok(val) => match val {
|
||||
Ok(n) => foo(n),
|
||||
Err(_) => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// statement before inner match
|
||||
match res_opt {
|
||||
Ok(val) => {
|
||||
"hi buddy";
|
||||
match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// statement after inner match
|
||||
match res_opt {
|
||||
Ok(val) => {
|
||||
match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
}
|
||||
"hi buddy";
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// wild branches do not match
|
||||
match res_opt {
|
||||
Ok(val) => match val {
|
||||
Some(n) => foo(n),
|
||||
_ => {
|
||||
"sup";
|
||||
return;
|
||||
},
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// binding used in if guard
|
||||
match res_opt {
|
||||
Ok(val) if val.is_some() => match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// binding used in inner match body
|
||||
match res_opt {
|
||||
Ok(val) => match val {
|
||||
Some(_) => take(val),
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// if guard on inner match
|
||||
{
|
||||
match res_opt {
|
||||
Ok(val) => match val {
|
||||
Some(n) if make() => foo(n),
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
match res_opt {
|
||||
Ok(val) => match val {
|
||||
_ => make(),
|
||||
_ if make() => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
|
||||
// differing macro contexts
|
||||
{
|
||||
macro_rules! mac {
|
||||
($val:ident) => {
|
||||
match $val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
}
|
||||
};
|
||||
}
|
||||
match res_opt {
|
||||
Ok(val) => mac!(val),
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
|
||||
// OR pattern
|
||||
enum E<T> {
|
||||
A(T),
|
||||
B(T),
|
||||
C(T),
|
||||
};
|
||||
match make::<E<Option<u32>>>() {
|
||||
E::A(val) | E::B(val) => match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
match make::<Option<E<u32>>>() {
|
||||
Some(val) => match val {
|
||||
E::A(val) | E::B(val) => foo(val),
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
|
||||
fn make<T>() -> T {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn foo<T, U>(t: T) -> U {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn take<T>(t: T) {}
|
||||
|
||||
fn main() {}
|
179
tests/ui/collapsible_match.stderr
Normal file
179
tests/ui/collapsible_match.stderr
Normal file
@ -0,0 +1,179 @@
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match.rs:7:20
|
||||
|
|
||||
LL | Ok(val) => match val {
|
||||
| ____________________^
|
||||
LL | | Some(n) => foo(n),
|
||||
LL | | _ => return,
|
||||
LL | | },
|
||||
| |_________^
|
||||
|
|
||||
= note: `-D clippy::collapsible-match` implied by `-D warnings`
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match.rs:7:12
|
||||
|
|
||||
LL | Ok(val) => match val {
|
||||
| ^^^ Replace this binding
|
||||
LL | Some(n) => foo(n),
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match.rs:16:20
|
||||
|
|
||||
LL | Ok(val) => match val {
|
||||
| ____________________^
|
||||
LL | | Some(n) => foo(n),
|
||||
LL | | _ => return,
|
||||
LL | | },
|
||||
| |_________^
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match.rs:16:12
|
||||
|
|
||||
LL | Ok(val) => match val {
|
||||
| ^^^ Replace this binding
|
||||
LL | Some(n) => foo(n),
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match.rs:25:9
|
||||
|
|
||||
LL | / if let Some(n) = val {
|
||||
LL | | take(n);
|
||||
LL | | }
|
||||
| |_________^
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match.rs:24:15
|
||||
|
|
||||
LL | if let Ok(val) = res_opt {
|
||||
| ^^^ Replace this binding
|
||||
LL | if let Some(n) = val {
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match.rs:32:9
|
||||
|
|
||||
LL | / if let Some(n) = val {
|
||||
LL | | take(n);
|
||||
LL | | } else {
|
||||
LL | | return;
|
||||
LL | | }
|
||||
| |_________^
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match.rs:31:15
|
||||
|
|
||||
LL | if let Ok(val) = res_opt {
|
||||
| ^^^ Replace this binding
|
||||
LL | if let Some(n) = val {
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match.rs:43:9
|
||||
|
|
||||
LL | / match val {
|
||||
LL | | Some(n) => foo(n),
|
||||
LL | | _ => (),
|
||||
LL | | }
|
||||
| |_________^
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match.rs:42:15
|
||||
|
|
||||
LL | if let Ok(val) = res_opt {
|
||||
| ^^^ Replace this binding
|
||||
LL | match val {
|
||||
LL | Some(n) => foo(n),
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match.rs:52:13
|
||||
|
|
||||
LL | / if let Some(n) = val {
|
||||
LL | | take(n);
|
||||
LL | | }
|
||||
| |_____________^
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match.rs:51:12
|
||||
|
|
||||
LL | Ok(val) => {
|
||||
| ^^^ Replace this binding
|
||||
LL | if let Some(n) = val {
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match.rs:61:9
|
||||
|
|
||||
LL | / match val {
|
||||
LL | | Some(n) => foo(n),
|
||||
LL | | _ => return,
|
||||
LL | | }
|
||||
| |_________^
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match.rs:60:15
|
||||
|
|
||||
LL | if let Ok(val) = res_opt {
|
||||
| ^^^ Replace this binding
|
||||
LL | match val {
|
||||
LL | Some(n) => foo(n),
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match.rs:72:13
|
||||
|
|
||||
LL | / if let Some(n) = val {
|
||||
LL | | take(n);
|
||||
LL | | } else {
|
||||
LL | | return;
|
||||
LL | | }
|
||||
| |_____________^
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match.rs:71:12
|
||||
|
|
||||
LL | Ok(val) => {
|
||||
| ^^^ Replace this binding
|
||||
LL | if let Some(n) = val {
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match.rs:83:20
|
||||
|
|
||||
LL | Ok(val) => match val {
|
||||
| ____________________^
|
||||
LL | | Some(n) => foo(n),
|
||||
LL | | None => return,
|
||||
LL | | },
|
||||
| |_________^
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match.rs:83:12
|
||||
|
|
||||
LL | Ok(val) => match val {
|
||||
| ^^^ Replace this binding
|
||||
LL | Some(n) => foo(n),
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match.rs:92:22
|
||||
|
|
||||
LL | Some(val) => match val {
|
||||
| ______________________^
|
||||
LL | | Some(n) => foo(n),
|
||||
LL | | _ => return,
|
||||
LL | | },
|
||||
| |_________^
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match.rs:92:14
|
||||
|
|
||||
LL | Some(val) => match val {
|
||||
| ^^^ Replace this binding
|
||||
LL | Some(n) => foo(n),
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: aborting due to 10 previous errors
|
||||
|
53
tests/ui/collapsible_match2.rs
Normal file
53
tests/ui/collapsible_match2.rs
Normal file
@ -0,0 +1,53 @@
|
||||
#![warn(clippy::collapsible_match)]
|
||||
#![allow(clippy::needless_return, clippy::no_effect, clippy::single_match)]
|
||||
|
||||
fn lint_cases(opt_opt: Option<Option<u32>>, res_opt: Result<Option<u32>, String>) {
|
||||
// if guards on outer match
|
||||
{
|
||||
match res_opt {
|
||||
Ok(val) if make() => match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
match res_opt {
|
||||
Ok(val) => match val {
|
||||
Some(n) => foo(n),
|
||||
_ => return,
|
||||
},
|
||||
_ if make() => return,
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
|
||||
// macro
|
||||
{
|
||||
macro_rules! mac {
|
||||
($outer:expr => $pat:pat, $e:expr => $inner_pat:pat, $then:expr) => {
|
||||
match $outer {
|
||||
$pat => match $e {
|
||||
$inner_pat => $then,
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
};
|
||||
}
|
||||
// Lint this since the patterns are not defined by the macro.
|
||||
// Allows the lint to work on if_chain! for example.
|
||||
// Fixing the lint requires knowledge of the specific macro, but we optimistically assume that
|
||||
// there is still a better way to write this.
|
||||
mac!(res_opt => Ok(val), val => Some(n), foo(n));
|
||||
}
|
||||
}
|
||||
|
||||
fn make<T>() -> T {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn foo<T, U>(t: T) -> U {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn main() {}
|
61
tests/ui/collapsible_match2.stderr
Normal file
61
tests/ui/collapsible_match2.stderr
Normal file
@ -0,0 +1,61 @@
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match2.rs:8:34
|
||||
|
|
||||
LL | Ok(val) if make() => match val {
|
||||
| __________________________________^
|
||||
LL | | Some(n) => foo(n),
|
||||
LL | | _ => return,
|
||||
LL | | },
|
||||
| |_____________^
|
||||
|
|
||||
= note: `-D clippy::collapsible-match` implied by `-D warnings`
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match2.rs:8:16
|
||||
|
|
||||
LL | Ok(val) if make() => match val {
|
||||
| ^^^ Replace this binding
|
||||
LL | Some(n) => foo(n),
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match2.rs:15:24
|
||||
|
|
||||
LL | Ok(val) => match val {
|
||||
| ________________________^
|
||||
LL | | Some(n) => foo(n),
|
||||
LL | | _ => return,
|
||||
LL | | },
|
||||
| |_____________^
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match2.rs:15:16
|
||||
|
|
||||
LL | Ok(val) => match val {
|
||||
| ^^^ Replace this binding
|
||||
LL | Some(n) => foo(n),
|
||||
| ^^^^^^^ with this pattern
|
||||
|
||||
error: Unnecessary nested match
|
||||
--> $DIR/collapsible_match2.rs:29:29
|
||||
|
|
||||
LL | $pat => match $e {
|
||||
| _____________________________^
|
||||
LL | | $inner_pat => $then,
|
||||
LL | | _ => return,
|
||||
LL | | },
|
||||
| |_____________________^
|
||||
...
|
||||
LL | mac!(res_opt => Ok(val), val => Some(n), foo(n));
|
||||
| ------------------------------------------------- in this macro invocation
|
||||
|
|
||||
help: The outer pattern can be modified to include the inner pattern.
|
||||
--> $DIR/collapsible_match2.rs:41:28
|
||||
|
|
||||
LL | mac!(res_opt => Ok(val), val => Some(n), foo(n));
|
||||
| ^^^ ^^^^^^^ with this pattern
|
||||
| |
|
||||
| Replace this binding
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
Loading…
Reference in New Issue
Block a user