| use clippy_utils::diagnostics::span_lint_hir_and_then; | |
| use clippy_utils::return_ty; | |
| use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability}; | |
| use clippy_utils::sugg::DiagExt; | |
| use rustc_errors::Applicability; | |
| use rustc_hir as hir; | |
| use rustc_hir::attrs::AttributeKind; | |
| use rustc_hir::{Attribute, 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" | |
| } | |
| impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]); | |
| #[derive(Clone, Default)] | |
| pub struct NewWithoutDefault { | |
| impling_types: Option<HirIdSet>, | |
| } | |
| impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { | |
| #[expect(clippy::too_many_lines)] | |
| 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) { | |
| let Attribute::Parsed(AttributeKind::CfgTrace(attrs)) = attr else { | |
| // 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; | |
| }; | |
| for (_, attr_span) in attrs { | |
| 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 { | |
| let where_clause_sugg = | |
| snippet_with_applicability(cx, generics.where_clause_span, "", &mut app).to_string(); | |
| let mut where_clause_sugg = reindent_multiline(&where_clause_sugg, true, Some(4)); | |
| if impl_item.generics.has_where_clause_predicates { | |
| if !where_clause_sugg.ends_with(',') { | |
| where_clause_sugg.push(','); | |
| } | |
| let additional_where_preds = | |
| snippet_with_applicability(cx, impl_item.generics.where_clause_span, "", &mut app); | |
| let ident = indent_of(cx, generics.where_clause_span).unwrap_or(0); | |
| // Remove the leading `where ` keyword | |
| let additional_where_preds = additional_where_preds.trim_start_matches("where").trim_start(); | |
| where_clause_sugg.push('\n'); | |
| where_clause_sugg.extend(std::iter::repeat_n(' ', ident)); | |
| where_clause_sugg.push_str(additional_where_preds); | |
| } | |
| format!("\n{where_clause_sugg}\n") | |
| } else if impl_item.generics.has_where_clause_predicates { | |
| let where_clause_sugg = | |
| snippet_with_applicability(cx, impl_item.generics.where_clause_span, "", &mut app); | |
| let where_clause_sugg = reindent_multiline(&where_clause_sugg, true, Some(4)); | |
| format!("\n{}\n", where_clause_sugg.trim_start()) | |
| } 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() | |
| }} | |
| }}") | |
| } |