295 lines
12 KiB
Rust
295 lines
12 KiB
Rust
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
|
|
use clippy_utils::source::snippet_with_context;
|
|
use clippy_utils::ty::{has_drop, is_copy};
|
|
use clippy_utils::{contains_name, get_parent_expr, in_automatically_derived, is_from_proc_macro};
|
|
use rustc_data_structures::fx::FxHashSet;
|
|
use rustc_errors::Applicability;
|
|
use rustc_hir::def::Res;
|
|
use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
|
|
use rustc_lint::{LateContext, LateLintPass};
|
|
use rustc_middle::ty;
|
|
use rustc_middle::ty::print::with_forced_trimmed_paths;
|
|
use rustc_session::impl_lint_pass;
|
|
use rustc_span::symbol::{Ident, Symbol};
|
|
use rustc_span::{sym, Span};
|
|
|
|
declare_clippy_lint! {
|
|
/// ### What it does
|
|
/// Checks for literal calls to `Default::default()`.
|
|
///
|
|
/// ### Why is this bad?
|
|
/// It's easier for the reader if the name of the type is used, rather than the
|
|
/// generic `Default`.
|
|
///
|
|
/// ### Example
|
|
/// ```no_run
|
|
/// let s: String = Default::default();
|
|
/// ```
|
|
///
|
|
/// Use instead:
|
|
/// ```no_run
|
|
/// let s = String::default();
|
|
/// ```
|
|
#[clippy::version = "pre 1.29.0"]
|
|
pub DEFAULT_TRAIT_ACCESS,
|
|
pedantic,
|
|
"checks for literal calls to `Default::default()`"
|
|
}
|
|
|
|
declare_clippy_lint! {
|
|
/// ### What it does
|
|
/// Checks for immediate reassignment of fields initialized
|
|
/// with Default::default().
|
|
///
|
|
/// ### Why is this bad?
|
|
///It's more idiomatic to use the [functional update syntax](https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax).
|
|
///
|
|
/// ### Known problems
|
|
/// Assignments to patterns that are of tuple type are not linted.
|
|
///
|
|
/// ### Example
|
|
/// ```no_run
|
|
/// # #[derive(Default)]
|
|
/// # struct A { i: i32 }
|
|
/// let mut a: A = Default::default();
|
|
/// a.i = 42;
|
|
/// ```
|
|
///
|
|
/// Use instead:
|
|
/// ```no_run
|
|
/// # #[derive(Default)]
|
|
/// # struct A { i: i32 }
|
|
/// let a = A {
|
|
/// i: 42,
|
|
/// .. Default::default()
|
|
/// };
|
|
/// ```
|
|
#[clippy::version = "1.49.0"]
|
|
pub FIELD_REASSIGN_WITH_DEFAULT,
|
|
style,
|
|
"binding initialized with Default should have its fields set in the initializer"
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct Default {
|
|
// Spans linted by `field_reassign_with_default`.
|
|
reassigned_linted: FxHashSet<Span>,
|
|
}
|
|
|
|
impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
|
|
|
|
impl<'tcx> LateLintPass<'tcx> for Default {
|
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
|
if !expr.span.from_expansion()
|
|
// Avoid cases already linted by `field_reassign_with_default`
|
|
&& !self.reassigned_linted.contains(&expr.span)
|
|
&& let ExprKind::Call(path, ..) = expr.kind
|
|
&& !in_automatically_derived(cx.tcx, expr.hir_id)
|
|
&& let ExprKind::Path(ref qpath) = path.kind
|
|
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
|
|
&& cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
|
|
&& !is_update_syntax_base(cx, expr)
|
|
// Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
|
|
&& let QPath::Resolved(None, _path) = qpath
|
|
&& let expr_ty = cx.typeck_results().expr_ty(expr)
|
|
&& let ty::Adt(def, ..) = expr_ty.kind()
|
|
&& !is_from_proc_macro(cx, expr)
|
|
{
|
|
let replacement = with_forced_trimmed_paths!(format!("{}::default()", cx.tcx.def_path_str(def.did())));
|
|
span_lint_and_sugg(
|
|
cx,
|
|
DEFAULT_TRAIT_ACCESS,
|
|
expr.span,
|
|
format!("calling `{replacement}` is more clear than this expression"),
|
|
"try",
|
|
replacement,
|
|
Applicability::Unspecified, // First resolve the TODO above
|
|
);
|
|
}
|
|
}
|
|
|
|
#[expect(clippy::too_many_lines)]
|
|
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
|
|
// start from the `let mut _ = _::default();` and look at all the following
|
|
// statements, see if they re-assign the fields of the binding
|
|
let stmts_head = match block.stmts {
|
|
[] | [_] => return,
|
|
// Skip the last statement since there cannot possibly be any following statements that re-assign fields.
|
|
[head @ .., _] => head,
|
|
};
|
|
for (stmt_idx, stmt) in stmts_head.iter().enumerate() {
|
|
// find all binding statements like `let mut _ = T::default()` where `T::default()` is the
|
|
// `default` method of the `Default` trait, and store statement index in current block being
|
|
// checked and the name of the bound variable
|
|
let (local, variant, binding_name, binding_type, span) = if let StmtKind::Let(local) = stmt.kind
|
|
// only take `let ...` statements
|
|
&& let Some(expr) = local.init
|
|
&& !in_automatically_derived(cx.tcx, expr.hir_id)
|
|
&& !expr.span.from_expansion()
|
|
// only take bindings to identifiers
|
|
&& let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind
|
|
// only when assigning `... = Default::default()`
|
|
&& is_expr_default(expr, cx)
|
|
&& let binding_type = cx.typeck_results().node_type(binding_id)
|
|
&& let ty::Adt(adt, args) = *binding_type.kind()
|
|
&& adt.is_struct()
|
|
&& let variant = adt.non_enum_variant()
|
|
&& (adt.did().is_local() || !variant.is_field_list_non_exhaustive())
|
|
&& let module_did = cx.tcx.parent_module(stmt.hir_id)
|
|
&& variant
|
|
.fields
|
|
.iter()
|
|
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx))
|
|
&& let all_fields_are_copy = variant
|
|
.fields
|
|
.iter()
|
|
.all(|field| {
|
|
is_copy(cx, cx.tcx.type_of(field.did).instantiate(cx.tcx, args))
|
|
})
|
|
&& (!has_drop(cx, binding_type) || all_fields_are_copy)
|
|
{
|
|
(local, variant, ident.name, binding_type, expr.span)
|
|
} else {
|
|
continue;
|
|
};
|
|
|
|
let init_ctxt = local.span.ctxt();
|
|
|
|
// find all "later statement"'s where the fields of the binding set as
|
|
// Default::default() get reassigned, unless the reassignment refers to the original binding
|
|
let mut first_assign = None;
|
|
let mut assigned_fields = Vec::new();
|
|
let mut cancel_lint = false;
|
|
for consecutive_statement in &block.stmts[stmt_idx + 1..] {
|
|
// find out if and which field was set by this `consecutive_statement`
|
|
if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) {
|
|
// interrupt and cancel lint if assign_rhs references the original binding
|
|
if contains_name(binding_name, assign_rhs, cx) || init_ctxt != consecutive_statement.span.ctxt() {
|
|
cancel_lint = true;
|
|
break;
|
|
}
|
|
|
|
// if the field was previously assigned, replace the assignment, otherwise insert the assignment
|
|
if let Some(prev) = assigned_fields
|
|
.iter_mut()
|
|
.find(|(field_name, _)| field_name == &field_ident.name)
|
|
{
|
|
*prev = (field_ident.name, assign_rhs);
|
|
} else {
|
|
assigned_fields.push((field_ident.name, assign_rhs));
|
|
}
|
|
|
|
// also set first instance of error for help message
|
|
if first_assign.is_none() {
|
|
first_assign = Some(consecutive_statement);
|
|
}
|
|
}
|
|
// interrupt if no field was assigned, since we only want to look at consecutive statements
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if there are incorrectly assigned fields, do a span_lint_and_note to suggest
|
|
// construction using `Ty { fields, ..Default::default() }`
|
|
if !assigned_fields.is_empty() && !cancel_lint {
|
|
// if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
|
|
let ext_with_default = !variant
|
|
.fields
|
|
.iter()
|
|
.all(|field| assigned_fields.iter().any(|(a, _)| a == &field.name));
|
|
|
|
let mut app = Applicability::Unspecified;
|
|
let field_list = assigned_fields
|
|
.into_iter()
|
|
.map(|(field, rhs)| {
|
|
// extract and store the assigned value for help message
|
|
let value_snippet = snippet_with_context(cx, rhs.span, init_ctxt, "..", &mut app).0;
|
|
format!("{field}: {value_snippet}")
|
|
})
|
|
.collect::<Vec<String>>()
|
|
.join(", ");
|
|
|
|
// give correct suggestion if generics are involved (see #6944)
|
|
let binding_type = if let ty::Adt(adt_def, args) = binding_type.kind()
|
|
&& !args.is_empty()
|
|
{
|
|
let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
|
|
let generic_args = args.iter().collect::<Vec<_>>();
|
|
let tys_str = generic_args
|
|
.iter()
|
|
.map(ToString::to_string)
|
|
.collect::<Vec<_>>()
|
|
.join(", ");
|
|
format!("{adt_def_ty_name}::<{tys_str}>")
|
|
} else {
|
|
binding_type.to_string()
|
|
};
|
|
|
|
let sugg = if ext_with_default {
|
|
if field_list.is_empty() {
|
|
format!("{binding_type}::default()")
|
|
} else {
|
|
format!("{binding_type} {{ {field_list}, ..Default::default() }}")
|
|
}
|
|
} else {
|
|
format!("{binding_type} {{ {field_list} }}")
|
|
};
|
|
|
|
// span lint once per statement that binds default
|
|
span_lint_and_note(
|
|
cx,
|
|
FIELD_REASSIGN_WITH_DEFAULT,
|
|
first_assign.unwrap().span,
|
|
"field assignment outside of initializer for an instance created with Default::default()",
|
|
Some(local.span),
|
|
format!("consider initializing the variable with `{sugg}` and removing relevant reassignments"),
|
|
);
|
|
self.reassigned_linted.insert(span);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Checks if the given expression is the `default` method belonging to the `Default` trait.
|
|
fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
|
|
if let ExprKind::Call(fn_expr, _) = &expr.kind
|
|
&& let ExprKind::Path(qpath) = &fn_expr.kind
|
|
&& let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
|
|
{
|
|
// right hand side of assignment is `Default::default`
|
|
cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Returns the reassigned field and the assigning expression (right-hand side of assign).
|
|
fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
|
|
if let StmtKind::Semi(later_expr) = this.kind
|
|
// only take assignments
|
|
&& let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind
|
|
// only take assignments to fields where the left-hand side field is a field of
|
|
// the same binding as the previous statement
|
|
&& let ExprKind::Field(binding, field_ident) = assign_lhs.kind
|
|
&& let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind
|
|
&& let Some(second_binding_name) = path.segments.last()
|
|
&& second_binding_name.ident.name == binding_name
|
|
{
|
|
Some((field_ident, assign_rhs))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }`
|
|
fn is_update_syntax_base<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
|
|
if let Some(parent) = get_parent_expr(cx, expr)
|
|
&& let ExprKind::Struct(_, _, Some(base)) = parent.kind
|
|
{
|
|
base.hir_id == expr.hir_id
|
|
} else {
|
|
false
|
|
}
|
|
}
|