]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/utils/mod.rs
Apply suggestions from code review
[rust.git] / clippy_lints / src / utils / mod.rs
index 8297b9d128dde9b863c03e62b36c61f779cce849..e83371f8b99a4c3007641b1661fbc6745e318e6b 100644 (file)
@@ -14,6 +14,7 @@
 pub mod higher;
 mod hir_utils;
 pub mod inspector;
+#[cfg(feature = "internal-lints")]
 pub mod internal_lints;
 pub mod numeric_literal;
 pub mod paths;
 pub mod qualify_min_const_fn;
 pub mod sugg;
 pub mod usage;
+pub mod visitors;
 
 pub use self::attrs::*;
 pub use self::diagnostics::*;
 pub use self::hir_utils::{both, eq_expr_value, over, SpanlessEq, SpanlessHash};
 
 use std::borrow::Cow;
+use std::collections::hash_map::Entry;
+use std::hash::BuildHasherDefault;
 use std::mem;
 
 use if_chain::if_chain;
 use rustc_ast::ast::{self, Attribute, LitKind};
 use rustc_attr as attr;
+use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
-use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
+use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
 use rustc_hir::Node;
 use rustc_hir::{
     def, Arm, Block, Body, Constness, Crate, Expr, ExprKind, FnDecl, HirId, ImplItem, ImplItemKind, Item, ItemKind,
 use rustc_middle::hir::map::Map;
 use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
 use rustc_middle::ty::{self, layout::IntegerExt, Ty, TyCtxt, TypeFoldable};
+use rustc_semver::RustcVersion;
+use rustc_session::Session;
 use rustc_span::hygiene::{ExpnKind, MacroKind};
 use rustc_span::source_map::original_sp;
+use rustc_span::sym as rustc_sym;
 use rustc_span::symbol::{self, kw, Symbol};
 use rustc_span::{BytePos, Pos, Span, DUMMY_SP};
 use rustc_target::abi::Integer;
 
 use crate::consts::{constant, Constant};
 
+pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
+    if let Ok(version) = RustcVersion::parse(msrv) {
+        return Some(version);
+    } else if let Some(sess) = sess {
+        if let Some(span) = span {
+            sess.span_err(span, &format!("`{}` is not a valid Rust version", msrv));
+        }
+    }
+    None
+}
+
+pub fn meets_msrv(msrv: Option<&RustcVersion>, lint_msrv: &RustcVersion) -> bool {
+    msrv.map_or(true, |msrv| msrv.meets(*lint_msrv))
+}
+
+macro_rules! extract_msrv_attr {
+    (LateContext) => {
+        extract_msrv_attr!(@LateContext, ());
+    };
+    (EarlyContext) => {
+        extract_msrv_attr!(@EarlyContext);
+    };
+    (@$context:ident$(, $call:tt)?) => {
+        fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'tcx>, attrs: &'tcx [rustc_ast::ast::Attribute]) {
+            use $crate::utils::get_unique_inner_attr;
+            match get_unique_inner_attr(cx.sess$($call)?, attrs, "msrv") {
+                Some(msrv_attr) => {
+                    if let Some(msrv) = msrv_attr.value_str() {
+                        self.msrv = $crate::utils::parse_msrv(
+                            &msrv.to_string(),
+                            Some(cx.sess$($call)?),
+                            Some(msrv_attr.span),
+                        );
+                    } else {
+                        cx.sess$($call)?.span_err(msrv_attr.span, "bad clippy attribute");
+                    }
+                },
+                _ => (),
+            }
+        }
+    };
+}
+
 /// Returns `true` if the two spans come from differing expansions (i.e., one is
 /// from a macro and one isn't).
 #[must_use]
@@ -268,6 +319,7 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> {
             krate: *krate,
             index: CRATE_DEF_INDEX,
         };
+        let mut current_item = None;
         let mut items = cx.tcx.item_children(krate);
         let mut path_it = path.iter().skip(1).peekable();
 
@@ -277,6 +329,12 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> {
                 None => return None,
             };
 
+            // `get_def_path` seems to generate these empty segments for extern blocks.
+            // We can just ignore them.
+            if segment.is_empty() {
+                continue;
+            }
+
             let result = SmallVec::<[_; 8]>::new();
             for item in mem::replace(&mut items, cx.tcx.arena.alloc_slice(&result)).iter() {
                 if item.ident.name.as_str() == *segment {
@@ -284,10 +342,28 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> {
                         return Some(item.res);
                     }
 
+                    current_item = Some(item);
                     items = cx.tcx.item_children(item.res.def_id());
                     break;
                 }
             }
