| 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, is_mutable, path_to_local}; |
| use rustc_hir::{Expr, ExprField, ExprKind, Path, QPath, StructTailExpr, UnOp}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_session::declare_lint_pass; |
| |
| 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; |
| }; |
| |
| 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 |
| && 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)) && path_to_local(expr_b).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 |
| } |