use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::SyntaxContext; use std::borrow::Cow; declare_clippy_lint! { /// ### What it does /// Checks for tuple structs initialized with field syntax. /// It will however not lint if a base initializer is present. /// The lint will also ignore code in macros. /// /// ### Why is this bad? /// This may be confusing to the uninitiated and adds no /// benefit as opposed to tuple initializers /// /// ### Example /// ```no_run /// struct TupleStruct(u8, u16); /// /// let _ = TupleStruct { /// 0: 1, /// 1: 23, /// }; /// /// // should be written as /// let base = TupleStruct(1, 23); /// /// // This is OK however /// let _ = TupleStruct { 0: 42, ..base }; /// ``` #[clippy::version = "1.59.0"] pub INIT_NUMBERED_FIELDS, style, "numbered fields in tuple struct initializer" } declare_lint_pass!(NumberedFields => [INIT_NUMBERED_FIELDS]); impl<'tcx> LateLintPass<'tcx> for NumberedFields { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { if let ExprKind::Struct(path, fields @ [field, ..], None) = e.kind // If the first character of any field is a digit it has to be a tuple. && field.ident.as_str().as_bytes().first().is_some_and(u8::is_ascii_digit) // Type aliases can't be used as functions. && !matches!( cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias | DefKind::AssocTy, _) ) // This is the only syntax macros can use that works for all struct types. && !e.span.from_expansion() && let mut has_side_effects = false && let Ok(mut expr_spans) = fields .iter() .map(|f| { has_side_effects |= f.expr.can_have_side_effects(); f.ident.as_str().parse::().map(|x| (x, f.expr.span)) }) .collect::, _>>() // We can only reorder the expressions if there are no side effects. && (!has_side_effects || expr_spans.is_sorted_by_key(|&(idx, _)| idx)) { span_lint_and_then( cx, INIT_NUMBERED_FIELDS, e.span, "used a field initializer for a tuple struct", |diag| { if !has_side_effects { // We already checked the order if there are side effects. expr_spans.sort_by_key(|&(idx, _)| idx); } let mut app = Applicability::MachineApplicable; diag.span_suggestion( e.span, "use tuple initialization", format!( "{}({})", snippet_with_applicability(cx, path.span(), "..", &mut app), expr_spans .into_iter() .map( |(_, span)| snippet_with_context(cx, span, SyntaxContext::root(), "..", &mut app).0 ) .intersperse(Cow::Borrowed(", ")) .collect::() ), app, ); }, ); } } }