+
+            // The segment isn't a child_item.
+            // Try to find it under an inherent impl.
+            if_chain! {
+                if path_it.peek().is_none();
+                if let Some(current_item) = current_item;
+                let item_def_id = current_item.res.def_id();
+                if cx.tcx.def_kind(item_def_id) == DefKind::Struct;
+                then {
+                    // Bad `find_map` suggestion. See #4193.
+                    #[allow(clippy::find_map)]
+                    return cx.tcx.inherent_impls(item_def_id).iter()
+                        .flat_map(|&impl_def_id| cx.tcx.item_children(impl_def_id))
+                        .find(|item| item.ident.name.as_str() == *segment)
+                        .map(|item| item.res);
+                }
+            }
         }
     } else {
         None
@@ -334,7 +410,10 @@ pub fn implements_trait<'tcx>(
     if ty.has_infer_types() {
         return false;
     }
-    let ty = cx.tcx.erase_regions(&ty);
+    let ty = cx.tcx.erase_regions(ty);
+    if ty.has_escaping_bound_vars() {
+        return false;
+    }
     let ty_params = cx.tcx.mk_substs(ty_params.iter());
     cx.tcx.type_implements_trait((trait_id, ty, ty_params, cx.param_env))
 }
@@ -439,6 +518,13 @@ pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool {
         .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id.to_def_id())
 }
 
+/// Returns `true` if the expression is in the program's `#[panic_handler]`.
+pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+    let parent = cx.tcx.hir().get_parent_item(e.hir_id);
+    let def_id = cx.tcx.hir().local_def_id(parent).to_def_id();
+    Some(def_id) == cx.tcx.lang_items().panic_impl()
+}
+
 /// Gets the name of the item the expression is in, if available.
 pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
     let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id);
@@ -487,6 +573,67 @@ pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool {
     cn.result
 }
 
+/// Returns `true` if `expr` contains a return expression
+pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
+    struct RetCallFinder {
+        found: bool,
+    }
+
+    impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder {
+        type Map = Map<'tcx>;
+
+        fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+            if self.found {
+                return;
+            }
+            if let hir::ExprKind::Ret(..) = &expr.kind {
+                self.found = true;
+            } else {
+                hir::intravisit::walk_expr(self, expr);
+            }
+        }
+
+        fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<Self::Map> {
+            hir::intravisit::NestedVisitorMap::None
+        }
+    }
+
+    let mut visitor = RetCallFinder { found: false };
+    visitor.visit_expr(expr);
+    visitor.found
+}
+
+struct FindMacroCalls<'a, 'b> {
+    names: &'a [&'b str],
+    result: Vec<Span>,
+}
+
+impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> {
+    type Map = Map<'tcx>;
+
+    fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+        if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
+            self.result.push(expr.span);
+        }
+        // and check sub-expressions
+        intravisit::walk_expr(self, expr);
+    }
+
+    fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+        NestedVisitorMap::None
+    }
+}
+
+/// Finds calls of the specified macros in a function body.
+pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> {
+    let mut fmc = FindMacroCalls {
+        names,
+        result: Vec::new(),
+    };
+    fmc.visit_expr(&body.value);
+    fmc.result
+}
+
 /// Converts a span to a code snippet if available, otherwise use default.
 ///
 /// This is useful if you want to provide suggestions for your lint or more generally, if you want
@@ -630,6 +777,35 @@ pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
     snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
 }
 
+/// Returns the positon just before rarrow
+///
+/// ```rust,ignore
+/// fn into(self) -> () {}
+///              ^
+/// // in case of unformatted code
+/// fn into2(self)-> () {}
+///               ^
+/// fn into3(self)   -> () {}
+///               ^
+/// ```
+#[allow(clippy::needless_pass_by_value)]
+pub fn position_before_rarrow(s: String) -> Option<usize> {
+    s.rfind("->").map(|rpos| {
+        let mut rpos = rpos;
+        let chars: Vec<char> = s.chars().collect();
+        while rpos > 1 {
+            if let Some(c) = chars.get(rpos - 1) {
+                if c.is_whitespace() {
+                    rpos -= 1;
+                    continue;
+                }
+            }
+            break;
+        }
+        rpos
+    })
+}
+
 /// Extends the span to the beginning of the spans line, incl. whitespaces.
 ///
 /// ```rust,ignore
@@ -858,7 +1034,7 @@ pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
 pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId) -> Ty<'tcx> {
     let fn_def_id = cx.tcx.hir().local_def_id(fn_item);
     let ret_ty = cx.tcx.fn_sig(fn_def_id).output();
