Skip to main content

rustc_lint/
interior_mutable_consts.rs

1use rustc_attr_ir::find_attr;
2use rustc_hir::def::{DefKind, Res};
3use rustc_hir::{Expr, ExprKind, ItemKind, Node};
4use rustc_lint_defs::{declare_lint, declare_lint_pass};
5use rustc_middle::ty::adjustment::Adjust;
6
7use crate::diagnostics::{
8    ConstItemInteriorMutationsDiag, ConstItemInteriorMutationsSuggestionStatic,
9};
10use crate::{LateContext, LateLintPass, LintContext};
11
12#[doc = r" The `const_item_interior_mutations` lint checks for calls which"]
#[doc = r" mutates an interior mutable const-item."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" use std::sync::Once;"]
#[doc = r""]
#[doc =
r" const INIT: Once = Once::new(); // using `INIT` will always create a temporary and"]
#[doc =
r"                                 // never modify it-self on use, should be a `static`"]
#[doc = r"                                 // instead for shared use"]
#[doc = r""]
#[doc = r" fn init() {"]
#[doc = r"     INIT.call_once(|| {"]
#[doc = r#"         println!("Once::call_once first call");"#]
#[doc = r"     });"]
#[doc =
r"     INIT.call_once(|| {                          // this second will also print"]
#[doc =
r#"         println!("Once::call_once second call"); // as each call to `INIT` creates"#]
#[doc = r"     });                                          // new temporary"]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Calling a method which mutates an interior mutable type has no effect as const-item"]
#[doc =
r" are essentially inlined wherever they are used, meaning that they are copied"]
#[doc =
r" directly into the relevant context when used rendering modification through"]
#[doc = r" interior mutability ineffective across usage of that const-item."]
#[doc = r""]
#[doc =
r" The current implementation of this lint only warns on significant `std` and"]
#[doc =
r" `core` interior mutable types, like `Once`, `AtomicI32`, ... this is done out"]
#[doc =
r" of prudence to avoid false-positive and may be extended in the future."]
pub static CONST_ITEM_INTERIOR_MUTATIONS: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "CONST_ITEM_INTERIOR_MUTATIONS",
            default_level: ::rustc_lint_defs::Warn,
            desc: "checks for calls which mutates a interior mutable const-item",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
