|
1 | | -use clippy_utils::diagnostics::span_lint_hir_and_then; |
| 1 | +use clippy_utils::diagnostics::span_lint_and_then; |
| 2 | +use clippy_utils::is_lint_allowed; |
2 | 3 | use clippy_utils::ty::{implements_trait, is_copy}; |
3 | 4 | use rustc_ast::ImplPolarity; |
| 5 | +use rustc_hir::def_id::DefId; |
4 | 6 | use rustc_hir::{Item, ItemKind}; |
5 | 7 | use rustc_lint::{LateContext, LateLintPass}; |
6 | 8 | use rustc_middle::ty::{self, subst::GenericArgKind, Ty}; |
7 | 9 | use rustc_session::{declare_lint_pass, declare_tool_lint}; |
8 | | -use rustc_span::sym; |
| 10 | +use rustc_span::symbol::Symbol; |
| 11 | +use rustc_span::{sym, Span}; |
9 | 12 |
|
10 | 13 | declare_clippy_lint! { |
11 | 14 | /// ### What it does |
@@ -64,51 +67,98 @@ impl<'tcx> LateLintPass<'tcx> for NonSendFieldInSendTy { |
64 | 67 | if let self_ty = ty_trait_ref.self_ty(); |
65 | 68 | if let ty::Adt(adt_def, impl_trait_substs) = self_ty.kind(); |
66 | 69 | then { |
| 70 | + let mut non_send_fields = Vec::new(); |
| 71 | + |
| 72 | + let hir_map = cx.tcx.hir(); |
67 | 73 | for variant in &adt_def.variants { |
68 | 74 | for field in &variant.fields { |
69 | | - let field_ty = field.ty(cx.tcx, impl_trait_substs); |
70 | | - |
71 | | - if raw_pointer_in_ty_def(cx, field_ty) |
72 | | - || implements_trait(cx, field_ty, send_trait, &[]) |
73 | | - || is_copy(cx, field_ty) |
74 | | - { |
75 | | - continue; |
| 75 | + if_chain! { |
| 76 | + if let Some(field_hir_id) = field |
| 77 | + .did |
| 78 | + .as_local() |
| 79 | + .map(|local_def_id| hir_map.local_def_id_to_hir_id(local_def_id)); |
| 80 | + if !is_lint_allowed(cx, NON_SEND_FIELD_IN_SEND_TY, field_hir_id); |
| 81 | + if let field_ty = field.ty(cx.tcx, impl_trait_substs); |
| 82 | + if !ty_allowed_in_send(cx, field_ty, send_trait); |
| 83 | + if let Some(field_span) = hir_map.span_if_local(field.did); |
| 84 | + then { |
| 85 | + non_send_fields.push(NonSendField { |
| 86 | + name: hir_map.name(field_hir_id), |
| 87 | + span: field_span, |
| 88 | + ty: field_ty, |
| 89 | + generic_params: collect_generic_params(cx, field_ty), |
| 90 | + }) |
| 91 | + } |
76 | 92 | } |
| 93 | + } |
| 94 | + } |
77 | 95 |
|
78 | | - if let Some(field_hir_id) = field |
79 | | - .did |
80 | | - .as_local() |
81 | | - .map(|local_def_id| cx.tcx.hir().local_def_id_to_hir_id(local_def_id)) |
82 | | - { |
83 | | - if let Some(field_span) = cx.tcx.hir().span_if_local(field.did) { |
84 | | - span_lint_hir_and_then( |
85 | | - cx, |
86 | | - NON_SEND_FIELD_IN_SEND_TY, |
87 | | - field_hir_id, |
88 | | - field_span, |
89 | | - "non-`Send` field found in a `Send` struct", |
90 | | - |diag| { |
91 | | - diag.span_note( |
92 | | - item.span, |
93 | | - &format!( |
94 | | - "type `{}` doesn't implement `Send` when `{}` is `Send`", |
95 | | - field_ty, self_ty |
96 | | - ), |
97 | | - ); |
98 | | - if is_ty_param(field_ty) { |
99 | | - diag.help(&format!("add `{}: Send` bound", field_ty)); |
100 | | - } |
101 | | - }, |
| 96 | + if !non_send_fields.is_empty() { |
| 97 | + span_lint_and_then( |
| 98 | + cx, |
| 99 | + NON_SEND_FIELD_IN_SEND_TY, |
| 100 | + item.span, |
| 101 | + &format!( |
| 102 | + "this implementation is unsound, as some fields in `{}` are `!Send`", |
| 103 | + self_ty |
| 104 | + ), |
| 105 | + |diag| { |
| 106 | + for field in non_send_fields { |
| 107 | + diag.span_note( |
| 108 | + field.span, |
| 109 | + &format!("the field `{}` has type `{}` which is not `Send`", field.name, field.ty), |
102 | 110 | ); |
| 111 | + |
| 112 | + match field.generic_params.len() { |
| 113 | + 0 => diag.help("use a thread-safe type that implements `Send`"), |
| 114 | + 1 if is_ty_param(field.ty) => diag.help(&format!("add `{}: Send` bound in `Send` impl", field.ty)), |
| 115 | + _ => diag.help(&format!( |
| 116 | + "add bounds on type parameter{} `{}` that satisfy `{}: Send`", |
| 117 | + if field.generic_params.len() > 1 { "s" } else { "" }, |
| 118 | + field.generic_params_string(), |
| 119 | + field.ty |
| 120 | + )), |
| 121 | + }; |
103 | 122 | } |
104 | | - } |
105 | | - } |
| 123 | + }, |
| 124 | + ) |
106 | 125 | } |
107 | 126 | } |
108 | 127 | } |
109 | 128 | } |
110 | 129 | } |
111 | 130 |
|
| 131 | +struct NonSendField<'tcx> { |
| 132 | + name: Symbol, |
| 133 | + span: Span, |
| 134 | + ty: Ty<'tcx>, |
| 135 | + generic_params: Vec<Ty<'tcx>>, |
| 136 | +} |
| 137 | + |
| 138 | +impl<'tcx> NonSendField<'tcx> { |
| 139 | + fn generic_params_string(&self) -> String { |
| 140 | + self.generic_params |
| 141 | + .iter() |
| 142 | + .map(ToString::to_string) |
| 143 | + .collect::<Vec<_>>() |
| 144 | + .join(", ") |
| 145 | + } |
| 146 | +} |
| 147 | + |
| 148 | +fn collect_generic_params<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Vec<Ty<'tcx>> { |
| 149 | + ty.walk(cx.tcx) |
| 150 | + .filter_map(|inner| match inner.unpack() { |
| 151 | + GenericArgKind::Type(inner_ty) => Some(inner_ty), |
| 152 | + _ => None, |
| 153 | + }) |
| 154 | + .filter(|&inner_ty| is_ty_param(inner_ty)) |
| 155 | + .collect() |
| 156 | +} |
| 157 | + |
| 158 | +fn ty_allowed_in_send<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, send_trait: DefId) -> bool { |
| 159 | + raw_pointer_in_ty_def(cx, ty) || implements_trait(cx, ty, send_trait, &[]) || is_copy(cx, ty) |
| 160 | +} |
| 161 | + |
112 | 162 | /// Returns `true` if the type itself is a raw pointer or has a raw pointer as a |
113 | 163 | /// generic parameter, e.g., `Vec<*const u8>`. |
114 | 164 | /// Note that it does not look into enum variants or struct fields. |
|
0 commit comments