-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Implement a lint for RefCell<impl Copy> #13388
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
use clippy_config::Conf; | ||
use rustc_hir::{FieldDef, LetStmt, LocalSource}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_middle::ty; | ||
use rustc_middle::ty::layout::TyAndLayout; | ||
use rustc_session::impl_lint_pass; | ||
use rustc_span::symbol::sym; | ||
use rustc_span::Span; | ||
|
||
use clippy_utils::diagnostics::span_lint_and_help; | ||
use clippy_utils::ty::implements_trait; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// | ||
/// Detects crate-local usage of `RefCell<T>` where `T` implements `Copy` | ||
/// | ||
/// ### Why is this bad? | ||
/// | ||
/// `RefCell` has to perform extra book-keeping in order to support references, whereas `Cell` does not. | ||
/// | ||
/// ### Example | ||
/// ```no_run | ||
/// let interior_mutable = std::cell::RefCell::new(0_u8); | ||
/// | ||
/// *interior_mutable.borrow_mut() = 1; | ||
/// ``` | ||
/// Use instead: | ||
/// ```no_run | ||
/// let interior_mutable = std::cell::Cell::new(0_u8); | ||
/// | ||
/// interior_mutable.set(1); | ||
/// ``` | ||
#[clippy::version = "1.83.0"] | ||
pub COPY_REFCELL, | ||
pedantic, | ||
"usage of `RefCell<T>` where `T` implements `Copy`" | ||
} | ||
|
||
pub struct CopyRefCell { | ||
maximum_size: u64, | ||
avoid_breaking_exported_api: bool, | ||
} | ||
|
||
impl CopyRefCell { | ||
pub fn new(conf: &'static Conf) -> Self { | ||
Self { | ||
maximum_size: conf.large_cell_limit, | ||
avoid_breaking_exported_api: conf.avoid_breaking_exported_api, | ||
} | ||
} | ||
|
||
fn check_copy_refcell<'tcx>(&self, cx: &LateContext<'tcx>, ty: ty::Ty<'tcx>, span: Span) { | ||
let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy) else { | ||
return; | ||
}; | ||
|
||
let ty::Adt(adt, generics) = ty.kind() else { | ||
return; | ||
}; | ||
|
||
if !cx.tcx.is_diagnostic_item(sym::RefCell, adt.did()) { | ||
return; | ||
} | ||
|
||
let inner_ty = generics.type_at(0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we could have a check for if this is explicitly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wdym "explicitly Copy", or even removing the Copy bound? I don't see why you would want to remove the Copy implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. struct A<T: Copy>(RefCell<T>); This could suggest removing the |
||
let Ok(TyAndLayout { layout, .. }) = cx.tcx.layout_of(cx.param_env.and(inner_ty)) else { | ||
return; | ||
}; | ||
|
||
if layout.size().bytes() >= self.maximum_size { | ||
return; | ||
} | ||
|
||
if implements_trait(cx, inner_ty, copy_def_id, &[]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would probably also be better as a long There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, procedural macros can use any arbitrary span which also means its input tokens. Those won't be counted as from expansion so it'll still lint. |
||
span_lint_and_help( | ||
cx, | ||
COPY_REFCELL, | ||
span, | ||
"`RefCell` used with a type that implements `Copy`", | ||
None, | ||
"replace the usage of `RefCell` with `Cell`, which does not have to track borrowing at runtime", | ||
); | ||
} | ||
} | ||
} | ||
|
||
impl_lint_pass!(CopyRefCell => [COPY_REFCELL]); | ||
|
||
impl<'tcx> LateLintPass<'tcx> for CopyRefCell { | ||
fn check_field_def(&mut self, cx: &LateContext<'tcx>, field_def: &'tcx FieldDef<'tcx>) { | ||
// Don't want to lint macro expansions. | ||
if field_def.span.from_expansion() { | ||
GnomedDev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
} | ||
|
||
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(field_def.def_id) { | ||
return; | ||
} | ||
|
||
let field_ty = rustc_hir_analysis::lower_ty(cx.tcx, field_def.ty); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
self.check_copy_refcell(cx, field_ty, field_def.ty.span); | ||
} | ||
|
||
fn check_local(&mut self, cx: &LateContext<'tcx>, local_def: &'tcx LetStmt<'tcx>) { | ||
// Don't want to lint macro expansions or desugaring. | ||
if local_def.span.from_expansion() || !matches!(local_def.source, LocalSource::Normal) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shall I remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah the |
||
return; | ||
} | ||
|
||
let Some(init_expr) = local_def.init else { | ||
return; | ||
}; | ||
|
||
let init_ty = cx.typeck_results().expr_ty(init_expr); | ||
self.check_copy_refcell(cx, init_ty, init_expr.span); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
#![warn(clippy::await_holding_refcell_ref)] | ||
#![allow(clippy::copy_refcell)] | ||
|
||
use std::cell::RefCell; | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.