| use clippy_utils::diagnostics::span_lint_and_sugg; | |
| use clippy_utils::res::MaybeResPath as _; | |
| use clippy_utils::source::snippet; | |
| use clippy_utils::ty::is_copy; | |
| use clippy_utils::{get_parent_expr, is_mutable}; | |
| use rustc_hir::{Expr, ExprField, ExprKind, Path, QPath, StructTailExpr, UnOp}; | |
| use rustc_lint::{LateContext, LateLintPass, declare_lint_pass}; | |
| use rustc_span::{DesugaringKind, Ident}; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for initialization of an identical `struct` from another instance | |
| /// of the type, either by copying a base without setting any field or by | |
| /// moving all fields individually. | |
| /// | |
| /// ### 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; | |
| /// ``` | |
| /// | |
| /// The struct literal ``S { ..a }`` in the assignment to ``b`` could be replaced | |
| /// with just ``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). | |
| /// | |
| /// Empty structs are ignored by the lint. | |
| #[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<'_>) { | |
| let ExprKind::Struct(_, fields, base) = expr.kind else { | |
| return; | |
| }; | |
| let expr_span = expr.range_span().unwrap_or(expr.span); | |
| if expr_span.from_expansion() { | |
| // Prevent lint from hitting inside macro code | |
| return; | |
| } | |
| let field_path = same_path_in_all_fields(cx, expr, fields); | |
| let sugg = match (field_path, base) { | |
| (Some(&path), StructTailExpr::None | StructTailExpr::DefaultFields(_)) => { | |
| // all fields match, no base given | |
| path.span | |
| }, | |
| (Some(path), StructTailExpr::Base(base)) | |
| if base_is_suitable(cx, expr, base) && path_matches_base(path, base) => | |
| { | |
| // all fields match, has base: ensure that the path of the base matches | |
| base.span | |
| }, | |
| (None, StructTailExpr::Base(base)) if fields.is_empty() && base_is_suitable(cx, expr, base) => { | |
| // just the base, no explicit fields | |
| base.span | |
| }, | |
| _ => return, | |
| }; | |
| span_lint_and_sugg( | |
| cx, | |
| UNNECESSARY_STRUCT_INITIALIZATION, | |
| expr_span, | |
| "unnecessary struct building", | |
| "replace with", | |
| snippet(cx, sugg, "..").into_owned(), | |
| rustc_errors::Applicability::MachineApplicable, | |
| ); | |
| } | |
| } | |
| fn base_is_suitable(cx: &LateContext<'_>, expr: &Expr<'_>, base: &Expr<'_>) -> bool { | |
| if !check_references(cx, expr, base) { | |
| return false; | |
| } | |
| // 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 false; | |
| } | |
| true | |
| } | |
| /// Check whether all fields of a struct assignment match. | |
| /// Returns a [Path] item that one can obtain a span from for the lint suggestion. | |
| /// | |
| /// Conditions that must be satisfied to trigger this variant of the lint: | |
| /// | |
| /// - source struct of the assignment must be of same type as the destination | |
| /// - names of destination struct fields must match the field names of the source | |
| /// | |
| /// We don’t check here if all struct fields are assigned as the remainder may | |
| /// be filled in from a base struct. | |
| fn same_path_in_all_fields<'tcx>( | |
| cx: &LateContext<'_>, | |
| expr: &Expr<'_>, | |
| fields: &[ExprField<'tcx>], | |
| ) -> Option<&'tcx Path<'tcx>> { | |
| let ty = cx.typeck_results().expr_ty(expr); | |
| let mut found = None; | |
| for f in fields { | |
| // fields are assigned from expression | |
| if let ExprKind::Field(src_expr, ident) = f.expr.kind | |
| // expression type matches | |
| && ty == cx.typeck_results().expr_ty(src_expr) | |
| // field name matches | |
| && ident_without_range_desugaring(f.ident) == ident | |
| // assigned from a path expression | |
| && let ExprKind::Path(QPath::Resolved(None, src_path)) = src_expr.kind | |
| { | |
| let Some((_, p)) = found else { | |
| // this is the first field assignment in the list | |
| found = Some((src_expr, src_path)); | |
| continue; | |
| }; | |
| if p.res == src_path.res { | |
| // subsequent field assignment with same origin struct as before | |
| continue; | |
| } | |
| } | |
| // source of field assignment doesn’t qualify | |
| return None; | |
| } | |
| if let Some((src_expr, src_path)) = found | |
| && check_references(cx, expr, src_expr) | |
| { | |
| Some(src_path) | |
| } else { | |
| None | |
| } | |
| } | |
| fn check_references(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool { | |
| if let Some(parent) = get_parent_expr(cx, expr_a) | |
| && 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_a)) && expr_b.res_local_id().is_some() { | |
| // When the type implements `Copy`, a reference to the new struct works on the | |
| // copy. Using the original would borrow it. | |
| return false; | |
| } | |
| if parent_ty.is_mutable_ptr() && !is_mutable(cx, expr_b) { | |
| // The original can be used in a mutable reference context only if it is mutable. | |
| return false; | |
| } | |
| } | |
| true | |
| } | |
| /// When some fields are assigned from a base struct and others individually | |
| /// the lint applies only if the source of the field is the same as the base. | |
| /// This is enforced here by comparing the path of the base expression; | |
| /// needless to say the lint only applies if it (or whatever expression it is | |
| /// a reference of) actually has a path. | |
| fn path_matches_base(path: &Path<'_>, base: &Expr<'_>) -> bool { | |
| let base_path = match base.kind { | |
| ExprKind::Unary(UnOp::Deref, base_expr) => { | |
| if let ExprKind::Path(QPath::Resolved(_, base_path)) = base_expr.kind { | |
| base_path | |
| } else { | |
| return false; | |
| } | |
| }, | |
| ExprKind::Path(QPath::Resolved(_, base_path)) => base_path, | |
| _ => return false, | |
| }; | |
| path.res == base_path.res | |
| } | |
| fn ident_without_range_desugaring(ident: Ident) -> Ident { | |
| if ident.span.desugaring_kind() == Some(DesugaringKind::RangeExpr) { | |
| Ident { | |
| span: ident.span.parent_callsite().unwrap(), | |
| ..ident | |
| } | |
| } else { | |
| ident | |
| } | |
| } |