From fdf880145accf50f9f43dda1c65c4a4dfc43dd9f Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Fri, 6 Jun 2025 14:21:43 +0200 Subject: [PATCH 01/13] add new lint: `rest_when_destructuring_struct` --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + .../src/rest_when_destructuring_struct.rs | 57 +++++++++++++++++++ tests/ui/rest_when_destructuring_struct.rs | 34 +++++++++++ .../ui/rest_when_destructuring_struct.stderr | 20 +++++++ 6 files changed, 115 insertions(+) create mode 100644 clippy_lints/src/rest_when_destructuring_struct.rs create mode 100644 tests/ui/rest_when_destructuring_struct.rs create mode 100644 tests/ui/rest_when_destructuring_struct.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb2755be0ee..486c556c800f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6821,6 +6821,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 diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index a754eea31165..ffa465f2443b 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -658,6 +658,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::repeat_vec_with_capacity::REPEAT_VEC_WITH_CAPACITY_INFO, crate::replace_box::REPLACE_BOX_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, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 033f85e70ef0..ed6917ca4ec1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -314,6 +314,7 @@ mod regex; mod repeat_vec_with_capacity; mod replace_box; mod reserve_after_initialization; +mod rest_when_destructuring_struct; mod return_self_not_must_use; mod returns; mod same_name_method; @@ -819,5 +820,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)); store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites)); store.register_late_pass(|_| Box::new(replace_box::ReplaceBox::default())); + store.register_early_pass(|| Box::new(rest_when_destructuring_struct::RestWhenDestructuringStruct)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs new file mode 100644 index 000000000000..5e9882c4f36b --- /dev/null +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -0,0 +1,57 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_ast::ast::{Pat, PatFieldsRest, PatKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +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 EarlyLintPass for RestWhenDestructuringStruct { + fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) { + if let PatKind::Struct(_, _, _, PatFieldsRest::Rest) = pat.kind { + span_lint_and_help( + cx, + REST_WHEN_DESTRUCTURING_STRUCT, + pat.span, + "struct destructuring with rest (..)", + None, + "consider explicitly ignoring remaining fields with wildcard patterns (x: _)", + ); + } + } +} diff --git a/tests/ui/rest_when_destructuring_struct.rs b/tests/ui/rest_when_destructuring_struct.rs new file mode 100644 index 000000000000..9c881d6cdfd5 --- /dev/null +++ b/tests/ui/rest_when_destructuring_struct.rs @@ -0,0 +1,34 @@ +#![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 }, +} + +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 + } + + match e { + E::A { a1: _, a2: _ } => (), + E::B { b1, b2: _ } => (), + } +} diff --git a/tests/ui/rest_when_destructuring_struct.stderr b/tests/ui/rest_when_destructuring_struct.stderr new file mode 100644 index 000000000000..209514821c9c --- /dev/null +++ b/tests/ui/rest_when_destructuring_struct.stderr @@ -0,0 +1,20 @@ +error: struct destructuring with rest (..) + --> tests/ui/rest_when_destructuring_struct.rs:19:9 + | +LL | let S { a, b, .. } = s; + | ^^^^^^^^^^^^^^ + | + = help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) + = note: `-D clippy::rest-when-destructuring-struct` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::rest_when_destructuring_struct)]` + +error: struct destructuring with rest (..) + --> tests/ui/rest_when_destructuring_struct.rs:26:9 + | +LL | E::B { .. } => (), + | ^^^^^^^^^^^ + | + = help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) + +error: aborting due to 2 previous errors + From f62d3fa235bd3b0117cf5839d5c8c161c6c73e16 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Sun, 8 Jun 2025 11:39:22 +0200 Subject: [PATCH 02/13] add suggestions to `rest_when_destructuring_struct` --- clippy_lints/src/lib.rs | 2 +- .../src/rest_when_destructuring_struct.rs | 70 +++++++++++++++---- tests/ui/rest_when_destructuring_struct.fixed | 39 +++++++++++ tests/ui/rest_when_destructuring_struct.rs | 7 +- .../ui/rest_when_destructuring_struct.stderr | 42 +++++++++-- 5 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 tests/ui/rest_when_destructuring_struct.fixed diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ed6917ca4ec1..c8381817db26 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -820,6 +820,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)); store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites)); store.register_late_pass(|_| Box::new(replace_box::ReplaceBox::default())); - store.register_early_pass(|| Box::new(rest_when_destructuring_struct::RestWhenDestructuringStruct)); + 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` } diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs index 5e9882c4f36b..8ddda5e0645b 100644 --- a/clippy_lints/src/rest_when_destructuring_struct.rs +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -1,6 +1,8 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use rustc_ast::ast::{Pat, PatFieldsRest, PatKind}; -use rustc_lint::{EarlyContext, EarlyLintPass}; +use crate::rustc_lint::LintContext as _; +use clippy_utils::diagnostics::span_lint_and_then; +use itertools::Itertools; +use rustc_lint::LateLintPass; +use rustc_middle::ty; use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -41,17 +43,57 @@ declare_clippy_lint! { } declare_lint_pass!(RestWhenDestructuringStruct => [REST_WHEN_DESTRUCTURING_STRUCT]); -impl EarlyLintPass for RestWhenDestructuringStruct { - fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) { - if let PatKind::Struct(_, _, _, PatFieldsRest::Rest) = pat.kind { - span_lint_and_help( - cx, - REST_WHEN_DESTRUCTURING_STRUCT, - pat.span, - "struct destructuring with rest (..)", - None, - "consider explicitly ignoring remaining fields with wildcard patterns (x: _)", - ); +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 = a.variant_index_with_id(qty.def_id()); + 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, + ); + }, + ); + } } } } diff --git a/tests/ui/rest_when_destructuring_struct.fixed b/tests/ui/rest_when_destructuring_struct.fixed new file mode 100644 index 000000000000..f0c5a161f5e9 --- /dev/null +++ b/tests/ui/rest_when_destructuring_struct.fixed @@ -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 {} => (), + } +} diff --git a/tests/ui/rest_when_destructuring_struct.rs b/tests/ui/rest_when_destructuring_struct.rs index 9c881d6cdfd5..6c9d90d5f400 100644 --- a/tests/ui/rest_when_destructuring_struct.rs +++ b/tests/ui/rest_when_destructuring_struct.rs @@ -11,6 +11,7 @@ struct S { enum E { A { a1: u8, a2: u8 }, B { b1: u8, b2: u8 }, + C {}, } fn main() { @@ -25,10 +26,14 @@ fn main() { 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, b2: _ } => (), + E::B { b1: _, .. } => (), + //~^ rest_when_destructuring_struct + E::C {} => (), } } diff --git a/tests/ui/rest_when_destructuring_struct.stderr b/tests/ui/rest_when_destructuring_struct.stderr index 209514821c9c..79f4d16d4232 100644 --- a/tests/ui/rest_when_destructuring_struct.stderr +++ b/tests/ui/rest_when_destructuring_struct.stderr @@ -1,20 +1,52 @@ error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:19:9 + --> tests/ui/rest_when_destructuring_struct.rs:20:9 | LL | let S { a, b, .. } = s; | ^^^^^^^^^^^^^^ | - = help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) = 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:26:9 + --> tests/ui/rest_when_destructuring_struct.rs:27:9 | LL | E::B { .. } => (), | ^^^^^^^^^^^ | - = help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) +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 2 previous errors +error: aborting due to 4 previous errors From b2d79f4ae64b1ac1da0c1adfbf478772a05a0ed7 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Sun, 8 Jun 2025 12:02:56 +0200 Subject: [PATCH 03/13] guard `rest_when_destructuring_struct` against panics --- clippy_lints/src/rest_when_destructuring_struct.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs index 8ddda5e0645b..b0088fd9ec86 100644 --- a/clippy_lints/src/rest_when_destructuring_struct.rs +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -1,6 +1,7 @@ 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; @@ -53,7 +54,9 @@ impl<'tcx> LateLintPass<'tcx> for RestWhenDestructuringStruct { 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 = a.variant_index_with_id(qty.def_id()); + 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() From fc69b18405fed17cf32a19a41993cabfc240ace1 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Fri, 13 Jun 2025 18:35:07 +0200 Subject: [PATCH 04/13] impl WithSearchPat for patterns --- clippy_utils/src/check_proc_macro.rs | 97 ++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 544218e09930..8266339978bd 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -540,6 +540,102 @@ fn ident_search_pat(ident: Ident) -> (Pat, Pat) { (Pat::Sym(ident.name), Pat::Sym(ident.name)) } +fn pat_search_pat(tcx: TyCtxt<'_>, pat: &rustc_hir::Pat<'_>) -> (Pat, Pat) { + match pat.kind { + rustc_hir::PatKind::Missing | rustc_hir::PatKind::Err(_) => (Pat::Str(""), Pat::Str("")), + rustc_hir::PatKind::Wild => (Pat::Sym(kw::Underscore), Pat::Sym(kw::Underscore)), + rustc_hir::PatKind::Binding(binding_mode, _, ident, Some(end_pat)) => { + let start = match binding_mode.0 { + rustc_hir::ByRef::Yes(rustc_hir::Mutability::Not) => Pat::Str("ref"), + rustc_hir::ByRef::Yes(rustc_hir::Mutability::Mut) => Pat::Str("ref mut"), + rustc_hir::ByRef::No => { + let (s, _) = ident_search_pat(ident); + s + }, + }; + + let (_, end) = pat_search_pat(tcx, end_pat); + (start, end) + }, + rustc_hir::PatKind::Binding(binding_mode, _, ident, None) => { + let (s, end) = ident_search_pat(ident); + let start = match binding_mode.0 { + rustc_hir::ByRef::Yes(rustc_hir::Mutability::Not) => Pat::Str("ref"), + rustc_hir::ByRef::Yes(rustc_hir::Mutability::Mut) => Pat::Str("ref mut"), + rustc_hir::ByRef::No => s, + }; + + (start, end) + }, + rustc_hir::PatKind::Struct(path, _, _) => { + let (start, _) = qpath_search_pat(&path); + (start, Pat::Str("}")) + }, + rustc_hir::PatKind::TupleStruct(path, _, _) => { + let (start, _) = qpath_search_pat(&path); + (start, Pat::Str(")")) + }, + rustc_hir::PatKind::Or(plist) => { + // documented invariant + debug_assert!(plist.len() >= 2); + let (start, _) = pat_search_pat(tcx, plist.first().unwrap()); + let (_, end) = pat_search_pat(tcx, plist.last().unwrap()); + (start, end) + }, + rustc_hir::PatKind::Never => (Pat::Str("!"), Pat::Str("")), + rustc_hir::PatKind::Tuple(_, _) => (Pat::Str("("), Pat::Str(")")), + rustc_hir::PatKind::Box(p) => { + let (_, end) = pat_search_pat(tcx, p); + (Pat::Str("box"), end) + }, + rustc_hir::PatKind::Deref(_) => (Pat::Str("deref!("), Pat::Str(")")), + rustc_hir::PatKind::Ref(p, _) => { + let (_, end) = pat_search_pat(tcx, p); + (Pat::Str("&"), end) + }, + rustc_hir::PatKind::Expr(expr) => pat_search_pat_expr_kind(expr), + rustc_hir::PatKind::Guard(pat, guard) => { + let (start, _) = pat_search_pat(tcx, pat); + let (_, end) = expr_search_pat(tcx, guard); + (start, end) + }, + rustc_hir::PatKind::Range(None, None, range) => match range { + rustc_hir::RangeEnd::Included => (Pat::Str("..="), Pat::Str("")), + rustc_hir::RangeEnd::Excluded => (Pat::Str(".."), Pat::Str("")), + }, + rustc_hir::PatKind::Range(r_start, r_end, range) => { + let (start, _) = match r_start { + Some(e) => pat_search_pat_expr_kind(e), + None => match range { + rustc_hir::RangeEnd::Included => (Pat::Str("..="), Pat::Str("")), + rustc_hir::RangeEnd::Excluded => (Pat::Str(".."), Pat::Str("")), + }, + }; + + let (_, end) = match r_end { + Some(e) => pat_search_pat_expr_kind(e), + None => match range { + rustc_hir::RangeEnd::Included => (Pat::Str(""), Pat::Str("..=")), + rustc_hir::RangeEnd::Excluded => (Pat::Str(""), Pat::Str("..")), + }, + }; + (start, end) + }, + rustc_hir::PatKind::Slice(_, _, _) => (Pat::Str("["), Pat::Str("]")), + } +} + +fn pat_search_pat_expr_kind(expr: &crate::PatExpr<'_>) -> (Pat, Pat) { + match expr.kind { + crate::PatExprKind::Lit { lit, negated } => { + let (start, end) = lit_search_pat(&lit.node); + if negated { (Pat::Str("!"), end) } else { (start, end) } + }, + crate::PatExprKind::ConstBlock(_block) => (Pat::Str("const {"), Pat::Str("}")), + crate::PatExprKind::Path(path) => qpath_search_pat(&path), + } +} + pub trait WithSearchPat<'cx> { type Context: LintContext; fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat); @@ -568,6 +664,7 @@ impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ty<'_>) => ty_search_pat(se impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ident) => ident_search_pat(*self)); impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&self.node)); impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self)); +impl_with_search_pat!((cx: LateContext<'tcx>, self: rustc_hir::Pat<'_>) => pat_search_pat(cx.tcx, self)); impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: Attribute) => attr_search_pat(self)); impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: ast::Ty) => ast_ty_search_pat(self)); From b634f9dd5a2cc927481df360972c2883081064c0 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Fri, 13 Jun 2025 18:36:20 +0200 Subject: [PATCH 05/13] `rest_when_destructuring_struct`: check if inside of proc macro --- .../src/rest_when_destructuring_struct.rs | 5 +++++ tests/ui/rest_when_destructuring_struct.fixed | 17 ++++++++++++++ tests/ui/rest_when_destructuring_struct.rs | 17 ++++++++++++++ .../ui/rest_when_destructuring_struct.stderr | 22 ++++++++++++++----- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs index b0088fd9ec86..6d5e49c0ce1f 100644 --- a/clippy_lints/src/rest_when_destructuring_struct.rs +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -1,5 +1,6 @@ use crate::rustc_lint::LintContext as _; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_from_proc_macro; use itertools::Itertools; use rustc_abi::VariantIdx; use rustc_lint::LateLintPass; @@ -50,6 +51,10 @@ impl<'tcx> LateLintPass<'tcx> for RestWhenDestructuringStruct { return; } + if is_from_proc_macro(cx, pat) { + 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); diff --git a/tests/ui/rest_when_destructuring_struct.fixed b/tests/ui/rest_when_destructuring_struct.fixed index f0c5a161f5e9..ad1409ae37c8 100644 --- a/tests/ui/rest_when_destructuring_struct.fixed +++ b/tests/ui/rest_when_destructuring_struct.fixed @@ -1,7 +1,10 @@ +//@aux-build:proc_macros.rs #![warn(clippy::rest_when_destructuring_struct)] #![allow(dead_code)] #![allow(unused_variables)] +extern crate proc_macros; + struct S { a: u8, b: u8, @@ -20,6 +23,9 @@ fn main() { let S { a, b, c: _ } = s; //~^ rest_when_destructuring_struct + let S { a, b, c, } = s; + //~^ rest_when_destructuring_struct + let e = E::A { a1: 1, a2: 2 }; match e { @@ -36,4 +42,15 @@ fn main() { //~^ rest_when_destructuring_struct E::C {} => (), } + + proc_macros::external! { + let s1 = S { a: 1, b: 2, c: 3 }; + let S { a, b, .. } = s1; + } + + proc_macros::with_span! { + span + let s2 = S { a: 1, b: 2, c: 3 }; + let S { a, b, .. } = s2; + } } diff --git a/tests/ui/rest_when_destructuring_struct.rs b/tests/ui/rest_when_destructuring_struct.rs index 6c9d90d5f400..ec5a5e905ed3 100644 --- a/tests/ui/rest_when_destructuring_struct.rs +++ b/tests/ui/rest_when_destructuring_struct.rs @@ -1,7 +1,10 @@ +//@aux-build:proc_macros.rs #![warn(clippy::rest_when_destructuring_struct)] #![allow(dead_code)] #![allow(unused_variables)] +extern crate proc_macros; + struct S { a: u8, b: u8, @@ -20,6 +23,9 @@ fn main() { let S { a, b, .. } = s; //~^ rest_when_destructuring_struct + let S { a, b, c, .. } = s; + //~^ rest_when_destructuring_struct + let e = E::A { a1: 1, a2: 2 }; match e { @@ -36,4 +42,15 @@ fn main() { //~^ rest_when_destructuring_struct E::C {} => (), } + + proc_macros::external! { + let s1 = S { a: 1, b: 2, c: 3 }; + let S { a, b, .. } = s1; + } + + proc_macros::with_span! { + span + let s2 = S { a: 1, b: 2, c: 3 }; + let S { a, b, .. } = s2; + } } diff --git a/tests/ui/rest_when_destructuring_struct.stderr b/tests/ui/rest_when_destructuring_struct.stderr index 79f4d16d4232..c7667c4920c3 100644 --- a/tests/ui/rest_when_destructuring_struct.stderr +++ b/tests/ui/rest_when_destructuring_struct.stderr @@ -1,5 +1,5 @@ error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:20:9 + --> tests/ui/rest_when_destructuring_struct.rs:23:9 | LL | let S { a, b, .. } = s; | ^^^^^^^^^^^^^^ @@ -13,7 +13,19 @@ LL + let S { a, b, c: _ } = s; | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:27:9 + --> tests/ui/rest_when_destructuring_struct.rs:26:9 + | +LL | let S { a, b, c, .. } = s; + | ^^^^^^^^^^^^^^^^^ + | +help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) + | +LL - let S { a, b, c, .. } = s; +LL + let S { a, b, c, } = s; + | + +error: struct destructuring with rest (..) + --> tests/ui/rest_when_destructuring_struct.rs:33:9 | LL | E::B { .. } => (), | ^^^^^^^^^^^ @@ -25,7 +37,7 @@ LL + E::B { b1: _, b2: _ } => (), | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:29:9 + --> tests/ui/rest_when_destructuring_struct.rs:35:9 | LL | E::C { .. } => (), | ^^^^^^^^^^^ @@ -37,7 +49,7 @@ LL + E::C { } => (), | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:35:9 + --> tests/ui/rest_when_destructuring_struct.rs:41:9 | LL | E::B { b1: _, .. } => (), | ^^^^^^^^^^^^^^^^^^ @@ -48,5 +60,5 @@ LL - E::B { b1: _, .. } => (), LL + E::B { b1: _, b2: _ } => (), | -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors From 62a1d3caadd4d8aa1c3d6f9f2079e39ff61929d4 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Sun, 17 Aug 2025 17:08:18 +0200 Subject: [PATCH 06/13] remove debug log --- clippy_lints/src/rest_when_destructuring_struct.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs index 6d5e49c0ce1f..056dc22f6a82 100644 --- a/clippy_lints/src/rest_when_destructuring_struct.rs +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -92,7 +92,6 @@ impl<'tcx> LateLintPass<'tcx> for RestWhenDestructuringStruct { pat.span, "struct destructuring with rest (..)", |diag| { - // println!("{:?}", pat); diag.span_suggestion_verbose( dotdot, "consider explicitly ignoring remaining fields with wildcard patterns (x: _)", From 8ab5d437e1585c0bd9ae130293ecffd72ff5df29 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Thu, 21 Aug 2025 08:18:41 +0200 Subject: [PATCH 07/13] use let chains to get more in line with the general style --- .../src/rest_when_destructuring_struct.rs | 94 +++++++++---------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs index 056dc22f6a82..1c24f19f082b 100644 --- a/clippy_lints/src/rest_when_destructuring_struct.rs +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -47,60 +47,54 @@ declare_lint_pass!(RestWhenDestructuringStruct => [REST_WHEN_DESTRUCTURING_STRUC 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 is_from_proc_macro(cx, pat) { - 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(", "); + if let rustc_hir::PatKind::Struct(path, fields, true) = pat.kind + && !pat.span.in_external_macro(cx.tcx.sess.source_map()) + && !is_from_proc_macro(cx, pat) + && let qty = cx.typeck_results().qpath_res(&path, pat.hir_id) + && let ty = cx.typeck_results().pat_ty(pat) + && 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(); + 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: + // 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 == '.'); + // 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| { - diag.span_suggestion_verbose( - dotdot, - "consider explicitly ignoring remaining fields with wildcard patterns (x: _)", - fmt_fields, - rustc_errors::Applicability::MachineApplicable, - ); - }, - ); - } + span_lint_and_then( + cx, + REST_WHEN_DESTRUCTURING_STRUCT, + pat.span, + "struct destructuring with rest (..)", + |diag| { + diag.span_suggestion_verbose( + dotdot, + "consider explicitly ignoring remaining fields with wildcard patterns (x: _)", + fmt_fields, + rustc_errors::Applicability::MachineApplicable, + ); + }, + ); } } } From 85b73c340775c921f1b4aec60bdbb65517720fd5 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Mon, 8 Sep 2025 17:28:41 +0200 Subject: [PATCH 08/13] Use the span of .. now that the compiler provides it --- .../src/rest_when_destructuring_struct.rs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs index 1c24f19f082b..389ac7fa8219 100644 --- a/clippy_lints/src/rest_when_destructuring_struct.rs +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -1,4 +1,3 @@ -use crate::rustc_lint::LintContext as _; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; use itertools::Itertools; @@ -47,7 +46,7 @@ declare_lint_pass!(RestWhenDestructuringStruct => [REST_WHEN_DESTRUCTURING_STRUC impl<'tcx> LateLintPass<'tcx> for RestWhenDestructuringStruct { fn check_pat(&mut self, cx: &rustc_lint::LateContext<'tcx>, pat: &'tcx rustc_hir::Pat<'tcx>) { - if let rustc_hir::PatKind::Struct(path, fields, true) = pat.kind + if let rustc_hir::PatKind::Struct(path, fields, Some(dotdot)) = pat.kind && !pat.span.in_external_macro(cx.tcx.sess.source_map()) && !is_from_proc_macro(cx, pat) && let qty = cx.typeck_results().qpath_res(&path, pat.hir_id) @@ -65,22 +64,6 @@ impl<'tcx> LateLintPass<'tcx> for RestWhenDestructuringStruct { .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, From c3e9310cca3ac9b8e514d3c156e9d639b524212a Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Wed, 10 Sep 2025 06:44:17 +0200 Subject: [PATCH 09/13] handle non local non-exhaustive structs as well as private fields --- .../src/rest_when_destructuring_struct.rs | 24 +++++++++++++++++-- tests/ui/auxiliary/non-exhaustive-struct.rs | 7 ++++++ tests/ui/rest_when_destructuring_struct.fixed | 14 +++++++++++ tests/ui/rest_when_destructuring_struct.rs | 14 +++++++++++ .../ui/rest_when_destructuring_struct.stderr | 23 +++++++++++++----- 5 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 tests/ui/auxiliary/non-exhaustive-struct.rs diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs index 389ac7fa8219..46917167ee23 100644 --- a/clippy_lints/src/rest_when_destructuring_struct.rs +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -3,7 +3,7 @@ use clippy_utils::is_from_proc_macro; use itertools::Itertools; use rustc_abi::VariantIdx; use rustc_lint::LateLintPass; -use rustc_middle::ty; +use rustc_middle::ty::{self, Visibility}; use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -56,13 +56,33 @@ impl<'tcx> LateLintPass<'tcx> for RestWhenDestructuringStruct { let vid = qty .opt_def_id() .map_or(VariantIdx::ZERO, |x| a.variant_index_with_id(x)); + + let leave_dotdot = a.variants()[vid].field_list_has_applicable_non_exhaustive(); + let mut rest_fields = a.variants()[vid] .fields .iter() + .filter(|f| { + if a.did().is_local() { + true + } else { + matches!(f.vis, Visibility::Public) + } + }) .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 mut fmt_fields = rest_fields.join(", "); + + if fmt_fields.is_empty() && leave_dotdot { + // The struct is non_exhaustive, from a non-local crate and all public fields are explicitly named. + return; + } + + if leave_dotdot { + fmt_fields.push_str(", .."); + } span_lint_and_then( cx, diff --git a/tests/ui/auxiliary/non-exhaustive-struct.rs b/tests/ui/auxiliary/non-exhaustive-struct.rs new file mode 100644 index 000000000000..3d944d9775a2 --- /dev/null +++ b/tests/ui/auxiliary/non-exhaustive-struct.rs @@ -0,0 +1,7 @@ +#[non_exhaustive] +#[derive(Default)] +pub struct NonExhaustiveStruct { + pub field1: i32, + pub field2: i32, + _private: i32, +} diff --git a/tests/ui/rest_when_destructuring_struct.fixed b/tests/ui/rest_when_destructuring_struct.fixed index ad1409ae37c8..3af8ad56b2e5 100644 --- a/tests/ui/rest_when_destructuring_struct.fixed +++ b/tests/ui/rest_when_destructuring_struct.fixed @@ -1,10 +1,15 @@ //@aux-build:proc_macros.rs +//@aux-build:non-exhaustive-struct.rs #![warn(clippy::rest_when_destructuring_struct)] #![allow(dead_code)] #![allow(unused_variables)] extern crate proc_macros; +extern crate non_exhaustive_struct; + +use non_exhaustive_struct::NonExhaustiveStruct; + struct S { a: u8, b: u8, @@ -53,4 +58,13 @@ fn main() { let s2 = S { a: 1, b: 2, c: 3 }; let S { a, b, .. } = s2; } + + let ne = NonExhaustiveStruct::default(); + let NonExhaustiveStruct { field1: _, field2: _, .. } = ne; + //~^ rest_when_destructuring_struct + + let ne = NonExhaustiveStruct::default(); + let NonExhaustiveStruct { + field1: _, field2: _, .. + } = ne; } diff --git a/tests/ui/rest_when_destructuring_struct.rs b/tests/ui/rest_when_destructuring_struct.rs index ec5a5e905ed3..f782bbaea804 100644 --- a/tests/ui/rest_when_destructuring_struct.rs +++ b/tests/ui/rest_when_destructuring_struct.rs @@ -1,10 +1,15 @@ //@aux-build:proc_macros.rs +//@aux-build:non-exhaustive-struct.rs #![warn(clippy::rest_when_destructuring_struct)] #![allow(dead_code)] #![allow(unused_variables)] extern crate proc_macros; +extern crate non_exhaustive_struct; + +use non_exhaustive_struct::NonExhaustiveStruct; + struct S { a: u8, b: u8, @@ -53,4 +58,13 @@ fn main() { let s2 = S { a: 1, b: 2, c: 3 }; let S { a, b, .. } = s2; } + + let ne = NonExhaustiveStruct::default(); + let NonExhaustiveStruct { field1: _, .. } = ne; + //~^ rest_when_destructuring_struct + + let ne = NonExhaustiveStruct::default(); + let NonExhaustiveStruct { + field1: _, field2: _, .. + } = ne; } diff --git a/tests/ui/rest_when_destructuring_struct.stderr b/tests/ui/rest_when_destructuring_struct.stderr index c7667c4920c3..c18ac9af88e1 100644 --- a/tests/ui/rest_when_destructuring_struct.stderr +++ b/tests/ui/rest_when_destructuring_struct.stderr @@ -1,5 +1,5 @@ error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:23:9 + --> tests/ui/rest_when_destructuring_struct.rs:28:9 | LL | let S { a, b, .. } = s; | ^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + let S { a, b, c: _ } = s; | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:26:9 + --> tests/ui/rest_when_destructuring_struct.rs:31:9 | LL | let S { a, b, c, .. } = s; | ^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + let S { a, b, c, } = s; | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:33:9 + --> tests/ui/rest_when_destructuring_struct.rs:38:9 | LL | E::B { .. } => (), | ^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL + E::B { b1: _, b2: _ } => (), | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:35:9 + --> tests/ui/rest_when_destructuring_struct.rs:40:9 | LL | E::C { .. } => (), | ^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + E::C { } => (), | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:41:9 + --> tests/ui/rest_when_destructuring_struct.rs:46:9 | LL | E::B { b1: _, .. } => (), | ^^^^^^^^^^^^^^^^^^ @@ -60,5 +60,16 @@ LL - E::B { b1: _, .. } => (), LL + E::B { b1: _, b2: _ } => (), | -error: aborting due to 5 previous errors +error: struct destructuring with rest (..) + --> tests/ui/rest_when_destructuring_struct.rs:63:9 + | +LL | let NonExhaustiveStruct { field1: _, .. } = ne; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) + | +LL | let NonExhaustiveStruct { field1: _, field2: _, .. } = ne; + | ++++++++++ + +error: aborting due to 6 previous errors From 50e9c09a87f0051cd2dc047c3a40693748172874 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Wed, 8 Oct 2025 20:35:08 +0000 Subject: [PATCH 10/13] handle feedback from review --- .../src/rest_when_destructuring_struct.rs | 22 ++--- clippy_utils/src/check_proc_macro.rs | 85 +++++++++---------- tests/ui/rest_when_destructuring_struct.fixed | 14 +++ tests/ui/rest_when_destructuring_struct.rs | 14 +++ .../ui/rest_when_destructuring_struct.stderr | 25 ++++-- 5 files changed, 96 insertions(+), 64 deletions(-) diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs index 46917167ee23..648a3805f02d 100644 --- a/clippy_lints/src/rest_when_destructuring_struct.rs +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -3,7 +3,7 @@ use clippy_utils::is_from_proc_macro; use itertools::Itertools; use rustc_abi::VariantIdx; use rustc_lint::LateLintPass; -use rustc_middle::ty::{self, Visibility}; +use rustc_middle::ty; use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -39,7 +39,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.89.0"] pub REST_WHEN_DESTRUCTURING_STRUCT, - nursery, + restriction, "rest (..) in destructuring expression" } declare_lint_pass!(RestWhenDestructuringStruct => [REST_WHEN_DESTRUCTURING_STRUCT]); @@ -57,21 +57,17 @@ impl<'tcx> LateLintPass<'tcx> for RestWhenDestructuringStruct { .opt_def_id() .map_or(VariantIdx::ZERO, |x| a.variant_index_with_id(x)); - let leave_dotdot = a.variants()[vid].field_list_has_applicable_non_exhaustive(); + let leave_dotdot = a.variants()[vid] + .fields + .iter() + .any(|f| !f.vis.is_accessible_from(cx.tcx.parent_module(pat.hir_id), cx.tcx)); let mut rest_fields = a.variants()[vid] .fields .iter() - .filter(|f| { - if a.did().is_local() { - true - } else { - matches!(f.vis, Visibility::Public) - } - }) - .map(|field| field.ident(cx.tcx)) - .filter(|pf| !fields.iter().any(|x| x.ident == *pf)) - .map(|x| format!("{x}: _")); + .filter(|f| f.vis.is_accessible_from(cx.tcx.parent_module(pat.hir_id), cx.tcx)) + .filter(|pf| !fields.iter().any(|x| x.ident.name == pf.name)) + .map(|x| format!("{}: _", x.ident(cx.tcx))); let mut fmt_fields = rest_fields.join(", "); diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 8266339978bd..4ea2ff4b5951 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -16,15 +16,15 @@ use rustc_abi::ExternAbi; use rustc_ast as ast; use rustc_ast::AttrStyle; use rustc_ast::ast::{ - AttrKind, Attribute, GenericArgs, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy, + AttrKind, Attribute, BindingMode, GenericArgs, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy, }; use rustc_ast::token::CommentKind; use rustc_hir::intravisit::FnKind; use rustc_hir::{ Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, FnRetTy, HirId, Impl, - ImplItem, ImplItemImplKind, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, - QPath, Safety, TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, - YieldSource, + ImplItem, ImplItemImplKind, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, + PatExpr, PatExprKind, PatKind, Path, QPath, Safety, TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, + UnsafeSource, Variant, VariantData, YieldSource, }; use rustc_lint::{EarlyContext, LateContext, LintContext}; use rustc_middle::ty::TyCtxt; @@ -542,97 +542,94 @@ fn ident_search_pat(ident: Ident) -> (Pat, Pat) { fn pat_search_pat(tcx: TyCtxt<'_>, pat: &rustc_hir::Pat<'_>) -> (Pat, Pat) { match pat.kind { - rustc_hir::PatKind::Missing | rustc_hir::PatKind::Err(_) => (Pat::Str(""), Pat::Str("")), - rustc_hir::PatKind::Wild => (Pat::Sym(kw::Underscore), Pat::Sym(kw::Underscore)), - rustc_hir::PatKind::Binding(binding_mode, _, ident, Some(end_pat)) => { - let start = match binding_mode.0 { - rustc_hir::ByRef::Yes(rustc_hir::Mutability::Not) => Pat::Str("ref"), - rustc_hir::ByRef::Yes(rustc_hir::Mutability::Mut) => Pat::Str("ref mut"), - rustc_hir::ByRef::No => { - let (s, _) = ident_search_pat(ident); - s - }, + PatKind::Missing | PatKind::Err(_) => (Pat::Str(""), Pat::Str("")), + PatKind::Wild => (Pat::Sym(kw::Underscore), Pat::Sym(kw::Underscore)), + PatKind::Binding(binding_mode, _, ident, Some(end_pat)) => { + let start = if binding_mode == BindingMode::NONE { + ident_search_pat(ident).0 + } else { + Pat::Str(binding_mode.prefix_str()) }; let (_, end) = pat_search_pat(tcx, end_pat); (start, end) }, - rustc_hir::PatKind::Binding(binding_mode, _, ident, None) => { + PatKind::Binding(binding_mode, _, ident, None) => { let (s, end) = ident_search_pat(ident); - let start = match binding_mode.0 { - rustc_hir::ByRef::Yes(rustc_hir::Mutability::Not) => Pat::Str("ref"), - rustc_hir::ByRef::Yes(rustc_hir::Mutability::Mut) => Pat::Str("ref mut"), - rustc_hir::ByRef::No => s, + let start = if binding_mode == BindingMode::NONE { + s + } else { + Pat::Str(binding_mode.prefix_str()) }; (start, end) }, - rustc_hir::PatKind::Struct(path, _, _) => { + PatKind::Struct(path, _, _) => { let (start, _) = qpath_search_pat(&path); (start, Pat::Str("}")) }, - rustc_hir::PatKind::TupleStruct(path, _, _) => { + PatKind::TupleStruct(path, _, _) => { let (start, _) = qpath_search_pat(&path); (start, Pat::Str(")")) }, - rustc_hir::PatKind::Or(plist) => { + PatKind::Or(plist) => { // documented invariant debug_assert!(plist.len() >= 2); let (start, _) = pat_search_pat(tcx, plist.first().unwrap()); let (_, end) = pat_search_pat(tcx, plist.last().unwrap()); (start, end) }, - rustc_hir::PatKind::Never => (Pat::Str("!"), Pat::Str("")), - rustc_hir::PatKind::Tuple(_, _) => (Pat::Str("("), Pat::Str(")")), - rustc_hir::PatKind::Box(p) => { + PatKind::Never => (Pat::Str("!"), Pat::Str("")), + PatKind::Tuple(_, _) => (Pat::Str("("), Pat::Str(")")), + PatKind::Box(p) => { let (_, end) = pat_search_pat(tcx, p); (Pat::Str("box"), end) }, - rustc_hir::PatKind::Deref(_) => (Pat::Str("deref!("), Pat::Str(")")), - rustc_hir::PatKind::Ref(p, _) => { + PatKind::Deref(_) => (Pat::Str("deref!("), Pat::Str(")")), + PatKind::Ref(p, _) => { let (_, end) = pat_search_pat(tcx, p); (Pat::Str("&"), end) }, - rustc_hir::PatKind::Expr(expr) => pat_search_pat_expr_kind(expr), - rustc_hir::PatKind::Guard(pat, guard) => { + PatKind::Expr(expr) => pat_search_pat_expr_kind(expr), + PatKind::Guard(pat, guard) => { let (start, _) = pat_search_pat(tcx, pat); let (_, end) = expr_search_pat(tcx, guard); (start, end) }, - rustc_hir::PatKind::Range(None, None, range) => match range { + PatKind::Range(None, None, range) => match range { rustc_hir::RangeEnd::Included => (Pat::Str("..="), Pat::Str("")), rustc_hir::RangeEnd::Excluded => (Pat::Str(".."), Pat::Str("")), }, - rustc_hir::PatKind::Range(r_start, r_end, range) => { - let (start, _) = match r_start { - Some(e) => pat_search_pat_expr_kind(e), + PatKind::Range(r_start, r_end, range) => { + let start = match r_start { + Some(e) => pat_search_pat_expr_kind(e).0, None => match range { - rustc_hir::RangeEnd::Included => (Pat::Str("..="), Pat::Str("")), - rustc_hir::RangeEnd::Excluded => (Pat::Str(".."), Pat::Str("")), + rustc_hir::RangeEnd::Included => Pat::Str("..="), + rustc_hir::RangeEnd::Excluded => Pat::Str(".."), }, }; - let (_, end) = match r_end { - Some(e) => pat_search_pat_expr_kind(e), + let end = match r_end { + Some(e) => pat_search_pat_expr_kind(e).1, None => match range { - rustc_hir::RangeEnd::Included => (Pat::Str(""), Pat::Str("..=")), - rustc_hir::RangeEnd::Excluded => (Pat::Str(""), Pat::Str("..")), + rustc_hir::RangeEnd::Included => Pat::Str("..="), + rustc_hir::RangeEnd::Excluded => Pat::Str(".."), }, }; (start, end) }, - rustc_hir::PatKind::Slice(_, _, _) => (Pat::Str("["), Pat::Str("]")), + PatKind::Slice(_, _, _) => (Pat::Str("["), Pat::Str("]")), } } -fn pat_search_pat_expr_kind(expr: &crate::PatExpr<'_>) -> (Pat, Pat) { +fn pat_search_pat_expr_kind(expr: &PatExpr<'_>) -> (Pat, Pat) { match expr.kind { - crate::PatExprKind::Lit { lit, negated } => { + PatExprKind::Lit { lit, negated } => { let (start, end) = lit_search_pat(&lit.node); if negated { (Pat::Str("!"), end) } else { (start, end) } }, - crate::PatExprKind::ConstBlock(_block) => (Pat::Str("const {"), Pat::Str("}")), - crate::PatExprKind::Path(path) => qpath_search_pat(&path), + PatExprKind::ConstBlock(_block) => (Pat::Str("const {"), Pat::Str("}")), + PatExprKind::Path(path) => qpath_search_pat(&path), } } diff --git a/tests/ui/rest_when_destructuring_struct.fixed b/tests/ui/rest_when_destructuring_struct.fixed index 3af8ad56b2e5..31d04558b594 100644 --- a/tests/ui/rest_when_destructuring_struct.fixed +++ b/tests/ui/rest_when_destructuring_struct.fixed @@ -22,6 +22,15 @@ enum E { C {}, } +mod m { + #[derive(Default)] + pub struct Sm { + pub a: u8, + pub(crate) b: u8, + c: u8, + } +} + fn main() { let s = S { a: 1, b: 2, c: 3 }; @@ -67,4 +76,9 @@ fn main() { let NonExhaustiveStruct { field1: _, field2: _, .. } = ne; + + use m::Sm; + + let Sm { a: _, b: _, .. } = Sm::default(); + //~^ rest_when_destructuring_struct } diff --git a/tests/ui/rest_when_destructuring_struct.rs b/tests/ui/rest_when_destructuring_struct.rs index f782bbaea804..218dd5c7f555 100644 --- a/tests/ui/rest_when_destructuring_struct.rs +++ b/tests/ui/rest_when_destructuring_struct.rs @@ -22,6 +22,15 @@ enum E { C {}, } +mod m { + #[derive(Default)] + pub struct Sm { + pub a: u8, + pub(crate) b: u8, + c: u8, + } +} + fn main() { let s = S { a: 1, b: 2, c: 3 }; @@ -67,4 +76,9 @@ fn main() { let NonExhaustiveStruct { field1: _, field2: _, .. } = ne; + + use m::Sm; + + let Sm { .. } = Sm::default(); + //~^ rest_when_destructuring_struct } diff --git a/tests/ui/rest_when_destructuring_struct.stderr b/tests/ui/rest_when_destructuring_struct.stderr index c18ac9af88e1..3e6febddf162 100644 --- a/tests/ui/rest_when_destructuring_struct.stderr +++ b/tests/ui/rest_when_destructuring_struct.stderr @@ -1,5 +1,5 @@ error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:28:9 + --> tests/ui/rest_when_destructuring_struct.rs:37:9 | LL | let S { a, b, .. } = s; | ^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + let S { a, b, c: _ } = s; | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:31:9 + --> tests/ui/rest_when_destructuring_struct.rs:40:9 | LL | let S { a, b, c, .. } = s; | ^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + let S { a, b, c, } = s; | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:38:9 + --> tests/ui/rest_when_destructuring_struct.rs:47:9 | LL | E::B { .. } => (), | ^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL + E::B { b1: _, b2: _ } => (), | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:40:9 + --> tests/ui/rest_when_destructuring_struct.rs:49:9 | LL | E::C { .. } => (), | ^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + E::C { } => (), | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:46:9 + --> tests/ui/rest_when_destructuring_struct.rs:55:9 | LL | E::B { b1: _, .. } => (), | ^^^^^^^^^^^^^^^^^^ @@ -61,7 +61,7 @@ LL + E::B { b1: _, b2: _ } => (), | error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:63:9 + --> tests/ui/rest_when_destructuring_struct.rs:72:9 | LL | let NonExhaustiveStruct { field1: _, .. } = ne; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -71,5 +71,16 @@ help: consider explicitly ignoring remaining fields with wildcard patterns (x: _ LL | let NonExhaustiveStruct { field1: _, field2: _, .. } = ne; | ++++++++++ -error: aborting due to 6 previous errors +error: struct destructuring with rest (..) + --> tests/ui/rest_when_destructuring_struct.rs:82:9 + | +LL | let Sm { .. } = Sm::default(); + | ^^^^^^^^^ + | +help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) + | +LL | let Sm { a: _, b: _, .. } = Sm::default(); + | +++++++++++ + +error: aborting due to 7 previous errors From b7837ea7ddc49f4392ff8961eaf215565f52e6e3 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Sun, 12 Oct 2025 20:51:56 +0000 Subject: [PATCH 11/13] handle another round of feedback --- .../src/rest_when_destructuring_struct.rs | 12 +++++- clippy_utils/src/check_proc_macro.rs | 8 ++-- tests/ui/rest_when_destructuring_struct.fixed | 6 --- tests/ui/rest_when_destructuring_struct.rs | 6 --- .../ui/rest_when_destructuring_struct.stderr | 42 +++++++++---------- 5 files changed, 35 insertions(+), 39 deletions(-) diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs index 648a3805f02d..f092dc8cd0d0 100644 --- a/clippy_lints/src/rest_when_destructuring_struct.rs +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -80,15 +80,23 @@ impl<'tcx> LateLintPass<'tcx> for RestWhenDestructuringStruct { fmt_fields.push_str(", .."); } + let message = if a.variants()[vid].fields.is_empty() { + "consider remove rest pattern (`..`)" + } else if fields.is_empty() { + "consider explicitly ignoring fields with wildcard patterns (`x: _`)" + } else { + "consider explicitly ignoring remaining fields with wildcard patterns (`x: _`)" + }; + span_lint_and_then( cx, REST_WHEN_DESTRUCTURING_STRUCT, pat.span, - "struct destructuring with rest (..)", + "struct destructuring with rest (`..`)", |diag| { diag.span_suggestion_verbose( dotdot, - "consider explicitly ignoring remaining fields with wildcard patterns (x: _)", + message, fmt_fields, rustc_errors::Applicability::MachineApplicable, ); diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 4ea2ff4b5951..554f93b483ca 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -590,7 +590,7 @@ fn pat_search_pat(tcx: TyCtxt<'_>, pat: &rustc_hir::Pat<'_>) -> (Pat, Pat) { let (_, end) = pat_search_pat(tcx, p); (Pat::Str("&"), end) }, - PatKind::Expr(expr) => pat_search_pat_expr_kind(expr), + PatKind::Expr(expr) => pat_expr_search_pat(expr), PatKind::Guard(pat, guard) => { let (start, _) = pat_search_pat(tcx, pat); let (_, end) = expr_search_pat(tcx, guard); @@ -602,7 +602,7 @@ fn pat_search_pat(tcx: TyCtxt<'_>, pat: &rustc_hir::Pat<'_>) -> (Pat, Pat) { }, PatKind::Range(r_start, r_end, range) => { let start = match r_start { - Some(e) => pat_search_pat_expr_kind(e).0, + Some(e) => pat_expr_search_pat(e).0, None => match range { rustc_hir::RangeEnd::Included => Pat::Str("..="), rustc_hir::RangeEnd::Excluded => Pat::Str(".."), @@ -610,7 +610,7 @@ fn pat_search_pat(tcx: TyCtxt<'_>, pat: &rustc_hir::Pat<'_>) -> (Pat, Pat) { }; let end = match r_end { - Some(e) => pat_search_pat_expr_kind(e).1, + Some(e) => pat_expr_search_pat(e).1, None => match range { rustc_hir::RangeEnd::Included => Pat::Str("..="), rustc_hir::RangeEnd::Excluded => Pat::Str(".."), @@ -622,7 +622,7 @@ fn pat_search_pat(tcx: TyCtxt<'_>, pat: &rustc_hir::Pat<'_>) -> (Pat, Pat) { } } -fn pat_search_pat_expr_kind(expr: &PatExpr<'_>) -> (Pat, Pat) { +fn pat_expr_search_pat(expr: &PatExpr<'_>) -> (Pat, Pat) { match expr.kind { PatExprKind::Lit { lit, negated } => { let (start, end) = lit_search_pat(&lit.node); diff --git a/tests/ui/rest_when_destructuring_struct.fixed b/tests/ui/rest_when_destructuring_struct.fixed index 31d04558b594..e46d4bf83707 100644 --- a/tests/ui/rest_when_destructuring_struct.fixed +++ b/tests/ui/rest_when_destructuring_struct.fixed @@ -1,12 +1,6 @@ //@aux-build:proc_macros.rs //@aux-build:non-exhaustive-struct.rs #![warn(clippy::rest_when_destructuring_struct)] -#![allow(dead_code)] -#![allow(unused_variables)] - -extern crate proc_macros; - -extern crate non_exhaustive_struct; use non_exhaustive_struct::NonExhaustiveStruct; diff --git a/tests/ui/rest_when_destructuring_struct.rs b/tests/ui/rest_when_destructuring_struct.rs index 218dd5c7f555..57f7ef790492 100644 --- a/tests/ui/rest_when_destructuring_struct.rs +++ b/tests/ui/rest_when_destructuring_struct.rs @@ -1,12 +1,6 @@ //@aux-build:proc_macros.rs //@aux-build:non-exhaustive-struct.rs #![warn(clippy::rest_when_destructuring_struct)] -#![allow(dead_code)] -#![allow(unused_variables)] - -extern crate proc_macros; - -extern crate non_exhaustive_struct; use non_exhaustive_struct::NonExhaustiveStruct; diff --git a/tests/ui/rest_when_destructuring_struct.stderr b/tests/ui/rest_when_destructuring_struct.stderr index 3e6febddf162..d1be5213f5d4 100644 --- a/tests/ui/rest_when_destructuring_struct.stderr +++ b/tests/ui/rest_when_destructuring_struct.stderr @@ -1,83 +1,83 @@ -error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:37:9 +error: struct destructuring with rest (`..`) + --> tests/ui/rest_when_destructuring_struct.rs:31: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: _) +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:40:9 +error: struct destructuring with rest (`..`) + --> tests/ui/rest_when_destructuring_struct.rs:34:9 | LL | let S { a, b, c, .. } = s; | ^^^^^^^^^^^^^^^^^ | -help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) +help: consider explicitly ignoring remaining fields with wildcard patterns (`x: _`) | LL - let S { a, b, c, .. } = s; LL + let S { a, b, c, } = s; | -error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:47:9 +error: struct destructuring with rest (`..`) + --> tests/ui/rest_when_destructuring_struct.rs:41:9 | LL | E::B { .. } => (), | ^^^^^^^^^^^ | -help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) +help: consider explicitly ignoring 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:49:9 +error: struct destructuring with rest (`..`) + --> tests/ui/rest_when_destructuring_struct.rs:43:9 | LL | E::C { .. } => (), | ^^^^^^^^^^^ | -help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) +help: consider remove rest pattern (`..`) | LL - E::C { .. } => (), LL + E::C { } => (), | -error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:55:9 +error: struct destructuring with rest (`..`) + --> tests/ui/rest_when_destructuring_struct.rs:49:9 | LL | E::B { b1: _, .. } => (), | ^^^^^^^^^^^^^^^^^^ | -help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) +help: consider explicitly ignoring remaining fields with wildcard patterns (`x: _`) | LL - E::B { b1: _, .. } => (), LL + E::B { b1: _, b2: _ } => (), | -error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:72:9 +error: struct destructuring with rest (`..`) + --> tests/ui/rest_when_destructuring_struct.rs:66:9 | LL | let NonExhaustiveStruct { field1: _, .. } = ne; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) +help: consider explicitly ignoring remaining fields with wildcard patterns (`x: _`) | LL | let NonExhaustiveStruct { field1: _, field2: _, .. } = ne; | ++++++++++ -error: struct destructuring with rest (..) - --> tests/ui/rest_when_destructuring_struct.rs:82:9 +error: struct destructuring with rest (`..`) + --> tests/ui/rest_when_destructuring_struct.rs:76:9 | LL | let Sm { .. } = Sm::default(); | ^^^^^^^^^ | -help: consider explicitly ignoring remaining fields with wildcard patterns (x: _) +help: consider explicitly ignoring fields with wildcard patterns (`x: _`) | LL | let Sm { a: _, b: _, .. } = Sm::default(); | +++++++++++ From e7b7c82343bb09b83f351eb68a621d4f437a77e1 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Mon, 13 Oct 2025 17:06:48 +0200 Subject: [PATCH 12/13] fix small grammatical nit --- clippy_lints/src/rest_when_destructuring_struct.rs | 2 +- tests/ui/rest_when_destructuring_struct.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/rest_when_destructuring_struct.rs b/clippy_lints/src/rest_when_destructuring_struct.rs index f092dc8cd0d0..59ca8ba2a3af 100644 --- a/clippy_lints/src/rest_when_destructuring_struct.rs +++ b/clippy_lints/src/rest_when_destructuring_struct.rs @@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for RestWhenDestructuringStruct { } let message = if a.variants()[vid].fields.is_empty() { - "consider remove rest pattern (`..`)" + "consider removing the rest pattern (`..`)" } else if fields.is_empty() { "consider explicitly ignoring fields with wildcard patterns (`x: _`)" } else { diff --git a/tests/ui/rest_when_destructuring_struct.stderr b/tests/ui/rest_when_destructuring_struct.stderr index d1be5213f5d4..94554c7c5623 100644 --- a/tests/ui/rest_when_destructuring_struct.stderr +++ b/tests/ui/rest_when_destructuring_struct.stderr @@ -42,7 +42,7 @@ error: struct destructuring with rest (`..`) LL | E::C { .. } => (), | ^^^^^^^^^^^ | -help: consider remove rest pattern (`..`) +help: consider removing the rest pattern (`..`) | LL - E::C { .. } => (), LL + E::C { } => (), From e95f1417f00a86d0aa7a0aab0aa5b0fa1e7c2559 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Sun, 2 Nov 2025 10:02:13 +0000 Subject: [PATCH 13/13] handle other forms of the nightly deref! "macro" --- clippy_utils/src/check_proc_macro.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 554f93b483ca..9364add1fe7a 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -585,7 +585,7 @@ fn pat_search_pat(tcx: TyCtxt<'_>, pat: &rustc_hir::Pat<'_>) -> (Pat, Pat) { let (_, end) = pat_search_pat(tcx, p); (Pat::Str("box"), end) }, - PatKind::Deref(_) => (Pat::Str("deref!("), Pat::Str(")")), + PatKind::Deref(_) => (Pat::Str("deref!"), Pat::Str("")), PatKind::Ref(p, _) => { let (_, end) = pat_search_pat(tcx, p); (Pat::Str("&"), end)