Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
phi-gamma committed May 16, 2024
1 parent 84d2ef0 commit 3119797
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 132 deletions.
1 change: 0 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5692,7 +5692,6 @@ Released 2018-09-13
[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
[`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards
[`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals
[`redundant_owned_struct_recreation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_owned_struct_recreation
[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
Expand Down
1 change: 0 additions & 1 deletion clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::redundant_else::REDUNDANT_ELSE_INFO,
crate::redundant_field_names::REDUNDANT_FIELD_NAMES_INFO,
crate::redundant_locals::REDUNDANT_LOCALS_INFO,
crate::redundant_owned_struct_recreation::REDUNDANT_OWNED_STRUCT_RECREATION_INFO,
crate::redundant_pub_crate::REDUNDANT_PUB_CRATE_INFO,
crate::redundant_slicing::DEREF_BY_SLICING_INFO,
crate::redundant_slicing::REDUNDANT_SLICING_INFO,
Expand Down
2 changes: 0 additions & 2 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,6 @@ mod redundant_closure_call;
mod redundant_else;
mod redundant_field_names;
mod redundant_locals;
mod redundant_owned_struct_recreation;
mod redundant_pub_crate;
mod redundant_slicing;
mod redundant_static_lifetimes;
Expand Down Expand Up @@ -705,7 +704,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(default_numeric_fallback::DefaultNumericFallback));
store.register_late_pass(|_| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_late_pass(|_| Box::new(redundant_owned_struct_recreation::RedundantOwnedStructRecreation));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv())));
let format_args = format_args_storage.clone();
Expand Down
8 changes: 0 additions & 8 deletions clippy_lints/src/redundant_owned_struct_recreation.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
#![allow(unused)]
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;

use rustc_hir::{self as hir, ExprKind, Path, PathSegment, QPath};

use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use std::fmt::{self, Write as _};
Expand Down
141 changes: 137 additions & 4 deletions clippy_lints/src/unnecessary_struct_initialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ 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::{BindingMode, Expr, ExprKind, Node, PatKind, UnOp};
use rustc_hir::{BindingMode, Expr, ExprField, ExprKind, Node, PatKind, Path, QPath, 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.
/// 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.
Expand All @@ -29,9 +30,14 @@ declare_clippy_lint! {
/// 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,
Expand All @@ -41,7 +47,13 @@ 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 {
let ExprKind::Struct(_, fields, base) = expr.kind else {
return;
};

if fields.is_empty()
&& let Some(base) = base
{
if let Some(parent) = get_parent_expr(cx, expr)
&& let parent_ty = cx.typeck_results().expr_ty_adjusted(parent)
&& parent_ty.is_any_ptr()
Expand Down Expand Up @@ -76,8 +88,108 @@ impl LateLintPass<'_> for UnnecessaryStruct {
snippet(cx, base.span, "..").into_owned(),
rustc_errors::Applicability::MachineApplicable,
);
} else if !expr.span.from_expansion()
// prevent this lint from triggering inside macro code
{
// Conditions that must be satisfied to trigger this variant of the lint:
// - source of the assignment must be a struct
// - type of struct in expr must match
// - of destination struct fields must match the names of the source
// - all fields musts be assigned (no Default)

let field_path = same_path_in_all_fields(cx, expr, fields);

let sugg = match (field_path, base) {
(Some(&path), None) => {
// all fields match, no base given
path.span
},
/*
(Some(&path), Some(&Expr{ kind: ExprKind::Struct(&base_path, _, _), .. }))
//if ExprKind::Path(base_path) == path => {
if base_path == path => {
// all fields match, has base: ensure that the path of the base matches
path.span
},
*/
(Some(&path), Some(base)) if base_is_suitable(cx, expr, base) => {
eprintln!("[phg] »»» path: {:?}", path);
eprintln!("[phg] »»» base: {:?}", base);
return;
// all fields match, has base: ensure that the path of the base matches
//path.span
},
(None, Some(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",
"try replacing it with",
snippet(cx, sugg, "..").into_owned(),
rustc_errors::Applicability::MachineApplicable,
);
}
}
}

fn base_is_suitable(cx: &LateContext<'_>, expr: &Expr<'_>, base: &Expr<'_>) -> bool {
if !mutability_matches(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
}

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 seen_path = None;

for f in fields.iter().filter(|f| !f.is_shorthand) {
// fields are assigned from expression
if let ExprKind::Field(expr, ident) = f.expr.kind
// expression type matches
&& ty == cx.typeck_results().expr_ty(expr)
// field name matches
&& f.ident == ident
// assigned from a path expression
&& let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind
{
let Some(p) = seen_path else {
// this is the first field assignment in the list
seen_path = Some(path);
continue;
};

if p.res == path.res {
// subsequent field assignment with same origin struct as before
continue;
}
}
// source of field assignment doesn’t qualify
return None;
}

seen_path
}

fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
Expand All @@ -89,3 +201,24 @@ fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
true
}
}

fn mutability_matches(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
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(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
}

68 changes: 0 additions & 68 deletions tests/ui/redundant_owned_struct_recreation.rs

This file was deleted.

41 changes: 0 additions & 41 deletions tests/ui/redundant_owned_struct_recreation.stderr

This file was deleted.

16 changes: 16 additions & 0 deletions tests/ui/unnecessary_struct_initialization.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ struct V {
f: u32,
}

struct W {
f1: u32,
f2: u32,
}

impl Clone for V {
fn clone(&self) -> Self {
// Lint: `Self` implements `Copy`
Expand Down Expand Up @@ -68,4 +73,15 @@ fn main() {

// Should lint: the result of an expression is mutable and temporary
let p = &mut *Box::new(T { f: 5 });

// Should lint: all fields of `q` would be consumed anyway
let q = W { f1: 42, f2: 1337 };
let r = q;

// Should not lint: not all fields of `t` from same source
let s = W { f1: 1337, f2: 42 };
let t = W { f1: s.f1, f2: r.f2 };

// Should not lint: different fields of `s` assigned
let u = W { f1: s.f2, f2: s.f1 };
}
19 changes: 19 additions & 0 deletions tests/ui/unnecessary_struct_initialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ struct V {
f: u32,
}

struct W {
f1: u32,
f2: u32,
}

impl Clone for V {
fn clone(&self) -> Self {
// Lint: `Self` implements `Copy`
Expand Down Expand Up @@ -72,4 +77,18 @@ fn main() {
let p = &mut T {
..*Box::new(T { f: 5 })
};

// Should lint: all fields of `q` would be consumed anyway
let q = W { f1: 42, f2: 1337 };
let r = W { f1: q.f1, f2: q.f2 };

// Should not lint: not all fields of `t` from same source
let s = W { f1: 1337, f2: 42 };
let t = W { f1: s.f1, f2: r.f2 };

// Should not lint: different fields of `s` assigned
let u = W { f1: s.f2, f2: s.f1 };

// Should not lint as `u` is not mutable
let v = &mut W { f1: u.f1, f2: u.f2 };
}

0 comments on commit 3119797

Please sign in to comment.