|
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