Skip to content

Commit 70913ba

Browse files
committed
Implement a lint for RefCell<impl Copy>
1 parent 2536745 commit 70913ba

File tree

7 files changed

+151
-0
lines changed

7 files changed

+151
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5374,6 +5374,7 @@ Released 2018-09-13
53745374
[`const_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_is_empty
53755375
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
53765376
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
5377+
[`copy_refcell`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_refcell
53775378
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def
53785379
[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
53795380
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute

clippy_config/src/conf.rs

+3
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,9 @@ define_Conf! {
471471
/// A list of paths to types that should be treated as if they do not contain interior mutability
472472
#[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)]
473473
ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()]),
474+
/// The maximum size of a `T` in `RefCell<T>` to suggest to swap to `Cell` if applicable.
475+
#[lints(copy_refcell)]
476+
large_cell_limit: u64 = 128,
474477
/// The maximum size of the `Err`-variant in a `Result` returned from a function
475478
#[lints(result_large_err)]
476479
large_error_threshold: u64 = 128,

clippy_lints/src/copy_refcell.rs

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use clippy_config::Conf;
2+
use rustc_hir::{FieldDef, LetStmt, LocalSource};
3+
use rustc_lint::{LateContext, LateLintPass};
4+
use rustc_middle::ty;
5+
use rustc_middle::ty::layout::TyAndLayout;
6+
use rustc_session::impl_lint_pass;
7+
use rustc_span::symbol::sym;
8+
use rustc_span::Span;
9+
10+
use clippy_utils::diagnostics::span_lint_and_help;
11+
use clippy_utils::ty::implements_trait;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
///
16+
/// Detects crate-local usage of `RefCell<T>` where `T` implements `Copy`
17+
///
18+
/// ### Why is this bad?
19+
///
20+
/// `RefCell` has to perform extra book-keeping in order to support references, whereas `Cell` does not.
21+
///
22+
/// ### Example
23+
/// ```no_run
24+
/// let interior_mutable = std::cell::RefCell::new(0_u8);
25+
///
26+
/// *interior_mutable.borrow_mut() = 1;
27+
/// ```
28+
/// Use instead:
29+
/// ```no_run
30+
/// let interior_mutable = std::cell::Cell::new(0_u8);
31+
///
32+
/// interior_mutable.set(1);
33+
/// ```
34+
#[clippy::version = "1.83.0"]
35+
pub COPY_REFCELL,
36+
perf,
37+
"usage of `RefCell<T>` where `T` implements `Copy`"
38+
}
39+
40+
pub struct CopyRefcell {
41+
maximum_size: u64,
42+
}
43+
44+
impl CopyRefcell {
45+
pub fn new(conf: &'static Conf) -> Self {
46+
Self {
47+
maximum_size: conf.large_cell_limit,
48+
}
49+
}
50+
51+
fn check_copy_refcell<'tcx>(&self, cx: &LateContext<'tcx>, ty: ty::Ty<'tcx>, span: Span) {
52+
let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy) else {
53+
return;
54+
};
55+
56+
let ty::Adt(adt, generics) = ty.kind() else {
57+
return;
58+
};
59+
60+
if !cx.tcx.is_diagnostic_item(sym::RefCell, adt.did()) {
61+
return;
62+
}
63+
64+
let inner_ty = generics.type_at(0);
65+
let Ok(TyAndLayout { layout, .. }) = cx.tcx.layout_of(cx.param_env.and(inner_ty)) else {
66+
return;
67+
};
68+
69+
if layout.size().bytes() >= self.maximum_size {
70+
return;
71+
}
72+
73+
if implements_trait(cx, inner_ty, copy_def_id, &[]) {
74+
span_lint_and_help(
75+
cx,
76+
COPY_REFCELL,
77+
span,
78+
"std::cell::RefCell used with a type that implements Copy",
79+
None,
80+
"replace the usage of RefCell with Cell, which is simpler and zero-cost",
81+
);
82+
}
83+
}
84+
}
85+
86+
impl_lint_pass!(CopyRefcell => [COPY_REFCELL]);
87+
88+
impl<'tcx> LateLintPass<'tcx> for CopyRefcell {
89+
fn check_field_def(&mut self, cx: &LateContext<'tcx>, field_def: &'tcx FieldDef<'tcx>) {
90+
// Don't want to lint macro expansions.
91+
if field_def.span.from_expansion() {
92+
return;
93+
}
94+
95+
let field_ty = rustc_hir_analysis::lower_ty(cx.tcx, field_def.ty);
96+
self.check_copy_refcell(cx, field_ty, field_def.ty.span);
97+
}
98+
99+
fn check_local(&mut self, cx: &LateContext<'tcx>, local_def: &'tcx LetStmt<'tcx>) {
100+
// Don't want to lint macro expansions or desugaring.
101+
if local_def.span.from_expansion() || !matches!(local_def.source, LocalSource::Normal) {
102+
return;
103+
}
104+
105+
let Some(init_expr) = local_def.init else {
106+
return;
107+
};
108+
109+
let init_ty = cx.typeck_results().expr_ty(init_expr);
110+
self.check_copy_refcell(cx, init_ty, init_expr.span);
111+
}
112+
}

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
108108
crate::copies::IF_SAME_THEN_ELSE_INFO,
109109
crate::copies::SAME_FUNCTIONS_IN_IF_CONDITION_INFO,
110110
crate::copy_iterator::COPY_ITERATOR_INFO,
111+
crate::copy_refcell::COPY_REFCELL_INFO,
111112
crate::crate_in_macro_def::CRATE_IN_MACRO_DEF_INFO,
112113
crate::create_dir::CREATE_DIR_INFO,
113114
crate::dbg_macro::DBG_MACRO_INFO,

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ mod collection_is_never_read;
9999
mod comparison_chain;
100100
mod copies;
101101
mod copy_iterator;
102+
mod copy_refcell;
102103
mod crate_in_macro_def;
103104
mod create_dir;
104105
mod dbg_macro;
@@ -942,5 +943,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
942943
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
943944
store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
944945
store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
946+
store.register_late_pass(move |_| Box::new(copy_refcell::CopyRefcell::new(conf)));
945947
// add lints here, do not remove this comment, it's used in `new_lint`
946948
}

tests/ui/copy_refcell.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#![warn(clippy::copy_refcell)]
2+
3+
use std::cell::RefCell;
4+
5+
struct MyStruct {
6+
field: RefCell<u8>,
7+
}
8+
9+
fn main() {
10+
let local = RefCell::new(0_u8);
11+
let large_local = RefCell::new([0_u8; 1024]);
12+
}

tests/ui/copy_refcell.stderr

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: std::cell::RefCell used with a type that implements Copy
2+
--> tests/ui/copy_refcell.rs:6:12
3+
|
4+
LL | field: RefCell<u8>,
5+
| ^^^^^^^^^^^
6+
|
7+
= help: replace the usage of RefCell with Cell, which is simpler and zero-cost
8+
= note: `-D clippy::copy-refcell` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::copy_refcell)]`
10+
11+
error: std::cell::RefCell used with a type that implements Copy
12+
--> tests/ui/copy_refcell.rs:10:17
13+
|
14+
LL | let local = RefCell::new(0_u8);
15+
| ^^^^^^^^^^^^^^^^^^
16+
|
17+
= help: replace the usage of RefCell with Cell, which is simpler and zero-cost
18+
19+
error: aborting due to 2 previous errors
20+

0 commit comments

Comments
 (0)