|
1 |
| -use crate::utils::{get_item_name, snippet_with_applicability, span_lint, span_lint_and_sugg}; |
| 1 | +use crate::utils::{ |
| 2 | + get_item_name, get_parent_as_impl, is_allowed, snippet_with_applicability, span_lint, span_lint_and_sugg, |
| 3 | + span_lint_and_then, |
| 4 | +}; |
| 5 | +use if_chain::if_chain; |
2 | 6 | use rustc_ast::ast::LitKind;
|
3 | 7 | use rustc_data_structures::fx::FxHashSet;
|
4 | 8 | use rustc_errors::Applicability;
|
5 |
| -use rustc_hir::def_id::DefId; |
6 |
| -use rustc_hir::{AssocItemKind, BinOpKind, Expr, ExprKind, Impl, ImplItemRef, Item, ItemKind, TraitItemRef}; |
| 9 | +use rustc_hir::{ |
| 10 | + def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item, |
| 11 | + ItemKind, Mutability, Node, TraitItemRef, TyKind, |
| 12 | +}; |
7 | 13 | use rustc_lint::{LateContext, LateLintPass};
|
8 |
| -use rustc_middle::ty; |
| 14 | +use rustc_middle::ty::{self, AssocKind, FnSig}; |
9 | 15 | use rustc_session::{declare_lint_pass, declare_tool_lint};
|
10 | 16 | use rustc_span::source_map::{Span, Spanned, Symbol};
|
11 | 17 |
|
@@ -113,14 +119,38 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
|
113 | 119 | return;
|
114 | 120 | }
|
115 | 121 |
|
116 |
| - match item.kind { |
117 |
| - ItemKind::Trait(_, _, _, _, ref trait_items) => check_trait_items(cx, item, trait_items), |
118 |
| - ItemKind::Impl(Impl { |
119 |
| - of_trait: None, |
120 |
| - items: ref impl_items, |
121 |
| - .. |
122 |
| - }) => check_impl_items(cx, item, impl_items), |
123 |
| - _ => (), |
| 122 | + if let ItemKind::Trait(_, _, _, _, ref trait_items) = item.kind { |
| 123 | + check_trait_items(cx, item, trait_items); |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { |
| 128 | + if_chain! { |
| 129 | + if item.ident.as_str() == "len"; |
| 130 | + if let ImplItemKind::Fn(sig, _) = &item.kind; |
| 131 | + if sig.decl.implicit_self.has_implicit_self(); |
| 132 | + if cx.access_levels.is_exported(item.hir_id()); |
| 133 | + if matches!(sig.decl.output, FnRetTy::Return(_)); |
| 134 | + if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id()); |
| 135 | + if imp.of_trait.is_none(); |
| 136 | + if let TyKind::Path(ty_path) = &imp.self_ty.kind; |
| 137 | + if let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id(); |
| 138 | + if let Some(local_id) = ty_id.as_local(); |
| 139 | + let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id); |
| 140 | + if !is_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id); |
| 141 | + then { |
| 142 | + let (name, kind) = match cx.tcx.hir().find(ty_hir_id) { |
| 143 | + Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"), |
| 144 | + Some(Node::Item(x)) => match x.kind { |
| 145 | + ItemKind::Struct(..) => (x.ident.name, "struct"), |
| 146 | + ItemKind::Enum(..) => (x.ident.name, "enum"), |
| 147 | + ItemKind::Union(..) => (x.ident.name, "union"), |
| 148 | + _ => (x.ident.name, "type"), |
| 149 | + } |
| 150 | + _ => return, |
| 151 | + }; |
| 152 | + check_for_is_empty(cx, sig.span, sig.decl.implicit_self, ty_id, name, kind) |
| 153 | + } |
124 | 154 | }
|
125 | 155 | }
|
126 | 156 |
|
@@ -202,40 +232,94 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items
|
202 | 232 | }
|
203 | 233 | }
|
204 | 234 |
|
205 |
| -fn check_impl_items(cx: &LateContext<'_>, item: &Item<'_>, impl_items: &[ImplItemRef<'_>]) { |
206 |
| - fn is_named_self(cx: &LateContext<'_>, item: &ImplItemRef<'_>, name: &str) -> bool { |
207 |
| - item.ident.name.as_str() == name |
208 |
| - && if let AssocItemKind::Fn { has_self } = item.kind { |
209 |
| - has_self && cx.tcx.fn_sig(item.id.def_id).inputs().skip_binder().len() == 1 |
210 |
| - } else { |
211 |
| - false |
212 |
| - } |
| 235 | +/// Checks if the given signature matches the expectations for `is_empty` |
| 236 | +fn check_is_empty_sig(cx: &LateContext<'_>, sig: FnSig<'_>, self_kind: ImplicitSelfKind) -> bool { |
| 237 | + match &**sig.inputs_and_output { |
| 238 | + [arg, res] if *res == cx.tcx.types.bool => { |
| 239 | + matches!( |
| 240 | + (arg.kind(), self_kind), |
| 241 | + (ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef) |
| 242 | + | (ty::Ref(_, _, Mutability::Mut), ImplicitSelfKind::MutRef) |
| 243 | + ) || (!arg.is_ref() && matches!(self_kind, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut)) |
| 244 | + }, |
| 245 | + _ => false, |
213 | 246 | }
|
| 247 | +} |
214 | 248 |
|
215 |
| - let is_empty = if let Some(is_empty) = impl_items.iter().find(|i| is_named_self(cx, i, "is_empty")) { |
216 |
| - if cx.access_levels.is_exported(is_empty.id.hir_id()) { |
217 |
| - return; |
218 |
| - } |
219 |
| - "a private" |
220 |
| - } else { |
221 |
| - "no corresponding" |
222 |
| - }; |
223 |
| - |
224 |
| - if let Some(i) = impl_items.iter().find(|i| is_named_self(cx, i, "len")) { |
225 |
| - if cx.access_levels.is_exported(i.id.hir_id()) { |
226 |
| - let ty = cx.tcx.type_of(item.def_id); |
| 249 | +/// Checks if the given type has an `is_empty` method with the appropriate signature. |
| 250 | +fn check_for_is_empty( |
| 251 | + cx: &LateContext<'_>, |
| 252 | + span: Span, |
| 253 | + self_kind: ImplicitSelfKind, |
| 254 | + impl_ty: DefId, |
| 255 | + item_name: Symbol, |
| 256 | + item_kind: &str, |
| 257 | +) { |
| 258 | + let is_empty = Symbol::intern("is_empty"); |
| 259 | + let is_empty = cx |
| 260 | + .tcx |
| 261 | + .inherent_impls(impl_ty) |
| 262 | + .iter() |
| 263 | + .flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty)) |
| 264 | + .find(|item| item.kind == AssocKind::Fn); |
227 | 265 |
|
228 |
| - span_lint( |
229 |
| - cx, |
230 |
| - LEN_WITHOUT_IS_EMPTY, |
231 |
| - item.span, |
232 |
| - &format!( |
233 |
| - "item `{}` has a public `len` method but {} `is_empty` method", |
234 |
| - ty, is_empty |
| 266 | + let (msg, is_empty_span, self_kind) = match is_empty { |
| 267 | + None => ( |
| 268 | + format!( |
| 269 | + "{} `{}` has a public `len` method, but no `is_empty` method", |
| 270 | + item_kind, |
| 271 | + item_name.as_str(), |
| 272 | + ), |
| 273 | + None, |
| 274 | + None, |
| 275 | + ), |
| 276 | + Some(is_empty) |
| 277 | + if !cx |
| 278 | + .access_levels |
| 279 | + .is_exported(cx.tcx.hir().local_def_id_to_hir_id(is_empty.def_id.expect_local())) => |
| 280 | + { |
| 281 | + ( |
| 282 | + format!( |
| 283 | + "{} `{}` has a public `len` method, but a private `is_empty` method", |
| 284 | + item_kind, |
| 285 | + item_name.as_str(), |
235 | 286 | ),
|
236 |
| - ); |
| 287 | + Some(cx.tcx.def_span(is_empty.def_id)), |
| 288 | + None, |
| 289 | + ) |
| 290 | + }, |
| 291 | + Some(is_empty) |
| 292 | + if !(is_empty.fn_has_self_parameter |
| 293 | + && check_is_empty_sig(cx, cx.tcx.fn_sig(is_empty.def_id).skip_binder(), self_kind)) => |
| 294 | + { |
| 295 | + ( |
| 296 | + format!( |
| 297 | + "{} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature", |
| 298 | + item_kind, |
| 299 | + item_name.as_str(), |
| 300 | + ), |
| 301 | + Some(cx.tcx.def_span(is_empty.def_id)), |
| 302 | + Some(self_kind), |
| 303 | + ) |
| 304 | + }, |
| 305 | + Some(_) => return, |
| 306 | + }; |
| 307 | + |
| 308 | + span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, &msg, |db| { |
| 309 | + if let Some(span) = is_empty_span { |
| 310 | + db.span_note(span, "`is_empty` defined here"); |
237 | 311 | }
|
238 |
| - } |
| 312 | + if let Some(self_kind) = self_kind { |
| 313 | + db.note(&format!( |
| 314 | + "expected signature: `({}self) -> bool`", |
| 315 | + match self_kind { |
| 316 | + ImplicitSelfKind::ImmRef => "&", |
| 317 | + ImplicitSelfKind::MutRef => "&mut ", |
| 318 | + _ => "", |
| 319 | + } |
| 320 | + )); |
| 321 | + } |
| 322 | + }); |
239 | 323 | }
|
240 | 324 |
|
241 | 325 | fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
|
|
0 commit comments