about summary refs log tree commit diff
path: root/clippy_lints/src
diff options
context:
space:
mode:
Diffstat (limited to 'clippy_lints/src')
-rw-r--r--clippy_lints/src/lib.rs16
-rw-r--r--clippy_lints/src/loops.rs361
-rw-r--r--clippy_lints/src/manual_async_fn.rs159
-rw-r--r--clippy_lints/src/manual_non_exhaustive.rs173
-rw-r--r--clippy_lints/src/match_on_vec_items.rs23
-rw-r--r--clippy_lints/src/misc_early.rs21
-rw-r--r--clippy_lints/src/unwrap.rs21
-rw-r--r--clippy_lints/src/utils/conf.rs2
-rw-r--r--clippy_lints/src/utils/mod.rs34
-rw-r--r--clippy_lints/src/utils/paths.rs3
-rw-r--r--clippy_lints/src/wildcard_imports.rs74
11 files changed, 660 insertions, 227 deletions
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 1f135cba6e4..51b5401da7d 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -247,6 +247,8 @@ mod literal_representation;
 mod loops;
 mod macro_use;
 mod main_recursion;
+mod manual_async_fn;
+mod manual_non_exhaustive;
 mod map_clone;
 mod map_unit_fn;
 mod match_on_vec_items;
@@ -628,6 +630,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         &loops::WHILE_LET_ON_ITERATOR,
         &macro_use::MACRO_USE_IMPORTS,
         &main_recursion::MAIN_RECURSION,
+        &manual_async_fn::MANUAL_ASYNC_FN,
+        &manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
         &map_clone::MAP_CLONE,
         &map_unit_fn::OPTION_MAP_UNIT_FN,
         &map_unit_fn::RESULT_MAP_UNIT_FN,
@@ -1054,7 +1058,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     let max_struct_bools = conf.max_struct_bools;
     store.register_early_pass(move || box excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools));
     store.register_early_pass(|| box option_env_unwrap::OptionEnvUnwrap);
-    store.register_late_pass(|| box wildcard_imports::WildcardImports);
+    let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports;
+    store.register_late_pass(move || box wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports));
     store.register_early_pass(|| box macro_use::MacroUseImports);
     store.register_late_pass(|| box verbose_file_reads::VerboseFileReads);
     store.register_late_pass(|| box redundant_pub_crate::RedundantPubCrate::default());
@@ -1064,6 +1069,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls);
     store.register_late_pass(|| box if_let_mutex::IfLetMutex);
     store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems);
+    store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive);
+    store.register_late_pass(|| box manual_async_fn::ManualAsyncFn);
 
     store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
         LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1138,6 +1145,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&loops::EXPLICIT_INTO_ITER_LOOP),
         LintId::of(&loops::EXPLICIT_ITER_LOOP),
         LintId::of(&macro_use::MACRO_USE_IMPORTS),
+        LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS),
         LintId::of(&matches::MATCH_BOOL),
         LintId::of(&matches::SINGLE_MATCH_ELSE),
         LintId::of(&methods::FILTER_MAP),
@@ -1280,10 +1288,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&loops::WHILE_LET_LOOP),
         LintId::of(&loops::WHILE_LET_ON_ITERATOR),
         LintId::of(&main_recursion::MAIN_RECURSION),
+        LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
+        LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
         LintId::of(&map_clone::MAP_CLONE),
         LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN),
         LintId::of(&map_unit_fn::RESULT_MAP_UNIT_FN),
-        LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS),
         LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH),
         LintId::of(&matches::MATCH_AS_REF),
         LintId::of(&matches::MATCH_OVERLAPPING_ARM),
@@ -1474,6 +1483,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&loops::NEEDLESS_RANGE_LOOP),
         LintId::of(&loops::WHILE_LET_ON_ITERATOR),
         LintId::of(&main_recursion::MAIN_RECURSION),
+        LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
+        LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
         LintId::of(&map_clone::MAP_CLONE),
         LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH),
         LintId::of(&matches::MATCH_OVERLAPPING_ARM),
@@ -1647,7 +1658,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&loops::NEVER_LOOP),
         LintId::of(&loops::REVERSE_RANGE_LOOP),
         LintId::of(&loops::WHILE_IMMUTABLE_CONDITION),
-        LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS),
         LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM),
         LintId::of(&mem_replace::MEM_REPLACE_WITH_UNINIT),
         LintId::of(&methods::CLONE_DOUBLE_REF),
diff --git a/clippy_lints/src/loops.rs b/clippy_lints/src/loops.rs
index f7c7fd82d83..2bbf4dba614 100644
--- a/clippy_lints/src/loops.rs
+++ b/clippy_lints/src/loops.rs
@@ -10,7 +10,6 @@ use crate::utils::{
 };
 use crate::utils::{is_type_diagnostic_item, qpath_res, same_tys, sext, sugg};
 use if_chain::if_chain;
-use itertools::Itertools;
 use rustc_ast::ast;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_errors::Applicability;
@@ -772,40 +771,48 @@ fn check_for_loop<'a, 'tcx>(
 
 fn same_var<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &Expr<'_>, var: HirId) -> bool {
     if_chain! {
-        if let ExprKind::Path(ref qpath) = expr.kind;
-        if let QPath::Resolved(None, ref path) = *qpath;
+        if let ExprKind::Path(qpath) = &expr.kind;
+        if let QPath::Resolved(None, path) = qpath;
         if path.segments.len() == 1;
         if let Res::Local(local_id) = qpath_res(cx, qpath, expr.hir_id);
-        // our variable!
-        if local_id == var;
         then {
-            return true;
+            // our variable!
+            local_id == var
+        } else {
+            false
         }
     }
+}
 
