Skip to content

Commit 8697533

Browse files
authored
overhaul mutex_{atomic,integer} (#15632)
- only lint on definitions of offending mutexes, not all their uses (fixes rust-lang/rust-clippy#13378) - give more orderly help messages - stop linting on `Mutex<*const T>` (see the corresponding commit for context) - offer (partial) suggestions The last change might be deemed a bit too much for the feature freeze, but it can be easily extracted out into a separate PR. changelog: [`mutex_atomic`]: only lint the definitions, not uses changelog: [`mutex_atomic`]: better help messages, and suggestions changelog: [`mutex_atomic`]: don't lint `Mutex<*const T>` changelog: [`mutex_integer`]: only lint the definitions, not uses changelog: [`mutex_integer`]: better help messages, and suggestions
2 parents ab9eb10 + 778da58 commit 8697533

File tree

6 files changed

+317
-76
lines changed

6 files changed

+317
-76
lines changed

clippy_lints/src/mutex_atomic.rs

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
use clippy_utils::diagnostics::span_lint;
1+
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::res::MaybeDef;
3-
use rustc_hir::Expr;
4-
use rustc_lint::{LateContext, LateLintPass};
3+
use clippy_utils::source::{IntoSpan, SpanRangeExt};
4+
use clippy_utils::sugg::Sugg;
5+
use clippy_utils::ty::ty_from_hir_ty;
6+
use rustc_errors::{Applicability, Diag};
7+
use rustc_hir::{self as hir, Expr, ExprKind, Item, ItemKind, LetStmt, QPath};
8+
use rustc_lint::{LateContext, LateLintPass, LintContext};
9+
use rustc_middle::mir::Mutability;
510
use rustc_middle::ty::{self, IntTy, Ty, UintTy};
611
use rustc_session::declare_lint_pass;
712
use rustc_span::sym;
@@ -88,24 +93,83 @@ declare_clippy_lint! {
8893

8994
declare_lint_pass!(Mutex => [MUTEX_ATOMIC, MUTEX_INTEGER]);
9095

96+
// NOTE: we don't use `check_expr` because that would make us lint every _use_ of such mutexes, not
97+
// just their definitions
9198
impl<'tcx> LateLintPass<'tcx> for Mutex {
92-
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
93-
let ty = cx.typeck_results().expr_ty(expr);
94-
if let ty::Adt(_, subst) = ty.kind()
95-
&& ty.is_diag_item(cx, sym::Mutex)
99+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
100+
if !item.span.from_expansion()
101+
&& let ItemKind::Static(_, _, ty, body_id) = item.kind
96102
{
97-
let mutex_param = subst.type_at(0);
98-
if let Some(atomic_name) = get_atomic_name(mutex_param) {
99-
let msg = format!(
100-
"consider using an `{atomic_name}` instead of a `Mutex` here; if you just want the locking \
101-
behavior and not the internal type, consider using `Mutex<()>`"
102-
);
103-
match *mutex_param.kind() {
104-
ty::Uint(t) if t != UintTy::Usize => span_lint(cx, MUTEX_INTEGER, expr.span, msg),
105-
ty::Int(t) if t != IntTy::Isize => span_lint(cx, MUTEX_INTEGER, expr.span, msg),
106-
_ => span_lint(cx, MUTEX_ATOMIC, expr.span, msg),
103+
let body = cx.tcx.hir_body(body_id);
104+
let mid_ty = ty_from_hir_ty(cx, ty);
105+
check_expr(cx, body.value.peel_blocks(), &TypeAscriptionKind::Required(ty), mid_ty);
106+
}
107+
}
108+
fn check_local(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx LetStmt<'_>) {
109+
if !stmt.span.from_expansion()
110+
&& let Some(init) = stmt.init
111+
{
112+
let mid_ty = cx.typeck_results().expr_ty(init);
113+
check_expr(cx, init.peel_blocks(), &TypeAscriptionKind::Optional(stmt.ty), mid_ty);
114+
}
115+
}
116+
}
117+
118+
/// Whether the type ascription `: Mutex<X>` (which we'll suggest replacing with `AtomicX`) is
119+
/// required
120+
enum TypeAscriptionKind<'tcx> {
121+
/// Yes; for us, this is the case for statics
122+
Required(&'tcx hir::Ty<'tcx>),
123+
/// No; the ascription might've been necessary in an expression like:
124+
/// ```ignore
125+
/// let mutex: Mutex<u64> = Mutex::new(0);
126+
/// ```
127+
/// to specify the type of `0`, but since `AtomicX` already refers to a concrete type, we won't
128+
/// need this ascription anymore.
129+
Optional(Option<&'tcx hir::Ty<'tcx>>),
130+
}
131+
132+
fn check_expr<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, ty_ascription: &TypeAscriptionKind<'tcx>, ty: Ty<'tcx>) {
133+
if let ty::Adt(_, subst) = ty.kind()
134+
&& ty.is_diag_item(cx, sym::Mutex)
135+
&& let mutex_param = subst.type_at(0)
136+
&& let Some(atomic_name) = get_atomic_name(mutex_param)
137+
{
138+
let msg = "using a `Mutex` where an atomic would do";
139+
let diag = |diag: &mut Diag<'_, _>| {
140+
// if `expr = Mutex::new(arg)`, we can try emitting a suggestion
141+
if let ExprKind::Call(qpath, [arg]) = expr.kind
142+
&& let ExprKind::Path(QPath::TypeRelative(_mutex, new)) = qpath.kind
143+
&& new.ident.name == sym::new
144+
{
145+
let mut applicability = Applicability::MaybeIncorrect;
146+
let arg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability);
147+
let mut suggs = vec![(expr.span, format!("std::sync::atomic::{atomic_name}::new({arg})"))];
148+
match ty_ascription {
149+
TypeAscriptionKind::Required(ty_ascription) => {
150+
suggs.push((ty_ascription.span, format!("std::sync::atomic::{atomic_name}")));
151+
},
152+
TypeAscriptionKind::Optional(Some(ty_ascription)) => {
153+
// See https://github.com/rust-lang/rust-clippy/pull/15386 for why this is
154+
// required
155+
let colon_ascription = (cx.sess().source_map())
156+
.span_extend_to_prev_char_before(ty_ascription.span, ':', true)
157+
.with_leading_whitespace(cx)
158+
.into_span();
159+
suggs.push((colon_ascription, String::new()));
160+
},
161+
TypeAscriptionKind::Optional(None) => {}, // nothing to remove/replace
107162
}
163+
diag.multipart_suggestion("try", suggs, applicability);
164+
} else {
165+
diag.help(format!("consider using an `{atomic_name}` instead"));
108166
}
167+
diag.help("if you just want the locking behavior and not the internal type, consider using `Mutex<()>`");
168+
};
169+
match *mutex_param.kind() {
170+
ty::Uint(t) if t != UintTy::Usize => span_lint_and_then(cx, MUTEX_INTEGER, expr.span, msg, diag),
171+
ty::Int(t) if t != IntTy::Isize => span_lint_and_then(cx, MUTEX_INTEGER, expr.span, msg, diag),
172+
_ => span_lint_and_then(cx, MUTEX_ATOMIC, expr.span, msg, diag),
109173
}
110174
}
111175
}
@@ -135,7 +199,8 @@ fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> {
135199
IntTy::I128 => None,
136200
}
137201
},
138-
ty::RawPtr(_, _) => Some("AtomicPtr"),
202+
// `AtomicPtr` only accepts `*mut T`
203+
ty::RawPtr(_, Mutability::Mut) => Some("AtomicPtr"),
139204
_ => None,
140205
}
141206
}

