use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::is_copy; use clippy_utils::{get_parent_expr, path_to_local}; use rustc_hir::{BindingAnnotation, Expr, ExprKind, Node, PatKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks for initialization of a `struct` by copying a base without setting /// any field. /// /// ### Why is this bad? /// Readability suffers from unnecessary struct building. /// /// ### Example /// ```no_run /// struct S { s: String } /// /// let a = S { s: String::from("Hello, world!") }; /// let b = S { ..a }; /// ``` /// Use instead: /// ```no_run /// struct S { s: String } /// /// let a = S { s: String::from("Hello, world!") }; /// let b = a; /// ``` /// /// ### Known Problems /// Has false positives when the base is a place expression that cannot be /// moved out of, see [#10547](https://github.com/rust-lang/rust-clippy/issues/10547). #[clippy::version = "1.70.0"] pub UNNECESSARY_STRUCT_INITIALIZATION, nursery, "struct built from a base that can be written mode concisely" } declare_lint_pass!(UnnecessaryStruct => [UNNECESSARY_STRUCT_INITIALIZATION]); impl LateLintPass<'_> for UnnecessaryStruct { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if let ExprKind::Struct(_, &[], Some(base)) = expr.kind { if let Some(parent) = get_parent_expr(cx, expr) && let parent_ty = cx.typeck_results().expr_ty_adjusted(parent) && parent_ty.is_any_ptr() { if is_copy(cx, cx.typeck_results().expr_ty(expr)) && path_to_local(base).is_some() { // When the type implements `Copy`, a reference to the new struct works on the // copy. Using the original would borrow it. return; } if parent_ty.is_mutable_ptr() && !is_mutable(cx, base) { // The original can be used in a mutable reference context only if it is mutable. return; } } // TODO: do not propose to replace *XX if XX is not Copy if let ExprKind::Unary(UnOp::Deref, target) = base.kind && matches!(target.kind, ExprKind::Path(..)) && !is_copy(cx, cx.typeck_results().expr_ty(expr)) { // `*base` cannot be used instead of the struct in the general case if it is not Copy. return; } span_lint_and_sugg( cx, UNNECESSARY_STRUCT_INITIALIZATION, expr.span, "unnecessary struct building", "replace with", snippet(cx, base.span, "..").into_owned(), rustc_errors::Applicability::MachineApplicable, ); } } } fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let Some(hir_id) = path_to_local(expr) && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) { matches!(pat.kind, PatKind::Binding(BindingAnnotation::MUT, ..)) } else { true } }