-    false
+#[derive(Clone, Copy)]
+enum OffsetSign {
+    Positive,
+    Negative,
 }
 
 struct Offset {
     value: String,
-    negate: bool,
+    sign: OffsetSign,
 }
 
 impl Offset {
-    fn negative(s: String) -> Self {
-        Self { value: s, negate: true }
+    fn negative(value: String) -> Self {
+        Self {
+            value,
+            sign: OffsetSign::Negative,
+        }
     }
 
-    fn positive(s: String) -> Self {
+    fn positive(value: String) -> Self {
         Self {
-            value: s,
-            negate: false,
+            value,
+            sign: OffsetSign::Positive,
         }
     }
 }
 
-struct FixedOffsetVar {
-    var_name: String,
+struct FixedOffsetVar<'hir> {
+    var: &'hir Expr<'hir>,
     offset: Offset,
 }
 
@@ -819,10 +826,20 @@ fn is_slice_like<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'_>) -> bool {
     is_slice || is_type_diagnostic_item(cx, ty, sym!(vec_type)) || is_type_diagnostic_item(cx, ty, sym!(vecdeque_type))
 }
 
-fn get_fixed_offset_var<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &Expr<'_>, var: HirId) -> Option<FixedOffsetVar> {
+fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
+    if_chain! {
+        if let ExprKind::MethodCall(method, _, args) = expr.kind;
+        if method.ident.name == sym!(clone);
+        if args.len() == 1;
+        if let Some(arg) = args.get(0);
+        then { arg } else { expr }
+    }
+}
+
+fn get_offset<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, idx: &Expr<'_>, var: HirId) -> Option<Offset> {
     fn extract_offset<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, e: &Expr<'_>, var: HirId) -> Option<String> {
