]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/len_zero.rs
ast/hir: Rename field-related structures
[rust.git] / clippy_lints / src / len_zero.rs
index aa538a4be9de049ba35d8143405ab947fab3f71a..1e1023b2743502e8d732eaf825e8c09f6414e9ec 100644 (file)
@@ -1,13 +1,19 @@
-use crate::utils::{get_item_name, snippet_with_applicability, span_lint, span_lint_and_sugg, walk_ptrs_ty};
-use rustc::ty;
+use crate::utils::{
+    get_item_name, get_parent_as_impl, is_allowed, snippet_with_applicability, span_lint, span_lint_and_sugg,
+    span_lint_and_then,
+};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::Applicability;
-use rustc_hir::def_id::DefId;
-use rustc_hir::*;
+use rustc_hir::{
+    def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item,
+    ItemKind, Mutability, Node, TraitItemRef, TyKind,
+};
 use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, AssocKind, FnSig};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
-use rustc_span::source_map::{Span, Spanned};
-use syntax::ast::{LitKind, Name};
+use rustc_span::source_map::{Span, Spanned, Symbol};
 
 declare_clippy_lint! {
     /// **What it does:** Checks for getting the length of something via `.len()`
     "traits or impls with a public `len` method but no corresponding `is_empty` method"
 }
 
-declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY]);
+declare_clippy_lint! {
+    /// **What it does:** Checks for comparing to an empty slice such as `""` or `[]`,
+    /// and suggests using `.is_empty()` where applicable.
+    ///
+    /// **Why is this bad?** Some structures can answer `.is_empty()` much faster
+    /// than checking for equality. So it is good to get into the habit of using
+    /// `.is_empty()`, and having it is cheap.
+    /// Besides, it makes the intent clearer than a manual comparison in some contexts.
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Example:**
+    ///
+    /// ```ignore
+    /// if s == "" {
+    ///     ..
+    /// }
+    ///
+    /// if arr == [] {
+    ///     ..
+    /// }
+    /// ```
+    /// Use instead:
+    /// ```ignore
+    /// if s.is_empty() {
+    ///     ..
+    /// }
+    ///
+    /// if arr.is_empty() {
+    ///     ..
+    /// }
+    /// ```
+    pub COMPARISON_TO_EMPTY,
+    style,
+    "checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead"
+}
 
-impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LenZero {
-    fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) {
+declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]);
+
+impl<'tcx> LateLintPass<'tcx> for LenZero {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
         if item.span.from_expansion() {
             return;
         }
 
-        match item.kind {
-            ItemKind::Trait(_, _, _, _, ref trait_items) => check_trait_items(cx, item, trait_items),
-            ItemKind::Impl {
-                of_trait: None,
-                items: ref impl_items,
-                ..
-            } => check_impl_items(cx, item, impl_items),
-            _ => (),
+        if let ItemKind::Trait(_, _, _, _, ref trait_items) = item.kind {
+            check_trait_items(cx, item, trait_items);
+        }
+    }
+
+    fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+        if_chain! {
+            if item.ident.as_str() == "len";
+            if let ImplItemKind::Fn(sig, _) = &item.kind;
+            if sig.decl.implicit_self.has_implicit_self();
+            if cx.access_levels.is_exported(item.hir_id());
+            if matches!(sig.decl.output, FnRetTy::Return(_));
+            if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id());
+            if imp.of_trait.is_none();
+            if let TyKind::Path(ty_path) = &imp.self_ty.kind;
+            if let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id();
+            if let Some(local_id) = ty_id.as_local();
+            let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id);
+            if !is_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id);
+            then {
+                let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
+                    Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
+                    Some(Node::Item(x)) => match x.kind {
+                        ItemKind::Struct(..) => (x.ident.name, "struct"),
+                        ItemKind::Enum(..) => (x.ident.name, "enum"),
+                        ItemKind::Union(..) => (x.ident.name, "union"),
+                        _ => (x.ident.name, "type"),
+                    }
+                    _ => return,
+                };
+                check_for_is_empty(cx, sig.span, sig.decl.implicit_self, ty_id, name, kind)
+            }
         }
     }
 
