|
1 | | -use clippy_utils::diagnostics::span_lint; |
| 1 | +use clippy_utils::diagnostics::span_lint_and_then; |
2 | 2 | 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; |
5 | 10 | use rustc_middle::ty::{self, IntTy, Ty, UintTy}; |
6 | 11 | use rustc_session::declare_lint_pass; |
7 | 12 | use rustc_span::sym; |
@@ -88,24 +93,83 @@ declare_clippy_lint! { |
88 | 93 |
|
89 | 94 | declare_lint_pass!(Mutex => [MUTEX_ATOMIC, MUTEX_INTEGER]); |
90 | 95 |
|
| 96 | +// NOTE: we don't use `check_expr` because that would make us lint every _use_ of such mutexes, not |
| 97 | +// just their definitions |
91 | 98 | 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 |
96 | 102 | { |
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 |
107 | 162 | } |
| 163 | + diag.multipart_suggestion("try", suggs, applicability); |
| 164 | + } else { |
| 165 | + diag.help(format!("consider using an `{atomic_name}` instead")); |
108 | 166 | } |
| 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), |
109 | 173 | } |
110 | 174 | } |
111 | 175 | } |
@@ -135,7 +199,8 @@ fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> { |
135 | 199 | IntTy::I128 => None, |
136 | 200 | } |
137 | 201 | }, |
138 | | - ty::RawPtr(_, _) => Some("AtomicPtr"), |
| 202 | + // `AtomicPtr` only accepts `*mut T` |
| 203 | + ty::RawPtr(_, Mutability::Mut) => Some("AtomicPtr"), |
139 | 204 | _ => None, |
140 | 205 | } |
141 | 206 | } |
0 commit comments