rust-clippy/clippy_lints/src/misc_early.rs

694 lines
23 KiB
Rust
Raw Normal View History

2019-08-19 02:18:05 +00:00
use crate::utils::{
constants, snippet_opt, snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg,
span_lint_and_then,
2019-08-19 02:18:05 +00:00
};
2018-11-27 20:14:15 +00:00
use if_chain::if_chain;
2020-03-01 03:23:33 +00:00
use rustc_ast::ast::{
BindingMode, Block, Expr, ExprKind, GenericParamKind, Generics, Lit, LitFloatType, LitIntType, LitKind, Mutability,
NodeId, Pat, PatKind, StmtKind, UnOp,
2020-03-01 03:23:33 +00:00
};
use rustc_ast::visit::{walk_expr, FnKind, Visitor};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
2020-01-12 06:08:41 +00:00
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
2020-01-11 11:37:08 +00:00
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::symbol::Ident;
2018-03-28 13:24:26 +00:00
declare_clippy_lint! {
/// **What it does:** Checks for structure field patterns bound to wildcards.
///
/// **Why is this bad?** Using `..` instead is shorter and leaves the focus on
/// the fields that are actually bound.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # struct Foo {
/// # a: i32,
/// # b: i32,
/// # c: i32,
/// # }
/// let f = Foo { a: 0, b: 0, c: 0 };
///
/// // Bad
/// match f {
/// Foo { a: _, b: 0, .. } => {},
/// Foo { a: _, b: _, c: _ } => {},
/// }
///
/// // Good
/// match f {
/// Foo { b: 0, .. } => {},
/// Foo { .. } => {},
/// }
/// ```
pub UNNEEDED_FIELD_PATTERN,
restriction,
"struct fields bound to a wildcard instead of using `..`"
}
2018-03-28 13:24:26 +00:00
declare_clippy_lint! {
/// **What it does:** Checks for function arguments having the similar names
/// differing by an underscore.
///
/// **Why is this bad?** It affects code readability.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// // Bad
/// fn foo(a: i32, _a: i32) {}
///
/// // Good
/// fn bar(a: i32, _b: i32) {}
/// ```
pub DUPLICATE_UNDERSCORE_ARGUMENT,
2018-03-28 13:24:26 +00:00
style,
"function arguments having names which only differ by an underscore"
}
2018-03-28 13:24:26 +00:00
declare_clippy_lint! {
/// **What it does:** Detects closures called in the same expression where they
/// are defined.
///
/// **Why is this bad?** It is unnecessarily adding to the expression's
/// complexity.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust,ignore
/// // Bad
/// let a = (|| 42)()
///
/// // Good
/// let a = 42
/// ```
pub REDUNDANT_CLOSURE_CALL,
2018-03-29 11:41:53 +00:00
complexity,
"throwaway closures called in the expression they are defined"
}
2018-03-28 13:24:26 +00:00
declare_clippy_lint! {
/// **What it does:** Detects expressions of the form `--x`.
///
/// **Why is this bad?** It can mislead C/C++ programmers to think `x` was
/// decremented.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
2019-03-05 22:23:50 +00:00
/// let mut x = 3;
/// --x;
/// ```
pub DOUBLE_NEG,
2018-03-28 13:24:26 +00:00
style,
"`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++"
2016-06-29 23:00:25 +00:00
}
2018-03-28 13:24:26 +00:00
declare_clippy_lint! {
/// **What it does:** Warns on hexadecimal literals with mixed-case letter
/// digits.
///
/// **Why is this bad?** It looks confusing.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// // Bad
/// let y = 0x1a9BAcD;
///
/// // Good
/// let y = 0x1A9BACD;
/// ```
pub MIXED_CASE_HEX_LITERALS,
2018-03-28 13:24:26 +00:00
style,
"hex literals whose letter digits are not consistently upper- or lowercased"
}
2018-03-28 13:24:26 +00:00
declare_clippy_lint! {
/// **What it does:** Warns if literal suffixes are not separated by an
/// underscore.
///
/// **Why is this bad?** It is much less readable.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// // Bad
/// let y = 123832i32;
///
/// // Good
/// let y = 123832_i32;
/// ```
pub UNSEPARATED_LITERAL_SUFFIX,
2018-03-28 13:24:26 +00:00
pedantic,
"literals whose suffix is not separated by an underscore"
}
2018-03-28 13:24:26 +00:00
declare_clippy_lint! {
/// **What it does:** Warns if an integral constant literal starts with `0`.
///
/// **Why is this bad?** In some languages (including the infamous C language
/// and most of its
/// family), this marks an octal constant. In Rust however, this is a decimal
/// constant. This could
/// be confusing for both the writer and a reader of the constant.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// In Rust:
/// ```rust
/// fn main() {
/// let a = 0123;
/// println!("{}", a);
/// }
/// ```
///
/// prints `123`, while in C:
///
/// ```c
/// #include <stdio.h>
///
/// int main() {
/// int a = 0123;
/// printf("%d\n", a);
/// }
/// ```
///
/// prints `83` (as `83 == 0o123` while `123 == 0o173`).
2016-08-20 16:11:15 +00:00
pub ZERO_PREFIXED_LITERAL,
2018-03-28 13:24:26 +00:00
complexity,
2016-08-20 16:11:15 +00:00
"integer literals starting with `0`"
}
2018-03-28 13:24:26 +00:00
declare_clippy_lint! {
/// **What it does:** Warns if a generic shadows a built-in type.
///
/// **Why is this bad?** This gives surprising type errors.
///
/// **Known problems:** None.
///
/// **Example:**
///
2019-03-05 22:23:50 +00:00
/// ```ignore
/// impl<u32> Foo<u32> {
/// fn impl_func(&self) -> u32 {
/// 42
/// }
/// }
/// ```
2016-08-27 23:52:01 +00:00
pub BUILTIN_TYPE_SHADOW,
2018-03-28 13:24:26 +00:00
style,
2016-08-27 23:52:01 +00:00
"shadowing a builtin type"
}
2019-09-02 19:49:14 +00:00
declare_clippy_lint! {
/// **What it does:** Checks for patterns in the form `name @ _`.
///
/// **Why is this bad?** It's almost always more readable to just use direct
/// bindings.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # let v = Some("abc");
///
/// // Bad
/// match v {
/// Some(x) => (),
/// y @ _ => (),
/// }
///
/// // Good
2019-09-02 19:49:14 +00:00
/// match v {
/// Some(x) => (),
/// y => (),
2019-09-02 19:49:14 +00:00
/// }
/// ```
pub REDUNDANT_PATTERN,
style,
"using `name @ _` in a pattern"
}
2019-09-12 06:25:05 +00:00
declare_clippy_lint! {
/// **What it does:** Checks for tuple patterns with a wildcard
/// pattern (`_`) is next to a rest pattern (`..`).
2019-09-12 06:25:05 +00:00
///
/// _NOTE_: While `_, ..` means there is at least one element left, `..`
/// means there are 0 or more elements left. This can make a difference
/// when refactoring, but shouldn't result in errors in the refactored code,
/// since the wildcard pattern isn't used anyway.
2019-09-12 06:25:05 +00:00
/// **Why is this bad?** The wildcard pattern is unneeded as the rest pattern
/// can match that element as well.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # struct TupleStruct(u32, u32, u32);
/// # let t = TupleStruct(1, 2, 3);
///
/// // Bad
2019-09-12 06:25:05 +00:00
/// match t {
/// TupleStruct(0, .., _) => (),
/// _ => (),
/// }
///
/// // Good
2019-09-12 06:25:05 +00:00
/// match t {
/// TupleStruct(0, ..) => (),
/// _ => (),
/// }
/// ```
pub UNNEEDED_WILDCARD_PATTERN,
complexity,
"tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)"
2019-09-12 06:25:05 +00:00
}
2019-04-08 20:43:55 +00:00
declare_lint_pass!(MiscEarlyLints => [
UNNEEDED_FIELD_PATTERN,
DUPLICATE_UNDERSCORE_ARGUMENT,
REDUNDANT_CLOSURE_CALL,
DOUBLE_NEG,
MIXED_CASE_HEX_LITERALS,
UNSEPARATED_LITERAL_SUFFIX,
ZERO_PREFIXED_LITERAL,
2019-09-02 19:49:14 +00:00
BUILTIN_TYPE_SHADOW,
2019-09-12 06:25:05 +00:00
REDUNDANT_PATTERN,
UNNEEDED_WILDCARD_PATTERN,
2019-04-08 20:43:55 +00:00
]);
2019-01-31 01:15:29 +00:00
// Used to find `return` statements or equivalents e.g., `?`
struct ReturnVisitor {
found_return: bool,
}
impl ReturnVisitor {
#[must_use]
2018-11-14 06:43:35 +00:00
fn new() -> Self {
2018-11-27 20:14:15 +00:00
Self { found_return: false }
}
}
impl<'ast> Visitor<'ast> for ReturnVisitor {
fn visit_expr(&mut self, ex: &'ast Expr) {
2019-09-27 15:16:06 +00:00
if let ExprKind::Ret(_) = ex.kind {
self.found_return = true;
2019-09-27 15:16:06 +00:00
} else if let ExprKind::Try(_) = ex.kind {
self.found_return = true;
}
walk_expr(self, ex)
}
}
2019-04-08 20:43:55 +00:00
impl EarlyLintPass for MiscEarlyLints {
2018-07-23 11:01:12 +00:00
fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
for param in &gen.params {
if let GenericParamKind::Type { .. } = param.kind {
2018-06-28 13:46:58 +00:00
let name = param.ident.as_str();
if constants::BUILTIN_TYPES.contains(&&*name) {
span_lint(
cx,
BUILTIN_TYPE_SHADOW,
param.ident.span,
&format!("This generic shadows the built-in type `{}`", name),
);
}
2016-08-27 23:52:01 +00:00
}
}
}
fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
2019-09-27 15:16:06 +00:00
if let PatKind::Struct(ref npat, ref pfields, _) = pat.kind {
let mut wilds = 0;
2018-11-27 20:14:15 +00:00
let type_name = npat
.segments
2017-08-09 07:30:56 +00:00
.last()
.expect("A path must have at least one segment")
.ident
2017-08-09 07:30:56 +00:00
.name;
for field in pfields {
2019-09-27 15:16:06 +00:00
if let PatKind::Wild = field.pat.kind {
wilds += 1;
}
}
if !pfields.is_empty() && wilds == pfields.len() {
span_lint_and_help(
2017-08-09 07:30:56 +00:00
cx,
UNNEEDED_FIELD_PATTERN,
pat.span,
"All the struct fields are matched to a wildcard pattern, consider using `..`.",
None,
2017-08-09 07:30:56 +00:00
&format!("Try with `{} {{ .. }}` instead", type_name),
);
return;
}
if wilds > 0 {
for field in pfields {
2019-09-27 15:16:06 +00:00
if let PatKind::Wild = field.pat.kind {
2016-01-02 04:52:13 +00:00
wilds -= 1;
if wilds > 0 {
2017-08-09 07:30:56 +00:00
span_lint(
cx,
UNNEEDED_FIELD_PATTERN,
field.span,
"You matched a field with a wildcard pattern. Consider using `..` instead",
);
2016-01-02 04:52:13 +00:00
} else {
let mut normal = vec![];
for field in pfields {
match field.pat.kind {
PatKind::Wild => {},
_ => {
if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) {
normal.push(n);
}
},
}
}
span_lint_and_help(
2017-08-09 07:30:56 +00:00
cx,
UNNEEDED_FIELD_PATTERN,
field.span,
"You matched a field with a wildcard pattern. Consider using `..` \
2017-09-05 09:33:04 +00:00
instead",
None,
2017-08-09 07:30:56 +00:00
&format!("Try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
);
2016-01-02 04:52:13 +00:00
}
}
}
}
}
2019-09-02 19:49:14 +00:00
if let PatKind::Ident(left, ident, Some(ref right)) = pat.kind {
let left_binding = match left {
BindingMode::ByRef(Mutability::Mut) => "ref mut ",
BindingMode::ByRef(Mutability::Not) => "ref ",
BindingMode::ByValue(..) => "",
};
2019-09-27 15:16:06 +00:00
if let PatKind::Wild = right.kind {
2019-09-04 15:50:22 +00:00
span_lint_and_sugg(
2019-09-02 19:49:14 +00:00
cx,
REDUNDANT_PATTERN,
pat.span,
&format!(
"the `{} @ _` pattern can be written as just `{}`",
ident.name, ident.name,
),
2019-09-04 15:50:22 +00:00
"try",
format!("{}{}", left_binding, ident.name),
2019-09-04 15:50:22 +00:00
Applicability::MachineApplicable,
2019-09-02 19:49:14 +00:00
);
}
}
2019-09-12 06:25:05 +00:00
2019-09-12 06:36:05 +00:00
check_unneeded_wildcard_pattern(cx, pat);
}
2020-02-06 19:29:58 +00:00
fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
2020-02-06 19:29:58 +00:00
for arg in &fn_kind.decl().inputs {
2019-09-27 15:16:06 +00:00
if let PatKind::Ident(_, ident, None) = arg.pat.kind {
2018-06-28 13:46:58 +00:00
let arg_name = ident.to_string();
if arg_name.starts_with('_') {
if let Some(correspondence) = registered_names.get(&arg_name[1..]) {
2017-08-09 07:30:56 +00:00
span_lint(
cx,
DUPLICATE_UNDERSCORE_ARGUMENT,
*correspondence,
&format!(
"`{}` already exists, having another argument having almost the same \
2017-09-05 09:33:04 +00:00
name makes code comprehension and documentation more difficult",
2017-08-09 07:30:56 +00:00
arg_name[1..].to_owned()
),
2019-08-01 05:09:57 +00:00
);
2016-01-03 15:55:09 +00:00
}
} else {
registered_names.insert(arg_name, arg.pat.span);
}
}
}
}
2018-07-23 11:01:12 +00:00
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
2018-07-24 06:55:38 +00:00
if in_external_macro(cx.sess(), expr.span) {
2017-05-09 15:15:13 +00:00
return;
}
2019-09-27 15:16:06 +00:00
match expr.kind {
2018-11-27 20:14:15 +00:00
ExprKind::Call(ref paren, _) => {
2019-09-27 15:16:06 +00:00
if let ExprKind::Paren(ref closure) = paren.kind {
if let ExprKind::Closure(_, _, _, ref decl, ref block, _) = closure.kind {
2018-11-27 20:14:15 +00:00
let mut visitor = ReturnVisitor::new();
visitor.visit_expr(block);
if !visitor.found_return {
span_lint_and_then(
cx,
REDUNDANT_CLOSURE_CALL,
expr.span,
"Try not to call a closure in the expression where it is declared.",
|diag| {
2018-11-27 20:14:15 +00:00
if decl.inputs.is_empty() {
let mut app = Applicability::MachineApplicable;
let hint =
snippet_with_applicability(cx, block.span, "..", &mut app).into_owned();
diag.span_suggestion(expr.span, "Try doing something like: ", hint, app);
2018-11-27 20:14:15 +00:00
}
},
);
}
}
}
2016-12-20 17:21:30 +00:00
},
2018-11-27 20:14:15 +00:00
ExprKind::Unary(UnOp::Neg, ref inner) => {
2019-09-27 15:16:06 +00:00
if let ExprKind::Unary(UnOp::Neg, _) = inner.kind {
2018-11-27 20:14:15 +00:00
span_lint(
cx,
DOUBLE_NEG,
expr.span,
"`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op",
);
}
2017-09-05 09:33:04 +00:00
},
2019-10-03 19:09:32 +00:00
ExprKind::Lit(ref lit) => Self::check_lit(cx, lit),
2016-12-20 17:21:30 +00:00
_ => (),
}
}
2018-07-23 11:01:12 +00:00
fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
fn count_closure_usage(block: &Block, ident: &Ident) -> usize {
struct ClosureUsageCount<'ast> {
ident: &'ast Ident,
count: usize,
};
impl<'ast> Visitor<'ast> for ClosureUsageCount<'ast> {
fn visit_expr(&mut self, expr: &'ast Expr) {
if_chain! {
if let ExprKind::Call(ref closure, _) = expr.kind;
if let ExprKind::Path(_, ref path) = closure.kind;
if self.ident == &path.segments[0].ident;
then {
self.count += 1;
}
}
walk_expr(self, expr);
}
}
let mut closure_usage_count = ClosureUsageCount { ident, count: 0 };
closure_usage_count.visit_block(block);
closure_usage_count.count
}
for w in block.stmts.windows(2) {
if_chain! {
2019-09-27 15:16:06 +00:00
if let StmtKind::Local(ref local) = w[0].kind;
if let Option::Some(ref t) = local.init;
2019-09-27 15:16:06 +00:00
if let ExprKind::Closure(..) = t.kind;
if let PatKind::Ident(_, ident, _) = local.pat.kind;
if let StmtKind::Semi(ref second) = w[1].kind;
if let ExprKind::Assign(_, ref call, _) = second.kind;
2019-09-27 15:16:06 +00:00
if let ExprKind::Call(ref closure, _) = call.kind;
if let ExprKind::Path(_, ref path) = closure.kind;
if ident == path.segments[0].ident;
if count_closure_usage(block, &ident) == 1;
then {
span_lint(
cx,
REDUNDANT_CLOSURE_CALL,
second.span,
"Closure called just once immediately after it was declared",
);
}
}
}
}
}
2019-04-08 20:43:55 +00:00
impl MiscEarlyLints {
2019-10-03 19:09:32 +00:00
fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
// We test if first character in snippet is a number, because the snippet could be an expansion
// from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`.
// Note that this check also covers special case that `line!()` is eagerly expanded by compiler.
// See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
// FIXME: Find a better way to detect those cases.
2019-08-26 09:11:43 +00:00
let lit_snip = match snippet_opt(cx, lit.span) {
Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip,
2019-08-26 09:11:43 +00:00
_ => return,
};
2019-09-27 15:16:06 +00:00
if let LitKind::Int(value, lit_int_type) = lit.kind {
2019-08-26 09:11:43 +00:00
let suffix = match lit_int_type {
2019-11-07 12:27:00 +00:00
LitIntType::Signed(ty) => ty.name_str(),
LitIntType::Unsigned(ty) => ty.name_str(),
2019-08-26 09:11:43 +00:00
LitIntType::Unsuffixed => "",
};
let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
val
} else {
return; // It's useless so shouldn't lint.
};
2019-08-26 09:11:43 +00:00
// Do not lint when literal is unsuffixed.
if !suffix.is_empty() && lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' {
span_lint_and_sugg(
cx,
UNSEPARATED_LITERAL_SUFFIX,
lit.span,
"integer type suffix should be separated by an underscore",
"add an underscore",
format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix),
Applicability::MachineApplicable,
);
}
if lit_snip.starts_with("0x") {
if maybe_last_sep_idx <= 2 {
// It's meaningless or causes range error.
return;
}
2019-08-26 09:11:43 +00:00
let mut seen = (false, false);
for ch in lit_snip.as_bytes()[2..=maybe_last_sep_idx].iter() {
match ch {
b'a'..=b'f' => seen.0 = true,
b'A'..=b'F' => seen.1 = true,
_ => {},
}
if seen.0 && seen.1 {
2019-08-26 09:11:43 +00:00
span_lint(
cx,
MIXED_CASE_HEX_LITERALS,
lit.span,
"inconsistent casing in hexadecimal literal",
);
break;
}
2019-08-26 09:11:43 +00:00
}
} else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") {
/* nothing to do */
} else if value != 0 && lit_snip.starts_with('0') {
span_lint_and_then(
cx,
ZERO_PREFIXED_LITERAL,
lit.span,
"this is a decimal constant",
|diag| {
diag.span_suggestion(
lit.span,
2019-08-26 09:11:43 +00:00
"if you mean to use a decimal constant, remove the `0` to avoid confusion",
lit_snip.trim_start_matches(|c| c == '_' || c == '0').to_string(),
Applicability::MaybeIncorrect,
);
diag.span_suggestion(
lit.span,
"if you mean to use an octal constant, use `0o`",
2019-08-26 09:11:43 +00:00
format!("0o{}", lit_snip.trim_start_matches(|c| c == '_' || c == '0')),
Applicability::MaybeIncorrect,
);
2019-08-26 09:11:43 +00:00
},
);
}
2019-11-07 12:27:00 +00:00
} else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind {
let suffix = float_ty.name_str();
let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
val
} else {
return; // It's useless so shouldn't lint.
};
2019-08-26 09:11:43 +00:00
if lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' {
span_lint_and_sugg(
cx,
UNSEPARATED_LITERAL_SUFFIX,
lit.span,
"float type suffix should be separated by an underscore",
"add an underscore",
format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix),
Applicability::MachineApplicable,
);
}
}
}
}
2019-09-12 06:36:05 +00:00
fn check_unneeded_wildcard_pattern(cx: &EarlyContext<'_>, pat: &Pat) {
2019-09-27 15:16:06 +00:00
if let PatKind::TupleStruct(_, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind {
2019-09-12 06:36:05 +00:00
fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) {
span_lint_and_sugg(
cx,
UNNEEDED_WILDCARD_PATTERN,
span,
if only_one {
"this pattern is unneeded as the `..` pattern can match that element"
} else {
"these patterns are unneeded as the `..` pattern can match those elements"
},
if only_one { "remove it" } else { "remove them" },
"".to_string(),
Applicability::MachineApplicable,
);
}
if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) {
2019-09-12 06:36:05 +00:00
if let Some((left_index, left_pat)) = patterns[..rest_index]
.iter()
.rev()
2020-07-05 20:11:19 +00:00
.take_while(|pat| matches!(pat.kind, PatKind::Wild))
2019-09-12 06:36:05 +00:00
.enumerate()
.last()
{
span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0);
}
2020-07-05 20:11:19 +00:00
if let Some((right_index, right_pat)) = patterns[rest_index + 1..]
.iter()
.take_while(|pat| matches!(pat.kind, PatKind::Wild))
.enumerate()
.last()
2019-09-12 06:36:05 +00:00
{
span_lint(
cx,
patterns[rest_index].span.shrink_to_hi().to(right_pat.span),
right_index == 0,
);
}
}
}
}