tests/ui/mutex_atomic.fixed

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#![warn(clippy::mutex_integer)]
2+
#![warn(clippy::mutex_atomic)]
3+
#![allow(clippy::borrow_as_ptr)]
4+
5+
use std::sync::Mutex;
6+
7+
fn main() {
8+
let _ = std::sync::atomic::AtomicBool::new(true);
9+
//~^ mutex_atomic
10+
11+
let _ = std::sync::atomic::AtomicUsize::new(5usize);
12+
//~^ mutex_atomic
13+
14+
let _ = std::sync::atomic::AtomicIsize::new(9isize);
15+
//~^ mutex_atomic
16+
17+
let mut x = 4u32;
18+
// `AtomicPtr` only accepts `*mut T`, so this should not lint
19+
let _ = Mutex::new(&x as *const u32);
20+
21+
let _ = std::sync::atomic::AtomicPtr::new(&mut x as *mut u32);
22+
//~^ mutex_atomic
23+
24+
let _ = std::sync::atomic::AtomicU32::new(0u32);
25+
//~^ mutex_integer
26+
27+
let _ = std::sync::atomic::AtomicI32::new(0i32);
28+
//~^ mutex_integer
29+
30+
let _ = Mutex::new(0f32); // there are no float atomics, so this should not lint
31+
let _ = std::sync::atomic::AtomicU8::new(0u8);
32+
//~^ mutex_integer
33+
34+
let _ = std::sync::atomic::AtomicI16::new(0i16);
35+
//~^ mutex_integer
36+
37+
let _x = std::sync::atomic::AtomicI8::new(0);
38+
//~^ mutex_integer
39+
40+
const X: i64 = 0;
41+
let _ = std::sync::atomic::AtomicI64::new(X);
42+
//~^ mutex_integer
43+
44+
// there are no 128 atomics, so these two should not lint
45+
{
46+
let _ = Mutex::new(0u128);
47+
let _x: Mutex<i128> = Mutex::new(0);
48+
}
49+
}
50+
51+
// don't lint on _use_, only declaration
52+
fn issue13378() {
53+
static MTX: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
54+
//~^ mutex_integer
55+
56+
let mtx = std::sync::atomic::AtomicI32::new(0);
57+
//~^ mutex_integer
58+
// This will still lint, since we're reassigning the mutex to a variable -- oh well.
59+
// But realistically something like this won't really come up.
60+
let reassigned = mtx;
61+
//~^ mutex_integer
62+
63+
// don't eat the `)` when removing the type ascription -- see
64+
// https://github.com/rust-lang/rust-clippy/issues/15377
65+
let (funky_mtx) = std::sync::atomic::AtomicU64::new(0);
66+
//~^ mutex_integer
67+
}

