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