Skip to content

add new lint: rest_when_destructuring_struct #15000

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6238,6 +6238,7 @@ Released 2018-09-13
[`repr_packed_without_abi`]: https://rust-lang.github.io/rust-clippy/master/index.html#repr_packed_without_abi
[`reserve_after_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#reserve_after_initialization
[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
[`rest_when_destructuring_struct`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_when_destructuring_struct
[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used
[`result_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_filter_map
[`result_large_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::regex::TRIVIAL_REGEX_INFO,
crate::repeat_vec_with_capacity::REPEAT_VEC_WITH_CAPACITY_INFO,
crate::reserve_after_initialization::RESERVE_AFTER_INITIALIZATION_INFO,
crate::rest_when_destructuring_struct::REST_WHEN_DESTRUCTURING_STRUCT_INFO,
crate::return_self_not_must_use::RETURN_SELF_NOT_MUST_USE_INFO,
crate::returns::LET_AND_RETURN_INFO,
crate::returns::NEEDLESS_RETURN_INFO,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ mod reference;
mod regex;
mod repeat_vec_with_capacity;
mod reserve_after_initialization;
mod rest_when_destructuring_struct;
mod return_self_not_must_use;
mod returns;
mod same_name_method;
Expand Down Expand Up @@ -951,5 +952,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom));
store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny));
store.register_late_pass(|_| Box::new(rest_when_destructuring_struct::RestWhenDestructuringStruct));
// add lints here, do not remove this comment, it's used in `new_lint`
}
102 changes: 102 additions & 0 deletions clippy_lints/src/rest_when_destructuring_struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use crate::rustc_lint::LintContext as _;
use clippy_utils::diagnostics::span_lint_and_then;
use itertools::Itertools;
use rustc_abi::VariantIdx;
use rustc_lint::LateLintPass;
use rustc_middle::ty;
use rustc_session::declare_lint_pass;

declare_clippy_lint! {
/// ### What it does
/// Disallows the use of rest patterns when destructuring structs.
///
/// ### Why is this bad?
/// It might lead to unhandled fields when the struct changes.
///
/// ### Example
/// ```no_run
/// struct S {
/// a: u8,
/// b: u8,
/// c: u8,
/// }
///
/// let s = S { a: 1, b: 2, c: 3 };
///
/// let S { a, b, .. } = s;
/// ```
/// Use instead:
/// ```no_run
/// struct S {
/// a: u8,
/// b: u8,
/// c: u8,
/// }
///
/// let s = S { a: 1, b: 2, c: 3 };
///
/// let S { a, b, c: _ } = s;
/// ```
#[clippy::version = "1.89.0"]
pub REST_WHEN_DESTRUCTURING_STRUCT,
nursery,
"rest (..) in destructuring expression"
}
declare_lint_pass!(RestWhenDestructuringStruct => [REST_WHEN_DESTRUCTURING_STRUCT]);

impl<'tcx> LateLintPass<'tcx> for RestWhenDestructuringStruct {
fn check_pat(&mut self, cx: &rustc_lint::LateContext<'tcx>, pat: &'tcx rustc_hir::Pat<'tcx>) {
if pat.span.in_external_macro(cx.tcx.sess.source_map()) {
return;
}

if let rustc_hir::PatKind::Struct(path, fields, true) = pat.kind {
let qty = cx.typeck_results().qpath_res(&path, pat.hir_id);
let ty = cx.typeck_results().pat_ty(pat);
if let ty::Adt(a, _) = ty.kind() {
let vid = qty
.opt_def_id()
.map_or(VariantIdx::ZERO, |x| a.variant_index_with_id(x));
let mut rest_fields = a.variants()[vid]
.fields
.iter()
.map(|field| field.ident(cx.tcx))
.filter(|pf| !fields.iter().any(|x| x.ident == *pf))
.map(|x| format!("{x}: _"));
let fmt_fields = rest_fields.join(", ");

let sm = cx.sess().source_map();

// It is not possible to get the span of the et cetera symbol at HIR level
// so we have to get it in a bit of a roundabout way:

// Find the end of the last field if any.
let last_field = fields.iter().last().map(|x| x.span.shrink_to_hi());
// If no last field take the whole pattern.
let last_field = last_field.unwrap_or(pat.span.shrink_to_lo());
// Create a new span starting and ending just before the first .
let before_dot = sm.span_extend_to_next_char(last_field, '.', true).shrink_to_hi();
// Extend the span to the end of the line
let rest_of_line = sm.span_extend_to_next_char(before_dot, '\n', true);
// Shrink the span so it only contains dots
let dotdot = sm.span_take_while(rest_of_line, |x| *x == '.');

span_lint_and_then(
cx,
REST_WHEN_DESTRUCTURING_STRUCT,
pat.span,
"struct destructuring with rest (..)",
|diag| {
// println!("{:?}", pat);
diag.span_suggestion_verbose(
dotdot,
"consider explicitly ignoring remaining fields with wildcard patterns (x: _)",
fmt_fields,
rustc_errors::Applicability::MachineApplicable,
);
},
);
}
}
}
}
39 changes: 39 additions & 0 deletions tests/ui/rest_when_destructuring_struct.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#![warn(clippy::rest_when_destructuring_struct)]
#![allow(dead_code)]
#![allow(unused_variables)]

struct S {
a: u8,
b: u8,
c: u8,
}

enum E {
A { a1: u8, a2: u8 },
B { b1: u8, b2: u8 },
C {},
}

fn main() {
let s = S { a: 1, b: 2, c: 3 };

let S { a, b, c: _ } = s;
//~^ rest_when_destructuring_struct

let e = E::A { a1: 1, a2: 2 };

match e {
E::A { a1, a2 } => (),
E::B { b1: _, b2: _ } => (),
//~^ rest_when_destructuring_struct
E::C { } => (),
//~^ rest_when_destructuring_struct
}

match e {
E::A { a1: _, a2: _ } => (),
E::B { b1: _, b2: _ } => (),
//~^ rest_when_destructuring_struct
E::C {} => (),
}
}
39 changes: 39 additions & 0 deletions tests/ui/rest_when_destructuring_struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#![warn(clippy::rest_when_destructuring_struct)]
#![allow(dead_code)]
#![allow(unused_variables)]

struct S {
a: u8,
b: u8,
c: u8,
}

enum E {
A { a1: u8, a2: u8 },
B { b1: u8, b2: u8 },
C {},
}

fn main() {
let s = S { a: 1, b: 2, c: 3 };

let S { a, b, .. } = s;
//~^ rest_when_destructuring_struct

let e = E::A { a1: 1, a2: 2 };

match e {
E::A { a1, a2 } => (),
E::B { .. } => (),
//~^ rest_when_destructuring_struct
E::C { .. } => (),
//~^ rest_when_destructuring_struct
}

match e {
E::A { a1: _, a2: _ } => (),
E::B { b1: _, .. } => (),
//~^ rest_when_destructuring_struct
E::C {} => (),
}
}
52 changes: 52 additions & 0 deletions tests/ui/rest_when_destructuring_struct.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
error: struct destructuring with rest (..)
--> tests/ui/rest_when_destructuring_struct.rs:20:9
|
LL | let S { a, b, .. } = s;
| ^^^^^^^^^^^^^^
|
= note: `-D clippy::rest-when-destructuring-struct` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::rest_when_destructuring_struct)]`
help: consider explicitly ignoring remaining fields with wildcard patterns (x: _)
|
LL - let S { a, b, .. } = s;
LL + let S { a, b, c: _ } = s;
|

error: struct destructuring with rest (..)
--> tests/ui/rest_when_destructuring_struct.rs:27:9
|
LL | E::B { .. } => (),
| ^^^^^^^^^^^
|
help: consider explicitly ignoring remaining fields with wildcard patterns (x: _)
|
LL - E::B { .. } => (),
LL + E::B { b1: _, b2: _ } => (),
|

error: struct destructuring with rest (..)
--> tests/ui/rest_when_destructuring_struct.rs:29:9
|
LL | E::C { .. } => (),
| ^^^^^^^^^^^
|
help: consider explicitly ignoring remaining fields with wildcard patterns (x: _)
|
LL - E::C { .. } => (),
LL + E::C { } => (),
|

error: struct destructuring with rest (..)
--> tests/ui/rest_when_destructuring_struct.rs:35:9
|
LL | E::B { b1: _, .. } => (),
| ^^^^^^^^^^^^^^^^^^
|
help: consider explicitly ignoring remaining fields with wildcard patterns (x: _)
|
LL - E::B { b1: _, .. } => (),
LL + E::B { b1: _, b2: _ } => (),
|

error: aborting due to 4 previous errors