tests/ui/mutex_atomic.rs

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,66 @@
22
#![warn(clippy::mutex_atomic)]
33
#![allow(clippy::borrow_as_ptr)]
44

5+
use std::sync::Mutex;
6+
57
fn main() {
6-
use std::sync::Mutex;
7-
Mutex::new(true);
8+
let _ = Mutex::new(true);
89
//~^ mutex_atomic
910

10-
Mutex::new(5usize);
11+
let _ = Mutex::new(5usize);
1112
//~^ mutex_atomic
1213

13-
Mutex::new(9isize);
14+
let _ = Mutex::new(9isize);
1415
//~^ mutex_atomic
1516

1617
let mut x = 4u32;
17-
Mutex::new(&x as *const u32);
18-
//~^ mutex_atomic
18+
// `AtomicPtr` only accepts `*mut T`, so this should not lint
19+
let _ = Mutex::new(&x as *const u32);
1920

20-
Mutex::new(&mut x as *mut u32);
21+
let _ = Mutex::new(&mut x as *mut u32);
2122
//~^ mutex_atomic
2223

23-
Mutex::new(0u32);
24+
let _ = Mutex::new(0u32);
2425
//~^ mutex_integer
2526

26-
Mutex::new(0i32);
27+
let _ = Mutex::new(0i32);
2728
//~^ mutex_integer
2829

29-
Mutex::new(0f32); // there are no float atomics, so this should not lint
30-
Mutex::new(0u8);
30+
let _ = Mutex::new(0f32); // there are no float atomics, so this should not lint
31+
let _ = Mutex::new(0u8);
3132
//~^ mutex_integer
3233

33-
Mutex::new(0i16);
34+
let _ = Mutex::new(0i16);
3435
//~^ mutex_integer
3536

3637
let _x: Mutex<i8> = Mutex::new(0);
3738
//~^ mutex_integer
3839

3940
const X: i64 = 0;
40-
Mutex::new(X);
41+
let _ = Mutex::new(X);
4142
//~^ mutex_integer
4243

4344
// there are no 128 atomics, so these two should not lint
4445
{
45-
Mutex::new(0u128);
46+
let _ = Mutex::new(0u128);
4647
let _x: Mutex<i128> = Mutex::new(0);
4748
}
4849
}
50+
51+
// don't lint on _use_, only declaration
52+
fn issue13378() {
53+
static MTX: Mutex<u32> = Mutex::new(0);
54+
//~^ mutex_integer
55+
56+
let mtx = Mutex::new(0);
57+
//~^ mutex_integer
58+
// This will still lint, since we're reassigning the mutex to a variable -- oh well.
59+
// But realistically something like this won't really come up.
60+
let reassigned = mtx;
61+
//~^ mutex_integer
62+
63+
// don't eat the `)` when removing the type ascription -- see
64+
// https://github.com/rust-lang/rust-clippy/issues/15377
65+
let (funky_mtx): Mutex<u64> = Mutex::new(0);
66+
//~^ mutex_integer
67+
}

0 commit comments

Comments
 (0)