|
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