forked from rust-lang/rust
-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of rust-lang#8400 - Jarcho:split_matches, r=Manishearth
Split matches Part of rust-lang#6680 changelog: None
- Loading branch information
Showing
16 changed files
with
2,548 additions
and
2,445 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
44 changes: 44 additions & 0 deletions
44
clippy_lints/src/matches/infalliable_detructuring_match.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
use clippy_utils::diagnostics::span_lint_and_sugg; | ||
use clippy_utils::source::snippet_with_applicability; | ||
use clippy_utils::{path_to_local_id, peel_blocks, strip_pat_refs}; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{ExprKind, Local, MatchSource, PatKind, QPath}; | ||
use rustc_lint::LateContext; | ||
|
||
use super::INFALLIBLE_DESTRUCTURING_MATCH; | ||
|
||
pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool { | ||
if_chain! { | ||
if !local.span.from_expansion(); | ||
if let Some(expr) = local.init; | ||
if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind; | ||
if arms.len() == 1 && arms[0].guard.is_none(); | ||
if let PatKind::TupleStruct( | ||
QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind; | ||
if args.len() == 1; | ||
if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind; | ||
let body = peel_blocks(arms[0].body); | ||
if path_to_local_id(body, arg); | ||
|
||
then { | ||
let mut applicability = Applicability::MachineApplicable; | ||
span_lint_and_sugg( | ||
cx, | ||
INFALLIBLE_DESTRUCTURING_MATCH, | ||
local.span, | ||
"you seem to be trying to use `match` to destructure a single infallible pattern. \ | ||
Consider using `let`", | ||
"try this", | ||
format!( | ||
"let {}({}) = {};", | ||
snippet_with_applicability(cx, variant_name.span, "..", &mut applicability), | ||
snippet_with_applicability(cx, local.pat.span, "..", &mut applicability), | ||
snippet_with_applicability(cx, target.span, "..", &mut applicability), | ||
), | ||
applicability, | ||
); | ||
return true; | ||
} | ||
} | ||
false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
use clippy_utils::diagnostics::span_lint_and_sugg; | ||
use clippy_utils::source::snippet_with_applicability; | ||
use clippy_utils::{is_lang_ctor, peel_blocks}; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, LangItem, PatKind, QPath}; | ||
use rustc_lint::LateContext; | ||
use rustc_middle::ty; | ||
|
||
use super::MATCH_AS_REF; | ||
|
||
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { | ||
if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { | ||
let arm_ref: Option<BindingAnnotation> = if is_none_arm(cx, &arms[0]) { | ||
is_ref_some_arm(cx, &arms[1]) | ||
} else if is_none_arm(cx, &arms[1]) { | ||
is_ref_some_arm(cx, &arms[0]) | ||
} else { | ||
None | ||
}; | ||
if let Some(rb) = arm_ref { | ||
let suggestion = if rb == BindingAnnotation::Ref { | ||
"as_ref" | ||
} else { | ||
"as_mut" | ||
}; | ||
|
||
let output_ty = cx.typeck_results().expr_ty(expr); | ||
let input_ty = cx.typeck_results().expr_ty(ex); | ||
|
||
let cast = if_chain! { | ||
if let ty::Adt(_, substs) = input_ty.kind(); | ||
let input_ty = substs.type_at(0); | ||
if let ty::Adt(_, substs) = output_ty.kind(); | ||
let output_ty = substs.type_at(0); | ||
if let ty::Ref(_, output_ty, _) = *output_ty.kind(); | ||
if input_ty != output_ty; | ||
then { | ||
".map(|x| x as _)" | ||
} else { | ||
"" | ||
} | ||
}; | ||
|
||
let mut applicability = Applicability::MachineApplicable; | ||
span_lint_and_sugg( | ||
cx, | ||
MATCH_AS_REF, | ||
expr.span, | ||
&format!("use `{}()` instead", suggestion), | ||
"try this", | ||
format!( | ||
"{}.{}(){}", | ||
snippet_with_applicability(cx, ex.span, "_", &mut applicability), | ||
suggestion, | ||
cast, | ||
), | ||
applicability, | ||
); | ||
} | ||
} | ||
} | ||
|
||
// Checks if arm has the form `None => None` | ||
fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { | ||
matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, LangItem::OptionNone)) | ||
} | ||
|
||
// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) | ||
fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotation> { | ||
if_chain! { | ||
if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind; | ||
if is_lang_ctor(cx, qpath, LangItem::OptionSome); | ||
if let PatKind::Binding(rb, .., ident, _) = first_pat.kind; | ||
if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; | ||
if let ExprKind::Call(e, args) = peel_blocks(arm.body).kind; | ||
if let ExprKind::Path(ref some_path) = e.kind; | ||
if is_lang_ctor(cx, some_path, LangItem::OptionSome) && args.len() == 1; | ||
if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind; | ||
if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name; | ||
then { | ||
return Some(rb) | ||
} | ||
} | ||
None | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
use clippy_utils::diagnostics::span_lint_and_then; | ||
use clippy_utils::is_unit_expr; | ||
use clippy_utils::source::{expr_block, snippet}; | ||
use clippy_utils::sugg::Sugg; | ||
use rustc_ast::LitKind; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{Arm, Expr, ExprKind, PatKind}; | ||
use rustc_lint::LateContext; | ||
use rustc_middle::ty; | ||
|
||
use super::MATCH_BOOL; | ||
|
||
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { | ||
// Type of expression is `bool`. | ||
if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool { | ||
span_lint_and_then( | ||
cx, | ||
MATCH_BOOL, | ||
expr.span, | ||
"you seem to be trying to match on a boolean expression", | ||
move |diag| { | ||
if arms.len() == 2 { | ||
// no guards | ||
let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind { | ||
if let ExprKind::Lit(ref lit) = arm_bool.kind { | ||
match lit.node { | ||
LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)), | ||
LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)), | ||
_ => None, | ||
} | ||
} else { | ||
None | ||
} | ||
} else { | ||
None | ||
}; | ||
|
||
if let Some((true_expr, false_expr)) = exprs { | ||
let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { | ||
(false, false) => Some(format!( | ||
"if {} {} else {}", | ||
snippet(cx, ex.span, "b"), | ||
expr_block(cx, true_expr, None, "..", Some(expr.span)), | ||
expr_block(cx, false_expr, None, "..", Some(expr.span)) | ||
)), | ||
(false, true) => Some(format!( | ||
"if {} {}", | ||
snippet(cx, ex.span, "b"), | ||
expr_block(cx, true_expr, None, "..", Some(expr.span)) | ||
)), | ||
(true, false) => { | ||
let test = Sugg::hir(cx, ex, ".."); | ||
Some(format!( | ||
"if {} {}", | ||
!test, | ||
expr_block(cx, false_expr, None, "..", Some(expr.span)) | ||
)) | ||
}, | ||
(true, true) => None, | ||
}; | ||
|
||
if let Some(sugg) = sugg { | ||
diag.span_suggestion( | ||
expr.span, | ||
"consider using an `if`/`else` expression", | ||
sugg, | ||
Applicability::HasPlaceholders, | ||
); | ||
} | ||
} | ||
} | ||
}, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
use clippy_utils::diagnostics::span_lint_and_sugg; | ||
use clippy_utils::source::snippet_with_applicability; | ||
use clippy_utils::{higher, is_wild}; | ||
use rustc_ast::{Attribute, LitKind}; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{BorrowKind, Expr, ExprKind, Guard, MatchSource, Pat}; | ||
use rustc_lint::LateContext; | ||
use rustc_middle::ty; | ||
use rustc_span::source_map::Spanned; | ||
|
||
use super::MATCH_LIKE_MATCHES_MACRO; | ||
|
||
/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!` | ||
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { | ||
if let Some(higher::IfLet { | ||
let_pat, | ||
let_expr, | ||
if_then, | ||
if_else: Some(if_else), | ||
}) = higher::IfLet::hir(cx, expr) | ||
{ | ||
return find_matches_sugg( | ||
cx, | ||
let_expr, | ||
IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]), | ||
expr, | ||
true, | ||
); | ||
} | ||
|
||
if let ExprKind::Match(scrut, arms, MatchSource::Normal) = expr.kind { | ||
return find_matches_sugg( | ||
cx, | ||
scrut, | ||
arms.iter().map(|arm| { | ||
( | ||
cx.tcx.hir().attrs(arm.hir_id), | ||
Some(arm.pat), | ||
arm.body, | ||
arm.guard.as_ref(), | ||
) | ||
}), | ||
expr, | ||
false, | ||
); | ||
} | ||
|
||
false | ||
} | ||
|
||
/// Lint a `match` or `if let` for replacement by `matches!` | ||
fn find_matches_sugg<'a, 'b, I>( | ||
cx: &LateContext<'_>, | ||
ex: &Expr<'_>, | ||
mut iter: I, | ||
expr: &Expr<'_>, | ||
is_if_let: bool, | ||
) -> bool | ||
where | ||
'b: 'a, | ||
I: Clone | ||
+ DoubleEndedIterator | ||
+ ExactSizeIterator | ||
+ Iterator< | ||
Item = ( | ||
&'a [Attribute], | ||
Option<&'a Pat<'b>>, | ||
&'a Expr<'b>, | ||
Option<&'a Guard<'b>>, | ||
), | ||
>, | ||
{ | ||
if_chain! { | ||
if iter.len() >= 2; | ||
if cx.typeck_results().expr_ty(expr).is_bool(); | ||
if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back(); | ||
let iter_without_last = iter.clone(); | ||
if let Some((first_attrs, _, first_expr, first_guard)) = iter.next(); | ||
if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let); | ||
if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let); | ||
if b0 != b1; | ||
if first_guard.is_none() || iter.len() == 0; | ||
if first_attrs.is_empty(); | ||
if iter | ||
.all(|arm| { | ||
find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty() | ||
}); | ||
then { | ||
if let Some(last_pat) = last_pat_opt { | ||
if !is_wild(last_pat) { | ||
return false; | ||
} | ||
} | ||
|
||
// The suggestion may be incorrect, because some arms can have `cfg` attributes | ||
// evaluated into `false` and so such arms will be stripped before. | ||
let mut applicability = Applicability::MaybeIncorrect; | ||
let pat = { | ||
use itertools::Itertools as _; | ||
iter_without_last | ||
.filter_map(|arm| { | ||
let pat_span = arm.1?.span; | ||
Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability)) | ||
}) | ||
.join(" | ") | ||
}; | ||
let pat_and_guard = if let Some(Guard::If(g)) = first_guard { | ||
format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability)) | ||
} else { | ||
pat | ||
}; | ||
|
||
// strip potential borrows (#6503), but only if the type is a reference | ||
let mut ex_new = ex; | ||
if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind { | ||
if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() { | ||
ex_new = ex_inner; | ||
} | ||
}; | ||
span_lint_and_sugg( | ||
cx, | ||
MATCH_LIKE_MATCHES_MACRO, | ||
expr.span, | ||
&format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }), | ||
"try this", | ||
format!( | ||
"{}matches!({}, {})", | ||
if b0 { "" } else { "!" }, | ||
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), | ||
pat_and_guard, | ||
), | ||
applicability, | ||
); | ||
true | ||
} else { | ||
false | ||
} | ||
} | ||
} | ||
|
||
/// Extract a `bool` or `{ bool }` | ||
fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> { | ||
match ex { | ||
ExprKind::Lit(Spanned { | ||
node: LitKind::Bool(b), .. | ||
}) => Some(*b), | ||
ExprKind::Block( | ||
rustc_hir::Block { | ||
stmts: &[], | ||
expr: Some(exp), | ||
.. | ||
}, | ||
_, | ||
) if is_if_let => { | ||
if let ExprKind::Lit(Spanned { | ||
node: LitKind::Bool(b), .. | ||
}) = exp.kind | ||
{ | ||
Some(b) | ||
} else { | ||
None | ||
} | ||
}, | ||
_ => None, | ||
} | ||
} |
Oops, something went wrong.