frontend 0.4.0

rustc's frontend with no LLVM and no std: parsing through MIR, as a library
// `#![no_std]`: these arrive with the standard prelude and name no path, so a `std::`
// search cannot see them - and a `#[derive]` can use them without the name appearing
// in this file at all, which is why they are not trimmed by inspection.
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;

use crate::rustc_hir::def::{DefKind, Res};
use crate::rustc_hir::{Expr, ExprKind, ItemKind, Node, find_attr};
use crate::rustc_lint_defs::{declare_lint, declare_lint_pass};
use crate::rustc_middle::ty::adjustment::Adjust;

use crate::rustc_lint::diagnostics::{
    ConstItemInteriorMutationsDiag, ConstItemInteriorMutationsSuggestionStatic,
};
use crate::rustc_lint::{LateContext, LateLintPass, LintContext};

declare_lint! {
    /// The `const_item_interior_mutations` lint checks for calls which
    /// mutates an interior mutable const-item.
    ///
    /// ### Explanation
    ///
    /// Calling a method which mutates an interior mutable type has no effect as const-item
    /// are essentially inlined wherever they are used, meaning that they are copied
    /// directly into the relevant context when used rendering modification through
    /// interior mutability ineffective across usage of that const-item.
    ///
    /// The current implementation of this lint only warns on significant `std` and
    /// `core` interior mutable types, like `Once`, `AtomicI32`, ... this is done out
    /// of prudence to avoid false-positive and may be extended in the future.
    pub CONST_ITEM_INTERIOR_MUTATIONS,
    Warn,
    "checks for calls which mutates a interior mutable const-item"
}

declare_lint_pass!(InteriorMutableConsts => [CONST_ITEM_INTERIOR_MUTATIONS]);

impl<'tcx> LateLintPass<'tcx> for InteriorMutableConsts {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
        let typeck = cx.typeck_results();

        let (method_did, receiver) = match expr.kind {
            // matching on `<receiver>.method(..)`
            ExprKind::MethodCall(_, receiver, _, _) => {
                (typeck.type_dependent_def_id(expr.hir_id), receiver)
            }
            // matching on `function(&<receiver>, ...)`
            ExprKind::Call(path, [receiver, ..]) => match receiver.kind {
                ExprKind::AddrOf(_, _, receiver) => match path.kind {
                    ExprKind::Path(ref qpath) => {
                        (cx.qpath_res(qpath, path.hir_id).opt_def_id(), receiver)
                    }
                    _ => return,
                },
                _ => return,
            },
            _ => return,
        };

        let Some(method_did) = method_did else {
            return;
        };

        if let ExprKind::Path(qpath) = &receiver.kind
            && let Res::Def(DefKind::Const { .. } | DefKind::AssocConst { .. }, const_did) =
                typeck.qpath_res(qpath, receiver.hir_id)
            // Don't consider derefs as those can do arbitrary things
            // like using thread local (see rust-lang/rust#150157)
            && !cx
                .typeck_results()
                .expr_adjustments(receiver)
                .into_iter()
                .any(|adj| matches!(adj.kind, Adjust::Deref(_)))
            // Let's do the attribute check after the other checks for perf reasons
            && find_attr!(
                cx.tcx, method_did,
                RustcShouldNotBeCalledOnConstItems
            )
            && let Some(method_name) = cx.tcx.opt_item_ident(method_did)
            && let Some(const_name) = cx.tcx.opt_item_ident(const_did)
            && let Some(const_ty) = typeck.node_type_opt(receiver.hir_id)
        {
            // Find the local `const`-item and create the suggestion to use `static` instead
            let sugg_static = if let Some(Node::Item(const_item)) =
                cx.tcx.hir_get_if_local(const_did)
                && let ItemKind::Const(ident, _generics, _ty, _body_id) = const_item.kind
            {
                if let Some(vis_span) = const_item.vis_span.find_ancestor_inside(const_item.span)
                    && const_item.span.can_be_used_for_suggestions()
                    && vis_span.can_be_used_for_suggestions()
                {
                    Some(ConstItemInteriorMutationsSuggestionStatic::Spanful {
                        const_: const_item.vis_span.between(ident.span),
                        before: if !vis_span.is_empty() { " " } else { "" },
                        const_name,
                    })
                } else {
                    Some(ConstItemInteriorMutationsSuggestionStatic::Spanless { const_name })
                }
            } else {
                None
            };

            cx.emit_span_lint(
                CONST_ITEM_INTERIOR_MUTATIONS,
                expr.span,
                ConstItemInteriorMutationsDiag {
                    method_name,
                    const_name,
                    const_ty,
                    receiver_span: receiver.span,
                    sugg_static,
                },
            );
        }
    }
}