-    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
         if expr.span.from_expansion() {
             return;
         }
@@ -118,39 +185,35 @@ fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
     }
 }
 
-fn check_trait_items(cx: &LateContext<'_, '_>, visited_trait: &Item<'_>, trait_items: &[TraitItemRef]) {
-    fn is_named_self(cx: &LateContext<'_, '_>, item: &TraitItemRef, name: &str) -> bool {
+fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items: &[TraitItemRef]) {
+    fn is_named_self(cx: &LateContext<'_>, item: &TraitItemRef, name: &str) -> bool {
         item.ident.name.as_str() == name
-            && if let AssocItemKind::Method { has_self } = item.kind {
-                has_self && {
-                    let did = cx.tcx.hir().local_def_id(item.id.hir_id);
-                    cx.tcx.fn_sig(did).inputs().skip_binder().len() == 1
-                }
+            && if let AssocItemKind::Fn { has_self } = item.kind {
+                has_self && { cx.tcx.fn_sig(item.id.def_id).inputs().skip_binder().len() == 1 }
             } else {
                 false
             }
     }
 
     // fill the set with current and super traits
-    fn fill_trait_set(traitt: DefId, set: &mut FxHashSet<DefId>, cx: &LateContext<'_, '_>) {
+    fn fill_trait_set(traitt: DefId, set: &mut FxHashSet<DefId>, cx: &LateContext<'_>) {
         if set.insert(traitt) {
-            for supertrait in rustc_infer::traits::supertrait_def_ids(cx.tcx, traitt) {
+            for supertrait in rustc_trait_selection::traits::supertrait_def_ids(cx.tcx, traitt) {
                 fill_trait_set(supertrait, set, cx);
             }
         }
     }
 
-    if cx.access_levels.is_exported(visited_trait.hir_id) && trait_items.iter().any(|i| is_named_self(cx, i, "len")) {
+    if cx.access_levels.is_exported(visited_trait.hir_id()) && trait_items.iter().any(|i| is_named_self(cx, i, "len")) {
         let mut current_and_super_traits = FxHashSet::default();
-        let visited_trait_def_id = cx.tcx.hir().local_def_id(visited_trait.hir_id);
-        fill_trait_set(visited_trait_def_id, &mut current_and_super_traits, cx);
+        fill_trait_set(visited_trait.def_id.to_def_id(), &mut current_and_super_traits, cx);
 
         let is_empty_method_found = current_and_super_traits
             .iter()
             .flat_map(|&i| cx.tcx.associated_items(i).in_definition_order())
             .any(|i| {
-                i.kind == ty::AssocKind::Method
-                    && i.method_has_self_argument
+                i.kind == ty::AssocKind::Fn
+                    && i.fn_has_self_parameter
                     && i.ident.name == sym!(is_empty)
                     && cx.tcx.fn_sig(i.def_id).inputs().skip_binder().len() == 1
             });
@@ -169,49 +232,99 @@ fn fill_trait_set(traitt: DefId, set: &mut FxHashSet<DefId>, cx: &LateContext<'_
     }
 }
 
-fn check_impl_items(cx: &LateContext<'_, '_>, item: &Item<'_>, impl_items: &[ImplItemRef<'_>]) {
-    fn is_named_self(cx: &LateContext<'_, '_>, item: &ImplItemRef<'_>, name: &str) -> bool {
-        item.ident.name.as_str() == name
-            && if let AssocItemKind::Method { has_self } = item.kind {
-                has_self && {
-                    let did = cx.tcx.hir().local_def_id(item.id.hir_id);
-                    cx.tcx.fn_sig(did).inputs().skip_binder().len() == 1
-                }
-            } else {
-                false
-            }
+/// Checks if the given signature matches the expectations for `is_empty`
+fn check_is_empty_sig(cx: &LateContext<'_>, sig: FnSig<'_>, self_kind: ImplicitSelfKind) -> bool {
+    match &**sig.inputs_and_output {
+        [arg, res] if *res == cx.tcx.types.bool => {
+            matches!(
+                (arg.kind(), self_kind),
+                (ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef)
+                    | (ty::Ref(_, _, Mutability::Mut), ImplicitSelfKind::MutRef)
+            ) || (!arg.is_ref() && matches!(self_kind, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut))
+        },
+        _ => false,
     }
+}
 
-    let is_empty = if let Some(is_empty) = impl_items.iter().find(|i| is_named_self(cx, i, "is_empty")) {
-        if cx.access_levels.is_exported(is_empty.id.hir_id) {
-            return;
-        } else {
-            "a private"
-        }
-    } else {
-        "no corresponding"
-    };
-
-    if let Some(i) = impl_items.iter().find(|i| is_named_self(cx, i, "len")) {
-        if cx.access_levels.is_exported(i.id.hir_id) {
-            let def_id = cx.tcx.hir().local_def_id(item.hir_id);
-            let ty = cx.tcx.type_of(def_id);
+/// Checks if the given type has an `is_empty` method with the appropriate signature.
+fn check_for_is_empty(
+    cx: &LateContext<'_>,
+    span: Span,
+    self_kind: ImplicitSelfKind,
+    impl_ty: DefId,
+    item_name: Symbol,
+    item_kind: &str,
+) {
+    let is_empty = Symbol::intern("is_empty");
+    let is_empty = cx
+        .tcx
+        .inherent_impls(impl_ty)
+        .iter()
+        .flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty))
+        .find(|item| item.kind == AssocKind::Fn);
 
-            span_lint(
-                cx,
-                LEN_WITHOUT_IS_EMPTY,
-                item.span,
-                &format!(
-                    "item `{}` has a public `len` method but {} `is_empty` method",
-                    ty, is_empty
+    let (msg, is_empty_span, self_kind) = match is_empty {
+        None => (
+            format!(
+                "{} `{}` has a public `len` method, but no `is_empty` method",
+                item_kind,
+                item_name.as_str(),
+            ),
+            None,
+            None,
+        ),
+        Some(is_empty)
+            if !cx
+                .access_levels
+                .is_exported(cx.tcx.hir().local_def_id_to_hir_id(is_empty.def_id.expect_local())) =>
+        {
+            (
+                format!(
+                    "{} `{}` has a public `len` method, but a private `is_empty` method",
+                    item_kind,
+                    item_name.as_str(),
                 ),
-            );
+                Some(cx.tcx.def_span(is_empty.def_id)),
+                None,
+            )
+        },
+        Some(is_empty)
+            if !(is_empty.fn_has_self_parameter
+                && check_is_empty_sig(cx, cx.tcx.fn_sig(is_empty.def_id).skip_binder(), self_kind)) =>
+        {
+            (
+                format!(
+                    "{} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature",
+                    item_kind,
+                    item_name.as_str(),
+                ),
+                Some(cx.tcx.def_span(is_empty.def_id)),
+                Some(self_kind),
+            )
+        },
+        Some(_) => return,
+    };
+
+    span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, &msg, |db| {
+        if let Some(span) = is_empty_span {
+            db.span_note(span, "`is_empty` defined here");
         }
-    }
+        if let Some(self_kind) = self_kind {
+            db.note(&format!(
+                "expected signature: `({}self) -> bool`",
+                match self_kind {
+                    ImplicitSelfKind::ImmRef => "&",
+                    ImplicitSelfKind::MutRef => "&mut ",
+                    _ => "",
+                }
+            ));
+        }
+    });
 }
 
-fn check_cmp(cx: &LateContext<'_, '_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
-    if let (&ExprKind::MethodCall(ref method_path, _, ref args), &ExprKind::Lit(ref lit)) = (&method.kind, &lit.kind) {
+fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
+    if let (&ExprKind::MethodCall(ref method_path, _, ref args, _), &ExprKind::Lit(ref lit)) = (&method.kind, &lit.kind)
+    {
         // check if we are in an is_empty() method
         if let Some(name) = get_item_name(cx, method) {
             if name.as_str() == "is_empty" {
@@ -220,13 +333,15 @@ fn check_cmp(cx: &LateContext<'_, '_>, span: Span, method: &Expr<'_>, lit: &Expr
         }
 
         check_len(cx, span, method_path.ident.name, args, &lit.node, op, compare_to)
+    } else {
+        check_empty_expr(cx, span, method, lit, op)
     }
 }
 
 fn check_len(
-    cx: &LateContext<'_, '_>,
+    cx: &LateContext<'_>,
     span: Span,
-    method_name: Name,
+    method_name: Symbol,
     args: &[Expr<'_>],
     lit: &LitKind,
     op: &str,
@@ -257,11 +372,47 @@ fn check_len(
     }
 }
 
+fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) {
+    if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1) {
+        let mut applicability = Applicability::MachineApplicable;
+        span_lint_and_sugg(
+            cx,
+            COMPARISON_TO_EMPTY,
+            span,
+            "comparison to empty slice",
+            &format!("using `{}is_empty` is clearer and more explicit", op),
+            format!(
+                "{}{}.is_empty()",
+                op,
+                snippet_with_applicability(cx, lit1.span, "_", &mut applicability)
+            ),
+            applicability,
+        );
+    }
+}
+
+fn is_empty_string(expr: &Expr<'_>) -> bool {
+    if let ExprKind::Lit(ref lit) = expr.kind {
+        if let LitKind::Str(lit, _) = lit.node {
+            let lit = lit.as_str();
+            return lit == "";
+        }
+    }
+    false
+}
+
+fn is_empty_array(expr: &Expr<'_>) -> bool {
+    if let ExprKind::Array(ref arr) = expr.kind {
+        return arr.is_empty();
+    }
+    false
+}
+
 /// Checks if this type has an `is_empty` method.
-fn has_is_empty(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool {
+fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
     /// Gets an `AssocItem` and return true if it matches `is_empty(self)`.
-    fn is_is_empty(cx: &LateContext<'_, '_>, item: &ty::AssocItem) -> bool {
-        if let ty::AssocKind::Method = item.kind {
+    fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool {
+        if let ty::AssocKind::Fn = item.kind {
             if item.ident.name.as_str() == "is_empty" {
                 let sig = cx.tcx.fn_sig(item.def_id);
                 let ty = sig.skip_binder();
@@ -275,7 +426,7 @@ fn is_is_empty(cx: &LateContext<'_, '_>, item: &ty::AssocItem) -> bool {
     }
 
     /// Checks the inherent impl's items for an `is_empty(self)` method.
-    fn has_is_empty_impl(cx: &LateContext<'_, '_>, id: DefId) -> bool {
+    fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool {
         cx.tcx.inherent_impls(id).iter().any(|imp| {
             cx.tcx
                 .associated_items(*imp)
@@ -284,18 +435,14 @@ fn has_is_empty_impl(cx: &LateContext<'_, '_>, id: DefId) -> bool {
         })
     }
 
-    let ty = &walk_ptrs_ty(cx.tables.expr_ty(expr));
-    match ty.kind {
-        ty::Dynamic(ref tt, ..) => {
-            if let Some(principal) = tt.principal() {
-                cx.tcx
-                    .associated_items(principal.def_id())
-                    .in_definition_order()
-                    .any(|item| is_is_empty(cx, &item))
-            } else {
-                false
-            }
-        },
+    let ty = &cx.typeck_results().expr_ty(expr).peel_refs();
+    match ty.kind() {
+        ty::Dynamic(ref tt, ..) => tt.principal().map_or(false, |principal| {
+            cx.tcx
+                .associated_items(principal.def_id())
+                .in_definition_order()
+                .any(|item| is_is_empty(cx, &item))
+        }),
         ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id),
         ty::Adt(id, _) => has_is_empty_impl(cx, id.did),
         ty::Array(..) | ty::Slice(..) | ty::Str => true,