13    /// The `const_item_interior_mutations` lint checks for calls which
14    /// mutates an interior mutable const-item.
15    ///
16    /// ### Example
17    ///
18    /// ```rust
19    /// use std::sync::Once;
20    ///
21    /// const INIT: Once = Once::new(); // using `INIT` will always create a temporary and
22    ///                                 // never modify it-self on use, should be a `static`
23    ///                                 // instead for shared use
24    ///
25    /// fn init() {
26    ///     INIT.call_once(|| {
27    ///         println!("Once::call_once first call");
28    ///     });
29    ///     INIT.call_once(|| {                          // this second will also print
30    ///         println!("Once::call_once second call"); // as each call to `INIT` creates
31    ///     });                                          // new temporary
32    /// }
33    /// ```
34    ///
35    /// {{produces}}
36    ///
37    /// ### Explanation
38    ///
39    /// Calling a method which mutates an interior mutable type has no effect as const-item
40    /// are essentially inlined wherever they are used, meaning that they are copied
41    /// directly into the relevant context when used rendering modification through
42    /// interior mutability ineffective across usage of that const-item.
43    ///
44    /// The current implementation of this lint only warns on significant `std` and
45    /// `core` interior mutable types, like `Once`, `AtomicI32`, ... this is done out
46    /// of prudence to avoid false-positive and may be extended in the future.
47    pub CONST_ITEM_INTERIOR_MUTATIONS,
48    Warn,
49    "checks for calls which mutates a interior mutable const-item"
50}
51
52pub struct InteriorMutableConsts;
#[automatically_derived]
impl ::core::marker::Copy for InteriorMutableConsts { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for InteriorMutableConsts { }
#[automatically_derived]
impl ::core::clone::Clone for InteriorMutableConsts {
    #[inline]
    fn clone(&self) -> Self { *self }
}
impl ::rustc_lint_defs::LintPass for InteriorMutableConsts {
    fn name(&self) -> &'static str { "InteriorMutableConsts" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [CONST_ITEM_INTERIOR_MUTATIONS]))
    }
}
impl InteriorMutableConsts {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [CONST_ITEM_INTERIOR_MUTATIONS]))
    }
}declare_lint_pass!(InteriorMutableConsts => [CONST_ITEM_INTERIOR_MUTATIONS]);
53
54impl<'tcx> LateLintPass<'tcx> for InteriorMutableConsts {
55    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
56        let typeck = cx.typeck_results();
57
58        let (method_did, receiver) = match expr.kind {
59            // matching on `<receiver>.method(..)`
60            ExprKind::MethodCall(_, receiver, _, _) => {
61                (typeck.type_dependent_def_id(expr.hir_id), receiver)
62            }
63            // matching on `function(&<receiver>, ...)`
64            ExprKind::Call(path, [receiver, ..]) => match receiver.kind {
65                ExprKind::AddrOf(_, _, receiver) => match path.kind {
66                    ExprKind::Path(ref qpath) => {
67                        (cx.qpath_res(qpath, path.hir_id).opt_def_id(), receiver)
68                    }
69                    _ => return,
70                },
71                _ => return,
72            },
73            _ => return,
74        };
75
76        let Some(method_did) = method_did else {
77            return;
78        };
79
80        if let ExprKind::Path(qpath) = &receiver.kind
81            && let Res::Def(DefKind::Const  | DefKind::AssocConst , const_did) =
82                typeck.qpath_res(qpath, receiver.hir_id)
83            // Don't consider derefs as those can do arbitrary things
84            // like using thread local (see rust-lang/rust#150157)
85            && !cx
86                .typeck_results()
87                .expr_adjustments(receiver)
88                .into_iter()
89                .any(|adj| #[allow(non_exhaustive_omitted_patterns)] match adj.kind {
    Adjust::Deref(_) => true,
    _ => false,
}matches!(adj.kind, Adjust::Deref(_)))
90            // Let's do the attribute check after the other checks for perf reasons
91            && {
        {
            'done:
                {
                for i in
                    ::rustc_attr_ir::HasAttrs::get_attrs(method_did, &cx.tcx) {
                    #[allow(unused_imports)]
                    use ::rustc_attr_ir::AttributeKind::*;
                    let i: &::rustc_attr_ir::Attribute = i;
                    match i {
                        ::rustc_attr_ir::Attribute::Parsed(RustcShouldNotBeCalledOnConstItems)
                            => {
                            break 'done Some(());
                        }
                        ::rustc_attr_ir::Attribute::Unparsed(..) =>
                            {}
                            #[deny(unreachable_patterns)]
                            _ => {}
                    }
                }
                None
            }
        }
    }.is_some()find_attr!(
92                cx.tcx, method_did,
93                RustcShouldNotBeCalledOnConstItems
94            )
95            && let Some(method_name) = cx.tcx.opt_item_ident(method_did)
96            && let Some(const_name) = cx.tcx.opt_item_ident(const_did)
97            && let Some(const_ty) = typeck.node_type_opt(receiver.hir_id)
98        {
99            // Find the local `const`-item and create the suggestion to use `static` instead
100            let sugg_static = if let Some(Node::Item(const_item)) =
101                cx.tcx.hir_get_if_local(const_did)
102                && let ItemKind::Const(ident, _generics, _ty, _body_id) = const_item.kind
103            {
104                if let Some(vis_span) = const_item.vis_span.find_ancestor_inside(const_item.span)
105                    && const_item.span.can_be_used_for_suggestions()
106                    && vis_span.can_be_used_for_suggestions()
107                {
108                    Some(ConstItemInteriorMutationsSuggestionStatic::Spanful {
109                        const_: const_item.vis_span.between(ident.span),
110                        before: if !vis_span.is_empty() { " " } else { "" },
111                        const_name,
112                    })
113                } else {
114                    Some(ConstItemInteriorMutationsSuggestionStatic::Spanless { const_name })
115                }
116            } else {
117                None
118            };
119
120            cx.emit_span_lint(
121                CONST_ITEM_INTERIOR_MUTATIONS,
122                expr.span,
123                ConstItemInteriorMutationsDiag {
124                    method_name,
125                    const_name,
126                    const_ty,
127                    receiver_span: receiver.span,
128                    sugg_static,
129                },
130            );
131        }
132    }
133}