-use crate::utils::visitors::LocalUsedVisitor;
-use crate::utils::{span_lint_and_then, SpanlessEq};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::visitors::LocalUsedVisitor;
+use clippy_utils::{is_lang_ctor, path_to_local, 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, UnOp};
+use rustc_hir::LangItem::OptionNone;
+use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::{DefIdTree, TyCtxt};
+use rustc_middle::ty::TypeckResults;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{MultiSpan, Span};
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)) {
+ if let Some(wild_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
for arm in arms {
check_arm(arm, wild_arm, cx);
}
}
}
-fn check_arm(arm: &Arm<'_>, wild_outer_arm: &Arm<'_>, cx: &LateContext<'_>) {
+fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext<'tcx>) {
+ let expr = strip_singleton_blocks(arm.body);
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();
if arms_inner.iter().all(|arm| arm.guard.is_none());
// match expression must be a local binding
// match <local> { .. }
- if let Some(binding_id) = addr_adjusted_binding(expr_in, cx);
+ if let Some(binding_id) = path_to_local(strip_ref_operators(expr_in, cx.typeck_results()));
// 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));
+ if let Some(wild_inner_arm_idx) = arms_inner.iter().rposition(|arm_inner| arm_is_wild_like(cx, arm_inner));
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 "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
+ let mut used_visitor = LocalUsedVisitor::new(cx, binding_id);
if match arm.guard {
None => true,
- Some(Guard::If(expr) | Guard::IfLet(_, expr)) => {
- !LocalUsedVisitor::new(binding_id).check_expr(expr)
- }
+ Some(Guard::If(expr) | Guard::IfLet(_, expr)) => !used_visitor.check_expr(expr),
};
// ...or anywhere in the inner match
- if !arms_inner.iter().any(|arm| LocalUsedVisitor::new(binding_id).check_arm(arm));
+ if !arms_inner.iter().any(|arm| used_visitor.check_arm(arm));
then {
span_lint_and_then(
cx,
COLLAPSIBLE_MATCH,
expr.span,
- "Unnecessary nested match",
+ "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(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.");
+ diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
},
);
}
/// 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 {
+fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> 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,
+ PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
_ => false,
}
}
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
-}
-
-/// Retrieves a binding ID with optional `&` and/or `*` operators removed. (e.g. `&**foo`)
-/// Returns `None` if a non-reference type is de-referenced.
-/// For example, if `Vec` is de-referenced to a slice, `None` is returned.
-fn addr_adjusted_binding(mut expr: &Expr<'_>, cx: &LateContext<'_>) -> Option<HirId> {
+/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is
+/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed.
+fn strip_ref_operators<'hir>(mut expr: &'hir Expr<'hir>, typeck_results: &TypeckResults<'_>) -> &'hir Expr<'hir> {
loop {
match expr.kind {
ExprKind::AddrOf(_, _, e) => expr = e,
- ExprKind::Path(QPath::Resolved(None, path)) => match path.res {
- Res::Local(binding_id) => break Some(binding_id),
- _ => break None,
- },
- ExprKind::Unary(UnOp::UnDeref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e,
- _ => break None,
+ ExprKind::Unary(UnOp::Deref, e) if typeck_results.expr_ty(e).is_ref() => expr = e,
+ _ => break,
}
}
+ expr
}