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