add unnested_or_patterns lint

This commit is contained in:
Mazdak Farrokhzad 2020-03-19 14:14:52 +01:00 committed by flip1995
parent 67ec96c8f9
commit 7b6dc7b33d
No known key found for this signature in database
GPG key ID: 2CEFCDB27ED0BE79
15 changed files with 1314 additions and 20 deletions

View file

@ -1683,6 +1683,7 @@ Released 2018-09-13
[`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap [`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap
[`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern [`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern
[`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern [`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern
[`unnested_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns
[`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable [`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable
[`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal [`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal
[`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize [`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize

View file

@ -1,5 +1,6 @@
// error-pattern:cargo-clippy // error-pattern:cargo-clippy
#![feature(bindings_after_at)]
#![feature(box_syntax)] #![feature(box_syntax)]
#![feature(box_patterns)] #![feature(box_patterns)]
#![feature(or_patterns)] #![feature(or_patterns)]
@ -12,6 +13,7 @@
#![cfg_attr(feature = "deny-warnings", deny(warnings))] #![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(concat_idents)] #![feature(concat_idents)]
#![feature(drain_filter)]
// FIXME: switch to something more ergonomic here, once available. // FIXME: switch to something more ergonomic here, once available.
// (Currently there is no way to opt into sysroot crates without `extern crate`.) // (Currently there is no way to opt into sysroot crates without `extern crate`.)
@ -319,6 +321,7 @@ mod types;
mod unicode; mod unicode;
mod unnamed_address; mod unnamed_address;
mod unnecessary_sort_by; mod unnecessary_sort_by;
mod unnested_or_patterns;
mod unsafe_removed_from_name; mod unsafe_removed_from_name;
mod unused_io_amount; mod unused_io_amount;
mod unused_self; mod unused_self;
@ -836,6 +839,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&unnamed_address::FN_ADDRESS_COMPARISONS, &unnamed_address::FN_ADDRESS_COMPARISONS,
&unnamed_address::VTABLE_ADDRESS_COMPARISONS, &unnamed_address::VTABLE_ADDRESS_COMPARISONS,
&unnecessary_sort_by::UNNECESSARY_SORT_BY, &unnecessary_sort_by::UNNECESSARY_SORT_BY,
&unnested_or_patterns::UNNESTED_OR_PATTERNS,
&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, &unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
&unused_io_amount::UNUSED_IO_AMOUNT, &unused_io_amount::UNUSED_IO_AMOUNT,
&unused_self::UNUSED_SELF, &unused_self::UNUSED_SELF,
@ -1073,6 +1077,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(move || box non_expressive_names::NonExpressiveNames { store.register_early_pass(move || box non_expressive_names::NonExpressiveNames {
single_char_binding_names_threshold, single_char_binding_names_threshold,
}); });
store.register_early_pass(|| box unnested_or_patterns::UnnestedOrPatterns);
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(&arithmetic::FLOAT_ARITHMETIC), LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@ -1433,6 +1438,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS),
LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS),
LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY),
LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS),
LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT),
LintId::of(&unwrap::PANICKING_UNWRAP), LintId::of(&unwrap::PANICKING_UNWRAP),
@ -1616,6 +1622,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&types::UNNECESSARY_CAST), LintId::of(&types::UNNECESSARY_CAST),
LintId::of(&types::VEC_BOX), LintId::of(&types::VEC_BOX),
LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY),
LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS),
LintId::of(&unwrap::UNNECESSARY_UNWRAP), LintId::of(&unwrap::UNNECESSARY_UNWRAP),
LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&useless_conversion::USELESS_CONVERSION),
LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO),

View file

@ -0,0 +1,407 @@
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use crate::utils::ast_utils::{eq_field_pat, eq_id, eq_pat, eq_path};
use crate::utils::{over, span_lint_and_then};
use rustc_ast::ast::{self, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
use rustc_ast::mut_visit::*;
use rustc_ast::ptr::P;
use rustc_ast_pretty::pprust;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::DUMMY_SP;
use std::cell::Cell;
use std::mem;
declare_clippy_lint! {
/// **What it does:**
///
/// Checks for unnested or-patterns, e.g., `Some(0) | Some(2)` and
/// suggests replacing the pattern with a nested one, `Some(0 | 2)`.
///
/// Another way to think of this is that it rewrites patterns in
/// *disjunctive normal form (DNF)* into *conjunctive normal form (CNF)*.
///
/// **Why is this bad?**
///
/// In the example above, `Some` is repeated, which unncessarily complicates the pattern.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// fn main() {
/// if let Some(0) | Some(2) = Some(0) {}
/// }
/// ```
/// Use instead:
/// ```rust
/// #![feature(or_patterns)]
///
/// fn main() {
/// if let Some(0 | 2) = Some(0) {}
/// }
/// ```
pub UNNESTED_OR_PATTERNS,
complexity,
"unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`"
}
declare_lint_pass!(UnnestedOrPatterns => [UNNESTED_OR_PATTERNS]);
impl EarlyLintPass for UnnestedOrPatterns {
fn check_arm(&mut self, cx: &EarlyContext<'_>, a: &ast::Arm) {
lint_unnested_or_patterns(cx, &a.pat);
}
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
if let ast::ExprKind::Let(pat, _) = &e.kind {
lint_unnested_or_patterns(cx, pat);
}
}
fn check_param(&mut self, cx: &EarlyContext<'_>, p: &ast::Param) {
lint_unnested_or_patterns(cx, &p.pat);
}
fn check_local(&mut self, cx: &EarlyContext<'_>, l: &ast::Local) {
lint_unnested_or_patterns(cx, &l.pat);
}
}
fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) {
if !cx.sess.opts.unstable_features.is_nightly_build() {
// User cannot do `#![feature(or_patterns)]`, so bail.
return;
}
if let Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_) = pat.kind {
// This is a leaf pattern, so cloning is unprofitable.
return;
}
let mut pat = P(pat.clone());
// Nix all the paren patterns everywhere so that they aren't in our way.
remove_all_parens(&mut pat);
// Transform all unnested or-patterns into nested ones, and if there were none, quit.
if !unnest_or_patterns(&mut pat) {
return;
}
span_lint_and_then(cx, UNNESTED_OR_PATTERNS, pat.span, "unnested or-patterns", |db| {
insert_necessary_parens(&mut pat);
db.span_suggestion_verbose(
pat.span,
"nest the patterns",
pprust::pat_to_string(&pat),
Applicability::MachineApplicable,
);
});
}
/// Remove all `(p)` patterns in `pat`.
fn remove_all_parens(pat: &mut P<Pat>) {
struct Visitor;
impl MutVisitor for Visitor {
fn visit_pat(&mut self, pat: &mut P<Pat>) {
noop_visit_pat(pat, self);
let inner = match &mut pat.kind {
Paren(i) => mem::replace(&mut i.kind, Wild),
_ => return,
};
pat.kind = inner;
}
}
Visitor.visit_pat(pat);
}
/// Insert parens where necessary according to Rust's precedence rules for patterns.
fn insert_necessary_parens(pat: &mut P<Pat>) {
struct Visitor;
impl MutVisitor for Visitor {
fn visit_pat(&mut self, pat: &mut P<Pat>) {
use ast::{BindingMode::*, Mutability::*};
noop_visit_pat(pat, self);
let target = match &mut pat.kind {
// `i @ a | b`, `box a | b`, and `& mut? a | b`.
Ident(.., Some(p)) | Box(p) | Ref(p, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p,
Ref(p, Not) if matches!(p.kind, Ident(ByValue(Mut), ..)) => p, // `&(mut x)`
_ => return,
};
target.kind = Paren(P(take_pat(target)));
}
}
Visitor.visit_pat(pat);
}
/// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`.
/// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`.
fn unnest_or_patterns(pat: &mut P<Pat>) -> bool {
struct Visitor {
changed: bool,
}
impl MutVisitor for Visitor {
fn visit_pat(&mut self, p: &mut P<Pat>) {
// This is a bottom up transformation, so recurse first.
noop_visit_pat(p, self);
// Don't have an or-pattern? Just quit early on.
let alternatives = match &mut p.kind {
Or(ps) => ps,
_ => return,
};
// Collapse or-patterns directly nested in or-patterns.
let mut idx = 0;
let mut this_level_changed = false;
while idx < alternatives.len() {
let inner = if let Or(ps) = &mut alternatives[idx].kind {
mem::take(ps)
} else {
idx += 1;
continue;
};
this_level_changed = true;
alternatives.splice(idx..=idx, inner);
}
// Focus on `p_n` and then try to transform all `p_i` where `i > n`.
let mut focus_idx = 0;
while focus_idx < alternatives.len() {
this_level_changed |= transform_with_focus_on_idx(alternatives, focus_idx);
focus_idx += 1;
}
self.changed |= this_level_changed;
// Deal with `Some(Some(0)) | Some(Some(1))`.
if this_level_changed {
noop_visit_pat(p, self);
}
}
}
let mut visitor = Visitor { changed: false };
visitor.visit_pat(pat);
visitor.changed
}
/// Match `$scrutinee` against `$pat` and extract `$then` from it.
/// Panics if there is no match.
macro_rules! always_pat {
($scrutinee:expr, $pat:pat => $then:expr) => {
match $scrutinee {
$pat => $then,
_ => unreachable!(),
}
};
}
/// Focus on `focus_idx` in `alternatives`,
/// attempting to extend it with elements of the same constructor `C`
/// in `alternatives[focus_idx + 1..]`.
fn transform_with_focus_on_idx(alternatives: &mut Vec<P<Pat>>, focus_idx: usize) -> bool {
// Extract the kind; we'll need to make some changes in it.
let mut focus_kind = mem::replace(&mut alternatives[focus_idx].kind, PatKind::Wild);
// We'll focus on `alternatives[focus_idx]`,
// so we're draining from `alternatives[focus_idx + 1..]`.
let start = focus_idx + 1;
// We're trying to find whatever kind (~"constructor") we found in `alternatives[start..]`.
let changed = match &mut focus_kind {
// These pattern forms are "leafs" and do not have sub-patterns.
// Therefore they are not some form of constructor `C`,
// with which a pattern `C(P0)` may be formed,
// which we would want to join with other `C(Pj)`s.
Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_)
// Dealt with elsewhere.
| Or(_) | Paren(_) => false,
// Transform `box x | ... | box y` into `box (x | y)`.
//
// The cases below until `Slice(...)` deal *singleton* products.
// These patterns have the shape `C(p)`, and not e.g., `C(p0, ..., pn)`.
Box(target) => extend_with_matching(
target, start, alternatives,
|k| matches!(k, Box(_)),
|k| always_pat!(k, Box(p) => p),
),
// Transform `&m x | ... | &m y` into `&m (x, y)`.
Ref(target, m1) => extend_with_matching(
target, start, alternatives,
|k| matches!(k, Ref(_, m2) if m1 == m2), // Mutabilities must match.
|k| always_pat!(k, Ref(p, _) => p),
),
// Transform `b @ p0 | ... b @ p1` into `b @ (p0 | p1)`.
Ident(b1, i1, Some(target)) => extend_with_matching(
target, start, alternatives,
// Binding names must match.
|k| matches!(k, Ident(b2, i2, Some(_)) if b1 == b2 && eq_id(*i1, *i2)),
|k| always_pat!(k, Ident(_, _, Some(p)) => p),
),
// Transform `[pre, x, post] | ... | [pre, y, post]` into `[pre, x | y, post]`.
Slice(ps1) => extend_with_matching_product(
ps1, start, alternatives,
|k, ps1, idx| matches!(k, Slice(ps2) if eq_pre_post(ps1, ps2, idx)),
|k| always_pat!(k, Slice(ps) => ps),
),
// Transform `(pre, x, post) | ... | (pre, y, post)` into `(pre, x | y, post]`.
Tuple(ps1) => extend_with_matching_product(
ps1, start, alternatives,
|k, ps1, idx| matches!(k, Tuple(ps2) if eq_pre_post(ps1, ps2, idx)),
|k| always_pat!(k, Tuple(ps) => ps),
),
// Transform `S(pre, x, post) | ... | S(pre, y, post)` into `S(pre, x | y, post]`.
TupleStruct(path1, ps1) => extend_with_matching_product(
ps1, start, alternatives,
|k, ps1, idx| matches!(
k,
TupleStruct(path2, ps2) if eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx)
),
|k| always_pat!(k, TupleStruct(_, ps) => ps),
),
// Transform a record pattern `S { fp_0, ..., fp_n }`.
Struct(path1, fps1, rest1) => extend_with_struct_pat(path1, fps1, *rest1, start, alternatives),
};
alternatives[focus_idx].kind = focus_kind;
changed
}
/// Here we focusing on a record pattern `S { fp_0, ..., fp_n }`.
/// In particular, for a record pattern, the order in which the field patterns is irrelevant.
/// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern
/// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal.
fn extend_with_struct_pat(
path1: &ast::Path,
fps1: &mut Vec<ast::FieldPat>,
rest1: bool,
start: usize,
alternatives: &mut Vec<P<Pat>>,
) -> bool {
(0..fps1.len()).any(|idx| {
let pos_in_2 = Cell::new(None); // The element `k`.
let tail_or = drain_matching(
start,
alternatives,
|k| {
matches!(k, Struct(path2, fps2, rest2)
if rest1 == *rest2 // If one struct pattern has `..` so must the other.
&& eq_path(path1, path2)
&& fps1.len() == fps2.len()
&& fps1.iter().enumerate().all(|(idx_1, fp1)| {
if idx_1 == idx {
// In the case of `k`, we merely require identical field names
// so that we will transform into `ident_k: p1_k | p2_k`.
let pos = fps2.iter().position(|fp2| eq_id(fp1.ident, fp2.ident));
pos_in_2.set(pos);
pos.is_some()
} else {
fps2.iter().any(|fp2| eq_field_pat(fp1, fp2))
}
}))
},
// Extract `p2_k`.
|k| always_pat!(k, Struct(_, mut fps, _) => fps.swap_remove(pos_in_2.take().unwrap()).pat),
);
extend_with_tail_or(&mut fps1[idx].pat, tail_or)
})
}
/// Like `extend_with_matching` but for products with > 1 factor, e.g., `C(p_0, ..., p_n)`.
/// Here, the idea is that we fixate on some `p_k` in `C`,
/// allowing it to vary between two `targets` and `ps2` (returned by `extract`),
/// while also requiring `ps1[..n] ~ ps2[..n]` (pre) and `ps1[n + 1..] ~ ps2[n + 1..]` (post),
/// where `~` denotes semantic equality.
fn extend_with_matching_product(
targets: &mut Vec<P<Pat>>,
start: usize,
alternatives: &mut Vec<P<Pat>>,
predicate: impl Fn(&PatKind, &[P<Pat>], usize) -> bool,
extract: impl Fn(PatKind) -> Vec<P<Pat>>,
) -> bool {
(0..targets.len()).any(|idx| {
let tail_or = drain_matching(
start,
alternatives,
|k| predicate(k, targets, idx),
|k| extract(k).swap_remove(idx),
);
extend_with_tail_or(&mut targets[idx], tail_or)
})
}
/// Extract the pattern from the given one and replace it with `Wild`.
/// This is meant for temporarily swapping out the pattern for manipulation.
fn take_pat(from: &mut Pat) -> Pat {
let dummy = Pat {
id: DUMMY_NODE_ID,
kind: Wild,
span: DUMMY_SP,
};
mem::replace(from, dummy)
}
/// Extend `target` as an or-pattern with the alternatives
/// in `tail_or` if there are any and return if there were.
fn extend_with_tail_or(target: &mut Pat, tail_or: Vec<P<Pat>>) -> bool {
fn extend(target: &mut Pat, mut tail_or: Vec<P<Pat>>) {
match target {
// On an existing or-pattern in the target, append to it.
Pat { kind: Or(ps), .. } => ps.append(&mut tail_or),
// Otherwise convert the target to an or-pattern.
target => {
let mut init_or = vec![P(take_pat(target))];
init_or.append(&mut tail_or);
target.kind = Or(init_or);
},
}
}
let changed = !tail_or.is_empty();
if changed {
// Extend the target.
extend(target, tail_or);
}
changed
}
// Extract all inner patterns in `alternatives` matching our `predicate`.
// Only elements beginning with `start` are considered for extraction.
fn drain_matching(
start: usize,
alternatives: &mut Vec<P<Pat>>,
predicate: impl Fn(&PatKind) -> bool,
extract: impl Fn(PatKind) -> P<Pat>,
) -> Vec<P<Pat>> {
let mut tail_or = vec![];
let mut idx = 0;
for pat in alternatives.drain_filter(|p| {
// Check if we should extract, but only if `idx >= start`.
idx += 1;
idx > start && predicate(&p.kind)
}) {
tail_or.push(extract(pat.into_inner().kind));
}
tail_or
}
fn extend_with_matching(
target: &mut Pat,
start: usize,
alternatives: &mut Vec<P<Pat>>,
predicate: impl Fn(&PatKind) -> bool,
extract: impl Fn(PatKind) -> P<Pat>,
) -> bool {
extend_with_tail_or(target, drain_matching(start, alternatives, predicate, extract))
}
/// Are the patterns in `ps1` and `ps2` equal save for `ps1[idx]` compared to `ps2[idx]`?
fn eq_pre_post(ps1: &[P<Pat>], ps2: &[P<Pat>], idx: usize) -> bool {
ps1[idx].is_rest() == ps2[idx].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`.
&& ps1.len() == ps2.len()
&& over(&ps1[..idx], &ps2[..idx], |l, r| eq_pat(l, r))
&& over(&ps1[idx + 1..], &ps2[idx + 1..], |l, r| eq_pat(l, r))
}

View file

@ -0,0 +1,525 @@
//! Utilities for manipulating and extracting information from `rustc_ast::ast`.
//!
//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s.
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
use crate::utils::{both, over};
use rustc_ast::ast::{self, *};
use rustc_ast::ptr::P;
use std::mem;
/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r)))
}
pub fn eq_id(l: Ident, r: Ident) -> bool {
l.name == r.name
}
pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
use PatKind::*;
match (&l.kind, &r.kind) {
(Paren(l), _) => eq_pat(l, r),
(_, Paren(r)) => eq_pat(l, r),
(Wild, Wild) | (Rest, Rest) => true,
(Lit(l), Lit(r)) => eq_expr(l, r),
(Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)),
(Range(lf, lt, le), Range(rf, rt, re)) => {
eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node)
},
(Box(l), Box(r))
| (Ref(l, Mutability::Not), Ref(r, Mutability::Not))
| (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r),
(Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)),
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
(TupleStruct(lp, lfs), TupleStruct(rp, rfs)) => eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)),
(Struct(lp, lfs, lr), Struct(rp, rfs, rr)) => {
lr == rr && eq_path(lp, rp) && unordered_over(lfs, rfs, |lf, rf| eq_field_pat(lf, rf))
},
(Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)),
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool {
match (l, r) {
(RangeEnd::Excluded, RangeEnd::Excluded) => true,
(RangeEnd::Included(l), RangeEnd::Included(r)) => {
matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq)
},
_ => false,
}
}
pub fn eq_field_pat(l: &FieldPat, r: &FieldPat) -> bool {
l.is_placeholder == r.is_placeholder
&& eq_id(l.ident, r.ident)
&& eq_pat(&l.pat, &r.pat)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
}
pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool {
l.position == r.position && eq_ty(&l.ty, &r.ty)
}
pub fn eq_path(l: &Path, r: &Path) -> bool {
over(&l.segments, &r.segments, |l, r| eq_path_seg(l, r))
}
pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool {
eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r))
}
pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool {
match (l, r) {
(GenericArgs::AngleBracketed(l), GenericArgs::AngleBracketed(r)) => {
over(&l.args, &r.args, |l, r| eq_angle_arg(l, r))
},
(GenericArgs::Parenthesized(l), GenericArgs::Parenthesized(r)) => {
over(&l.inputs, &r.inputs, |l, r| eq_ty(l, r)) && eq_fn_ret_ty(&l.output, &r.output)
},
_ => false,
}
}
pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool {
match (l, r) {
(AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r),
(AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r),
_ => false,
}
}
pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool {
match (l, r) {
(GenericArg::Lifetime(l), GenericArg::Lifetime(r)) => eq_id(l.ident, r.ident),
(GenericArg::Type(l), GenericArg::Type(r)) => eq_ty(l, r),
(GenericArg::Const(l), GenericArg::Const(r)) => eq_expr(&l.value, &r.value),
_ => false,
}
}
pub fn eq_expr_opt(l: &Option<P<Expr>>, r: &Option<P<Expr>>) -> bool {
both(l, r, |l, r| eq_expr(l, r))
}
pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
use ExprKind::*;
if !over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) {
return false;
}
match (&l.kind, &r.kind) {
(Paren(l), _) => eq_expr(l, r),
(_, Paren(r)) => eq_expr(l, r),
(Err, Err) => true,
(Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r),
(Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)),
(Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value),
(Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
(MethodCall(lc, la), MethodCall(rc, ra)) => eq_path_seg(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
(Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr),
(Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r),
(Lit(l), Lit(r)) => l.kind == r.kind,
(Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt),
(Let(lp, le), Let(rp, re)) => eq_pat(lp, rp) && eq_expr(le, re),
(If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re),
(While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt),
(ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => {
eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt)
},
(Loop(lt, ll), Loop(rt, rl)) => eq_label(ll, rl) && eq_block(lt, rt),
(Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb),
(TryBlock(l), TryBlock(r)) => eq_block(l, r),
(Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r),
(Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re),
(Continue(ll), Continue(rl)) => eq_label(ll, rl),
(Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2),
(AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv),
(Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp),
(Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, |l, r| eq_arm(l, r)),
(Closure(lc, la, lm, lf, lb, _), Closure(rc, ra, rm, rf, rb, _)) => {
lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(lb, rb)
},
(Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb),
(Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt),
(AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re),
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
(Struct(lp, lfs, lb), Struct(rp, rfs, rb)) => {
eq_path(lp, rp) && eq_expr_opt(lb, rb) && unordered_over(lfs, rfs, |l, r| eq_field(l, r))
},
_ => false,
}
}
pub fn eq_field(l: &Field, r: &Field) -> bool {
l.is_placeholder == r.is_placeholder
&& eq_id(l.ident, r.ident)
&& eq_expr(&l.expr, &r.expr)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
}
pub fn eq_arm(l: &Arm, r: &Arm) -> bool {
l.is_placeholder == r.is_placeholder
&& eq_pat(&l.pat, &r.pat)
&& eq_expr(&l.body, &r.body)
&& eq_expr_opt(&l.guard, &r.guard)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
}
pub fn eq_label(l: &Option<Label>, r: &Option<Label>) -> bool {
both(l, r, |l, r| eq_id(l.ident, r.ident))
}
pub fn eq_block(l: &Block, r: &Block) -> bool {
l.rules == r.rules && over(&l.stmts, &r.stmts, |l, r| eq_stmt(l, r))
}
pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
use StmtKind::*;
match (&l.kind, &r.kind) {
(Local(l), Local(r)) => {
eq_pat(&l.pat, &r.pat)
&& both(&l.ty, &r.ty, |l, r| eq_ty(l, r))
&& eq_expr_opt(&l.init, &r.init)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
},
(Item(l), Item(r)) => eq_item(l, r, eq_item_kind),
(Expr(l), Expr(r)) | (Semi(l), Semi(r)) => eq_expr(l, r),
(Empty, Empty) => true,
(MacCall(l), MacCall(r)) => l.1 == r.1 && eq_mac_call(&l.0, &r.0) && over(&l.2, &r.2, |l, r| eq_attr(l, r)),
_ => false,
}
}
pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool {
eq_id(l.ident, r.ident)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
&& eq_vis(&l.vis, &r.vis)
&& eq_kind(&l.kind, &r.kind)
}
pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
use ItemKind::*;
match (l, r) {
(ExternCrate(l), ExternCrate(r)) => l == r,
(Use(l), Use(r)) => eq_use_tree(l, r),
(Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(ld, lf, lg, lb), Fn(rd, rf, rg, rb)) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(Mod(l), Mod(r)) => l.inline == r.inline && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_item_kind)),
(ForeignMod(l), ForeignMod(r)) => {
both(&l.abi, &r.abi, |l, r| eq_str_lit(l, r))
&& over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
},
(TyAlias(ld, lg, lb, lt), TyAlias(rd, rg, rb, rt)) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& both(lt, rt, |l, r| eq_ty(l, r))
},
(Enum(le, lg), Enum(re, rg)) => {
over(&le.variants, &re.variants, |l, r| eq_variant(l, r)) && eq_generics(lg, rg)
},
(Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => {
eq_variant_data(lv, rv) && eq_generics(lg, rg)
},
(Trait(la, lu, lg, lb, li), Trait(ra, ru, rg, rb, ri)) => {
la == ra
&& matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
},
(TraitAlias(lg, lb), TraitAlias(rg, rb)) => eq_generics(lg, rg) && over(lb, rb, |l, r| eq_generic_bound(l, r)),
(
Impl {
unsafety: lu,
polarity: lp,
defaultness: ld,
constness: lc,
generics: lg,
of_trait: lot,
self_ty: lst,
items: li,
},
Impl {
unsafety: ru,
polarity: rp,
defaultness: rd,
constness: rc,
generics: rg,
of_trait: rot,
self_ty: rst,
items: ri,
},
) => {
matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
&& matches!(lp, ImplPolarity::Positive) == matches!(rp, ImplPolarity::Positive)
&& eq_defaultness(*ld, *rd)
&& matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No)
&& eq_generics(lg, rg)
&& both(lot, rot, |l, r| eq_path(&l.path, &r.path))
&& eq_ty(lst, rst)
&& over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
},
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
(MacroDef(l), MacroDef(r)) => l.macro_rules == r.macro_rules && eq_mac_args(&l.body, &r.body),
_ => false,
}
}
pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
use ForeignItemKind::*;
match (l, r) {
(Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(ld, lf, lg, lb), Fn(rd, rf, rg, rb)) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(TyAlias(ld, lg, lb, lt), TyAlias(rd, rg, rb, rt)) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& both(lt, rt, |l, r| eq_ty(l, r))
},
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
use AssocItemKind::*;
match (l, r) {
(Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(ld, lf, lg, lb), Fn(rd, rf, rg, rb)) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(TyAlias(ld, lg, lb, lt), TyAlias(rd, rg, rb, rt)) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& both(lt, rt, |l, r| eq_ty(l, r))
},
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_variant(l: &Variant, r: &Variant) -> bool {
l.is_placeholder == r.is_placeholder
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
&& eq_vis(&l.vis, &r.vis)
&& eq_id(l.ident, r.ident)
&& eq_variant_data(&l.data, &r.data)
&& both(&l.disr_expr, &r.disr_expr, |l, r| eq_expr(&l.value, &r.value))
}
pub fn eq_variant_data(l: &VariantData, r: &VariantData) -> bool {
use VariantData::*;
match (l, r) {
(Unit(_), Unit(_)) => true,
(Struct(l, _), Struct(r, _)) | (Tuple(l, _), Tuple(r, _)) => over(l, r, |l, r| eq_struct_field(l, r)),
_ => false,
}
}
pub fn eq_struct_field(l: &StructField, r: &StructField) -> bool {
l.is_placeholder == r.is_placeholder
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
&& eq_vis(&l.vis, &r.vis)
&& both(&l.ident, &r.ident, |l, r| eq_id(*l, *r))
&& eq_ty(&l.ty, &r.ty)
}
pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
eq_fn_decl(&l.decl, &r.decl) && eq_fn_header(&l.header, &r.header)
}
pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No)
&& l.asyncness.is_async() == r.asyncness.is_async()
&& matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
&& eq_ext(&l.ext, &r.ext)
}
pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
over(&l.params, &r.params, |l, r| eq_generic_param(l, r))
&& over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
eq_where_predicate(l, r)
})
}
pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
use WherePredicate::*;
match (l, r) {
(BoundPredicate(l), BoundPredicate(r)) => {
over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
eq_generic_param(l, r)
}) && eq_ty(&l.bounded_ty, &r.bounded_ty)
&& over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
},
(RegionPredicate(l), RegionPredicate(r)) => {
eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
},
(EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
_ => false,
}
}
pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
eq_path(&l.prefix, &r.prefix) && eq_use_tree_kind(&l.kind, &r.kind)
}
pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool {
use UseTreeKind::*;
match (l, r) {
(Glob, Glob) => true,
(Simple(l, _, _), Simple(r, _, _)) => both(l, r, |l, r| eq_id(*l, *r)),
(Nested(l), Nested(r)) => over(l, r, |(l, _), (r, _)| eq_use_tree(l, r)),
_ => false,
}
}
pub fn eq_defaultness(l: Defaultness, r: Defaultness) -> bool {
match (l, r) {
(Defaultness::Final, Defaultness::Final) | (Defaultness::Default(_), Defaultness::Default(_)) => true,
_ => false,
}
}
pub fn eq_vis(l: &Visibility, r: &Visibility) -> bool {
use VisibilityKind::*;
match (&l.node, &r.node) {
(Public, Public) | (Inherited, Inherited) | (Crate(_), Crate(_)) => true,
(Restricted { path: l, .. }, Restricted { path: r, .. }) => eq_path(l, r),
_ => false,
}
}
pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool {
eq_fn_ret_ty(&l.output, &r.output)
&& over(&l.inputs, &r.inputs, |l, r| {
l.is_placeholder == r.is_placeholder
&& eq_pat(&l.pat, &r.pat)
&& eq_ty(&l.ty, &r.ty)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
})
}
pub fn eq_fn_ret_ty(l: &FnRetTy, r: &FnRetTy) -> bool {
match (l, r) {
(FnRetTy::Default(_), FnRetTy::Default(_)) => true,
(FnRetTy::Ty(l), FnRetTy::Ty(r)) => eq_ty(l, r),
_ => false,
}
}
pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
use TyKind::*;
match (&l.kind, &r.kind) {
(Paren(l), _) => eq_ty(l, r),
(_, Paren(r)) => eq_ty(l, r),
(Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err, Err) | (CVarArgs, CVarArgs) => true,
(Slice(l), Slice(r)) => eq_ty(l, r),
(Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value),
(Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty),
(Rptr(ll, l), Rptr(rl, r)) => {
both(ll, rl, |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty)
},
(BareFn(l), BareFn(r)) => {
l.unsafety == r.unsafety
&& eq_ext(&l.ext, &r.ext)
&& over(&l.generic_params, &r.generic_params, |l, r| eq_generic_param(l, r))
&& eq_fn_decl(&l.decl, &r.decl)
},
(Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
(TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, |l, r| eq_generic_bound(l, r)),
(ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, |l, r| eq_generic_bound(l, r)),
(Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_ext(l: &Extern, r: &Extern) -> bool {
use Extern::*;
match (l, r) {
(None, None) | (Implicit, Implicit) => true,
(Explicit(l), Explicit(r)) => eq_str_lit(l, r),
_ => false,
}
}
pub fn eq_str_lit(l: &StrLit, r: &StrLit) -> bool {
l.style == r.style && l.symbol == r.symbol && l.suffix == r.suffix
}
pub fn eq_poly_ref_trait(l: &PolyTraitRef, r: &PolyTraitRef) -> bool {
eq_path(&l.trait_ref.path, &r.trait_ref.path)
&& over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
eq_generic_param(l, r)
})
}
pub fn eq_generic_param(l: &GenericParam, r: &GenericParam) -> bool {
use GenericParamKind::*;
l.is_placeholder == r.is_placeholder
&& eq_id(l.ident, r.ident)
&& over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
&& match (&l.kind, &r.kind) {
(Lifetime, Lifetime) => true,
(Type { default: l }, Type { default: r }) => both(l, r, |l, r| eq_ty(l, r)),
(Const { ty: l }, Const { ty: r }) => eq_ty(l, r),
_ => false,
}
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
}
pub fn eq_generic_bound(l: &GenericBound, r: &GenericBound) -> bool {
use GenericBound::*;
match (l, r) {
(Trait(ptr1, tbm1), Trait(ptr2, tbm2)) => tbm1 == tbm2 && eq_poly_ref_trait(ptr1, ptr2),
(Outlives(l), Outlives(r)) => eq_id(l.ident, r.ident),
_ => false,
}
}
pub fn eq_assoc_constraint(l: &AssocTyConstraint, r: &AssocTyConstraint) -> bool {
use AssocTyConstraintKind::*;
eq_id(l.ident, r.ident)
&& match (&l.kind, &r.kind) {
(Equality { ty: l }, Equality { ty: r }) => eq_ty(l, r),
(Bound { bounds: l }, Bound { bounds: r }) => over(l, r, |l, r| eq_generic_bound(l, r)),
_ => false,
}
}
pub fn eq_mac_call(l: &MacCall, r: &MacCall) -> bool {
eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args)
}
pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
use AttrKind::*;
l.style == r.style
&& match (&l.kind, &r.kind) {
(DocComment(l), DocComment(r)) => l == r,
(Normal(l), Normal(r)) => eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args),
_ => false,
}
}
pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
use MacArgs::*;
match (l, r) {
(Empty, Empty) => true,
(Delimited(_, ld, lts), Delimited(_, rd, rts)) => ld == rd && lts.eq_unspanned(rts),
(Eq(_, lts), Eq(_, rts)) => lts.eq_unspanned(rts),
_ => false,
}
}

View file

@ -332,19 +332,13 @@ fn swap_binop<'a>(
/// Checks if the two `Option`s are both `None` or some equal values as per /// Checks if the two `Option`s are both `None` or some equal values as per
/// `eq_fn`. /// `eq_fn`.
fn both<X, F>(l: &Option<X>, r: &Option<X>, mut eq_fn: F) -> bool pub fn both<X>(l: &Option<X>, r: &Option<X>, mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
where
F: FnMut(&X, &X) -> bool,
{
l.as_ref() l.as_ref()
.map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y))) .map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
} }
/// Checks if two slices are equal as per `eq_fn`. /// Checks if two slices are equal as per `eq_fn`.
fn over<X, F>(left: &[X], right: &[X], mut eq_fn: F) -> bool pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
where
F: FnMut(&X, &X) -> bool,
{
left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y)) left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
} }

View file

@ -1,6 +1,7 @@
#[macro_use] #[macro_use]
pub mod sym; pub mod sym;
pub mod ast_utils;
pub mod attrs; pub mod attrs;
pub mod author; pub mod author;
pub mod camel_case; pub mod camel_case;
@ -19,7 +20,7 @@ pub mod sugg;
pub mod usage; pub mod usage;
pub use self::attrs::*; pub use self::attrs::*;
pub use self::diagnostics::*; pub use self::diagnostics::*;
pub use self::hir_utils::{SpanlessEq, SpanlessHash}; pub use self::hir_utils::{both, over, SpanlessEq, SpanlessHash};
use std::borrow::Cow; use std::borrow::Cow;
use std::mem; use std::mem;

View file

@ -2327,6 +2327,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
deprecation: None, deprecation: None,
module: "misc_early", module: "misc_early",
}, },
Lint {
name: "unnested_or_patterns",
group: "complexity",
desc: "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`",
deprecation: None,
module: "unnested_or_patterns",
},
Lint { Lint {
name: "unreachable", name: "unreachable",
group: "restriction", group: "restriction",

View file

@ -4,6 +4,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
#[allow(clippy::unnested_or_patterns)]
#[warn(clippy::neg_cmp_op_on_partial_ord)] #[warn(clippy::neg_cmp_op_on_partial_ord)]
fn main() { fn main() {
let a_value = 1.0; let a_value = 1.0;

View file

@ -1,5 +1,5 @@
error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable. error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable.
--> $DIR/neg_cmp_op_on_partial_ord.rs:15:21 --> $DIR/neg_cmp_op_on_partial_ord.rs:16:21
| |
LL | let _not_less = !(a_value < another_value); LL | let _not_less = !(a_value < another_value);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -7,19 +7,19 @@ LL | let _not_less = !(a_value < another_value);
= note: `-D clippy::neg-cmp-op-on-partial-ord` implied by `-D warnings` = note: `-D clippy::neg-cmp-op-on-partial-ord` implied by `-D warnings`
error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable. error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable.
--> $DIR/neg_cmp_op_on_partial_ord.rs:18:30 --> $DIR/neg_cmp_op_on_partial_ord.rs:19:30
| |
LL | let _not_less_or_equal = !(a_value <= another_value); LL | let _not_less_or_equal = !(a_value <= another_value);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable. error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable.
--> $DIR/neg_cmp_op_on_partial_ord.rs:21:24 --> $DIR/neg_cmp_op_on_partial_ord.rs:22:24
| |
LL | let _not_greater = !(a_value > another_value); LL | let _not_greater = !(a_value > another_value);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable. error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable.
--> $DIR/neg_cmp_op_on_partial_ord.rs:24:33 --> $DIR/neg_cmp_op_on_partial_ord.rs:25:33
| |
LL | let _not_greater_or_equal = !(a_value >= another_value); LL | let _not_greater_or_equal = !(a_value >= another_value);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -0,0 +1,41 @@
// run-rustfix
#![feature(or_patterns)]
#![feature(box_patterns)]
#![warn(clippy::unnested_or_patterns)]
#![allow(clippy::cognitive_complexity, clippy::match_ref_pats)]
#![allow(unreachable_patterns, irrefutable_let_patterns, unused_variables)]
fn main() {
if let box (0 | 2) = Box::new(0) {}
if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {}
const C0: &u8 = &1;
if let &(0 | 2) | C0 = &0 {}
if let &mut (0 | 2) = &mut 0 {}
if let x @ (0 | 2) = 0 {}
if let (0, 1 | 2 | 3) = (0, 0) {}
if let (1 | 2 | 3, 0) = (0, 0) {}
if let (x, ..) | (x, 1 | 2) = (0, 1) {}
if let [0 | 1] = [0] {}
if let [x, 0 | 1] = [0, 1] {}
if let [x, 0 | 1 | 2] = [0, 1] {}
if let [x, ..] | [x, 1 | 2] = [0, 1] {}
struct TS(u8, u8);
if let TS(0 | 1, x) = TS(0, 0) {}
if let TS(1 | 2 | 3, 0) = TS(0, 0) {}
if let TS(x, ..) | TS(x, 1 | 2) = TS(0, 0) {}
struct S {
x: u8,
y: u8,
}
if let S { x: 0 | 1, y } = (S { x: 0, y: 1 }) {}
if let S { x: 0, y, .. } | S { y, x: 1 } = (S { x: 0, y: 1 }) {}
if let Some(Some(0 | 1)) = None {}
if let Some(Some(0 | 1 | 2)) = None {}
if let Some(Some(0 | 1 | 2 | 3 | 4)) = None {}
if let Some(Some(0 | 1 | 2)) = None {}
if let ((0 | 1 | 2,),) = ((0,),) {}
if let 0 | 1 | 2 = 0 {}
if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {}
if let box box (0 | 2 | 4) = Box::new(Box::new(0)) {}
}

View file

@ -0,0 +1,41 @@
// run-rustfix
#![feature(or_patterns)]
#![feature(box_patterns)]
#![warn(clippy::unnested_or_patterns)]
#![allow(clippy::cognitive_complexity, clippy::match_ref_pats)]
#![allow(unreachable_patterns, irrefutable_let_patterns, unused_variables)]
fn main() {
if let box 0 | box 2 = Box::new(0) {}
if let box ((0 | 1)) | box (2 | 3) | box 4 = Box::new(0) {}
const C0: &u8 = &1;
if let &0 | C0 | &2 = &0 {}
if let &mut 0 | &mut 2 = &mut 0 {}
if let x @ 0 | x @ 2 = 0 {}
if let (0, 1) | (0, 2) | (0, 3) = (0, 0) {}
if let (1, 0) | (2, 0) | (3, 0) = (0, 0) {}
if let (x, ..) | (x, 1) | (x, 2) = (0, 1) {}
if let [0] | [1] = [0] {}
if let [x, 0] | [x, 1] = [0, 1] {}
if let [x, 0] | [x, 1] | [x, 2] = [0, 1] {}
if let [x, ..] | [x, 1] | [x, 2] = [0, 1] {}
struct TS(u8, u8);
if let TS(0, x) | TS(1, x) = TS(0, 0) {}
if let TS(1, 0) | TS(2, 0) | TS(3, 0) = TS(0, 0) {}
if let TS(x, ..) | TS(x, 1) | TS(x, 2) = TS(0, 0) {}
struct S {
x: u8,
y: u8,
}
if let S { x: 0, y } | S { y, x: 1 } = (S { x: 0, y: 1 }) {}
if let S { x: 0, y, .. } | S { y, x: 1 } = (S { x: 0, y: 1 }) {}
if let Some(Some(0)) | Some(Some(1)) = None {}
if let Some(Some(0)) | Some(Some(1) | Some(2)) = None {}
if let Some(Some(0 | 1) | Some(2)) | Some(Some(3) | Some(4)) = None {}
if let Some(Some(0) | Some(1 | 2)) = None {}
if let ((0,),) | ((1,) | (2,),) = ((0,),) {}
if let 0 | (1 | 2) = 0 {}
if let box (0 | 1) | (box 2 | box (3 | 4)) = Box::new(0) {}
if let box box 0 | box (box 2 | box 4) = Box::new(Box::new(0)) {}
}

View file

@ -0,0 +1,267 @@
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:10:12
|
LL | if let box 0 | box 2 = Box::new(0) {}
| ^^^^^^^^^^^^^
|
= note: `-D clippy::unnested-or-patterns` implied by `-D warnings`
help: nest the patterns
|
LL | if let box (0 | 2) = Box::new(0) {}
| ^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:11:12
|
LL | if let box ((0 | 1)) | box (2 | 3) | box 4 = Box::new(0) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {}
| ^^^^^^^^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:13:12
|
LL | if let &0 | C0 | &2 = &0 {}
| ^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let &(0 | 2) | C0 = &0 {}
| ^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:14:12
|
LL | if let &mut 0 | &mut 2 = &mut 0 {}
| ^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let &mut (0 | 2) = &mut 0 {}
| ^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:15:12
|
LL | if let x @ 0 | x @ 2 = 0 {}
| ^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let x @ (0 | 2) = 0 {}
| ^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:16:12
|
LL | if let (0, 1) | (0, 2) | (0, 3) = (0, 0) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let (0, 1 | 2 | 3) = (0, 0) {}
| ^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:17:12
|
LL | if let (1, 0) | (2, 0) | (3, 0) = (0, 0) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let (1 | 2 | 3, 0) = (0, 0) {}
| ^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:18:12
|
LL | if let (x, ..) | (x, 1) | (x, 2) = (0, 1) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let (x, ..) | (x, 1 | 2) = (0, 1) {}
| ^^^^^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:19:12
|
LL | if let [0] | [1] = [0] {}
| ^^^^^^^^^
|
help: nest the patterns
|
LL | if let [0 | 1] = [0] {}
| ^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:20:12
|
LL | if let [x, 0] | [x, 1] = [0, 1] {}
| ^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let [x, 0 | 1] = [0, 1] {}
| ^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:21:12
|
LL | if let [x, 0] | [x, 1] | [x, 2] = [0, 1] {}
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let [x, 0 | 1 | 2] = [0, 1] {}
| ^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:22:12
|
LL | if let [x, ..] | [x, 1] | [x, 2] = [0, 1] {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let [x, ..] | [x, 1 | 2] = [0, 1] {}
| ^^^^^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:24:12
|
LL | if let TS(0, x) | TS(1, x) = TS(0, 0) {}
| ^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let TS(0 | 1, x) = TS(0, 0) {}
| ^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:25:12
|
LL | if let TS(1, 0) | TS(2, 0) | TS(3, 0) = TS(0, 0) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let TS(1 | 2 | 3, 0) = TS(0, 0) {}
| ^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:26:12
|
LL | if let TS(x, ..) | TS(x, 1) | TS(x, 2) = TS(0, 0) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let TS(x, ..) | TS(x, 1 | 2) = TS(0, 0) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:31:12
|
LL | if let S { x: 0, y } | S { y, x: 1 } = (S { x: 0, y: 1 }) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let S { x: 0 | 1, y } = (S { x: 0, y: 1 }) {}
| ^^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:33:12
|
LL | if let Some(Some(0)) | Some(Some(1)) = None {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let Some(Some(0 | 1)) = None {}
| ^^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:34:12
|
LL | if let Some(Some(0)) | Some(Some(1) | Some(2)) = None {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let Some(Some(0 | 1 | 2)) = None {}
| ^^^^^^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:35:12
|
LL | if let Some(Some(0 | 1) | Some(2)) | Some(Some(3) | Some(4)) = None {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let Some(Some(0 | 1 | 2 | 3 | 4)) = None {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:36:12
|
LL | if let Some(Some(0) | Some(1 | 2)) = None {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let Some(Some(0 | 1 | 2)) = None {}
| ^^^^^^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:37:12
|
LL | if let ((0,),) | ((1,) | (2,),) = ((0,),) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let ((0 | 1 | 2,),) = ((0,),) {}
| ^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:38:12
|
LL | if let 0 | (1 | 2) = 0 {}
| ^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let 0 | 1 | 2 = 0 {}
| ^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:39:12
|
LL | if let box (0 | 1) | (box 2 | box (3 | 4)) = Box::new(0) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {}
| ^^^^^^^^^^^^^^^^^^^^^^^
error: unnested or-patterns
--> $DIR/unnested_or_patterns.rs:40:12
|
LL | if let box box 0 | box (box 2 | box 4) = Box::new(Box::new(0)) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: nest the patterns
|
LL | if let box box (0 | 2 | 4) = Box::new(Box::new(0)) {}
| ^^^^^^^^^^^^^^^^^^^
error: aborting due to 24 previous errors

View file

@ -6,7 +6,8 @@
unused_variables, unused_variables,
dead_code, dead_code,
clippy::single_match, clippy::single_match,
clippy::wildcard_in_or_patterns clippy::wildcard_in_or_patterns,
clippy::unnested_or_patterns
)] )]
use std::io::ErrorKind; use std::io::ErrorKind;

View file

@ -6,7 +6,8 @@
unused_variables, unused_variables,
dead_code, dead_code,
clippy::single_match, clippy::single_match,
clippy::wildcard_in_or_patterns clippy::wildcard_in_or_patterns,
clippy::unnested_or_patterns
)] )]
use std::io::ErrorKind; use std::io::ErrorKind;

View file

@ -1,5 +1,5 @@
error: wildcard match will miss any future added variants error: wildcard match will miss any future added variants
--> $DIR/wildcard_enum_match_arm.rs:37:9 --> $DIR/wildcard_enum_match_arm.rs:38:9
| |
LL | _ => eprintln!("Not red"), LL | _ => eprintln!("Not red"),
| ^ help: try this: `Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan` | ^ help: try this: `Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan`
@ -11,25 +11,25 @@ LL | #![deny(clippy::wildcard_enum_match_arm)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: wildcard match will miss any future added variants error: wildcard match will miss any future added variants
--> $DIR/wildcard_enum_match_arm.rs:41:9 --> $DIR/wildcard_enum_match_arm.rs:42:9
| |
LL | _not_red => eprintln!("Not red"), LL | _not_red => eprintln!("Not red"),
| ^^^^^^^^ help: try this: `_not_red @ Color::Green | _not_red @ Color::Blue | _not_red @ Color::Rgb(..) | _not_red @ Color::Cyan` | ^^^^^^^^ help: try this: `_not_red @ Color::Green | _not_red @ Color::Blue | _not_red @ Color::Rgb(..) | _not_red @ Color::Cyan`
error: wildcard match will miss any future added variants error: wildcard match will miss any future added variants
--> $DIR/wildcard_enum_match_arm.rs:45:9 --> $DIR/wildcard_enum_match_arm.rs:46:9
| |
LL | not_red => format!("{:?}", not_red), LL | not_red => format!("{:?}", not_red),
| ^^^^^^^ help: try this: `not_red @ Color::Green | not_red @ Color::Blue | not_red @ Color::Rgb(..) | not_red @ Color::Cyan` | ^^^^^^^ help: try this: `not_red @ Color::Green | not_red @ Color::Blue | not_red @ Color::Rgb(..) | not_red @ Color::Cyan`
error: wildcard match will miss any future added variants error: wildcard match will miss any future added variants
--> $DIR/wildcard_enum_match_arm.rs:61:9 --> $DIR/wildcard_enum_match_arm.rs:62:9
| |
LL | _ => "No red", LL | _ => "No red",
| ^ help: try this: `Color::Red | Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan` | ^ help: try this: `Color::Red | Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan`
error: match on non-exhaustive enum doesn't explicitly match all known variants error: match on non-exhaustive enum doesn't explicitly match all known variants
--> $DIR/wildcard_enum_match_arm.rs:78:9 --> $DIR/wildcard_enum_match_arm.rs:79:9
| |
LL | _ => {}, LL | _ => {},
| ^ help: try this: `std::io::ErrorKind::PermissionDenied | std::io::ErrorKind::ConnectionRefused | std::io::ErrorKind::ConnectionReset | std::io::ErrorKind::ConnectionAborted | std::io::ErrorKind::NotConnected | std::io::ErrorKind::AddrInUse | std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::AlreadyExists | std::io::ErrorKind::WouldBlock | std::io::ErrorKind::InvalidInput | std::io::ErrorKind::InvalidData | std::io::ErrorKind::TimedOut | std::io::ErrorKind::WriteZero | std::io::ErrorKind::Interrupted | std::io::ErrorKind::Other | std::io::ErrorKind::UnexpectedEof | _` | ^ help: try this: `std::io::ErrorKind::PermissionDenied | std::io::ErrorKind::ConnectionRefused | std::io::ErrorKind::ConnectionReset | std::io::ErrorKind::ConnectionAborted | std::io::ErrorKind::NotConnected | std::io::ErrorKind::AddrInUse | std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::AlreadyExists | std::io::ErrorKind::WouldBlock | std::io::ErrorKind::InvalidInput | std::io::ErrorKind::InvalidData | std::io::ErrorKind::TimedOut | std::io::ErrorKind::WriteZero | std::io::ErrorKind::Interrupted | std::io::ErrorKind::Other | std::io::ErrorKind::UnexpectedEof | _`