rust-clippy/clippy_lints/src/matches/needless_match.rs

224 lines
7.7 KiB
Rust

use super::NEEDLESS_MATCH;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
use clippy_utils::{
eq_expr_value, get_parent_expr_for_hir, get_parent_node, higher, is_else_clause, is_lang_ctor,
peel_blocks_with_stmt,
};
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Node, Pat, PatKind, Path, PathSegment, QPath};
use rustc_lint::LateContext;
use rustc_span::sym;
use rustc_typeck::hir_ty_to_ty;
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
if arms.len() > 1 && !is_coercion_casting(cx, ex, expr) && check_all_arms(cx, ex, arms) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_MATCH,
expr.span,
"this match expression is unnecessary",
"replace it with",
snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
applicability,
);
}
}
/// Check for nop `if let` expression that assembled as unnecessary match
///
/// ```rust,ignore
/// if let Some(a) = option {
/// Some(a)
/// } else {
/// None
/// }
/// ```
/// OR
/// ```rust,ignore
/// if let SomeEnum::A = some_enum {
/// SomeEnum::A
/// } else if let SomeEnum::B = some_enum {
/// SomeEnum::B
/// } else {
/// some_enum
/// }
/// ```
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
if let Some(ref if_let) = higher::IfLet::hir(cx, ex) {
if !is_else_clause(cx.tcx, ex) && !is_coercion_casting(cx, if_let.let_expr, ex) && check_if_let(cx, if_let) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_MATCH,
ex.span,
"this if-let expression is unnecessary",
"replace it with",
snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
applicability,
);
}
}
}
fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
for arm in arms {
let arm_expr = peel_blocks_with_stmt(arm.body);
if let PatKind::Wild = arm.pat.kind {
return eq_expr_value(cx, match_expr, strip_return(arm_expr));
} else if !pat_same_as_expr(arm.pat, arm_expr) {
return false;
}
}
true
}
fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
if let Some(if_else) = if_let.if_else {
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
return false;
}
// Recurrsively check for each `else if let` phrase,
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
return check_if_let(cx, nested_if_let);
}
if matches!(if_else.kind, ExprKind::Block(..)) {
let else_expr = peel_blocks_with_stmt(if_else);
if matches!(else_expr.kind, ExprKind::Block(..)) {
return false;
}
let ret = strip_return(else_expr);
let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
if let ExprKind::Path(ref qpath) = ret.kind {
return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
}
return true;
}
return eq_expr_value(cx, if_let.let_expr, ret);
}
}
false
}
/// Strip `return` keyword if the expression type is `ExprKind::Ret`.
fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
if let ExprKind::Ret(Some(ret)) = expr.kind {
ret
} else {
expr
}
}
/// Manually check for coercion casting by checking if the type of the match operand or let expr
/// differs with the assigned local variable or the funtion return type.
fn is_coercion_casting(cx: &LateContext<'_>, match_expr: &Expr<'_>, expr: &Expr<'_>) -> bool {
if let Some(p_node) = get_parent_node(cx.tcx, expr.hir_id) {
match p_node {
// Compare match_expr ty with local in `let local = match match_expr {..}`
Node::Local(local) => {
let results = cx.typeck_results();
return !same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(match_expr));
},
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
Node::Item(..) => {
if let Some(fn_decl) = p_node.fn_decl() {
if let FnRetTy::Return(ret_ty) = fn_decl.output {
return !same_type_and_consts(
hir_ty_to_ty(cx.tcx, ret_ty),
cx.typeck_results().expr_ty(match_expr),
);
}
}
},
// check the parent expr for this whole block `{ match match_expr {..} }`
Node::Block(block) => {
if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
return is_coercion_casting(cx, match_expr, block_parent_expr);
}
},
// recursively call on `if xxx {..}` etc.
Node::Expr(p_expr) => {
return is_coercion_casting(cx, match_expr, p_expr);
},
_ => {},
}
}
false
}
fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
let expr = strip_return(expr);
match (&pat.kind, &expr.kind) {
// Example: `Some(val) => Some(val)`
(PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => {
if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
return same_segments(path.segments, call_path.segments)
&& same_non_ref_symbols(tuple_params, call_params);
}
},
// Example: `val => val`
(
PatKind::Binding(annot, _, pat_ident, _),
ExprKind::Path(QPath::Resolved(
_,
Path {
segments: [first_seg, ..],
..
},
)),
) => {
return !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut)
&& pat_ident.name == first_seg.ident.name;
},
// Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
(PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
return same_segments(p_path.segments, e_path.segments);
},
// Example: `5 => 5`
(PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind {
return pat_spanned.node == expr_spanned.node;
}
},
_ => {},
}
false
}
fn same_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
if left_segs.len() != right_segs.len() {
return false;
}
for i in 0..left_segs.len() {
if left_segs[i].ident.name != right_segs[i].ident.name {
return false;
}
}
true
}
fn same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool {
if pats.len() != exprs.len() {
return false;
}
for i in 0..pats.len() {
if !pat_same_as_expr(&pats[i], &exprs[i]) {
return false;
}
}
true
}