blob: 6fc034b6fc5d2aa64ddfecd551cc9ca5c0d28c20 [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::return_ty;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::DiagExt;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::HirIdSet;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::AssocKind;
use rustc_session::impl_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for public types with a `pub fn new() -> Self` method and no
/// implementation of
/// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html).
///
/// ### Why is this bad?
/// The user might expect to be able to use
/// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the
/// type can be constructed without arguments.
///
/// ### Example
/// ```ignore
/// pub struct Foo(Bar);
///
/// impl Foo {
/// pub fn new() -> Self {
/// Foo(Bar::new())
/// }
/// }
/// ```
///
/// To fix the lint, add a `Default` implementation that delegates to `new`:
///
/// ```ignore
/// pub struct Foo(Bar);
///
/// impl Default for Foo {
/// fn default() -> Self {
/// Foo::new()
/// }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub NEW_WITHOUT_DEFAULT,
style,
"`pub fn new() -> Self` method without `Default` implementation"
}
#[derive(Clone, Default)]
pub struct NewWithoutDefault {
impling_types: Option<HirIdSet>,
}
impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]);
impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let hir::ItemKind::Impl(hir::Impl {
of_trait: None,
generics,
self_ty: impl_self_ty,
..
}) = item.kind
else {
return;
};
for assoc_item in cx
.tcx
.associated_items(item.owner_id.def_id)
.filter_by_name_unhygienic(sym::new)
{
if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind
&& let assoc_item_hir_id = cx.tcx.local_def_id_to_hir_id(assoc_item.def_id.expect_local())
&& let impl_item = cx.tcx.hir_node(assoc_item_hir_id).expect_impl_item()
&& !impl_item.span.in_external_macro(cx.sess().source_map())
&& let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind
&& let id = impl_item.owner_id
// can't be implemented for unsafe new
&& !sig.header.is_unsafe()
// shouldn't be implemented when it is hidden in docs
&& !cx.tcx.is_doc_hidden(impl_item.owner_id.def_id)
// when the result of `new()` depends on a parameter we should not require
// an impl of `Default`
&& impl_item.generics.params.is_empty()
&& sig.decl.inputs.is_empty()
&& cx.effective_visibilities.is_exported(impl_item.owner_id.def_id)
&& let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& self_ty == return_ty(cx, impl_item.owner_id)
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
{
if self.impling_types.is_none() {
let mut impls = HirIdSet::default();
for &d in cx.tcx.local_trait_impls(default_trait_id) {
let ty = cx.tcx.type_of(d).instantiate_identity();
if let Some(ty_def) = ty.ty_adt_def()
&& let Some(local_def_id) = ty_def.did().as_local()
{
impls.insert(cx.tcx.local_def_id_to_hir_id(local_def_id));
}
}
self.impling_types = Some(impls);
}
// Check if a Default implementation exists for the Self type, regardless of
// generics
if let Some(ref impling_types) = self.impling_types
&& let self_def = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& let Some(self_def) = self_def.ty_adt_def()
&& let Some(self_local_did) = self_def.did().as_local()
&& let self_id = cx.tcx.local_def_id_to_hir_id(self_local_did)
&& impling_types.contains(&self_id)
{
return;
}
let mut app = Applicability::MachineApplicable;
let attrs_sugg = {
let mut sugg = String::new();
for attr in cx.tcx.hir_attrs(assoc_item_hir_id) {
if !attr.has_name(sym::cfg_trace) {
// This might be some other attribute that the `impl Default` ought to inherit.
// But it could also be one of the many attributes that:
// - can't be put on an impl block -- like `#[inline]`
// - we can't even build a suggestion for, since `Attribute::span` may panic.
//
// Because of all that, remain on the safer side -- don't inherit this attr, and just
// reduce the applicability
app = Applicability::MaybeIncorrect;
continue;
}
sugg.push_str(&snippet_with_applicability(cx.sess(), attr.span(), "_", &mut app));
sugg.push('\n');
}
sugg
};
let generics_sugg = snippet_with_applicability(cx, generics.span, "", &mut app);
let where_clause_sugg = if generics.has_where_clause_predicates {
format!(
"\n{}\n",
snippet_with_applicability(cx, generics.where_clause_span, "", &mut app)
)
} else {
String::new()
};
let self_ty_fmt = self_ty.to_string();
let self_type_snip = snippet_with_applicability(cx, impl_self_ty.span, &self_ty_fmt, &mut app);
span_lint_hir_and_then(
cx,
NEW_WITHOUT_DEFAULT,
id.into(),
impl_item.span,
format!("you should consider adding a `Default` implementation for `{self_type_snip}`"),
|diag| {
diag.suggest_prepend_item(
cx,
item.span,
"try adding this",
&create_new_without_default_suggest_msg(
&attrs_sugg,
&self_type_snip,
&generics_sugg,
&where_clause_sugg,
),
app,
);
},
);
}
}
}
}
fn create_new_without_default_suggest_msg(
attrs_sugg: &str,
self_type_snip: &str,
generics_sugg: &str,
where_clause_sugg: &str,
) -> String {
#[rustfmt::skip]
format!(
"{attrs_sugg}impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{
fn default() -> Self {{
Self::new()
}}
}}")
}