use clippy_utils::diagnostics::span_lint;
use clippy_utils::return_ty;
use clippy_utils::ty::contains_adt_constructor;
use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;

declare_clippy_lint! {
    /// ### What it does
    /// Warns when constructors have the same name as their types.
    ///
    /// ### Why is this bad?
    /// Repeating the name of the type is redundant.
    ///
    /// ### Example
    /// ```rust,ignore
    /// struct Foo {}
    ///
    /// impl Foo {
    ///     pub fn foo() -> Foo {
    ///         Foo {}
    ///     }
    /// }
    /// ```
    /// Use instead:
    /// ```rust,ignore
    /// struct Foo {}
    ///
    /// impl Foo {
    ///     pub fn new() -> Foo {
    ///         Foo {}
    ///     }
    /// }
    /// ```
    #[clippy::version = "1.55.0"]
    pub SELF_NAMED_CONSTRUCTORS,
    style,
    "method should not have the same name as the type it is implemented for"
}

declare_lint_pass!(SelfNamedConstructors => [SELF_NAMED_CONSTRUCTORS]);

impl<'tcx> LateLintPass<'tcx> for SelfNamedConstructors {
    fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
        match impl_item.kind {
            ImplItemKind::Fn(ref sig, _) => {
                if sig.decl.implicit_self.has_implicit_self() {
                    return;
                }
            },
            _ => return,
        }

        let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id;
        let item = cx.tcx.hir_expect_item(parent);
        let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
        let ret_ty = return_ty(cx, impl_item.owner_id);

        // Do not check trait impls
        if matches!(item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. })) {
            return;
        }

        // Ensure method is constructor-like
        if let Some(self_adt) = self_ty.ty_adt_def() {
            if !contains_adt_constructor(ret_ty, self_adt) {
                return;
            }
        } else if !ret_ty.contains(self_ty) {
            return;
        }

        if let Some(self_def) = self_ty.ty_adt_def()
            && let Some(self_local_did) = self_def.did().as_local()
            && let Node::Item(x) = cx.tcx.hir_node_by_def_id(self_local_did)
            && let Some(type_ident) = x.kind.ident()
            && let type_name = type_ident.name.as_str().to_lowercase()
            && (impl_item.ident.name.as_str() == type_name
                || impl_item.ident.name.as_str().replace('_', "") == type_name)
        {
            span_lint(
                cx,
                SELF_NAMED_CONSTRUCTORS,
                impl_item.span,
                format!("constructor `{}` has the same name as the type", impl_item.ident.name),
            );
        }
    }
}
