From b14a543f5c2583a86a548de87f2348bff6a1a2b0 Mon Sep 17 00:00:00 2001 From: steamproof <93405617+pbkx@users.noreply.github.com> Date: Wed, 17 Jun 2026 01:35:09 -0700 Subject: [PATCH] fix unused_braces suggestion for wildcard assignment --- compiler/rustc_lint/src/unused.rs | 45 ++++++++++-- ...braces-wildcard-assignment-issue-116536.rs | 72 +++++++++++++++++++ 2 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 tests/ui/lint/unused/unused-braces-wildcard-assignment-issue-116536.rs diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 1ff2a2cb1a404..f0ef061ea98b9 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1,4 +1,5 @@ use rustc_ast::util::{classify, parser}; +use rustc_ast::visit::{self, Visitor}; use rustc_ast::{self as ast, ExprKind, FnRetTy, ForLoop, HasAttrs as _, StmtKind}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::MultiSpan; @@ -64,6 +65,7 @@ enum UnusedDelimsCtx { FunctionArg, MethodArg, AssignedValue, + AssignedValueWithWildcard, AssignedValueLetElse, IfCond, WhileCond, @@ -85,9 +87,9 @@ impl From for &'static str { match ctx { UnusedDelimsCtx::FunctionArg => "function argument", UnusedDelimsCtx::MethodArg => "method argument", - UnusedDelimsCtx::AssignedValue | UnusedDelimsCtx::AssignedValueLetElse => { - "assigned value" - } + UnusedDelimsCtx::AssignedValue + | UnusedDelimsCtx::AssignedValueWithWildcard + | UnusedDelimsCtx::AssignedValueLetElse => "assigned value", UnusedDelimsCtx::IfCond => "`if` condition", UnusedDelimsCtx::WhileCond => "`while` condition", UnusedDelimsCtx::ForIterExpr => "`for` iterator expression", @@ -104,6 +106,32 @@ impl From for &'static str { } } +fn expr_contains_wildcard(expr: &ast::Expr) -> bool { + struct WildcardVisitor { + found: bool, + } + + impl<'ast> Visitor<'ast> for WildcardVisitor { + fn visit_expr(&mut self, expr: &'ast ast::Expr) { + if self.found { + return; + } + match &expr.kind { + ast::ExprKind::Underscore + | ast::ExprKind::Range(None, None, ast::RangeLimits::HalfOpen) => self.found = true, + ast::ExprKind::Struct(expr) if matches!(expr.rest, ast::StructRest::Rest(_)) => { + self.found = true; + } + _ => visit::walk_expr(self, expr), + } + } + } + + let mut visitor = WildcardVisitor { found: false }; + visitor.visit_expr(expr); + visitor.found +} + /// Used by both `UnusedParens` and `UnusedBraces` to prevent code duplication. trait UnusedDelimLint { const DELIM_STR: &'static str; @@ -412,7 +440,15 @@ trait UnusedDelimLint { Index(_, ref value, _) => (value, UnusedDelimsCtx::IndexExpr, false, None, None, false), - Assign(_, ref value, _) | AssignOp(.., ref value) => { + Assign(ref lhs, ref value, _) => { + let ctx = if expr_contains_wildcard(lhs) { + UnusedDelimsCtx::AssignedValueWithWildcard + } else { + UnusedDelimsCtx::AssignedValue + }; + (value, ctx, false, None, None, false) + } + AssignOp(.., ref value) => { (value, UnusedDelimsCtx::AssignedValue, false, None, None, false) } // either function/method call, or something this lint doesn't care about @@ -1101,6 +1137,7 @@ impl UnusedDelimLint for UnusedBraces { && (ctx != UnusedDelimsCtx::AnonConst || (matches!(expr.kind, ast::ExprKind::Lit(_)) && !expr.span.from_expansion())) + && ctx != UnusedDelimsCtx::AssignedValueWithWildcard && ctx != UnusedDelimsCtx::ClosureBody && !cx.sess().source_map().is_multiline(value.span) && value.attrs.is_empty() diff --git a/tests/ui/lint/unused/unused-braces-wildcard-assignment-issue-116536.rs b/tests/ui/lint/unused/unused-braces-wildcard-assignment-issue-116536.rs new file mode 100644 index 0000000000000..2ee9610264f6c --- /dev/null +++ b/tests/ui/lint/unused/unused-braces-wildcard-assignment-issue-116536.rs @@ -0,0 +1,72 @@ +//@ run-pass + +#![deny(unused_braces)] + +use std::cell::Cell; + +struct Error; +struct NotCopy; + +struct DropCounter<'a>(&'a Cell); + +impl Drop for DropCounter<'_> { + fn drop(&mut self) { + self.0.set(self.0.get() + 1); + } +} + +struct Pair<'a> { + first: DropCounter<'a>, + _second: DropCounter<'a>, +} + +fn wildcard_assignment_moves_value() { + let e = NotCopy; + _ = { e }; +} + +fn tuple_wildcard_assignment_moves_value() { + let e = (NotCopy, Error); + (_, Error) = { e }; +} + +fn main() { + wildcard_assignment_moves_value(); + tuple_wildcard_assignment_moves_value(); + + let drops = Cell::new(0); + + let e = (DropCounter(&drops), DropCounter(&drops)); + (..) = { e }; + assert_eq!(drops.get(), 2); + + let e = [DropCounter(&drops), DropCounter(&drops)]; + [..] = { e }; + assert_eq!(drops.get(), 4); + + let e = (Error, DropCounter(&drops)); + (Error, ..) = { e }; + assert_eq!(drops.get(), 5); + + let first; + let e = [DropCounter(&drops), DropCounter(&drops)]; + [first, ..] = { e }; + assert_eq!(drops.get(), 6); + drop(first); + assert_eq!(drops.get(), 7); + + let e = (Error, [DropCounter(&drops), DropCounter(&drops)]); + (Error, [..]) = { e }; + assert_eq!(drops.get(), 9); + + let e = Pair { first: DropCounter(&drops), _second: DropCounter(&drops) }; + Pair { .. } = { e }; + assert_eq!(drops.get(), 11); + + let first; + let e = Pair { first: DropCounter(&drops), _second: DropCounter(&drops) }; + Pair { first, .. } = { e }; + assert_eq!(drops.get(), 12); + drop(first); + assert_eq!(drops.get(), 13); +}