-    cx.tcx.erase_late_bound_regions(&ret_ty)
+    cx.tcx.erase_late_bound_regions(ret_ty)
 }
 
 /// Walks into `ty` and returns `true` if any inner type is the same as `other_ty`
@@ -946,7 +1122,7 @@ fn are_refutable<'a, I: Iterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, mut
 /// Checks for the `#[automatically_derived]` attribute all `#[derive]`d
 /// implementations have.
 pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool {
-    attrs.iter().any(|attr| attr.has_name(sym!(automatically_derived)))
+    attrs.iter().any(|attr| attr.has_name(rustc_sym::automatically_derived))
 }
 
 /// Remove blocks around an expression.
@@ -1167,7 +1343,7 @@ pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<
 /// Usage:
 ///
 /// ```rust,ignore
-/// if let Some(args) = match_function_call(cx, begin_panic_call, &paths::BEGIN_PANIC);
+/// if let Some(args) = match_function_call(cx, cmp_max_call, &paths::CMP_MAX);
 /// ```
 pub fn match_function_call<'tcx>(
     cx: &LateContext<'tcx>,
@@ -1191,7 +1367,7 @@ pub fn match_function_call<'tcx>(
 pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool {
     cx.tcx.infer_ctxt().enter(|infcx| {
         let cause = rustc_middle::traits::ObligationCause::dummy();
-        infcx.at(&cause, param_env).normalize(&ty).is_ok()
+        infcx.at(&cause, param_env).normalize(ty).is_ok()
     })
 }
 
@@ -1202,6 +1378,24 @@ pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -
     cx.match_def_path(did, &syms)
 }
 
+pub fn match_panic_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx [Expr<'tcx>]> {
+    match_function_call(cx, expr, &paths::BEGIN_PANIC)
+        .or_else(|| match_function_call(cx, expr, &paths::BEGIN_PANIC_FMT))
+        .or_else(|| match_function_call(cx, expr, &paths::PANIC_ANY))
+        .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC))
+        .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_FMT))
+        .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_STR))
+}
+
+pub fn match_panic_def_id(cx: &LateContext<'_>, did: DefId) -> bool {
+    match_def_path(cx, did, &paths::BEGIN_PANIC)
+        || match_def_path(cx, did, &paths::BEGIN_PANIC_FMT)
+        || match_def_path(cx, did, &paths::PANIC_ANY)
+        || match_def_path(cx, did, &paths::PANICKING_PANIC)
+        || match_def_path(cx, did, &paths::PANICKING_PANIC_FMT)
+        || match_def_path(cx, did, &paths::PANICKING_PANIC_STR)
+}
+
 /// Returns the list of condition expressions and the list of blocks in a
 /// sequence of `if/else`.
 /// E.g., this returns `([a, b], [c, d, e])` for the expression
@@ -1320,7 +1514,7 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
 
 pub fn is_no_std_crate(krate: &Crate<'_>) -> bool {
     krate.item.attrs.iter().any(|attr| {
-        if let ast::AttrKind::Normal(ref attr) = attr.kind {
+        if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
             attr.path == symbol::sym::no_std
         } else {
             false
@@ -1337,7 +1531,7 @@ pub fn is_no_std_crate(krate: &Crate<'_>) -> bool {
 /// ```
 pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool {
     if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
-        matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), .. })
+        matches!(item.kind, ItemKind::Impl { of_trait: Some(_), .. })
     } else {
         false
     }
@@ -1440,6 +1634,41 @@ pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<S
     None
 }
 
+/// returns list of all pairs (a, b) from `exprs` such that `eq(a, b)`
+/// `hash` must be comformed with `eq`
+pub fn search_same<T, Hash, Eq>(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)>
+where
+    Hash: Fn(&T) -> u64,
+    Eq: Fn(&T, &T) -> bool,
+{
+    if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) {
+        return vec![(&exprs[0], &exprs[1])];
+    }
+
+    let mut match_expr_list: Vec<(&T, &T)> = Vec::new();
+
+    let mut map: FxHashMap<_, Vec<&_>> =
+        FxHashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default());
+
+    for expr in exprs {
+        match map.entry(hash(expr)) {
+            Entry::Occupied(mut o) => {
+                for o in o.get() {
+                    if eq(o, expr) {
+                        match_expr_list.push((o, expr));
+                    }
+                }
+                o.get_mut().push(expr);
+            },
+            Entry::Vacant(v) => {
+                v.insert(vec![expr]);
+            },
+        }
+    }
+
+    match_expr_list
+}
+
 #[macro_export]
 macro_rules! unwrap_cargo_metadata {
     ($cx: ident, $lint: ident, $deps: expr) => {{