-        match e.kind {
-            ExprKind::Lit(ref l) => match l.node {
+        match &e.kind {
+            ExprKind::Lit(l) => match l.node {
                 ast::LitKind::Int(x, _ty) => Some(x.to_string()),
                 _ => None,
             },
@@ -831,115 +848,139 @@ fn get_fixed_offset_var<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &Expr<'_>, v
         }
     }
 
-    if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind {
-        let ty = cx.tables.expr_ty(seqexpr);
-        if !is_slice_like(cx, ty) {
-            return None;
-        }
-
-        let offset = match idx.kind {
-            ExprKind::Binary(op, ref lhs, ref rhs) => match op.node {
-                BinOpKind::Add => {
-                    let offset_opt = if same_var(cx, lhs, var) {
-                        extract_offset(cx, rhs, var)
-                    } else if same_var(cx, rhs, var) {
-                        extract_offset(cx, lhs, var)
-                    } else {
-                        None
-                    };
-
-                    offset_opt.map(Offset::positive)
-                },
-                BinOpKind::Sub if same_var(cx, lhs, var) => extract_offset(cx, rhs, var).map(Offset::negative),
-                _ => None,
-            },
-            ExprKind::Path(..) => {
-                if same_var(cx, idx, var) {
-                    Some(Offset::positive("0".into()))
+    match idx.kind {
+        ExprKind::Binary(op, lhs, rhs) => match op.node {
+            BinOpKind::Add => {
+                let offset_opt = if same_var(cx, lhs, var) {
+                    extract_offset(cx, rhs, var)
+                } else if same_var(cx, rhs, var) {
+                    extract_offset(cx, lhs, var)
                 } else {
                     None
-                }
+                };
+
+                offset_opt.map(Offset::positive)
             },
+            BinOpKind::Sub if same_var(cx, lhs, var) => extract_offset(cx, rhs, var).map(Offset::negative),
             _ => None,
-        };
-
-        offset.map(|o| FixedOffsetVar {
-            var_name: snippet_opt(cx, seqexpr.span).unwrap_or_else(|| "???".into()),
-            offset: o,
-        })
-    } else {
-        None
-    }
-}
-
-fn fetch_cloned_fixed_offset_var<'a, 'tcx>(
-    cx: &LateContext<'a, 'tcx>,
-    expr: &Expr<'_>,
-    var: HirId,
-) -> Option<FixedOffsetVar> {
-    if_chain! {
-        if let ExprKind::MethodCall(ref method, _, ref args) = expr.kind;
-        if method.ident.name == sym!(clone);
-        if args.len() == 1;
-        if let Some(arg) = args.get(0);
-        then {
-            return get_fixed_offset_var(cx, arg, var);
-        }
+        },
+        ExprKind::Path(..) if same_var(cx, idx, var) => Some(Offset::positive("0".into())),
+        _ => None,
     }
-
-    get_fixed_offset_var(cx, expr, var)
 }
 
-fn get_indexed_assignments<'a, 'tcx>(
-    cx: &LateContext<'a, 'tcx>,
-    body: &Expr<'_>,
-    var: HirId,
-) -> Vec<(FixedOffsetVar, FixedOffsetVar)> {
-    fn get_assignment<'a, 'tcx>(
-        cx: &LateContext<'a, 'tcx>,
-        e: &Expr<'_>,
-        var: HirId,
-    ) -> Option<(FixedOffsetVar, FixedOffsetVar)> {
-        if let ExprKind::Assign(ref lhs, ref rhs, _) = e.kind {
-            match (
-                get_fixed_offset_var(cx, lhs, var),
-                fetch_cloned_fixed_offset_var(cx, rhs, var),
-            ) {
-                (Some(offset_left), Some(offset_right)) => {
-                    // Source and destination must be different
-                    if offset_left.var_name == offset_right.var_name {
-                        None
-                    } else {
-                        Some((offset_left, offset_right))
-                    }
-                },
-                _ => None,
-            }
+fn get_assignments<'tcx>(body: &'tcx Expr<'tcx>) -> impl Iterator<Item = Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>> {
+    fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
+        if let ExprKind::Assign(lhs, rhs, _) = e.kind {
+            Some((lhs, rhs))
         } else {
             None
         }
     }
 
-    if let ExprKind::Block(ref b, _) = body.kind {
-        let Block {
-            ref stmts, ref expr, ..
-        } = **b;
+    // This is one of few ways to return different iterators
+    // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434
+    let mut iter_a = None;
+    let mut iter_b = None;
+
+    if let ExprKind::Block(b, _) = body.kind {
+        let Block { stmts, expr, .. } = *b;
 
-        stmts
+        iter_a = stmts
             .iter()
-            .map(|stmt| match stmt.kind {
+            .filter_map(|stmt| match stmt.kind {
                 StmtKind::Local(..) | StmtKind::Item(..) => None,
-                StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => Some(get_assignment(cx, e, var)),
+                StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e),
             })
-            .chain(expr.as_ref().into_iter().map(|e| Some(get_assignment(cx, &*e, var))))
-            .filter_map(|op| op)
-            .collect::<Option<Vec<_>>>()
-            .unwrap_or_default()
+            .chain(expr.into_iter())
+            .map(get_assignment)
+            .into()
     } else {
-        get_assignment(cx, body, var).into_iter().collect()
+        iter_b = Some(get_assignment(body))
     }
+
+    iter_a.into_iter().flatten().chain(iter_b.into_iter())
 }
 
+fn build_manual_memcpy_suggestion<'a, 'tcx>(
+    cx: &LateContext<'a, 'tcx>,
+    start: &Expr<'_>,
+    end: &Expr<'_>,
+    limits: ast::RangeLimits,
+    dst_var: FixedOffsetVar<'_>,
+    src_var: FixedOffsetVar<'_>,
+) -> String {
+    fn print_sum(arg1: &str, arg2: &Offset) -> String {
+        match (arg1, &arg2.value[..], arg2.sign) {
+            ("0", "0", _) => "0".into(),
+            ("0", x, OffsetSign::Positive) | (x, "0", _) => x.into(),
+            ("0", x, OffsetSign::Negative) => format!("-{}", x),
+            (x, y, OffsetSign::Positive) => format!("({} + {})", x, y),
+            (x, y, OffsetSign::Negative) => {
+                if x == y {
+                    "0".into()
+                } else {
+                    format!("({} - {})", x, y)
+                }
+            },
+        }
+    }
+
+    fn print_offset(start_str: &str, inline_offset: &Offset) -> String {
+        let offset = print_sum(start_str, inline_offset);
+        if offset.as_str() == "0" {
+            "".into()
+        } else {
+            offset
+        }
+    }
+
+    let print_limit = |end: &Expr<'_>, offset: Offset, var: &Expr<'_>| {
+        if_chain! {
+            if let ExprKind::MethodCall(method, _, len_args) = end.kind;
+            if method.ident.name == sym!(len);
+            if len_args.len() == 1;
+            if let Some(arg) = len_args.get(0);
+            if var_def_id(cx, arg) == var_def_id(cx, var);
+            then {
+                match offset.sign {
+                    OffsetSign::Negative => format!("({} - {})", snippet(cx, end.span, "<src>.len()"), offset.value),
+                    OffsetSign::Positive => "".into(),
+                }
+            } else {
+                let end_str = match limits {
+                    ast::RangeLimits::Closed => {
+                        let end = sugg::Sugg::hir(cx, end, "<count>");
+                        format!("{}", end + sugg::ONE)
+                    },
+                    ast::RangeLimits::HalfOpen => format!("{}", snippet(cx, end.span, "..")),
+                };
+
+                print_sum(&end_str, &offset)
+            }
+        }
+    };
+
+    let start_str = snippet(cx, start.span, "").to_string();
+    let dst_offset = print_offset(&start_str, &dst_var.offset);
+    let dst_limit = print_limit(end, dst_var.offset, dst_var.var);
+    let src_offset = print_offset(&start_str, &src_var.offset);
+    let src_limit = print_limit(end, src_var.offset, src_var.var);
+
+    let dst_var_name = snippet_opt(cx, dst_var.var.span).unwrap_or_else(|| "???".into());
+    let src_var_name = snippet_opt(cx, src_var.var.span).unwrap_or_else(|| "???".into());
+
+    let dst = if dst_offset == "" && dst_limit == "" {
+        dst_var_name
+    } else {
+        format!("{}[{}..{}]", dst_var_name, dst_offset, dst_limit)
+    };
+
+    format!(
+        "{}.clone_from_slice(&{}[{}..{}])",
+        dst, src_var_name, src_offset, src_limit
+    )
+}
 /// Checks for for loops that sequentially copy items from one slice-like
 /// object to another.
 fn detect_manual_memcpy<'a, 'tcx>(
@@ -951,93 +992,43 @@ fn detect_manual_memcpy<'a, 'tcx>(
 ) {
     if let Some(higher::Range {
         start: Some(start),
-        ref end,
+        end: Some(end),
         limits,
     }) = higher::range(cx, arg)
     {
         // the var must be a single name
         if let PatKind::Binding(_, canonical_id, _, _) = pat.kind {
-            let print_sum = |arg1: &Offset, arg2: &Offset| -> String {
-                match (&arg1.value[..], arg1.negate, &arg2.value[..], arg2.negate) {
-                    ("0", _, "0", _) => "".into(),
-                    ("0", _, x, false) | (x, false, "0", false) => x.into(),
-                    ("0", _, x, true) | (x, false, "0", true) => format!("-{}", x),
-                    (x, false, y, false) => format!("({} + {})", x, y),
-                    (x, false, y, true) => {
-                        if x == y {
-                            "0".into()
-                        } else {
-                            format!("({} - {})", x, y)
-                        }
-                    },
-                    (x, true, y, false) => {
-                        if x == y {
-                            "0".into()
-                        } else {
-                            format!("({} - {})", y, x)
-                        }
-                    },
-                    (x, true, y, true) => format!("-({} + {})", x, y),
-                }
-            };
-
-            let print_limit = |end: &Option<&Expr<'_>>, offset: Offset, var_name: &str| {
-                if let Some(end) = *end {
-                    if_chain! {
-                        if let ExprKind::MethodCall(ref method, _, ref len_args) = end.kind;
-                        if method.ident.name == sym!(len);
-                        if len_args.len() == 1;
-                        if let Some(arg) = len_args.get(0);
-                        if snippet(cx, arg.span, "??") == var_name;
-                        then {
-                            return if offset.negate {
-                                format!("({} - {})", snippet(cx, end.span, "<src>.len()"), offset.value)
-                            } else {
-                                String::new()
-                            };
-                        }
-                    }
-
-                    let end_str = match limits {
-                        ast::RangeLimits::Closed => {
-                            let end = sugg::Sugg::hir(cx, end, "<count>");
-                            format!("{}", end + sugg::ONE)
-                        },
-                        ast::RangeLimits::HalfOpen => format!("{}", snippet(cx, end.span, "..")),
-                    };
-
-                    print_sum(&Offset::positive(end_str), &offset)
-                } else {
-                    "..".into()
-                }
-            };
-
             // The only statements in the for loops can be indexed assignments from
             // indexed retrievals.
-            let manual_copies = get_indexed_assignments(cx, body, canonical_id);
-
-            let big_sugg = manual_copies
-                .into_iter()
-                .map(|(dst_var, src_var)| {
-                    let start_str = Offset::positive(snippet(cx, start.span, "").to_string());
-                    let dst_offset = print_sum(&start_str, &dst_var.offset);
-                    let dst_limit = print_limit(end, dst_var.offset, &dst_var.var_name);
-                    let src_offset = print_sum(&start_str, &src_var.offset);
-                    let src_limit = print_limit(end, src_var.offset, &src_var.var_name);
-                    let dst = if dst_offset == "" && dst_limit == "" {
-                        dst_var.var_name
-                    } else {
-                        format!("{}[{}..{}]", dst_var.var_name, dst_offset, dst_limit)
-                    };
-
-                    format!(
-                        "{}.clone_from_slice(&{}[{}..{}])",
-                        dst, src_var.var_name, src_offset, src_limit
-                    )
+            let big_sugg = get_assignments(body)
+                .map(|o| {
+                    o.and_then(|(lhs, rhs)| {
+                        let rhs = fetch_cloned_expr(rhs);
+                        if_chain! {
+                            if let ExprKind::Index(seqexpr_left, idx_left) = lhs.kind;
+                            if let ExprKind::Index(seqexpr_right, idx_right) = rhs.kind;
+                            if is_slice_like(cx, cx.tables.expr_ty(seqexpr_left))
+                                && is_slice_like(cx, cx.tables.expr_ty(seqexpr_right));
+                            if let Some(offset_left) = get_offset(cx, &idx_left, canonical_id);
+                            if let Some(offset_right) = get_offset(cx, &idx_right, canonical_id);
+
+                            // Source and destination must be different
+                            if var_def_id(cx, seqexpr_left) != var_def_id(cx, seqexpr_right);
+                            then {
+                                Some((FixedOffsetVar { var: seqexpr_left, offset: offset_left },
+                                    FixedOffsetVar { var: seqexpr_right, offset: offset_right }))
+                            } else {
+                                None
+                            }
+                        }
+                    })
                 })
-                .join("\n    ");
+                .map(|o| o.map(|(dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, dst, src)))
+                .collect::<Option<Vec<_>>>()
+                .filter(|v| !v.is_empty())
+                .map(|v| v.join("\n    "));
 
-            if !big_sugg.is_empty() {
+            if let Some(big_sugg) = big_sugg {
                 span_lint_and_sugg(
                     cx,
                     MANUAL_MEMCPY,
diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs
new file mode 100644
index 00000000000..cb72a240582
--- /dev/null
+++ b/clippy_lints/src/manual_async_fn.rs
@@ -0,0 +1,159 @@
+use crate::utils::paths::FUTURE_FROM_GENERATOR;
+use crate::utils::{match_function_call, snippet_block, snippet_opt, span_lint_and_then};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
+    AsyncGeneratorKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericBound, HirId, IsAsync,
+    ItemKind, TraitRef, Ty, TyKind, TypeBindingKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+    /// **What it does:** It checks for manual implementations of `async` functions.
+    ///
+    /// **Why is this bad?** It's more idiomatic to use the dedicated syntax.
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Example:**
+    ///
+    /// ```rust
+    /// use std::future::Future;
+    ///
+    /// fn foo() -> impl Future<Output = i32> { async { 42 } }
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// use std::future::Future;
+    ///
+    /// async fn foo() -> i32 { 42 }
+    /// ```
+    pub MANUAL_ASYNC_FN,
+    style,
+    "manual implementations of `async` functions can be simplified using the dedicated syntax"
+}
+
+declare_lint_pass!(ManualAsyncFn => [MANUAL_ASYNC_FN]);
+
+impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ManualAsyncFn {
+    fn check_fn(
+        &mut self,
+        cx: &LateContext<'a, 'tcx>,
+        kind: FnKind<'tcx>,
+        decl: &'tcx FnDecl<'_>,
+        body: &'tcx Body<'_>,
+        span: Span,
+        _: HirId,
+    ) {
+        if_chain! {
+            if let Some(header) = kind.header();
+            if let IsAsync::NotAsync = header.asyncness;
+            // Check that this function returns `impl Future`
+            if let FnRetTy::Return(ret_ty) = decl.output;
+            if let Some(trait_ref) = future_trait_ref(cx, ret_ty);
+            if let Some(output) = future_output_ty(trait_ref);
+            // Check that the body of the function consists of one async block
+            if let ExprKind::Block(block, _) = body.value.kind;
+            if block.stmts.is_empty();
+            if let Some(closure_body) = desugared_async_block(cx, block);
+            then {
+                let header_span = span.with_hi(ret_ty.span.hi());
+
+                span_lint_and_then(
+                    cx,
+                    MANUAL_ASYNC_FN,
+                    header_span,
+                    "this function can be simplified using the `async fn` syntax",
+                    |diag| {
+                        if_chain! {
+                            if let Some(header_snip) = snippet_opt(cx, header_span);
+                            if let Some(ret_pos) = header_snip.rfind("->");
+                            if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output);
+                            then {
+                                let help = format!("make the function `async` and {}", ret_sugg);
+                                diag.span_suggestion(
+                                    header_span,
+                                    &help,
+                                    format!("async {}{}", &header_snip[..ret_pos], ret_snip),
+                                    Applicability::MachineApplicable
+                                );
+
+                                let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
+                                diag.span_suggestion(
+                                    block.span,
+                                    "move the body of the async block to the enclosing function",
+                                    body_snip.to_string(),
+                                    Applicability::MachineApplicable
+                                );
+                            }
+                        }
+                    },
+                );
+            }
+        }
+    }
+}
+
+fn future_trait_ref<'tcx>(cx: &LateContext<'_, 'tcx>, ty: &'tcx Ty<'tcx>) -> Option<&'tcx TraitRef<'tcx>> {
+    if_chain! {
+        if let TyKind::Def(item_id, _) = ty.kind;
+        let item = cx.tcx.hir().item(item_id.id);
+        if let ItemKind::OpaqueTy(opaque) = &item.kind;
+        if opaque.bounds.len() == 1;
+        if let GenericBound::Trait(poly, _) = &opaque.bounds[0];
+        if poly.trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait();
+        then {
+            return Some(&poly.trait_ref);
+        }
+    }
+
+    None
+}
+
+fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> {
+    if_chain! {
+        if let Some(segment) = trait_ref.path.segments.last();
+        if let Some(args) = segment.args;
+        if args.bindings.len() == 1;
+        let binding = &args.bindings[0];
+        if binding.ident.as_str() == "Output";
+        if let TypeBindingKind::Equality{ty: output} = binding.kind;
+        then {
+            return Some(output)
+        }
+    }
+
+    None
+}
+
+fn desugared_async_block<'tcx>(cx: &LateContext<'_, 'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> {
+    if_chain! {
+        if let Some(block_expr) = block.expr;
+        if let Some(args) = match_function_call(cx, block_expr, &FUTURE_FROM_GENERATOR);
+        if args.len() == 1;
+        if let Expr{kind: ExprKind::Closure(_, _, body_id, ..), ..} = args[0];
+        let closure_body = cx.tcx.hir().body(body_id);
+        if let Some(GeneratorKind::Async(AsyncGeneratorKind::Block)) = closure_body.generator_kind;
+        then {
+            return Some(closure_body);
+        }
+    }
+
+    None
+}
+
+fn suggested_ret(cx: &LateContext<'_, '_>, output: &Ty<'_>) -> Option<(&'static str, String)> {
+    match output.kind {
+        TyKind::Tup(tys) if tys.is_empty() => {
+            let sugg = "remove the return type";
+            Some((sugg, "".into()))
+        },
+        _ => {
+            let sugg = "return the output of the future directly";
+            snippet_opt(cx, output.span).map(|snip| (sugg, format!("-> {}", snip)))
+        },
+    }
+}
diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs
new file mode 100644
index 00000000000..f3b8902e26f
--- /dev/null
+++ b/clippy_lints/src/manual_non_exhaustive.rs
@@ -0,0 +1,173 @@
+use crate::utils::{snippet_opt, span_lint_and_then};
+use if_chain::if_chain;
+use rustc_ast::ast::{Attribute, Item, ItemKind, StructField, Variant, VariantData, VisibilityKind};
+use rustc_attr as attr;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+    /// **What it does:** Checks for manual implementations of the non-exhaustive pattern.
+    ///
+    /// **Why is this bad?** Using the #[non_exhaustive] attribute expresses better the intent
+    /// and allows possible optimizations when applied to enums.
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Example:**
+    ///
+    /// ```rust
+    /// struct S {
+    ///     pub a: i32,
+    ///     pub b: i32,
+    ///     _c: (),
+    /// }
+    ///
+    /// enum E {
+    ///     A,
+    ///     B,
+    ///     #[doc(hidden)]
+    ///     _C,
+    /// }
+    ///
+    /// struct T(pub i32, pub i32, ());
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// #[non_exhaustive]
+    /// struct S {
+    ///     pub a: i32,
+    ///     pub b: i32,
+    /// }
+    ///
+    /// #[non_exhaustive]
+    /// enum E {
+    ///     A,
+    ///     B,
+    /// }
+    ///
+    /// #[non_exhaustive]
+    /// struct T(pub i32, pub i32);
+    /// ```
+    pub MANUAL_NON_EXHAUSTIVE,
+    style,
+    "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
+}
+
+declare_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
+
+impl EarlyLintPass for ManualNonExhaustive {
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+        match &item.kind {
+            ItemKind::Enum(def, _) => {
+                check_manual_non_exhaustive_enum(cx, item, &def.variants);
+            },
+            ItemKind::Struct(variant_data, _) => {
+                if let VariantData::Unit(..) = variant_data {
+                    return;
+                }
+
+                check_manual_non_exhaustive_struct(cx, item, variant_data);
+            },
+            _ => {},
+        }
+    }
+}
+
+fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants: &[Variant]) {
+    fn is_non_exhaustive_marker(variant: &Variant) -> bool {
+        matches!(variant.data, VariantData::Unit(_))
+            && variant.ident.as_str().starts_with('_')
+            && variant.attrs.iter().any(|a| is_doc_hidden(a))
+    }
+
+    fn is_doc_hidden(attr: &Attribute) -> bool {
+        attr.check_name(sym!(doc))
+            && match attr.meta_item_list() {
+                Some(l) => attr::list_contains_name(&l, sym!(hidden)),
+                None => false,
+            }
+    }
+
+    if_chain! {
+        let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v));
+        if let Some(marker) = markers.next();
+        if markers.count() == 0 && variants.len() > 1;
+        then {
+            span_lint_and_then(
+                cx,
+                MANUAL_NON_EXHAUSTIVE,
+                item.span,
+                "this seems like a manual implementation of the non-exhaustive pattern",
+                |diag| {
+                    if_chain! {
+                        if !attr::contains_name(&item.attrs, sym!(non_exhaustive));
+                        let header_span = cx.sess.source_map().span_until_char(item.span, '{');
+                        if let Some(snippet) = snippet_opt(cx, header_span);
+                        then {
+                            diag.span_suggestion(
+                                header_span,
+                                "add the attribute",
+                                format!("#[non_exhaustive] {}", snippet),
+                                Applicability::Unspecified,
+                            );
+                        }
+                    }
+                    diag.span_help(marker.span, "remove this variant");
+                });
+        }
+    }
+}
+
+fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) {
+    fn is_private(field: &StructField) -> bool {
+        matches!(field.vis.node, VisibilityKind::Inherited)
+    }
+
+    fn is_non_exhaustive_marker(field: &StructField) -> bool {
+        is_private(field) && field.ty.kind.is_unit() && field.ident.map_or(true, |n| n.as_str().starts_with('_'))
+    }
+
+    fn find_header_span(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) -> Span {
+        let delimiter = match data {
+            VariantData::Struct(..) => '{',
+            VariantData::Tuple(..) => '(',
+            VariantData::Unit(_) => unreachable!("`VariantData::Unit` is already handled above"),
+        };
+
+        cx.sess.source_map().span_until_char(item.span, delimiter)
+    }
+
+    let fields = data.fields();
+    let private_fields = fields.iter().filter(|f| is_private(f)).count();
+    let public_fields = fields.iter().filter(|f| f.vis.node.is_pub()).count();
+
+    if_chain! {
+        if private_fields == 1 && public_fields >= 1 && public_fields == fields.len() - 1;
+        if let Some(marker) = fields.iter().find(|f| is_non_exhaustive_marker(f));
+        then {
+            span_lint_and_then(
+                cx,
+                MANUAL_NON_EXHAUSTIVE,
+                item.span,
+                "this seems like a manual implementation of the non-exhaustive pattern",
+                |diag| {
+                    if_chain! {
+                        if !attr::contains_name(&item.attrs, sym!(non_exhaustive));
+                        let header_span = find_header_span(cx, item, data);
+                        if let Some(snippet) = snippet_opt(cx, header_span);
+                        then {
+                            diag.span_suggestion(
+                                header_span,
+                                "add the attribute",
+                                format!("#[non_exhaustive] {}", snippet),
+                                Applicability::Unspecified,
+                            );
+                        }
+                    }
+                    diag.span_help(marker.span, "remove this field");
+                });
+        }
+    }
+}
diff --git a/clippy_lints/src/match_on_vec_items.rs b/clippy_lints/src/match_on_vec_items.rs
index 4071406cc84..ee69628e9f0 100644
--- a/clippy_lints/src/match_on_vec_items.rs
+++ b/clippy_lints/src/match_on_vec_items.rs
@@ -1,4 +1,4 @@
-use crate::utils::{is_type_diagnostic_item, snippet, span_lint_and_sugg, walk_ptrs_ty};
+use crate::utils::{self, is_type_diagnostic_item, match_type, snippet, span_lint_and_sugg, walk_ptrs_ty};
 use if_chain::if_chain;
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, MatchSource};
@@ -38,7 +38,7 @@ declare_clippy_lint! {
     /// }
     /// ```
     pub MATCH_ON_VEC_ITEMS,
-    correctness,
+    pedantic,
     "matching on vector elements can panic"
 }
 
@@ -75,10 +75,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MatchOnVecItems {
 
 fn is_vec_indexing<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
     if_chain! {
-        if let ExprKind::Index(ref array, _) = expr.kind;
-        let ty = cx.tables.expr_ty(array);
-        let ty = walk_ptrs_ty(ty);
-        if is_type_diagnostic_item(cx, ty, sym!(vec_type));
+        if let ExprKind::Index(ref array, ref index) = expr.kind;
+        if is_vector(cx, array);
+        if !is_full_range(cx, index);
 
         then {
             return Some(expr);
@@ -87,3 +86,15 @@ fn is_vec_indexing<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>)
 
     None
 }
+
+fn is_vector(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool {
+    let ty = cx.tables.expr_ty(expr);
+    let ty = walk_ptrs_ty(ty);
+    is_type_diagnostic_item(cx, ty, sym!(vec_type))
+}
+
+fn is_full_range(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool {
+    let ty = cx.tables.expr_ty(expr);
+    let ty = walk_ptrs_ty(ty);
+    match_type(cx, ty, &utils::paths::RANGE_FULL)
+}
diff --git a/clippy_lints/src/misc_early.rs b/clippy_lints/src/misc_early.rs
index adfd8dfb1c1..62ee051624b 100644
--- a/clippy_lints/src/misc_early.rs
+++ b/clippy_lints/src/misc_early.rs
@@ -24,8 +24,25 @@ declare_clippy_lint! {
     /// **Known problems:** None.
     ///
     /// **Example:**
-    /// ```ignore
-    /// let { a: _, b: ref b, c: _ } = ..
+    /// ```rust
+    /// # struct Foo {
+    /// #     a: i32,
+    /// #     b: i32,
+    /// #     c: i32,
+    /// # }
+    /// let f = Foo { a: 0, b: 0, c: 0 };
+    ///
+    /// // Bad
+    /// match f {
+    ///     Foo { a: _, b: 0, .. } => {},
+    ///     Foo { a: _, b: _, c: _ } => {},
+    /// }
+    ///
+    /// // Good
+    /// match f {
+    ///     Foo { b: 0, .. } => {},
+    ///     Foo { .. } => {},
+    /// }
     /// ```
     pub UNNEEDED_FIELD_PATTERN,
     restriction,
diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs
index 5235c98efab..f3844c7d3b6 100644
--- a/clippy_lints/src/unwrap.rs
+++ b/clippy_lints/src/unwrap.rs
@@ -1,4 +1,7 @@
-use crate::utils::{higher::if_block, is_type_diagnostic_item, span_lint_and_then, usage::is_potentially_mutated};
+use crate::utils::{
+    differing_macro_contexts, higher::if_block, is_type_diagnostic_item, span_lint_and_then,
+    usage::is_potentially_mutated,
+};
 use if_chain::if_chain;
 use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor};
 use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Path, QPath, UnOp};
@@ -73,6 +76,8 @@ struct UnwrapInfo<'tcx> {
     ident: &'tcx Path<'tcx>,
     /// The check, like `x.is_ok()`
     check: &'tcx Expr<'tcx>,
+    /// The branch where the check takes place, like `if x.is_ok() { .. }`
+    branch: &'tcx Expr<'tcx>,
     /// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`).
     safe_to_unwrap: bool,
 }
@@ -82,19 +87,20 @@ struct UnwrapInfo<'tcx> {
 fn collect_unwrap_info<'a, 'tcx>(
     cx: &'a LateContext<'a, 'tcx>,
     expr: &'tcx Expr<'_>,
+    branch: &'tcx Expr<'_>,
     invert: bool,
 ) -> Vec<UnwrapInfo<'tcx>> {
     if let ExprKind::Binary(op, left, right) = &expr.kind {
         match (invert, op.node) {
             (false, BinOpKind::And) | (false, BinOpKind::BitAnd) | (true, BinOpKind::Or) | (true, BinOpKind::BitOr) => {
-                let mut unwrap_info = collect_unwrap_info(cx, left, invert);
-                unwrap_info.append(&mut collect_unwrap_info(cx, right, invert));
+                let mut unwrap_info = collect_unwrap_info(cx, left, branch, invert);
+                unwrap_info.append(&mut collect_unwrap_info(cx, right, branch, invert));
                 return unwrap_info;
             },
             _ => (),
         }
     } else if let ExprKind::Unary(UnOp::UnNot, expr) = &expr.kind {
-        return collect_unwrap_info(cx, expr, !invert);
+        return collect_unwrap_info(cx, expr, branch, !invert);
     } else {
         if_chain! {
             if let ExprKind::MethodCall(method_name, _, args) = &expr.kind;
@@ -111,7 +117,7 @@ fn collect_unwrap_info<'a, 'tcx>(
                     _ => unreachable!(),
                 };
                 let safe_to_unwrap = unwrappable != invert;
-                return vec![UnwrapInfo { ident: path, check: expr, safe_to_unwrap }];
+                return vec![UnwrapInfo { ident: path, check: expr, branch, safe_to_unwrap }];
             }
         }
     }
@@ -121,7 +127,7 @@ fn collect_unwrap_info<'a, 'tcx>(
 impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
     fn visit_branch(&mut self, cond: &'tcx Expr<'_>, branch: &'tcx Expr<'_>, else_branch: bool) {
         let prev_len = self.unwrappables.len();
-        for unwrap_info in collect_unwrap_info(self.cx, cond, else_branch) {
+        for unwrap_info in collect_unwrap_info(self.cx, cond, branch, else_branch) {
             if is_potentially_mutated(unwrap_info.ident, cond, self.cx)
                 || is_potentially_mutated(unwrap_info.ident, branch, self.cx)
             {
@@ -158,6 +164,9 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
                 let call_to_unwrap = method_name.ident.name == sym!(unwrap);
                 if let Some(unwrappable) = self.unwrappables.iter()
                     .find(|u| u.ident.res == path.res);
+                // Span contexts should not differ with the conditional branch
+                if !differing_macro_contexts(unwrappable.branch.span, expr.span);
+                if !differing_macro_contexts(unwrappable.branch.span, unwrappable.check.span);
                 then {
                     if call_to_unwrap == unwrappable.safe_to_unwrap {
                         span_lint_and_then(
diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs
index 4b81ff33495..57b9eafd14d 100644
--- a/clippy_lints/src/utils/conf.rs
+++ b/clippy_lints/src/utils/conf.rs
@@ -158,6 +158,8 @@ define_Conf! {
     (max_struct_bools, "max_struct_bools": u64, 3),
     /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
     (max_fn_params_bools, "max_fn_params_bools": u64, 3),
+    /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests).
+    (warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports": bool, false),
 }
 
 impl Default for Conf {
diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs
index 2fd080e9ef0..3b8ef18bfab 100644
--- a/clippy_lints/src/utils/mod.rs
+++ b/clippy_lints/src/utils/mod.rs
@@ -933,6 +933,7 @@ pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_, '_>, expr: &Exp
 }
 
 /// Returns `true` if a pattern is refutable.
+// TODO: should be implemented using rustc/mir_build/hair machinery
 pub fn is_refutable(cx: &LateContext<'_, '_>, pat: &Pat<'_>) -> bool {
     fn is_enum_variant(cx: &LateContext<'_, '_>, qpath: &QPath<'_>, id: HirId) -> bool {
         matches!(
@@ -946,27 +947,34 @@ pub fn is_refutable(cx: &LateContext<'_, '_>, pat: &Pat<'_>) -> bool {
     }
 
     match pat.kind {
-        PatKind::Binding(..) | PatKind::Wild => false,
+        PatKind::Wild => false,
+        PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)),
         PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => is_refutable(cx, pat),
         PatKind::Lit(..) | PatKind::Range(..) => true,
         PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id),
-        PatKind::Or(ref pats) | PatKind::Tuple(ref pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)),
+        PatKind::Or(ref pats) => {
+            // TODO: should be the honest check, that pats is exhaustive set
+            are_refutable(cx, pats.iter().map(|pat| &**pat))
+        },
+        PatKind::Tuple(ref pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)),
         PatKind::Struct(ref qpath, ref fields, _) => {
-            if is_enum_variant(cx, qpath, pat.hir_id) {
-                true
-            } else {
-                are_refutable(cx, fields.iter().map(|field| &*field.pat))
-            }
+            is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| &*field.pat))
         },
         PatKind::TupleStruct(ref qpath, ref pats, _) => {
-            if is_enum_variant(cx, qpath, pat.hir_id) {
-                true
-            } else {
-                are_refutable(cx, pats.iter().map(|pat| &**pat))
-            }
+            is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats.iter().map(|pat| &**pat))
         },
         PatKind::Slice(ref head, ref middle, ref tail) => {
-            are_refutable(cx, head.iter().chain(middle).chain(tail.iter()).map(|pat| &**pat))
+            match &cx.tables.node_type(pat.hir_id).kind {
+                ty::Slice(..) => {
+                    // [..] is the only irrefutable slice pattern.
+                    !head.is_empty() || middle.is_none() || !tail.is_empty()
+                },
+                ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter()).map(|pat| &**pat)),
+                _ => {
+                    // unreachable!()
+                    true
+                },
+            }
         },
     }
 }
diff --git a/clippy_lints/src/utils/paths.rs b/clippy_lints/src/utils/paths.rs
index ca2c4ade155..b3ad2ad9d99 100644
--- a/clippy_lints/src/utils/paths.rs
+++ b/clippy_lints/src/utils/paths.rs
@@ -42,6 +42,7 @@ pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments
 pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
 pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
 pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"];
+pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
 pub const HASH: [&str; 2] = ["hash", "Hash"];
 pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
 pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
@@ -85,7 +86,7 @@ pub const RANGE: [&str; 3] = ["core", "ops", "Range"];
 pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
 pub const RANGE_FROM: [&str; 3] = ["core", "ops", "RangeFrom"];
 pub const RANGE_FROM_STD: [&str; 3] = ["std", "ops", "RangeFrom"];
-pub const RANGE_FULL: [&str; 3] = ["core", "ops", "RangeFull"];
+pub const RANGE_FULL: [&str; 4] = ["core", "ops", "range", "RangeFull"];
 pub const RANGE_FULL_STD: [&str; 3] = ["std", "ops", "RangeFull"];
 pub const RANGE_INCLUSIVE_NEW: [&str; 4] = ["core", "ops", "RangeInclusive", "new"];
 pub const RANGE_INCLUSIVE_STD_NEW: [&str; 4] = ["std", "ops", "RangeInclusive", "new"];
diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs
index f3038861cee..32d9a45c37d 100644
--- a/clippy_lints/src/wildcard_imports.rs
+++ b/clippy_lints/src/wildcard_imports.rs
@@ -3,10 +3,10 @@ use if_chain::if_chain;
 use rustc_errors::Applicability;
 use rustc_hir::{
     def::{DefKind, Res},
-    Item, ItemKind, UseKind,
+    Item, ItemKind, PathSegment, UseKind,
 };
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::BytePos;
 
 declare_clippy_lint! {
@@ -43,9 +43,14 @@ declare_clippy_lint! {
     ///
     /// This can lead to confusing error messages at best and to unexpected behavior at worst.
     ///
-    /// Note that this will not warn about wildcard imports from modules named `prelude`; many
-    /// crates (including the standard library) provide modules named "prelude" specifically
-    /// designed for wildcard import.
+    /// **Exceptions:**
+    ///
+    /// Wildcard imports are allowed from modules named `prelude`. Many crates (including the standard library)
+    /// provide modules named "prelude" specifically designed for wildcard import.
+    ///
+    /// `use super::*` is allowed in test modules. This is defined as any module with "test" in the name.
+    ///
+    /// These exceptions can be disabled using the `warn-on-all-wildcard-imports` configuration flag.
     ///
     /// **Known problems:** If macros are imported through the wildcard, this macro is not included
     /// by the suggestion and has to be added by hand.
@@ -73,18 +78,34 @@ declare_clippy_lint! {
     "lint `use _::*` statements"
 }
 
-declare_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]);
+#[derive(Default)]
+pub struct WildcardImports {
+    warn_on_all: bool,
+    test_modules_deep: u32,
+}
+
+impl WildcardImports {
+    pub fn new(warn_on_all: bool) -> Self {
+        Self {
+            warn_on_all,
+            test_modules_deep: 0,
+        }
+    }
+}
+
+impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]);
 
 impl LateLintPass<'_, '_> for WildcardImports {
     fn check_item(&mut self, cx: &LateContext<'_, '_>, item: &Item<'_>) {
+        if is_test_module_or_function(item) {
+            self.test_modules_deep = self.test_modules_deep.saturating_add(1);
+        }
         if item.vis.node.is_pub() || item.vis.node.is_pub_restricted() {
             return;
         }
         if_chain! {
-            if !in_macro(item.span);
             if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind;
-            // don't lint prelude glob imports
-            if !use_path.segments.iter().last().map_or(false, |ps| ps.ident.as_str() == "prelude");
+            if self.warn_on_all || !self.check_exceptions(item, use_path.segments);
             let used_imports = cx.tcx.names_imported_by_glob_use(item.hir_id.owner);
             if !used_imports.is_empty(); // Already handled by `unused_imports`
             then {
@@ -109,8 +130,7 @@ impl LateLintPass<'_, '_> for WildcardImports {
                         span = use_path.span.with_hi(item.span.hi() - BytePos(1));
                     }
                     (
-                        span,
-                        false,
+                        span, false,
                     )
                 };
 
@@ -153,4 +173,36 @@ impl LateLintPass<'_, '_> for WildcardImports {
             }
         }
     }
+
+    fn check_item_post(&mut self, _: &LateContext<'_, '_>, item: &Item<'_>) {
+        if is_test_module_or_function(item) {
+            self.test_modules_deep = self.test_modules_deep.saturating_sub(1);
+        }
+    }
+}
+
+impl WildcardImports {
+    fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
+        in_macro(item.span)
+            || is_prelude_import(segments)
+            || (is_super_only_import(segments) && self.test_modules_deep > 0)
+    }
+}
+
+// Allow "...prelude::*" imports.
+// Many crates have a prelude, and it is imported as a glob by design.
+fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
+    segments
+        .iter()
+        .last()
+        .map_or(false, |ps| ps.ident.as_str() == "prelude")
+}
+
+// Allow "super::*" imports in tests.
+fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
+    segments.len() == 1 && segments[0].ident.as_str() == "super"
+}
+
+fn is_test_module_or_function(item: &Item<'_>) -> bool {
+    matches!(item.kind, ItemKind::Mod(..)) && item.ident.name.as_str().contains("test")
 }