about summary refs log tree commit diff
path: root/compiler/rustc_lint/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2020-08-30 15:57:57 +0000
committerbors <bors@rust-lang.org>2020-08-30 15:57:57 +0000
commit85fbf49ce0e2274d0acf798f6e703747674feec3 (patch)
tree158a05eb3f204a8e72939b58427d0c2787a4eade /compiler/rustc_lint/src
parentdb534b3ac286cf45688c3bbae6aa6e77439e52d2 (diff)
parent9e5f7d5631b8f4009ac1c693e585d4b7108d4275 (diff)
downloadrust-85fbf49ce0e2274d0acf798f6e703747674feec3.tar.gz
rust-85fbf49ce0e2274d0acf798f6e703747674feec3.zip
Auto merge of #74862 - mark-i-m:mv-compiler, r=petrochenkov
Move almost all compiler crates to compiler/

This PR implements https://github.com/rust-lang/compiler-team/issues/336 and moves all `rustc_*` crates from `src` to the new `compiler` directory.

`librustc_foo` directories are renamed to `rustc_foo`.
`src` directories are introduced inside `rustc_*` directories to mirror the scheme already use for `library` crates.
Diffstat (limited to 'compiler/rustc_lint/src')
-rw-r--r--compiler/rustc_lint/src/array_into_iter.rs97
-rw-r--r--compiler/rustc_lint/src/builtin.rs2422
-rw-r--r--compiler/rustc_lint/src/context.rs862
-rw-r--r--compiler/rustc_lint/src/early.rs381
-rw-r--r--compiler/rustc_lint/src/internal.rs247
-rw-r--r--compiler/rustc_lint/src/late.rs499
-rw-r--r--compiler/rustc_lint/src/levels.rs576
-rw-r--r--compiler/rustc_lint/src/lib.rs464
-rw-r--r--compiler/rustc_lint/src/non_ascii_idents.rs240
-rw-r--r--compiler/rustc_lint/src/nonstandard_style.rs456
-rw-r--r--compiler/rustc_lint/src/nonstandard_style/tests.rs21
-rw-r--r--compiler/rustc_lint/src/passes.rs285
-rw-r--r--compiler/rustc_lint/src/redundant_semicolon.rs41
-rw-r--r--compiler/rustc_lint/src/types.rs1240
-rw-r--r--compiler/rustc_lint/src/unused.rs1011
15 files changed, 8842 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs
new file mode 100644
index 00000000000..9d74ad3b2f5
--- /dev/null
+++ b/compiler/rustc_lint/src/array_into_iter.rs
@@ -0,0 +1,97 @@
+use crate::{LateContext, LateLintPass, LintContext};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_middle::ty;
+use rustc_middle::ty::adjustment::{Adjust, Adjustment};
+use rustc_session::lint::FutureIncompatibleInfo;
+use rustc_span::symbol::sym;
+
+declare_lint! {
+    pub ARRAY_INTO_ITER,
+    Warn,
+    "detects calling `into_iter` on arrays",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #66145 <https://github.com/rust-lang/rust/issues/66145>",
+        edition: None,
+    };
+}
+
+declare_lint_pass!(
+    /// Checks for instances of calling `into_iter` on arrays.
+    ArrayIntoIter => [ARRAY_INTO_ITER]
+);
+
+impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
+        // We only care about method call expressions.
+        if let hir::ExprKind::MethodCall(call, span, args, _) = &expr.kind {
+            if call.ident.name != sym::into_iter {
+                return;
+            }
+
+            // Check if the method call actually calls the libcore
+            // `IntoIterator::into_iter`.
+            let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
+            match cx.tcx.trait_of_item(def_id) {
+                Some(trait_id) if cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_id) => {}
+                _ => return,
+            };
+
+            // As this is a method call expression, we have at least one
+            // argument.
+            let receiver_arg = &args[0];
+
+            // Peel all `Box<_>` layers. We have to special case `Box` here as
+            // `Box` is the only thing that values can be moved out of via
+            // method call. `Box::new([1]).into_iter()` should trigger this
+            // lint.
+            let mut recv_ty = cx.typeck_results().expr_ty(receiver_arg);
+            let mut num_box_derefs = 0;
+            while recv_ty.is_box() {
+                num_box_derefs += 1;
+                recv_ty = recv_ty.boxed_ty();
+            }
+
+            // Make sure we found an array after peeling the boxes.
+            if !matches!(recv_ty.kind, ty::Array(..)) {
+                return;
+            }
+
+            // Make sure that there is an autoref coercion at the expected
+            // position. The first `num_box_derefs` adjustments are the derefs
+            // of the box.
+            match cx.typeck_results().expr_adjustments(receiver_arg).get(num_box_derefs) {
+                Some(Adjustment { kind: Adjust::Borrow(_), .. }) => {}
+                _ => return,
+            }
+
+            // Emit lint diagnostic.
+            let target = match cx.typeck_results().expr_ty_adjusted(receiver_arg).kind {
+                ty::Ref(_, ty::TyS { kind: ty::Array(..), .. }, _) => "[T; N]",
+                ty::Ref(_, ty::TyS { kind: ty::Slice(..), .. }, _) => "[T]",
+
+                // We know the original first argument type is an array type,
+                // we know that the first adjustment was an autoref coercion
+                // and we know that `IntoIterator` is the trait involved. The
+                // array cannot be coerced to something other than a reference
+                // to an array or to a slice.
+                _ => bug!("array type coerced to something other than array or slice"),
+            };
+            cx.struct_span_lint(ARRAY_INTO_ITER, *span, |lint| {
+                lint.build(&format!(
+                "this method call currently resolves to `<&{} as IntoIterator>::into_iter` (due \
+                    to autoref coercions), but that might change in the future when \
+                    `IntoIterator` impls for arrays are added.",
+                target,
+                ))
+                .span_suggestion(
+                    call.ident.span,
+                    "use `.iter()` instead of `.into_iter()` to avoid ambiguity",
+                    "iter".into(),
+                    Applicability::MachineApplicable,
+                )
+                .emit();
+            })
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
new file mode 100644
index 00000000000..ea624b9ed30
--- /dev/null
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -0,0 +1,2422 @@
+//! Lints in the Rust compiler.
+//!
+//! This contains lints which can feasibly be implemented as their own
+//! AST visitor. Also see `rustc_session::lint::builtin`, which contains the
+//! definitions of lints that are emitted directly inside the main compiler.
+//!
+//! To add a new lint to rustc, declare it here using `declare_lint!()`.
+//! Then add code to emit the new lint in the appropriate circumstances.
+//! You can do that in an existing `LintPass` if it makes sense, or in a
+//! new `LintPass`, or using `Session::add_lint` elsewhere in the
+//! compiler. Only do the latter if the check can't be written cleanly as a
+//! `LintPass` (also, note that such lints will need to be defined in
+//! `rustc_session::lint::builtin`, not here).
+//!
+//! If you define a new `EarlyLintPass`, you will also need to add it to the
+//! `add_early_builtin!` or `add_early_builtin_with_new!` invocation in
+//! `lib.rs`. Use the former for unit-like structs and the latter for structs
+//! with a `pub fn new()`.
+//!
+//! If you define a new `LateLintPass`, you will also need to add it to the
+//! `late_lint_methods!` invocation in `lib.rs`.
+
+use crate::{
+    types::CItemKind, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext,
+};
+use rustc_ast::attr::{self, HasAttrs};
+use rustc_ast::tokenstream::{TokenStream, TokenTree};
+use rustc_ast::visit::{FnCtxt, FnKind};
+use rustc_ast::{self as ast, *};
+use rustc_ast_pretty::pprust::{self, expr_to_string};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::stack::ensure_sufficient_stack;
+use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString};
+use rustc_feature::{deprecated_attributes, AttributeGate, AttributeTemplate, AttributeType};
+use rustc_feature::{GateIssue, Stability};
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{ForeignItemKind, GenericParamKind, PatKind};
+use rustc_hir::{HirId, HirIdSet, Node};
+use rustc_index::vec::Idx;
+use rustc_middle::lint::LintDiagnosticBuilder;
+use rustc_middle::ty::subst::{GenericArgKind, Subst};
+use rustc_middle::ty::{self, layout::LayoutError, Ty, TyCtxt};
+use rustc_session::lint::FutureIncompatibleInfo;
+use rustc_session::Session;
+use rustc_span::edition::Edition;
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::{BytePos, Span};
+use rustc_target::abi::{LayoutOf, VariantIdx};
+use rustc_trait_selection::traits::misc::can_type_implement_copy;
+
+use crate::nonstandard_style::{method_context, MethodLateContext};
+
+use std::fmt::Write;
+use tracing::{debug, trace};
+
+// hardwired lints from librustc_middle
+pub use rustc_session::lint::builtin::*;
+
+declare_lint! {
+    WHILE_TRUE,
+    Warn,
+    "suggest using `loop { }` instead of `while true { }`"
+}
+
+declare_lint_pass!(WhileTrue => [WHILE_TRUE]);
+
+/// Traverse through any amount of parenthesis and return the first non-parens expression.
+fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr {
+    while let ast::ExprKind::Paren(sub) = &expr.kind {
+        expr = sub;
+    }
+    expr
+}
+
+impl EarlyLintPass for WhileTrue {
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+        if let ast::ExprKind::While(cond, ..) = &e.kind {
+            if let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind {
+                if let ast::LitKind::Bool(true) = lit.kind {
+                    if !lit.span.from_expansion() {
+                        let msg = "denote infinite loops with `loop { ... }`";
+                        let condition_span = cx.sess.source_map().guess_head_span(e.span);
+                        cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| {
+                            lint.build(msg)
+                                .span_suggestion_short(
+                                    condition_span,
+                                    "use `loop`",
+                                    "loop".to_owned(),
+                                    Applicability::MachineApplicable,
+                                )
+                                .emit();
+                        })
+                    }
+                }
+            }
+        }
+    }
+}
+
+declare_lint! {
+    BOX_POINTERS,
+    Allow,
+    "use of owned (Box type) heap memory"
+}
+
+declare_lint_pass!(BoxPointers => [BOX_POINTERS]);
+
+impl BoxPointers {
+    fn check_heap_type(&self, cx: &LateContext<'_>, span: Span, ty: Ty<'_>) {
+        for leaf in ty.walk() {
+            if let GenericArgKind::Type(leaf_ty) = leaf.unpack() {
+                if leaf_ty.is_box() {
+                    cx.struct_span_lint(BOX_POINTERS, span, |lint| {
+                        lint.build(&format!("type uses owned (Box type) pointers: {}", ty)).emit()
+                    });
+                }
+            }
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for BoxPointers {
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        match it.kind {
+            hir::ItemKind::Fn(..)
+            | hir::ItemKind::TyAlias(..)
+            | hir::ItemKind::Enum(..)
+            | hir::ItemKind::Struct(..)
+            | hir::ItemKind::Union(..) => {
+                let def_id = cx.tcx.hir().local_def_id(it.hir_id);
+                self.check_heap_type(cx, it.span, cx.tcx.type_of(def_id))
+            }
+            _ => (),
+        }
+
+        // If it's a struct, we also have to check the fields' types
+        match it.kind {
+            hir::ItemKind::Struct(ref struct_def, _) | hir::ItemKind::Union(ref struct_def, _) => {
+                for struct_field in struct_def.fields() {
+                    let def_id = cx.tcx.hir().local_def_id(struct_field.hir_id);
+                    self.check_heap_type(cx, struct_field.span, cx.tcx.type_of(def_id));
+                }
+            }
+            _ => (),
+        }
+    }
+
+    fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) {
+        let ty = cx.typeck_results().node_type(e.hir_id);
+        self.check_heap_type(cx, e.span, ty);
+    }
+}
+
+declare_lint! {
+    NON_SHORTHAND_FIELD_PATTERNS,
+    Warn,
+    "using `Struct { x: x }` instead of `Struct { x }` in a pattern"
+}
+
+declare_lint_pass!(NonShorthandFieldPatterns => [NON_SHORTHAND_FIELD_PATTERNS]);
+
+impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns {
+    fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) {
+        if let PatKind::Struct(ref qpath, field_pats, _) = pat.kind {
+            let variant = cx
+                .typeck_results()
+                .pat_ty(pat)
+                .ty_adt_def()
+                .expect("struct pattern type is not an ADT")
+                .variant_of_res(cx.qpath_res(qpath, pat.hir_id));
+            for fieldpat in field_pats {
+                if fieldpat.is_shorthand {
+                    continue;
+                }
+                if fieldpat.span.from_expansion() {
+                    // Don't lint if this is a macro expansion: macro authors
+                    // shouldn't have to worry about this kind of style issue
+                    // (Issue #49588)
+                    continue;
+                }
+                if let PatKind::Binding(binding_annot, _, ident, None) = fieldpat.pat.kind {
+                    if cx.tcx.find_field_index(ident, &variant)
+                        == Some(cx.tcx.field_index(fieldpat.hir_id, cx.typeck_results()))
+                    {
+                        cx.struct_span_lint(NON_SHORTHAND_FIELD_PATTERNS, fieldpat.span, |lint| {
+                            let mut err = lint
+                                .build(&format!("the `{}:` in this pattern is redundant", ident));
+                            let binding = match binding_annot {
+                                hir::BindingAnnotation::Unannotated => None,
+                                hir::BindingAnnotation::Mutable => Some("mut"),
+                                hir::BindingAnnotation::Ref => Some("ref"),
+                                hir::BindingAnnotation::RefMut => Some("ref mut"),
+                            };
+                            let ident = if let Some(binding) = binding {
+                                format!("{} {}", binding, ident)
+                            } else {
+                                ident.to_string()
+                            };
+                            err.span_suggestion(
+                                fieldpat.span,
+                                "use shorthand field pattern",
+                                ident,
+                                Applicability::MachineApplicable,
+                            );
+                            err.emit();
+                        });
+                    }
+                }
+            }
+        }
+    }
+}
+
+declare_lint! {
+    UNSAFE_CODE,
+    Allow,
+    "usage of `unsafe` code"
+}
+
+declare_lint_pass!(UnsafeCode => [UNSAFE_CODE]);
+
+impl UnsafeCode {
+    fn report_unsafe(
+        &self,
+        cx: &EarlyContext<'_>,
+        span: Span,
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    ) {
+        // This comes from a macro that has `#[allow_internal_unsafe]`.
+        if span.allows_unsafe() {
+            return;
+        }
+
+        cx.struct_span_lint(UNSAFE_CODE, span, decorate);
+    }
+}
+
+impl EarlyLintPass for UnsafeCode {
+    fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
+        if cx.sess().check_name(attr, sym::allow_internal_unsafe) {
+            self.report_unsafe(cx, attr.span, |lint| {
+                lint.build(
+                    "`allow_internal_unsafe` allows defining \
+                                               macros using unsafe without triggering \
+                                               the `unsafe_code` lint at their call site",
+                )
+                .emit()
+            });
+        }
+    }
+
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+        if let ast::ExprKind::Block(ref blk, _) = e.kind {
+            // Don't warn about generated blocks; that'll just pollute the output.
+            if blk.rules == ast::BlockCheckMode::Unsafe(ast::UserProvided) {
+                self.report_unsafe(cx, blk.span, |lint| {
+                    lint.build("usage of an `unsafe` block").emit()
+                });
+            }
+        }
+    }
+
+    fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
+        match it.kind {
+            ast::ItemKind::Trait(_, ast::Unsafe::Yes(_), ..) => {
+                self.report_unsafe(cx, it.span, |lint| {
+                    lint.build("declaration of an `unsafe` trait").emit()
+                })
+            }
+
+            ast::ItemKind::Impl { unsafety: ast::Unsafe::Yes(_), .. } => {
+                self.report_unsafe(cx, it.span, |lint| {
+                    lint.build("implementation of an `unsafe` trait").emit()
+                })
+            }
+
+            _ => {}
+        }
+    }
+
+    fn check_fn(&mut self, cx: &EarlyContext<'_>, fk: FnKind<'_>, span: Span, _: ast::NodeId) {
+        if let FnKind::Fn(
+            ctxt,
+            _,
+            ast::FnSig { header: ast::FnHeader { unsafety: ast::Unsafe::Yes(_), .. }, .. },
+            _,
+            body,
+        ) = fk
+        {
+            let msg = match ctxt {
+                FnCtxt::Foreign => return,
+                FnCtxt::Free => "declaration of an `unsafe` function",
+                FnCtxt::Assoc(_) if body.is_none() => "declaration of an `unsafe` method",
+                FnCtxt::Assoc(_) => "implementation of an `unsafe` method",
+            };
+            self.report_unsafe(cx, span, |lint| lint.build(msg).emit());
+        }
+    }
+}
+
+declare_lint! {
+    pub MISSING_DOCS,
+    Allow,
+    "detects missing documentation for public members",
+    report_in_external_macro
+}
+
+pub struct MissingDoc {
+    /// Stack of whether `#[doc(hidden)]` is set at each level which has lint attributes.
+    doc_hidden_stack: Vec<bool>,
+
+    /// Private traits or trait items that leaked through. Don't check their methods.
+    private_traits: FxHashSet<hir::HirId>,
+}
+
+impl_lint_pass!(MissingDoc => [MISSING_DOCS]);
+
+fn has_doc(sess: &Session, attr: &ast::Attribute) -> bool {
+    if attr.is_doc_comment() {
+        return true;
+    }
+
+    if !sess.check_name(attr, sym::doc) {
+        return false;
+    }
+
+    if attr.is_value_str() {
+        return true;
+    }
+
+    if let Some(list) = attr.meta_item_list() {
+        for meta in list {
+            if meta.has_name(sym::include) || meta.has_name(sym::hidden) {
+                return true;
+            }
+        }
+    }
+
+    false
+}
+
+impl MissingDoc {
+    pub fn new() -> MissingDoc {
+        MissingDoc { doc_hidden_stack: vec![false], private_traits: FxHashSet::default() }
+    }
+
+    fn doc_hidden(&self) -> bool {
+        *self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
+    }
+
+    fn check_missing_docs_attrs(
+        &self,
+        cx: &LateContext<'_>,
+        id: Option<hir::HirId>,
+        attrs: &[ast::Attribute],
+        sp: Span,
+        article: &'static str,
+        desc: &'static str,
+    ) {
+        // If we're building a test harness, then warning about
+        // documentation is probably not really relevant right now.
+        if cx.sess().opts.test {
+            return;
+        }
+
+        // `#[doc(hidden)]` disables missing_docs check.
+        if self.doc_hidden() {
+            return;
+        }
+
+        // Only check publicly-visible items, using the result from the privacy pass.
+        // It's an option so the crate root can also use this function (it doesn't
+        // have a `NodeId`).
+        if let Some(id) = id {
+            if !cx.access_levels.is_exported(id) {
+                return;
+            }
+        }
+
+        let has_doc = attrs.iter().any(|a| has_doc(cx.sess(), a));
+        if !has_doc {
+            cx.struct_span_lint(
+                MISSING_DOCS,
+                cx.tcx.sess.source_map().guess_head_span(sp),
+                |lint| {
+                    lint.build(&format!("missing documentation for {} {}", article, desc)).emit()
+                },
+            );
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MissingDoc {
+    fn enter_lint_attrs(&mut self, cx: &LateContext<'_>, attrs: &[ast::Attribute]) {
+        let doc_hidden = self.doc_hidden()
+            || attrs.iter().any(|attr| {
+                cx.sess().check_name(attr, sym::doc)
+                    && match attr.meta_item_list() {
+                        None => false,
+                        Some(l) => attr::list_contains_name(&l, sym::hidden),
+                    }
+            });
+        self.doc_hidden_stack.push(doc_hidden);
+    }
+
+    fn exit_lint_attrs(&mut self, _: &LateContext<'_>, _attrs: &[ast::Attribute]) {
+        self.doc_hidden_stack.pop().expect("empty doc_hidden_stack");
+    }
+
+    fn check_crate(&mut self, cx: &LateContext<'_>, krate: &hir::Crate<'_>) {
+        self.check_missing_docs_attrs(cx, None, &krate.item.attrs, krate.item.span, "the", "crate");
+
+        for macro_def in krate.exported_macros {
+            let has_doc = macro_def.attrs.iter().any(|a| has_doc(cx.sess(), a));
+            if !has_doc {
+                cx.struct_span_lint(
+                    MISSING_DOCS,
+                    cx.tcx.sess.source_map().guess_head_span(macro_def.span),
+                    |lint| lint.build("missing documentation for macro").emit(),
+                );
+            }
+        }
+    }
+
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        match it.kind {
+            hir::ItemKind::Trait(.., trait_item_refs) => {
+                // Issue #11592: traits are always considered exported, even when private.
+                if let hir::VisibilityKind::Inherited = it.vis.node {
+                    self.private_traits.insert(it.hir_id);
+                    for trait_item_ref in trait_item_refs {
+                        self.private_traits.insert(trait_item_ref.id.hir_id);
+                    }
+                    return;
+                }
+            }
+            hir::ItemKind::Impl { of_trait: Some(ref trait_ref), items, .. } => {
+                // If the trait is private, add the impl items to `private_traits` so they don't get
+                // reported for missing docs.
+                let real_trait = trait_ref.path.res.def_id();
+                if let Some(def_id) = real_trait.as_local() {
+                    let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
+                    if let Some(Node::Item(item)) = cx.tcx.hir().find(hir_id) {
+                        if let hir::VisibilityKind::Inherited = item.vis.node {
+                            for impl_item_ref in items {
+                                self.private_traits.insert(impl_item_ref.id.hir_id);
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+
+            hir::ItemKind::TyAlias(..)
+            | hir::ItemKind::Fn(..)
+            | hir::ItemKind::Mod(..)
+            | hir::ItemKind::Enum(..)
+            | hir::ItemKind::Struct(..)
+            | hir::ItemKind::Union(..)
+            | hir::ItemKind::Const(..)
+            | hir::ItemKind::Static(..) => {}
+
+            _ => return,
+        };
+
+        let def_id = cx.tcx.hir().local_def_id(it.hir_id);
+        let (article, desc) = cx.tcx.article_and_description(def_id.to_def_id());
+
+        self.check_missing_docs_attrs(cx, Some(it.hir_id), &it.attrs, it.span, article, desc);
+    }
+
+    fn check_trait_item(&mut self, cx: &LateContext<'_>, trait_item: &hir::TraitItem<'_>) {
+        if self.private_traits.contains(&trait_item.hir_id) {
+            return;
+        }
+
+        let def_id = cx.tcx.hir().local_def_id(trait_item.hir_id);
+        let (article, desc) = cx.tcx.article_and_description(def_id.to_def_id());
+
+        self.check_missing_docs_attrs(
+            cx,
+            Some(trait_item.hir_id),
+            &trait_item.attrs,
+            trait_item.span,
+            article,
+            desc,
+        );
+    }
+
+    fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
+        // If the method is an impl for a trait, don't doc.
+        if method_context(cx, impl_item.hir_id) == MethodLateContext::TraitImpl {
+            return;
+        }
+
+        let def_id = cx.tcx.hir().local_def_id(impl_item.hir_id);
+        let (article, desc) = cx.tcx.article_and_description(def_id.to_def_id());
+        self.check_missing_docs_attrs(
+            cx,
+            Some(impl_item.hir_id),
+            &impl_item.attrs,
+            impl_item.span,
+            article,
+            desc,
+        );
+    }
+
+    fn check_struct_field(&mut self, cx: &LateContext<'_>, sf: &hir::StructField<'_>) {
+        if !sf.is_positional() {
+            self.check_missing_docs_attrs(
+                cx,
+                Some(sf.hir_id),
+                &sf.attrs,
+                sf.span,
+                "a",
+                "struct field",
+            )
+        }
+    }
+
+    fn check_variant(&mut self, cx: &LateContext<'_>, v: &hir::Variant<'_>) {
+        self.check_missing_docs_attrs(cx, Some(v.id), &v.attrs, v.span, "a", "variant");
+    }
+}
+
+declare_lint! {
+    pub MISSING_COPY_IMPLEMENTATIONS,
+    Allow,
+    "detects potentially-forgotten implementations of `Copy`"
+}
+
+declare_lint_pass!(MissingCopyImplementations => [MISSING_COPY_IMPLEMENTATIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        if !cx.access_levels.is_reachable(item.hir_id) {
+            return;
+        }
+        let (def, ty) = match item.kind {
+            hir::ItemKind::Struct(_, ref ast_generics) => {
+                if !ast_generics.params.is_empty() {
+                    return;
+                }
+                let def = cx.tcx.adt_def(cx.tcx.hir().local_def_id(item.hir_id));
+                (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[])))
+            }
+            hir::ItemKind::Union(_, ref ast_generics) => {
+                if !ast_generics.params.is_empty() {
+                    return;
+                }
+                let def = cx.tcx.adt_def(cx.tcx.hir().local_def_id(item.hir_id));
+                (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[])))
+            }
+            hir::ItemKind::Enum(_, ref ast_generics) => {
+                if !ast_generics.params.is_empty() {
+                    return;
+                }
+                let def = cx.tcx.adt_def(cx.tcx.hir().local_def_id(item.hir_id));
+                (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[])))
+            }
+            _ => return,
+        };
+        if def.has_dtor(cx.tcx) {
+            return;
+        }
+        let param_env = ty::ParamEnv::empty();
+        if ty.is_copy_modulo_regions(cx.tcx.at(item.span), param_env) {
+            return;
+        }
+        if can_type_implement_copy(cx.tcx, param_env, ty).is_ok() {
+            cx.struct_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, |lint| {
+                lint.build(
+                    "type could implement `Copy`; consider adding `impl \
+                          Copy`",
+                )
+                .emit()
+            })
+        }
+    }
+}
+
+declare_lint! {
+    MISSING_DEBUG_IMPLEMENTATIONS,
+    Allow,
+    "detects missing implementations of Debug"
+}
+
+#[derive(Default)]
+pub struct MissingDebugImplementations {
+    impling_types: Option<HirIdSet>,
+}
+
+impl_lint_pass!(MissingDebugImplementations => [MISSING_DEBUG_IMPLEMENTATIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        if !cx.access_levels.is_reachable(item.hir_id) {
+            return;
+        }
+
+        match item.kind {
+            hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {}
+            _ => return,
+        }
+
+        let debug = match cx.tcx.get_diagnostic_item(sym::debug_trait) {
+            Some(debug) => debug,
+            None => return,
+        };
+
+        if self.impling_types.is_none() {
+            let mut impls = HirIdSet::default();
+            cx.tcx.for_each_impl(debug, |d| {
+                if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() {
+                    if let Some(def_id) = ty_def.did.as_local() {
+                        impls.insert(cx.tcx.hir().local_def_id_to_hir_id(def_id));
+                    }
+                }
+            });
+
+            self.impling_types = Some(impls);
+            debug!("{:?}", self.impling_types);
+        }
+
+        if !self.impling_types.as_ref().unwrap().contains(&item.hir_id) {
+            cx.struct_span_lint(MISSING_DEBUG_IMPLEMENTATIONS, item.span, |lint| {
+                lint.build(&format!(
+                    "type does not implement `{}`; consider adding `#[derive(Debug)]` \
+                     or a manual implementation",
+                    cx.tcx.def_path_str(debug)
+                ))
+                .emit()
+            });
+        }
+    }
+}
+
+declare_lint! {
+    pub ANONYMOUS_PARAMETERS,
+    Allow,
+    "detects anonymous parameters",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #41686 <https://github.com/rust-lang/rust/issues/41686>",
+        edition: Some(Edition::Edition2018),
+    };
+}
+
+declare_lint_pass!(
+    /// Checks for use of anonymous parameters (RFC 1685).
+    AnonymousParameters => [ANONYMOUS_PARAMETERS]
+);
+
+impl EarlyLintPass for AnonymousParameters {
+    fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
+        if let ast::AssocItemKind::Fn(_, ref sig, _, _) = it.kind {
+            for arg in sig.decl.inputs.iter() {
+                if let ast::PatKind::Ident(_, ident, None) = arg.pat.kind {
+                    if ident.name == kw::Invalid {
+                        cx.struct_span_lint(ANONYMOUS_PARAMETERS, arg.pat.span, |lint| {
+                            let ty_snip = cx.sess.source_map().span_to_snippet(arg.ty.span);
+
+                            let (ty_snip, appl) = if let Ok(ref snip) = ty_snip {
+                                (snip.as_str(), Applicability::MachineApplicable)
+                            } else {
+                                ("<type>", Applicability::HasPlaceholders)
+                            };
+
+                            lint.build(
+                                "anonymous parameters are deprecated and will be \
+                                     removed in the next edition.",
+                            )
+                            .span_suggestion(
+                                arg.pat.span,
+                                "try naming the parameter or explicitly \
+                                            ignoring it",
+                                format!("_: {}", ty_snip),
+                                appl,
+                            )
+                            .emit();
+                        })
+                    }
+                }
+            }
+        }
+    }
+}
+
+/// Check for use of attributes which have been deprecated.
+#[derive(Clone)]
+pub struct DeprecatedAttr {
+    // This is not free to compute, so we want to keep it around, rather than
+    // compute it for every attribute.
+    depr_attrs: Vec<&'static (Symbol, AttributeType, AttributeTemplate, AttributeGate)>,
+}
+
+impl_lint_pass!(DeprecatedAttr => []);
+
+impl DeprecatedAttr {
+    pub fn new() -> DeprecatedAttr {
+        DeprecatedAttr { depr_attrs: deprecated_attributes() }
+    }
+}
+
+fn lint_deprecated_attr(
+    cx: &EarlyContext<'_>,
+    attr: &ast::Attribute,
+    msg: &str,
+    suggestion: Option<&str>,
+) {
+    cx.struct_span_lint(DEPRECATED, attr.span, |lint| {
+        lint.build(msg)
+            .span_suggestion_short(
+                attr.span,
+                suggestion.unwrap_or("remove this attribute"),
+                String::new(),
+                Applicability::MachineApplicable,
+            )
+            .emit();
+    })
+}
+
+impl EarlyLintPass for DeprecatedAttr {
+    fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
+        for &&(n, _, _, ref g) in &self.depr_attrs {
+            if attr.ident().map(|ident| ident.name) == Some(n) {
+                if let &AttributeGate::Gated(
+                    Stability::Deprecated(link, suggestion),
+                    ref name,
+                    ref reason,
+                    _,
+                ) = g
+                {
+                    let msg =
+                        format!("use of deprecated attribute `{}`: {}. See {}", name, reason, link);
+                    lint_deprecated_attr(cx, attr, &msg, suggestion);
+                }
+                return;
+            }
+        }
+        if cx.sess().check_name(attr, sym::no_start) || cx.sess().check_name(attr, sym::crate_id) {
+            let path_str = pprust::path_to_string(&attr.get_normal_item().path);
+            let msg = format!("use of deprecated attribute `{}`: no longer used.", path_str);
+            lint_deprecated_attr(cx, attr, &msg, None);
+        }
+    }
+}
+
+fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &[ast::Attribute]) {
+    let mut attrs = attrs.iter().peekable();
+
+    // Accumulate a single span for sugared doc comments.
+    let mut sugared_span: Option<Span> = None;
+
+    while let Some(attr) = attrs.next() {
+        if attr.is_doc_comment() {
+            sugared_span =
+                Some(sugared_span.map_or_else(|| attr.span, |span| span.with_hi(attr.span.hi())));
+        }
+
+        if attrs.peek().map(|next_attr| next_attr.is_doc_comment()).unwrap_or_default() {
+            continue;
+        }
+
+        let span = sugared_span.take().unwrap_or_else(|| attr.span);
+
+        if attr.is_doc_comment() || cx.sess().check_name(attr, sym::doc) {
+            cx.struct_span_lint(UNUSED_DOC_COMMENTS, span, |lint| {
+                let mut err = lint.build("unused doc comment");
+                err.span_label(
+                    node_span,
+                    format!("rustdoc does not generate documentation for {}", node_kind),
+                );
+                err.emit();
+            });
+        }
+    }
+}
+
+impl EarlyLintPass for UnusedDocComment {
+    fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
+        let kind = match stmt.kind {
+            ast::StmtKind::Local(..) => "statements",
+            ast::StmtKind::Item(..) => "inner items",
+            // expressions will be reported by `check_expr`.
+            ast::StmtKind::Empty
+            | ast::StmtKind::Semi(_)
+            | ast::StmtKind::Expr(_)
+            | ast::StmtKind::MacCall(_) => return,
+        };
+
+        warn_if_doc(cx, stmt.span, kind, stmt.kind.attrs());
+    }
+
+    fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
+        let arm_span = arm.pat.span.with_hi(arm.body.span.hi());
+        warn_if_doc(cx, arm_span, "match arms", &arm.attrs);
+    }
+
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+        warn_if_doc(cx, expr.span, "expressions", &expr.attrs);
+    }
+}
+
+declare_lint! {
+    NO_MANGLE_CONST_ITEMS,
+    Deny,
+    "const items will not have their symbols exported"
+}
+
+declare_lint! {
+    NO_MANGLE_GENERIC_ITEMS,
+    Warn,
+    "generic items must be mangled"
+}
+
+declare_lint_pass!(InvalidNoMangleItems => [NO_MANGLE_CONST_ITEMS, NO_MANGLE_GENERIC_ITEMS]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        match it.kind {
+            hir::ItemKind::Fn(.., ref generics, _) => {
+                if let Some(no_mangle_attr) = cx.sess().find_by_name(&it.attrs, sym::no_mangle) {
+                    for param in generics.params {
+                        match param.kind {
+                            GenericParamKind::Lifetime { .. } => {}
+                            GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => {
+                                cx.struct_span_lint(NO_MANGLE_GENERIC_ITEMS, it.span, |lint| {
+                                    lint.build(
+                                        "functions generic over types or consts must be mangled",
+                                    )
+                                    .span_suggestion_short(
+                                        no_mangle_attr.span,
+                                        "remove this attribute",
+                                        String::new(),
+                                        // Use of `#[no_mangle]` suggests FFI intent; correct
+                                        // fix may be to monomorphize source by hand
+                                        Applicability::MaybeIncorrect,
+                                    )
+                                    .emit();
+                                });
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+            hir::ItemKind::Const(..) => {
+                if cx.sess().contains_name(&it.attrs, sym::no_mangle) {
+                    // Const items do not refer to a particular location in memory, and therefore
+                    // don't have anything to attach a symbol to
+                    cx.struct_span_lint(NO_MANGLE_CONST_ITEMS, it.span, |lint| {
+                        let msg = "const items should never be `#[no_mangle]`";
+                        let mut err = lint.build(msg);
+
+                        // account for "pub const" (#45562)
+                        let start = cx
+                            .tcx
+                            .sess
+                            .source_map()
+                            .span_to_snippet(it.span)
+                            .map(|snippet| snippet.find("const").unwrap_or(0))
+                            .unwrap_or(0) as u32;
+                        // `const` is 5 chars
+                        let const_span = it.span.with_hi(BytePos(it.span.lo().0 + start + 5));
+                        err.span_suggestion(
+                            const_span,
+                            "try a static value",
+                            "pub static".to_owned(),
+                            Applicability::MachineApplicable,
+                        );
+                        err.emit();
+                    });
+                }
+            }
+            _ => {}
+        }
+    }
+}
+
+declare_lint! {
+    MUTABLE_TRANSMUTES,
+    Deny,
+    "mutating transmuted &mut T from &T may cause undefined behavior"
+}
+
+declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES]);
+
+impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+        use rustc_target::spec::abi::Abi::RustIntrinsic;
+        if let Some((&ty::Ref(_, _, from_mt), &ty::Ref(_, _, to_mt))) =
+            get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (&ty1.kind, &ty2.kind))
+        {
+            if to_mt == hir::Mutability::Mut && from_mt == hir::Mutability::Not {
+                let msg = "mutating transmuted &mut T from &T may cause undefined behavior, \
+                               consider instead using an UnsafeCell";
+                cx.struct_span_lint(MUTABLE_TRANSMUTES, expr.span, |lint| lint.build(msg).emit());
+            }
+        }
+
+        fn get_transmute_from_to<'tcx>(
+            cx: &LateContext<'tcx>,
+            expr: &hir::Expr<'_>,
+        ) -> Option<(Ty<'tcx>, Ty<'tcx>)> {
+            let def = if let hir::ExprKind::Path(ref qpath) = expr.kind {
+                cx.qpath_res(qpath, expr.hir_id)
+            } else {
+                return None;
+            };
+            if let Res::Def(DefKind::Fn, did) = def {
+                if !def_id_is_transmute(cx, did) {
+                    return None;
+                }
+                let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx);
+                let from = sig.inputs().skip_binder()[0];
+                let to = sig.output().skip_binder();
+                return Some((from, to));
+            }
+            None
+        }
+
+        fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool {
+            cx.tcx.fn_sig(def_id).abi() == RustIntrinsic
+                && cx.tcx.item_name(def_id) == sym::transmute
+        }
+    }
+}
+
+declare_lint! {
+    UNSTABLE_FEATURES,
+    Allow,
+    "enabling unstable features (deprecated. do not use)"
+}
+
+declare_lint_pass!(
+    /// Forbids using the `#[feature(...)]` attribute
+    UnstableFeatures => [UNSTABLE_FEATURES]
+);
+
+impl<'tcx> LateLintPass<'tcx> for UnstableFeatures {
+    fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
+        if cx.sess().check_name(attr, sym::feature) {
+            if let Some(items) = attr.meta_item_list() {
+                for item in items {
+                    cx.struct_span_lint(UNSTABLE_FEATURES, item.span(), |lint| {
+                        lint.build("unstable feature").emit()
+                    });
+                }
+            }
+        }
+    }
+}
+
+declare_lint! {
+    pub UNREACHABLE_PUB,
+    Allow,
+    "`pub` items not reachable from crate root"
+}
+
+declare_lint_pass!(
+    /// Lint for items marked `pub` that aren't reachable from other crates.
+    UnreachablePub => [UNREACHABLE_PUB]
+);
+
+impl UnreachablePub {
+    fn perform_lint(
+        &self,
+        cx: &LateContext<'_>,
+        what: &str,
+        id: hir::HirId,
+        vis: &hir::Visibility<'_>,
+        span: Span,
+        exportable: bool,
+    ) {
+        let mut applicability = Applicability::MachineApplicable;
+        match vis.node {
+            hir::VisibilityKind::Public if !cx.access_levels.is_reachable(id) => {
+                if span.from_expansion() {
+                    applicability = Applicability::MaybeIncorrect;
+                }
+                let def_span = cx.tcx.sess.source_map().guess_head_span(span);
+                cx.struct_span_lint(UNREACHABLE_PUB, def_span, |lint| {
+                    let mut err = lint.build(&format!("unreachable `pub` {}", what));
+                    let replacement = if cx.tcx.features().crate_visibility_modifier {
+                        "crate"
+                    } else {
+                        "pub(crate)"
+                    }
+                    .to_owned();
+
+                    err.span_suggestion(
+                        vis.span,
+                        "consider restricting its visibility",
+                        replacement,
+                        applicability,
+                    );
+                    if exportable {
+                        err.help("or consider exporting it for use by other crates");
+                    }
+                    err.emit();
+                });
+            }
+            _ => {}
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnreachablePub {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        self.perform_lint(cx, "item", item.hir_id, &item.vis, item.span, true);
+    }
+
+    fn check_foreign_item(&mut self, cx: &LateContext<'_>, foreign_item: &hir::ForeignItem<'tcx>) {
+        self.perform_lint(
+            cx,
+            "item",
+            foreign_item.hir_id,
+            &foreign_item.vis,
+            foreign_item.span,
+            true,
+        );
+    }
+
+    fn check_struct_field(&mut self, cx: &LateContext<'_>, field: &hir::StructField<'_>) {
+        self.perform_lint(cx, "field", field.hir_id, &field.vis, field.span, false);
+    }
+
+    fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
+        self.perform_lint(cx, "item", impl_item.hir_id, &impl_item.vis, impl_item.span, false);
+    }
+}
+
+declare_lint! {
+    TYPE_ALIAS_BOUNDS,
+    Warn,
+    "bounds in type aliases are not enforced"
+}
+
+declare_lint_pass!(
+    /// Lint for trait and lifetime bounds in type aliases being mostly ignored.
+    /// They are relevant when using associated types, but otherwise neither checked
+    /// at definition site nor enforced at use site.
+    TypeAliasBounds => [TYPE_ALIAS_BOUNDS]
+);
+
+impl TypeAliasBounds {
+    fn is_type_variable_assoc(qpath: &hir::QPath<'_>) -> bool {
+        match *qpath {
+            hir::QPath::TypeRelative(ref ty, _) => {
+                // If this is a type variable, we found a `T::Assoc`.
+                match ty.kind {
+                    hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => match path.res {
+                        Res::Def(DefKind::TyParam, _) => true,
+                        _ => false,
+                    },
+                    _ => false,
+                }
+            }
+            hir::QPath::Resolved(..) | hir::QPath::LangItem(..) => false,
+        }
+    }
+
+    fn suggest_changing_assoc_types(ty: &hir::Ty<'_>, err: &mut DiagnosticBuilder<'_>) {
+        // Access to associates types should use `<T as Bound>::Assoc`, which does not need a
+        // bound.  Let's see if this type does that.
+
+        // We use a HIR visitor to walk the type.
+        use rustc_hir::intravisit::{self, Visitor};
+        struct WalkAssocTypes<'a, 'db> {
+            err: &'a mut DiagnosticBuilder<'db>,
+        }
+        impl<'a, 'db, 'v> Visitor<'v> for WalkAssocTypes<'a, 'db> {
+            type Map = intravisit::ErasedMap<'v>;
+
+            fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+                intravisit::NestedVisitorMap::None
+            }
+
+            fn visit_qpath(&mut self, qpath: &'v hir::QPath<'v>, id: hir::HirId, span: Span) {
+                if TypeAliasBounds::is_type_variable_assoc(qpath) {
+                    self.err.span_help(
+                        span,
+                        "use fully disambiguated paths (i.e., `<T as Trait>::Assoc`) to refer to \
+                         associated types in type aliases",
+                    );
+                }
+                intravisit::walk_qpath(self, qpath, id, span)
+            }
+        }
+
+        // Let's go for a walk!
+        let mut visitor = WalkAssocTypes { err };
+        visitor.visit_ty(ty);
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
+    fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+        let (ty, type_alias_generics) = match item.kind {
+            hir::ItemKind::TyAlias(ref ty, ref generics) => (&*ty, generics),
+            _ => return,
+        };
+        if let hir::TyKind::OpaqueDef(..) = ty.kind {
+            // Bounds are respected for `type X = impl Trait`
+            return;
+        }
+        let mut suggested_changing_assoc_types = false;
+        // There must not be a where clause
+        if !type_alias_generics.where_clause.predicates.is_empty() {
+            cx.lint(
+                TYPE_ALIAS_BOUNDS,
+                |lint| {
+                    let mut err = lint.build("where clauses are not enforced in type aliases");
+                    let spans: Vec<_> = type_alias_generics
+                        .where_clause
+                        .predicates
+                        .iter()
+                        .map(|pred| pred.span())
+                        .collect();
+                    err.set_span(spans);
+                    err.span_suggestion(
+                        type_alias_generics.where_clause.span_for_predicates_or_empty_place(),
+                        "the clause will not be checked when the type alias is used, and should be removed",
+                        String::new(),
+                        Applicability::MachineApplicable,
+                    );
+                    if !suggested_changing_assoc_types {
+                        TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err);
+                        suggested_changing_assoc_types = true;
+                    }
+                    err.emit();
+                },
+            );
+        }
+        // The parameters must not have bounds
+        for param in type_alias_generics.params.iter() {
+            let spans: Vec<_> = param.bounds.iter().map(|b| b.span()).collect();
+            let suggestion = spans
+                .iter()
+                .map(|sp| {
+                    let start = param.span.between(*sp); // Include the `:` in `T: Bound`.
+                    (start.to(*sp), String::new())
+                })
+                .collect();
+            if !spans.is_empty() {
+                cx.struct_span_lint(TYPE_ALIAS_BOUNDS, spans, |lint| {
+                    let mut err =
+                        lint.build("bounds on generic parameters are not enforced in type aliases");
+                    let msg = "the bound will not be checked when the type alias is used, \
+                                   and should be removed";
+                    err.multipart_suggestion(&msg, suggestion, Applicability::MachineApplicable);
+                    if !suggested_changing_assoc_types {
+                        TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err);
+                        suggested_changing_assoc_types = true;
+                    }
+                    err.emit();
+                });
+            }
+        }
+    }
+}
+
+declare_lint_pass!(
+    /// Lint constants that are erroneous.
+    /// Without this lint, we might not get any diagnostic if the constant is
+    /// unused within this crate, even though downstream crates can't use it
+    /// without producing an error.
+    UnusedBrokenConst => []
+);
+
+fn check_const(cx: &LateContext<'_>, body_id: hir::BodyId) {
+    let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id();
+    // trigger the query once for all constants since that will already report the errors
+    // FIXME: Use ensure here
+    let _ = cx.tcx.const_eval_poly(def_id);
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnusedBrokenConst {
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        match it.kind {
+            hir::ItemKind::Const(_, body_id) => {
+                check_const(cx, body_id);
+            }
+            hir::ItemKind::Static(_, _, body_id) => {
+                check_const(cx, body_id);
+            }
+            _ => {}
+        }
+    }
+}
+
+declare_lint! {
+    TRIVIAL_BOUNDS,
+    Warn,
+    "these bounds don't depend on an type parameters"
+}
+
+declare_lint_pass!(
+    /// Lint for trait and lifetime bounds that don't depend on type parameters
+    /// which either do nothing, or stop the item from being used.
+    TrivialConstraints => [TRIVIAL_BOUNDS]
+);
+
+impl<'tcx> LateLintPass<'tcx> for TrivialConstraints {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        use rustc_middle::ty::fold::TypeFoldable;
+        use rustc_middle::ty::PredicateAtom::*;
+
+        if cx.tcx.features().trivial_bounds {
+            let def_id = cx.tcx.hir().local_def_id(item.hir_id);
+            let predicates = cx.tcx.predicates_of(def_id);
+            for &(predicate, span) in predicates.predicates {
+                let predicate_kind_name = match predicate.skip_binders() {
+                    Trait(..) => "Trait",
+                    TypeOutlives(..) |
+                    RegionOutlives(..) => "Lifetime",
+
+                    // Ignore projections, as they can only be global
+                    // if the trait bound is global
+                    Projection(..) |
+                    // Ignore bounds that a user can't type
+                    WellFormed(..) |
+                    ObjectSafe(..) |
+                    ClosureKind(..) |
+                    Subtype(..) |
+                    ConstEvaluatable(..) |
+                    ConstEquate(..) => continue,
+                };
+                if predicate.is_global() {
+                    cx.struct_span_lint(TRIVIAL_BOUNDS, span, |lint| {
+                        lint.build(&format!(
+                            "{} bound {} does not depend on any type \
+                                or lifetime parameters",
+                            predicate_kind_name, predicate
+                        ))
+                        .emit()
+                    });
+                }
+            }
+        }
+    }
+}
+
+declare_lint_pass!(
+    /// Does nothing as a lint pass, but registers some `Lint`s
+    /// which are used by other parts of the compiler.
+    SoftLints => [
+        WHILE_TRUE,
+        BOX_POINTERS,
+        NON_SHORTHAND_FIELD_PATTERNS,
+        UNSAFE_CODE,
+        MISSING_DOCS,
+        MISSING_COPY_IMPLEMENTATIONS,
+        MISSING_DEBUG_IMPLEMENTATIONS,
+        ANONYMOUS_PARAMETERS,
+        UNUSED_DOC_COMMENTS,
+        NO_MANGLE_CONST_ITEMS,
+        NO_MANGLE_GENERIC_ITEMS,
+        MUTABLE_TRANSMUTES,
+        UNSTABLE_FEATURES,
+        UNREACHABLE_PUB,
+        TYPE_ALIAS_BOUNDS,
+        TRIVIAL_BOUNDS
+    ]
+);
+
+declare_lint! {
+    pub ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
+    Warn,
+    "`...` range patterns are deprecated"
+}
+
+#[derive(Default)]
+pub struct EllipsisInclusiveRangePatterns {
+    /// If `Some(_)`, suppress all subsequent pattern
+    /// warnings for better diagnostics.
+    node_id: Option<ast::NodeId>,
+}
+
+impl_lint_pass!(EllipsisInclusiveRangePatterns => [ELLIPSIS_INCLUSIVE_RANGE_PATTERNS]);
+
+impl EarlyLintPass for EllipsisInclusiveRangePatterns {
+    fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &ast::Pat) {
+        if self.node_id.is_some() {
+            // Don't recursively warn about patterns inside range endpoints.
+            return;
+        }
+
+        use self::ast::{PatKind, RangeSyntax::DotDotDot};
+
+        /// If `pat` is a `...` pattern, return the start and end of the range, as well as the span
+        /// corresponding to the ellipsis.
+        fn matches_ellipsis_pat(pat: &ast::Pat) -> Option<(Option<&Expr>, &Expr, Span)> {
+            match &pat.kind {
+                PatKind::Range(
+                    a,
+                    Some(b),
+                    Spanned { span, node: RangeEnd::Included(DotDotDot) },
+                ) => Some((a.as_deref(), b, *span)),
+                _ => None,
+            }
+        }
+
+        let (parenthesise, endpoints) = match &pat.kind {
+            PatKind::Ref(subpat, _) => (true, matches_ellipsis_pat(&subpat)),
+            _ => (false, matches_ellipsis_pat(pat)),
+        };
+
+        if let Some((start, end, join)) = endpoints {
+            let msg = "`...` range patterns are deprecated";
+            let suggestion = "use `..=` for an inclusive range";
+            if parenthesise {
+                self.node_id = Some(pat.id);
+                cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, pat.span, |lint| {
+                    let end = expr_to_string(&end);
+                    let replace = match start {
+                        Some(start) => format!("&({}..={})", expr_to_string(&start), end),
+                        None => format!("&(..={})", end),
+                    };
+                    lint.build(msg)
+                        .span_suggestion(
+                            pat.span,
+                            suggestion,
+                            replace,
+                            Applicability::MachineApplicable,
+                        )
+                        .emit();
+                });
+            } else {
+                cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, join, |lint| {
+                    lint.build(msg)
+                        .span_suggestion_short(
+                            join,
+                            suggestion,
+                            "..=".to_owned(),
+                            Applicability::MachineApplicable,
+                        )
+                        .emit();
+                });
+            };
+        }
+    }
+
+    fn check_pat_post(&mut self, _cx: &EarlyContext<'_>, pat: &ast::Pat) {
+        if let Some(node_id) = self.node_id {
+            if pat.id == node_id {
+                self.node_id = None
+            }
+        }
+    }
+}
+
+declare_lint! {
+    UNNAMEABLE_TEST_ITEMS,
+    Warn,
+    "detects an item that cannot be named being marked as `#[test_case]`",
+    report_in_external_macro
+}
+
+pub struct UnnameableTestItems {
+    boundary: Option<hir::HirId>, // HirId of the item under which things are not nameable
+    items_nameable: bool,
+}
+
+impl_lint_pass!(UnnameableTestItems => [UNNAMEABLE_TEST_ITEMS]);
+
+impl UnnameableTestItems {
+    pub fn new() -> Self {
+        Self { boundary: None, items_nameable: true }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnnameableTestItems {
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        if self.items_nameable {
+            if let hir::ItemKind::Mod(..) = it.kind {
+            } else {
+                self.items_nameable = false;
+                self.boundary = Some(it.hir_id);
+            }
+            return;
+        }
+
+        if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::rustc_test_marker) {
+            cx.struct_span_lint(UNNAMEABLE_TEST_ITEMS, attr.span, |lint| {
+                lint.build("cannot test inner items").emit()
+            });
+        }
+    }
+
+    fn check_item_post(&mut self, _cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        if !self.items_nameable && self.boundary == Some(it.hir_id) {
+            self.items_nameable = true;
+        }
+    }
+}
+
+declare_lint! {
+    pub KEYWORD_IDENTS,
+    Allow,
+    "detects edition keywords being used as an identifier",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>",
+        edition: Some(Edition::Edition2018),
+    };
+}
+
+declare_lint_pass!(
+    /// Check for uses of edition keywords used as an identifier.
+    KeywordIdents => [KEYWORD_IDENTS]
+);
+
+struct UnderMacro(bool);
+
+impl KeywordIdents {
+    fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: TokenStream) {
+        for tt in tokens.into_trees() {
+            match tt {
+                // Only report non-raw idents.
+                TokenTree::Token(token) => {
+                    if let Some((ident, false)) = token.ident() {
+                        self.check_ident_token(cx, UnderMacro(true), ident);
+                    }
+                }
+                TokenTree::Delimited(_, _, tts) => self.check_tokens(cx, tts),
+            }
+        }
+    }
+
+    fn check_ident_token(
+        &mut self,
+        cx: &EarlyContext<'_>,
+        UnderMacro(under_macro): UnderMacro,
+        ident: Ident,
+    ) {
+        let next_edition = match cx.sess.edition() {
+            Edition::Edition2015 => {
+                match ident.name {
+                    kw::Async | kw::Await | kw::Try => Edition::Edition2018,
+
+                    // rust-lang/rust#56327: Conservatively do not
+                    // attempt to report occurrences of `dyn` within
+                    // macro definitions or invocations, because `dyn`
+                    // can legitimately occur as a contextual keyword
+                    // in 2015 code denoting its 2018 meaning, and we
+                    // do not want rustfix to inject bugs into working
+                    // code by rewriting such occurrences.
+                    //
+                    // But if we see `dyn` outside of a macro, we know
+                    // its precise role in the parsed AST and thus are
+                    // assured this is truly an attempt to use it as
+                    // an identifier.
+                    kw::Dyn if !under_macro => Edition::Edition2018,
+
+                    _ => return,
+                }
+            }
+
+            // There are no new keywords yet for the 2018 edition and beyond.
+            _ => return,
+        };
+
+        // Don't lint `r#foo`.
+        if cx.sess.parse_sess.raw_identifier_spans.borrow().contains(&ident.span) {
+            return;
+        }
+
+        cx.struct_span_lint(KEYWORD_IDENTS, ident.span, |lint| {
+            lint.build(&format!("`{}` is a keyword in the {} edition", ident, next_edition))
+                .span_suggestion(
+                    ident.span,
+                    "you can use a raw identifier to stay compatible",
+                    format!("r#{}", ident),
+                    Applicability::MachineApplicable,
+                )
+                .emit()
+        });
+    }
+}
+
+impl EarlyLintPass for KeywordIdents {
+    fn check_mac_def(&mut self, cx: &EarlyContext<'_>, mac_def: &ast::MacroDef, _id: ast::NodeId) {
+        self.check_tokens(cx, mac_def.body.inner_tokens());
+    }
+    fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) {
+        self.check_tokens(cx, mac.args.inner_tokens());
+    }
+    fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) {
+        self.check_ident_token(cx, UnderMacro(false), ident);
+    }
+}
+
+declare_lint_pass!(ExplicitOutlivesRequirements => [EXPLICIT_OUTLIVES_REQUIREMENTS]);
+
+impl ExplicitOutlivesRequirements {
+    fn lifetimes_outliving_lifetime<'tcx>(
+        inferred_outlives: &'tcx [(ty::Predicate<'tcx>, Span)],
+        index: u32,
+    ) -> Vec<ty::Region<'tcx>> {
+        inferred_outlives
+            .iter()
+            .filter_map(|(pred, _)| match pred.skip_binders() {
+                ty::PredicateAtom::RegionOutlives(ty::OutlivesPredicate(a, b)) => match a {
+                    ty::ReEarlyBound(ebr) if ebr.index == index => Some(b),
+                    _ => None,
+                },
+                _ => None,
+            })
+            .collect()
+    }
+
+    fn lifetimes_outliving_type<'tcx>(
+        inferred_outlives: &'tcx [(ty::Predicate<'tcx>, Span)],
+        index: u32,
+    ) -> Vec<ty::Region<'tcx>> {
+        inferred_outlives
+            .iter()
+            .filter_map(|(pred, _)| match pred.skip_binders() {
+                ty::PredicateAtom::TypeOutlives(ty::OutlivesPredicate(a, b)) => {
+                    a.is_param(index).then_some(b)
+                }
+                _ => None,
+            })
+            .collect()
+    }
+
+    fn collect_outlived_lifetimes<'tcx>(
+        &self,
+        param: &'tcx hir::GenericParam<'tcx>,
+        tcx: TyCtxt<'tcx>,
+        inferred_outlives: &'tcx [(ty::Predicate<'tcx>, Span)],
+        ty_generics: &'tcx ty::Generics,
+    ) -> Vec<ty::Region<'tcx>> {
+        let index =
+            ty_generics.param_def_id_to_index[&tcx.hir().local_def_id(param.hir_id).to_def_id()];
+
+        match param.kind {
+            hir::GenericParamKind::Lifetime { .. } => {
+                Self::lifetimes_outliving_lifetime(inferred_outlives, index)
+            }
+            hir::GenericParamKind::Type { .. } => {
+                Self::lifetimes_outliving_type(inferred_outlives, index)
+            }
+            hir::GenericParamKind::Const { .. } => Vec::new(),
+        }
+    }
+
+    fn collect_outlives_bound_spans<'tcx>(
+        &self,
+        tcx: TyCtxt<'tcx>,
+        bounds: &hir::GenericBounds<'_>,
+        inferred_outlives: &[ty::Region<'tcx>],
+        infer_static: bool,
+    ) -> Vec<(usize, Span)> {
+        use rustc_middle::middle::resolve_lifetime::Region;
+
+        bounds
+            .iter()
+            .enumerate()
+            .filter_map(|(i, bound)| {
+                if let hir::GenericBound::Outlives(lifetime) = bound {
+                    let is_inferred = match tcx.named_region(lifetime.hir_id) {
+                        Some(Region::Static) if infer_static => inferred_outlives
+                            .iter()
+                            .any(|r| if let ty::ReStatic = r { true } else { false }),
+                        Some(Region::EarlyBound(index, ..)) => inferred_outlives.iter().any(|r| {
+                            if let ty::ReEarlyBound(ebr) = r { ebr.index == index } else { false }
+                        }),
+                        _ => false,
+                    };
+                    is_inferred.then_some((i, bound.span()))
+                } else {
+                    None
+                }
+            })
+            .collect()
+    }
+
+    fn consolidate_outlives_bound_spans(
+        &self,
+        lo: Span,
+        bounds: &hir::GenericBounds<'_>,
+        bound_spans: Vec<(usize, Span)>,
+    ) -> Vec<Span> {
+        if bounds.is_empty() {
+            return Vec::new();
+        }
+        if bound_spans.len() == bounds.len() {
+            let (_, last_bound_span) = bound_spans[bound_spans.len() - 1];
+            // If all bounds are inferable, we want to delete the colon, so
+            // start from just after the parameter (span passed as argument)
+            vec![lo.to(last_bound_span)]
+        } else {
+            let mut merged = Vec::new();
+            let mut last_merged_i = None;
+
+            let mut from_start = true;
+            for (i, bound_span) in bound_spans {
+                match last_merged_i {
+                    // If the first bound is inferable, our span should also eat the leading `+`.
+                    None if i == 0 => {
+                        merged.push(bound_span.to(bounds[1].span().shrink_to_lo()));
+                        last_merged_i = Some(0);
+                    }
+                    // If consecutive bounds are inferable, merge their spans
+                    Some(h) if i == h + 1 => {
+                        if let Some(tail) = merged.last_mut() {
+                            // Also eat the trailing `+` if the first
+                            // more-than-one bound is inferable
+                            let to_span = if from_start && i < bounds.len() {
+                                bounds[i + 1].span().shrink_to_lo()
+                            } else {
+                                bound_span
+                            };
+                            *tail = tail.to(to_span);
+                            last_merged_i = Some(i);
+                        } else {
+                            bug!("another bound-span visited earlier");
+                        }
+                    }
+                    _ => {
+                        // When we find a non-inferable bound, subsequent inferable bounds
+                        // won't be consecutive from the start (and we'll eat the leading
+                        // `+` rather than the trailing one)
+                        from_start = false;
+                        merged.push(bounds[i - 1].span().shrink_to_hi().to(bound_span));
+                        last_merged_i = Some(i);
+                    }
+                }
+            }
+            merged
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+        use rustc_middle::middle::resolve_lifetime::Region;
+
+        let infer_static = cx.tcx.features().infer_static_outlives_requirements;
+        let def_id = cx.tcx.hir().local_def_id(item.hir_id);
+        if let hir::ItemKind::Struct(_, ref hir_generics)
+        | hir::ItemKind::Enum(_, ref hir_generics)
+        | hir::ItemKind::Union(_, ref hir_generics) = item.kind
+        {
+            let inferred_outlives = cx.tcx.inferred_outlives_of(def_id);
+            if inferred_outlives.is_empty() {
+                return;
+            }
+
+            let ty_generics = cx.tcx.generics_of(def_id);
+
+            let mut bound_count = 0;
+            let mut lint_spans = Vec::new();
+
+            for param in hir_generics.params {
+                let has_lifetime_bounds = param.bounds.iter().any(|bound| {
+                    if let hir::GenericBound::Outlives(_) = bound { true } else { false }
+                });
+                if !has_lifetime_bounds {
+                    continue;
+                }
+
+                let relevant_lifetimes =
+                    self.collect_outlived_lifetimes(param, cx.tcx, inferred_outlives, ty_generics);
+                if relevant_lifetimes.is_empty() {
+                    continue;
+                }
+
+                let bound_spans = self.collect_outlives_bound_spans(
+                    cx.tcx,
+                    &param.bounds,
+                    &relevant_lifetimes,
+                    infer_static,
+                );
+                bound_count += bound_spans.len();
+                lint_spans.extend(self.consolidate_outlives_bound_spans(
+                    param.span.shrink_to_hi(),
+                    &param.bounds,
+                    bound_spans,
+                ));
+            }
+
+            let mut where_lint_spans = Vec::new();
+            let mut dropped_predicate_count = 0;
+            let num_predicates = hir_generics.where_clause.predicates.len();
+            for (i, where_predicate) in hir_generics.where_clause.predicates.iter().enumerate() {
+                let (relevant_lifetimes, bounds, span) = match where_predicate {
+                    hir::WherePredicate::RegionPredicate(predicate) => {
+                        if let Some(Region::EarlyBound(index, ..)) =
+                            cx.tcx.named_region(predicate.lifetime.hir_id)
+                        {
+                            (
+                                Self::lifetimes_outliving_lifetime(inferred_outlives, index),
+                                &predicate.bounds,
+                                predicate.span,
+                            )
+                        } else {
+                            continue;
+                        }
+                    }
+                    hir::WherePredicate::BoundPredicate(predicate) => {
+                        // FIXME we can also infer bounds on associated types,
+                        // and should check for them here.
+                        match predicate.bounded_ty.kind {
+                            hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => {
+                                if let Res::Def(DefKind::TyParam, def_id) = path.res {
+                                    let index = ty_generics.param_def_id_to_index[&def_id];
+                                    (
+                                        Self::lifetimes_outliving_type(inferred_outlives, index),
+                                        &predicate.bounds,
+                                        predicate.span,
+                                    )
+                                } else {
+                                    continue;
+                                }
+                            }
+                            _ => {
+                                continue;
+                            }
+                        }
+                    }
+                    _ => continue,
+                };
+                if relevant_lifetimes.is_empty() {
+                    continue;
+                }
+
+                let bound_spans = self.collect_outlives_bound_spans(
+                    cx.tcx,
+                    bounds,
+                    &relevant_lifetimes,
+                    infer_static,
+                );
+                bound_count += bound_spans.len();
+
+                let drop_predicate = bound_spans.len() == bounds.len();
+                if drop_predicate {
+                    dropped_predicate_count += 1;
+                }
+
+                // If all the bounds on a predicate were inferable and there are
+                // further predicates, we want to eat the trailing comma.
+                if drop_predicate && i + 1 < num_predicates {
+                    let next_predicate_span = hir_generics.where_clause.predicates[i + 1].span();
+                    where_lint_spans.push(span.to(next_predicate_span.shrink_to_lo()));
+                } else {
+                    where_lint_spans.extend(self.consolidate_outlives_bound_spans(
+                        span.shrink_to_lo(),
+                        bounds,
+                        bound_spans,
+                    ));
+                }
+            }
+
+            // If all predicates are inferable, drop the entire clause
+            // (including the `where`)
+            if num_predicates > 0 && dropped_predicate_count == num_predicates {
+                let where_span = hir_generics
+                    .where_clause
+                    .span()
+                    .expect("span of (nonempty) where clause should exist");
+                // Extend the where clause back to the closing `>` of the
+                // generics, except for tuple struct, which have the `where`
+                // after the fields of the struct.
+                let full_where_span =
+                    if let hir::ItemKind::Struct(hir::VariantData::Tuple(..), _) = item.kind {
+                        where_span
+                    } else {
+                        hir_generics.span.shrink_to_hi().to(where_span)
+                    };
+                lint_spans.push(full_where_span);
+            } else {
+                lint_spans.extend(where_lint_spans);
+            }
+
+            if !lint_spans.is_empty() {
+                cx.struct_span_lint(EXPLICIT_OUTLIVES_REQUIREMENTS, lint_spans.clone(), |lint| {
+                    lint.build("outlives requirements can be inferred")
+                        .multipart_suggestion(
+                            if bound_count == 1 {
+                                "remove this bound"
+                            } else {
+                                "remove these bounds"
+                            },
+                            lint_spans
+                                .into_iter()
+                                .map(|span| (span, "".to_owned()))
+                                .collect::<Vec<_>>(),
+                            Applicability::MachineApplicable,
+                        )
+                        .emit();
+                });
+            }
+        }
+    }
+}
+
+declare_lint! {
+    pub INCOMPLETE_FEATURES,
+    Warn,
+    "incomplete features that may function improperly in some or all cases"
+}
+
+declare_lint_pass!(
+    /// Check for used feature gates in `INCOMPLETE_FEATURES` in `librustc_feature/active.rs`.
+    IncompleteFeatures => [INCOMPLETE_FEATURES]
+);
+
+impl EarlyLintPass for IncompleteFeatures {
+    fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
+        let features = cx.sess.features_untracked();
+        features
+            .declared_lang_features
+            .iter()
+            .map(|(name, span, _)| (name, span))
+            .chain(features.declared_lib_features.iter().map(|(name, span)| (name, span)))
+            .filter(|(name, _)| rustc_feature::INCOMPLETE_FEATURES.iter().any(|f| name == &f))
+            .for_each(|(&name, &span)| {
+                cx.struct_span_lint(INCOMPLETE_FEATURES, span, |lint| {
+                    let mut builder = lint.build(&format!(
+                        "the feature `{}` is incomplete and may not be safe to use \
+                         and/or cause compiler crashes",
+                        name,
+                    ));
+                    if let Some(n) = rustc_feature::find_feature_issue(name, GateIssue::Language) {
+                        builder.note(&format!(
+                            "see issue #{} <https://github.com/rust-lang/rust/issues/{}> \
+                             for more information",
+                            n, n,
+                        ));
+                    }
+                    builder.emit();
+                })
+            });
+    }
+}
+
+declare_lint! {
+    pub INVALID_VALUE,
+    Warn,
+    "an invalid value is being created (such as a NULL reference)"
+}
+
+declare_lint_pass!(InvalidValue => [INVALID_VALUE]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidValue {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
+        #[derive(Debug, Copy, Clone, PartialEq)]
+        enum InitKind {
+            Zeroed,
+            Uninit,
+        };
+
+        /// Information about why a type cannot be initialized this way.
+        /// Contains an error message and optionally a span to point at.
+        type InitError = (String, Option<Span>);
+
+        /// Test if this constant is all-0.
+        fn is_zero(expr: &hir::Expr<'_>) -> bool {
+            use hir::ExprKind::*;
+            use rustc_ast::LitKind::*;
+            match &expr.kind {
+                Lit(lit) => {
+                    if let Int(i, _) = lit.node {
+                        i == 0
+                    } else {
+                        false
+                    }
+                }
+                Tup(tup) => tup.iter().all(is_zero),
+                _ => false,
+            }
+        }
+
+        /// Determine if this expression is a "dangerous initialization".
+        fn is_dangerous_init(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<InitKind> {
+            // `transmute` is inside an anonymous module (the `extern` block?);
+            // `Invalid` represents the empty string and matches that.
+            // FIXME(#66075): use diagnostic items.  Somehow, that does not seem to work
+            // on intrinsics right now.
+            const TRANSMUTE_PATH: &[Symbol] =
+                &[sym::core, sym::intrinsics, kw::Invalid, sym::transmute];
+
+            if let hir::ExprKind::Call(ref path_expr, ref args) = expr.kind {
+                // Find calls to `mem::{uninitialized,zeroed}` methods.
+                if let hir::ExprKind::Path(ref qpath) = path_expr.kind {
+                    let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?;
+
+                    if cx.tcx.is_diagnostic_item(sym::mem_zeroed, def_id) {
+                        return Some(InitKind::Zeroed);
+                    } else if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, def_id) {
+                        return Some(InitKind::Uninit);
+                    } else if cx.match_def_path(def_id, TRANSMUTE_PATH) {
+                        if is_zero(&args[0]) {
+                            return Some(InitKind::Zeroed);
+                        }
+                    }
+                }
+            } else if let hir::ExprKind::MethodCall(_, _, ref args, _) = expr.kind {
+                // Find problematic calls to `MaybeUninit::assume_init`.
+                let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
+                if cx.tcx.is_diagnostic_item(sym::assume_init, def_id) {
+                    // This is a call to *some* method named `assume_init`.
+                    // See if the `self` parameter is one of the dangerous constructors.
+                    if let hir::ExprKind::Call(ref path_expr, _) = args[0].kind {
+                        if let hir::ExprKind::Path(ref qpath) = path_expr.kind {
+                            let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?;
+
+                            if cx.tcx.is_diagnostic_item(sym::maybe_uninit_zeroed, def_id) {
+                                return Some(InitKind::Zeroed);
+                            } else if cx.tcx.is_diagnostic_item(sym::maybe_uninit_uninit, def_id) {
+                                return Some(InitKind::Uninit);
+                            }
+                        }
+                    }
+                }
+            }
+
+            None
+        }
+
+        /// Test if this enum has several actually "existing" variants.
+        /// Zero-sized uninhabited variants do not always have a tag assigned and thus do not "exist".
+        fn is_multi_variant(adt: &ty::AdtDef) -> bool {
+            // As an approximation, we only count dataless variants. Those are definitely inhabited.
+            let existing_variants = adt.variants.iter().filter(|v| v.fields.is_empty()).count();
+            existing_variants > 1
+        }
+
+        /// Return `Some` only if we are sure this type does *not*
+        /// allow zero initialization.
+        fn ty_find_init_error<'tcx>(
+            tcx: TyCtxt<'tcx>,
+            ty: Ty<'tcx>,
+            init: InitKind,
+        ) -> Option<InitError> {
+            use rustc_middle::ty::TyKind::*;
+            match ty.kind {
+                // Primitive types that don't like 0 as a value.
+                Ref(..) => Some(("references must be non-null".to_string(), None)),
+                Adt(..) if ty.is_box() => Some(("`Box` must be non-null".to_string(), None)),
+                FnPtr(..) => Some(("function pointers must be non-null".to_string(), None)),
+                Never => Some(("the `!` type has no valid value".to_string(), None)),
+                RawPtr(tm) if matches!(tm.ty.kind, Dynamic(..)) =>
+                // raw ptr to dyn Trait
+                {
+                    Some(("the vtable of a wide raw pointer must be non-null".to_string(), None))
+                }
+                // Primitive types with other constraints.
+                Bool if init == InitKind::Uninit => {
+                    Some(("booleans must be either `true` or `false`".to_string(), None))
+                }
+                Char if init == InitKind::Uninit => {
+                    Some(("characters must be a valid Unicode codepoint".to_string(), None))
+                }
+                // Recurse and checks for some compound types.
+                Adt(adt_def, substs) if !adt_def.is_union() => {
+                    // First check if this ADT has a layout attribute (like `NonNull` and friends).
+                    use std::ops::Bound;
+                    match tcx.layout_scalar_valid_range(adt_def.did) {
+                        // We exploit here that `layout_scalar_valid_range` will never
+                        // return `Bound::Excluded`.  (And we have tests checking that we
+                        // handle the attribute correctly.)
+                        (Bound::Included(lo), _) if lo > 0 => {
+                            return Some((format!("`{}` must be non-null", ty), None));
+                        }
+                        (Bound::Included(_), _) | (_, Bound::Included(_))
+                            if init == InitKind::Uninit =>
+                        {
+                            return Some((
+                                format!(
+                                    "`{}` must be initialized inside its custom valid range",
+                                    ty,
+                                ),
+                                None,
+                            ));
+                        }
+                        _ => {}
+                    }
+                    // Now, recurse.
+                    match adt_def.variants.len() {
+                        0 => Some(("enums with no variants have no valid value".to_string(), None)),
+                        1 => {
+                            // Struct, or enum with exactly one variant.
+                            // Proceed recursively, check all fields.
+                            let variant = &adt_def.variants[VariantIdx::from_u32(0)];
+                            variant.fields.iter().find_map(|field| {
+                                ty_find_init_error(tcx, field.ty(tcx, substs), init).map(
+                                    |(mut msg, span)| {
+                                        if span.is_none() {
+                                            // Point to this field, should be helpful for figuring
+                                            // out where the source of the error is.
+                                            let span = tcx.def_span(field.did);
+                                            write!(
+                                                &mut msg,
+                                                " (in this {} field)",
+                                                adt_def.descr()
+                                            )
+                                            .unwrap();
+                                            (msg, Some(span))
+                                        } else {
+                                            // Just forward.
+                                            (msg, span)
+                                        }
+                                    },
+                                )
+                            })
+                        }
+                        // Multi-variant enum.
+                        _ => {
+                            if init == InitKind::Uninit && is_multi_variant(adt_def) {
+                                let span = tcx.def_span(adt_def.did);
+                                Some((
+                                    "enums have to be initialized to a variant".to_string(),
+                                    Some(span),
+                                ))
+                            } else {
+                                // In principle, for zero-initialization we could figure out which variant corresponds
+                                // to tag 0, and check that... but for now we just accept all zero-initializations.
+                                None
+                            }
+                        }
+                    }
+                }
+                Tuple(..) => {
+                    // Proceed recursively, check all fields.
+                    ty.tuple_fields().find_map(|field| ty_find_init_error(tcx, field, init))
+                }
+                // Conservative fallback.
+                _ => None,
+            }
+        }
+
+        if let Some(init) = is_dangerous_init(cx, expr) {
+            // This conjures an instance of a type out of nothing,
+            // using zeroed or uninitialized memory.
+            // We are extremely conservative with what we warn about.
+            let conjured_ty = cx.typeck_results().expr_ty(expr);
+            if let Some((msg, span)) = ty_find_init_error(cx.tcx, conjured_ty, init) {
+                cx.struct_span_lint(INVALID_VALUE, expr.span, |lint| {
+                    let mut err = lint.build(&format!(
+                        "the type `{}` does not permit {}",
+                        conjured_ty,
+                        match init {
+                            InitKind::Zeroed => "zero-initialization",
+                            InitKind::Uninit => "being left uninitialized",
+                        },
+                    ));
+                    err.span_label(expr.span, "this code causes undefined behavior when executed");
+                    err.span_label(
+                        expr.span,
+                        "help: use `MaybeUninit<T>` instead, \
+                            and only call `assume_init` after initialization is done",
+                    );
+                    if let Some(span) = span {
+                        err.span_note(span, &msg);
+                    } else {
+                        err.note(&msg);
+                    }
+                    err.emit();
+                });
+            }
+        }
+    }
+}
+
+declare_lint! {
+    pub CLASHING_EXTERN_DECLARATIONS,
+    Warn,
+    "detects when an extern fn has been declared with the same name but different types"
+}
+
+pub struct ClashingExternDeclarations {
+    seen_decls: FxHashMap<Symbol, HirId>,
+}
+
+/// Differentiate between whether the name for an extern decl came from the link_name attribute or
+/// just from declaration itself. This is important because we don't want to report clashes on
+/// symbol name if they don't actually clash because one or the other links against a symbol with a
+/// different name.
+enum SymbolName {
+    /// The name of the symbol + the span of the annotation which introduced the link name.
+    Link(Symbol, Span),
+    /// No link name, so just the name of the symbol.
+    Normal(Symbol),
+}
+
+impl SymbolName {
+    fn get_name(&self) -> Symbol {
+        match self {
+            SymbolName::Link(s, _) | SymbolName::Normal(s) => *s,
+        }
+    }
+}
+
+impl ClashingExternDeclarations {
+    crate fn new() -> Self {
+        ClashingExternDeclarations { seen_decls: FxHashMap::default() }
+    }
+    /// Insert a new foreign item into the seen set. If a symbol with the same name already exists
+    /// for the item, return its HirId without updating the set.
+    fn insert(&mut self, tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> Option<HirId> {
+        let hid = fi.hir_id;
+
+        let name =
+            &tcx.codegen_fn_attrs(tcx.hir().local_def_id(hid)).link_name.unwrap_or(fi.ident.name);
+
+        if self.seen_decls.contains_key(name) {
+            // Avoid updating the map with the new entry when we do find a collision. We want to
+            // make sure we're always pointing to the first definition as the previous declaration.
+            // This lets us avoid emitting "knock-on" diagnostics.
+            Some(*self.seen_decls.get(name).unwrap())
+        } else {
+            self.seen_decls.insert(*name, hid)
+        }
+    }
+
+    /// Get the name of the symbol that's linked against for a given extern declaration. That is,
+    /// the name specified in a #[link_name = ...] attribute if one was specified, else, just the
+    /// symbol's name.
+    fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> SymbolName {
+        let did = tcx.hir().local_def_id(fi.hir_id);
+        if let Some((overridden_link_name, overridden_link_name_span)) =
+            tcx.codegen_fn_attrs(did).link_name.map(|overridden_link_name| {
+                // FIXME: Instead of searching through the attributes again to get span
+                // information, we could have codegen_fn_attrs also give span information back for
+                // where the attribute was defined. However, until this is found to be a
+                // bottleneck, this does just fine.
+                (
+                    overridden_link_name,
+                    tcx.get_attrs(did.to_def_id())
+                        .iter()
+                        .find(|at| tcx.sess.check_name(at, sym::link_name))
+                        .unwrap()
+                        .span,
+                )
+            })
+        {
+            SymbolName::Link(overridden_link_name, overridden_link_name_span)
+        } else {
+            SymbolName::Normal(fi.ident.name)
+        }
+    }
+
+    /// Checks whether two types are structurally the same enough that the declarations shouldn't
+    /// clash. We need this so we don't emit a lint when two modules both declare an extern struct,
+    /// with the same members (as the declarations shouldn't clash).
+    fn structurally_same_type<'tcx>(
+        cx: &LateContext<'tcx>,
+        a: Ty<'tcx>,
+        b: Ty<'tcx>,
+        ckind: CItemKind,
+    ) -> bool {
+        fn structurally_same_type_impl<'tcx>(
+            seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>,
+            cx: &LateContext<'tcx>,
+            a: Ty<'tcx>,
+            b: Ty<'tcx>,
+            ckind: CItemKind,
+        ) -> bool {
+            debug!("structurally_same_type_impl(cx, a = {:?}, b = {:?})", a, b);
+            let tcx = cx.tcx;
+
+            // Given a transparent newtype, reach through and grab the inner
+            // type unless the newtype makes the type non-null.
+            let non_transparent_ty = |ty: Ty<'tcx>| -> Ty<'tcx> {
+                let mut ty = ty;
+                loop {
+                    if let ty::Adt(def, substs) = ty.kind {
+                        let is_transparent = def.subst(tcx, substs).repr.transparent();
+                        let is_non_null = crate::types::nonnull_optimization_guaranteed(tcx, &def);
+                        debug!(
+                            "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}",
+                            ty, is_transparent, is_non_null
+                        );
+                        if is_transparent && !is_non_null {
+                            debug_assert!(def.variants.len() == 1);
+                            let v = &def.variants[VariantIdx::new(0)];
+                            ty = v
+                                .transparent_newtype_field(tcx)
+                                .expect(
+                                    "single-variant transparent structure with zero-sized field",
+                                )
+                                .ty(tcx, substs);
+                            continue;
+                        }
+                    }
+                    debug!("non_transparent_ty -> {:?}", ty);
+                    return ty;
+                }
+            };
+
+            let a = non_transparent_ty(a);
+            let b = non_transparent_ty(b);
+
+            if !seen_types.insert((a, b)) {
+                // We've encountered a cycle. There's no point going any further -- the types are
+                // structurally the same.
+                return true;
+            }
+            let tcx = cx.tcx;
+            if a == b || rustc_middle::ty::TyS::same_type(a, b) {
+                // All nominally-same types are structurally same, too.
+                true
+            } else {
+                // Do a full, depth-first comparison between the two.
+                use rustc_middle::ty::TyKind::*;
+                let a_kind = &a.kind;
+                let b_kind = &b.kind;
+
+                let compare_layouts = |a, b| -> Result<bool, LayoutError<'tcx>> {
+                    debug!("compare_layouts({:?}, {:?})", a, b);
+                    let a_layout = &cx.layout_of(a)?.layout.abi;
+                    let b_layout = &cx.layout_of(b)?.layout.abi;
+                    debug!(
+                        "comparing layouts: {:?} == {:?} = {}",
+                        a_layout,
+                        b_layout,
+                        a_layout == b_layout
+                    );
+                    Ok(a_layout == b_layout)
+                };
+
+                #[allow(rustc::usage_of_ty_tykind)]
+                let is_primitive_or_pointer = |kind: &ty::TyKind<'_>| {
+                    kind.is_primitive() || matches!(kind, RawPtr(..) | Ref(..))
+                };
+
+                ensure_sufficient_stack(|| {
+                    match (a_kind, b_kind) {
+                        (Adt(a_def, a_substs), Adt(b_def, b_substs)) => {
+                            let a = a.subst(cx.tcx, a_substs);
+                            let b = b.subst(cx.tcx, b_substs);
+                            debug!("Comparing {:?} and {:?}", a, b);
+
+                            // We can immediately rule out these types as structurally same if
+                            // their layouts differ.
+                            match compare_layouts(a, b) {
+                                Ok(false) => return false,
+                                _ => (), // otherwise, continue onto the full, fields comparison
+                            }
+
+                            // Grab a flattened representation of all fields.
+                            let a_fields = a_def.variants.iter().flat_map(|v| v.fields.iter());
+                            let b_fields = b_def.variants.iter().flat_map(|v| v.fields.iter());
+
+                            // Perform a structural comparison for each field.
+                            a_fields.eq_by(
+                                b_fields,
+                                |&ty::FieldDef { did: a_did, .. },
+                                 &ty::FieldDef { did: b_did, .. }| {
+                                    structurally_same_type_impl(
+                                        seen_types,
+                                        cx,
+                                        tcx.type_of(a_did),
+                                        tcx.type_of(b_did),
+                                        ckind,
+                                    )
+                                },
+                            )
+                        }
+                        (Array(a_ty, a_const), Array(b_ty, b_const)) => {
+                            // For arrays, we also check the constness of the type.
+                            a_const.val == b_const.val
+                                && structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind)
+                        }
+                        (Slice(a_ty), Slice(b_ty)) => {
+                            structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind)
+                        }
+                        (RawPtr(a_tymut), RawPtr(b_tymut)) => {
+                            a_tymut.mutbl == b_tymut.mutbl
+                                && structurally_same_type_impl(
+                                    seen_types,
+                                    cx,
+                                    &a_tymut.ty,
+                                    &b_tymut.ty,
+                                    ckind,
+                                )
+                        }
+                        (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => {
+                            // For structural sameness, we don't need the region to be same.
+                            a_mut == b_mut
+                                && structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind)
+                        }
+                        (FnDef(..), FnDef(..)) => {
+                            let a_poly_sig = a.fn_sig(tcx);
+                            let b_poly_sig = b.fn_sig(tcx);
+
+                            // As we don't compare regions, skip_binder is fine.
+                            let a_sig = a_poly_sig.skip_binder();
+                            let b_sig = b_poly_sig.skip_binder();
+
+                            (a_sig.abi, a_sig.unsafety, a_sig.c_variadic)
+                                == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic)
+                                && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
+                                    structurally_same_type_impl(seen_types, cx, a, b, ckind)
+                                })
+                                && structurally_same_type_impl(
+                                    seen_types,
+                                    cx,
+                                    a_sig.output(),
+                                    b_sig.output(),
+                                    ckind,
+                                )
+                        }
+                        (Tuple(a_substs), Tuple(b_substs)) => {
+                            a_substs.types().eq_by(b_substs.types(), |a_ty, b_ty| {
+                                structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind)
+                            })
+                        }
+                        // For these, it's not quite as easy to define structural-sameness quite so easily.
+                        // For the purposes of this lint, take the conservative approach and mark them as
+                        // not structurally same.
+                        (Dynamic(..), Dynamic(..))
+                        | (Error(..), Error(..))
+                        | (Closure(..), Closure(..))
+                        | (Generator(..), Generator(..))
+                        | (GeneratorWitness(..), GeneratorWitness(..))
+                        | (Projection(..), Projection(..))
+                        | (Opaque(..), Opaque(..)) => false,
+
+                        // These definitely should have been caught above.
+                        (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(),
+
+                        // An Adt and a primitive or pointer type. This can be FFI-safe if non-null
+                        // enum layout optimisation is being applied.
+                        (Adt(..), other_kind) | (other_kind, Adt(..))
+                            if is_primitive_or_pointer(other_kind) =>
+                        {
+                            let (primitive, adt) =
+                                if is_primitive_or_pointer(&a.kind) { (a, b) } else { (b, a) };
+                            if let Some(ty) = crate::types::repr_nullable_ptr(cx, adt, ckind) {
+                                ty == primitive
+                            } else {
+                                compare_layouts(a, b).unwrap_or(false)
+                            }
+                        }
+                        // Otherwise, just compare the layouts. This may fail to lint for some
+                        // incompatible types, but at the very least, will stop reads into
+                        // uninitialised memory.
+                        _ => compare_layouts(a, b).unwrap_or(false),
+                    }
+                })
+            }
+        }
+        let mut seen_types = FxHashSet::default();
+        structurally_same_type_impl(&mut seen_types, cx, a, b, ckind)
+    }
+}
+
+impl_lint_pass!(ClashingExternDeclarations => [CLASHING_EXTERN_DECLARATIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for ClashingExternDeclarations {
+    fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, this_fi: &hir::ForeignItem<'_>) {
+        trace!("ClashingExternDeclarations: check_foreign_item: {:?}", this_fi);
+        if let ForeignItemKind::Fn(..) = this_fi.kind {
+            let tcx = *&cx.tcx;
+            if let Some(existing_hid) = self.insert(tcx, this_fi) {
+                let existing_decl_ty = tcx.type_of(tcx.hir().local_def_id(existing_hid));
+                let this_decl_ty = tcx.type_of(tcx.hir().local_def_id(this_fi.hir_id));
+                debug!(
+                    "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}",
+                    existing_hid, existing_decl_ty, this_fi.hir_id, this_decl_ty
+                );
+                // Check that the declarations match.
+                if !Self::structurally_same_type(
+                    cx,
+                    existing_decl_ty,
+                    this_decl_ty,
+                    CItemKind::Declaration,
+                ) {
+                    let orig_fi = tcx.hir().expect_foreign_item(existing_hid);
+                    let orig = Self::name_of_extern_decl(tcx, orig_fi);
+
+                    // We want to ensure that we use spans for both decls that include where the
+                    // name was defined, whether that was from the link_name attribute or not.
+                    let get_relevant_span =
+                        |fi: &hir::ForeignItem<'_>| match Self::name_of_extern_decl(tcx, fi) {
+                            SymbolName::Normal(_) => fi.span,
+                            SymbolName::Link(_, annot_span) => fi.span.to(annot_span),
+                        };
+                    // Finally, emit the diagnostic.
+                    tcx.struct_span_lint_hir(
+                        CLASHING_EXTERN_DECLARATIONS,
+                        this_fi.hir_id,
+                        get_relevant_span(this_fi),
+                        |lint| {
+                            let mut expected_str = DiagnosticStyledString::new();
+                            expected_str.push(existing_decl_ty.fn_sig(tcx).to_string(), false);
+                            let mut found_str = DiagnosticStyledString::new();
+                            found_str.push(this_decl_ty.fn_sig(tcx).to_string(), true);
+
+                            lint.build(&format!(
+                                "`{}` redeclare{} with a different signature",
+                                this_fi.ident.name,
+                                if orig.get_name() == this_fi.ident.name {
+                                    "d".to_string()
+                                } else {
+                                    format!("s `{}`", orig.get_name())
+                                }
+                            ))
+                            .span_label(
+                                get_relevant_span(orig_fi),
+                                &format!("`{}` previously declared here", orig.get_name()),
+                            )
+                            .span_label(
+                                get_relevant_span(this_fi),
+                                "this signature doesn't match the previous declaration",
+                            )
+                            .note_expected_found(&"", expected_str, &"", found_str)
+                            .emit()
+                        },
+                    );
+                }
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
new file mode 100644
index 00000000000..a6784ffffcd
--- /dev/null
+++ b/compiler/rustc_lint/src/context.rs
@@ -0,0 +1,862 @@
+//! Implementation of lint checking.
+//!
+//! The lint checking is mostly consolidated into one pass which runs
+//! after all other analyses. Throughout compilation, lint warnings
+//! can be added via the `add_lint` method on the Session structure. This
+//! requires a span and an ID of the node that the lint is being added to. The
+//! lint isn't actually emitted at that time because it is unknown what the
+//! actual lint level at that location is.
+//!
+//! To actually emit lint warnings/errors, a separate pass is used.
+//! A context keeps track of the current state of all lint levels.
+//! Upon entering a node of the ast which can modify the lint settings, the
+//! previous lint state is pushed onto a stack and the ast is then recursed
+//! upon. As the ast is traversed, this keeps track of the current lint level
+//! for all lint attributes.
+
+use self::TargetLint::*;
+
+use crate::levels::LintLevelsBuilder;
+use crate::passes::{EarlyLintPassObject, LateLintPassObject};
+use rustc_ast as ast;
+use rustc_ast::util::lev_distance::find_best_match_for_name;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::sync;
+use rustc_errors::{struct_span_err, Applicability};
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::def_id::{CrateNum, DefId};
+use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
+use rustc_middle::lint::LintDiagnosticBuilder;
+use rustc_middle::middle::privacy::AccessLevels;
+use rustc_middle::middle::stability;
+use rustc_middle::ty::layout::{LayoutError, TyAndLayout};
+use rustc_middle::ty::{self, print::Printer, subst::GenericArg, Ty, TyCtxt};
+use rustc_session::lint::{add_elided_lifetime_in_path_suggestion, BuiltinLintDiagnostics};
+use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
+use rustc_session::Session;
+use rustc_span::{symbol::Symbol, MultiSpan, Span, DUMMY_SP};
+use rustc_target::abi::LayoutOf;
+
+use std::cell::Cell;
+use std::slice;
+
+/// Information about the registered lints.
+///
+/// This is basically the subset of `Context` that we can
+/// build early in the compile pipeline.
+pub struct LintStore {
+    /// Registered lints.
+    lints: Vec<&'static Lint>,
+
+    /// Constructor functions for each variety of lint pass.
+    ///
+    /// These should only be called once, but since we want to avoid locks or
+    /// interior mutability, we don't enforce this (and lints should, in theory,
+    /// be compatible with being constructed more than once, though not
+    /// necessarily in a sane manner. This is safe though.)
+    pub pre_expansion_passes: Vec<Box<dyn Fn() -> EarlyLintPassObject + sync::Send + sync::Sync>>,
+    pub early_passes: Vec<Box<dyn Fn() -> EarlyLintPassObject + sync::Send + sync::Sync>>,
+    pub late_passes: Vec<Box<dyn Fn() -> LateLintPassObject + sync::Send + sync::Sync>>,
+    /// This is unique in that we construct them per-module, so not once.
+    pub late_module_passes: Vec<Box<dyn Fn() -> LateLintPassObject + sync::Send + sync::Sync>>,
+
+    /// Lints indexed by name.
+    by_name: FxHashMap<String, TargetLint>,
+
+    /// Map of registered lint groups to what lints they expand to.
+    lint_groups: FxHashMap<&'static str, LintGroup>,
+}
+
+/// The target of the `by_name` map, which accounts for renaming/deprecation.
+enum TargetLint {
+    /// A direct lint target
+    Id(LintId),
+
+    /// Temporary renaming, used for easing migration pain; see #16545
+    Renamed(String, LintId),
+
+    /// Lint with this name existed previously, but has been removed/deprecated.
+    /// The string argument is the reason for removal.
+    Removed(String),
+}
+
+pub enum FindLintError {
+    NotFound,
+    Removed,
+}
+
+struct LintAlias {
+    name: &'static str,
+    /// Whether deprecation warnings should be suppressed for this alias.
+    silent: bool,
+}
+
+struct LintGroup {
+    lint_ids: Vec<LintId>,
+    from_plugin: bool,
+    depr: Option<LintAlias>,
+}
+
+pub enum CheckLintNameResult<'a> {
+    Ok(&'a [LintId]),
+    /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name.
+    NoLint(Option<Symbol>),
+    /// The lint is either renamed or removed. This is the warning
+    /// message, and an optional new name (`None` if removed).
+    Warning(String, Option<String>),
+    /// The lint is from a tool. If the Option is None, then either
+    /// the lint does not exist in the tool or the code was not
+    /// compiled with the tool and therefore the lint was never
+    /// added to the `LintStore`. Otherwise the `LintId` will be
+    /// returned as if it where a rustc lint.
+    Tool(Result<&'a [LintId], (Option<&'a [LintId]>, String)>),
+}
+
+impl LintStore {
+    pub fn new() -> LintStore {
+        LintStore {
+            lints: vec![],
+            pre_expansion_passes: vec![],
+            early_passes: vec![],
+            late_passes: vec![],
+            late_module_passes: vec![],
+            by_name: Default::default(),
+            lint_groups: Default::default(),
+        }
+    }
+
+    pub fn get_lints<'t>(&'t self) -> &'t [&'static Lint] {
+        &self.lints
+    }
+
+    pub fn get_lint_groups<'t>(&'t self) -> Vec<(&'static str, Vec<LintId>, bool)> {
+        self.lint_groups
+            .iter()
+            .filter(|(_, LintGroup { depr, .. })| {
+                // Don't display deprecated lint groups.
+                depr.is_none()
+            })
+            .map(|(k, LintGroup { lint_ids, from_plugin, .. })| {
+                (*k, lint_ids.clone(), *from_plugin)
+            })
+            .collect()
+    }
+
+    pub fn register_early_pass(
+        &mut self,
+        pass: impl Fn() -> EarlyLintPassObject + 'static + sync::Send + sync::Sync,
+    ) {
+        self.early_passes.push(Box::new(pass));
+    }
+
+    pub fn register_pre_expansion_pass(
+        &mut self,
+        pass: impl Fn() -> EarlyLintPassObject + 'static + sync::Send + sync::Sync,
+    ) {
+        self.pre_expansion_passes.push(Box::new(pass));
+    }
+
+    pub fn register_late_pass(
+        &mut self,
+        pass: impl Fn() -> LateLintPassObject + 'static + sync::Send + sync::Sync,
+    ) {
+        self.late_passes.push(Box::new(pass));
+    }
+
+    pub fn register_late_mod_pass(
+        &mut self,
+        pass: impl Fn() -> LateLintPassObject + 'static + sync::Send + sync::Sync,
+    ) {
+        self.late_module_passes.push(Box::new(pass));
+    }
+
+    // Helper method for register_early/late_pass
+    pub fn register_lints(&mut self, lints: &[&'static Lint]) {
+        for lint in lints {
+            self.lints.push(lint);
+
+            let id = LintId::of(lint);
+            if self.by_name.insert(lint.name_lower(), Id(id)).is_some() {
+                bug!("duplicate specification of lint {}", lint.name_lower())
+            }
+
+            if let Some(FutureIncompatibleInfo { edition, .. }) = lint.future_incompatible {
+                if let Some(edition) = edition {
+                    self.lint_groups
+                        .entry(edition.lint_name())
+                        .or_insert(LintGroup {
+                            lint_ids: vec![],
+                            from_plugin: lint.is_plugin,
+                            depr: None,
+                        })
+                        .lint_ids
+                        .push(id);
+                }
+
+                self.lint_groups
+                    .entry("future_incompatible")
+                    .or_insert(LintGroup {
+                        lint_ids: vec![],
+                        from_plugin: lint.is_plugin,
+                        depr: None,
+                    })
+                    .lint_ids
+                    .push(id);
+            }
+        }
+    }
+
+    pub fn register_group_alias(&mut self, lint_name: &'static str, alias: &'static str) {
+        self.lint_groups.insert(
+            alias,
+            LintGroup {
+                lint_ids: vec![],
+                from_plugin: false,
+                depr: Some(LintAlias { name: lint_name, silent: true }),
+            },
+        );
+    }
+
+    pub fn register_group(
+        &mut self,
+        from_plugin: bool,
+        name: &'static str,
+        deprecated_name: Option<&'static str>,
+        to: Vec<LintId>,
+    ) {
+        let new = self
+            .lint_groups
+            .insert(name, LintGroup { lint_ids: to, from_plugin, depr: None })
+            .is_none();
+        if let Some(deprecated) = deprecated_name {
+            self.lint_groups.insert(
+                deprecated,
+                LintGroup {
+                    lint_ids: vec![],
+                    from_plugin,
+                    depr: Some(LintAlias { name, silent: false }),
+                },
+            );
+        }
+
+        if !new {
+            bug!("duplicate specification of lint group {}", name);
+        }
+    }
+
+    pub fn register_renamed(&mut self, old_name: &str, new_name: &str) {
+        let target = match self.by_name.get(new_name) {
+            Some(&Id(lint_id)) => lint_id,
+            _ => bug!("invalid lint renaming of {} to {}", old_name, new_name),
+        };
+        self.by_name.insert(old_name.to_string(), Renamed(new_name.to_string(), target));
+    }
+
+    pub fn register_removed(&mut self, name: &str, reason: &str) {
+        self.by_name.insert(name.into(), Removed(reason.into()));
+    }
+
+    pub fn find_lints(&self, mut lint_name: &str) -> Result<Vec<LintId>, FindLintError> {
+        match self.by_name.get(lint_name) {
+            Some(&Id(lint_id)) => Ok(vec![lint_id]),
+            Some(&Renamed(_, lint_id)) => Ok(vec![lint_id]),
+            Some(&Removed(_)) => Err(FindLintError::Removed),
+            None => loop {
+                return match self.lint_groups.get(lint_name) {
+                    Some(LintGroup { lint_ids, depr, .. }) => {
+                        if let Some(LintAlias { name, .. }) = depr {
+                            lint_name = name;
+                            continue;
+                        }
+                        Ok(lint_ids.clone())
+                    }
+                    None => Err(FindLintError::Removed),
+                };
+            },
+        }
+    }
+
+    /// Checks the validity of lint names derived from the command line
+    pub fn check_lint_name_cmdline(&self, sess: &Session, lint_name: &str, level: Level) {
+        let db = match self.check_lint_name(lint_name, None) {
+            CheckLintNameResult::Ok(_) => None,
+            CheckLintNameResult::Warning(ref msg, _) => Some(sess.struct_warn(msg)),
+            CheckLintNameResult::NoLint(suggestion) => {
+                let mut err =
+                    struct_span_err!(sess, DUMMY_SP, E0602, "unknown lint: `{}`", lint_name);
+
+                if let Some(suggestion) = suggestion {
+                    err.help(&format!("did you mean: `{}`", suggestion));
+                }
+
+                Some(err)
+            }
+            CheckLintNameResult::Tool(result) => match result {
+                Err((Some(_), new_name)) => Some(sess.struct_warn(&format!(
+                    "lint name `{}` is deprecated \
+                     and does not have an effect anymore. \
+                     Use: {}",
+                    lint_name, new_name
+                ))),
+                _ => None,
+            },
+        };
+
+        if let Some(mut db) = db {
+            let msg = format!(
+                "requested on the command line with `{} {}`",
+                match level {
+                    Level::Allow => "-A",
+                    Level::Warn => "-W",
+                    Level::Deny => "-D",
+                    Level::Forbid => "-F",
+                },
+                lint_name
+            );
+            db.note(&msg);
+            db.emit();
+        }
+    }
+
+    /// Checks the name of a lint for its existence, and whether it was
+    /// renamed or removed. Generates a DiagnosticBuilder containing a
+    /// warning for renamed and removed lints. This is over both lint
+    /// names from attributes and those passed on the command line. Since
+    /// it emits non-fatal warnings and there are *two* lint passes that
+    /// inspect attributes, this is only run from the late pass to avoid
+    /// printing duplicate warnings.
+    pub fn check_lint_name(
+        &self,
+        lint_name: &str,
+        tool_name: Option<Symbol>,
+    ) -> CheckLintNameResult<'_> {
+        let complete_name = if let Some(tool_name) = tool_name {
+            format!("{}::{}", tool_name, lint_name)
+        } else {
+            lint_name.to_string()
+        };
+        // If the lint was scoped with `tool::` check if the tool lint exists
+        if tool_name.is_some() {
+            match self.by_name.get(&complete_name) {
+                None => match self.lint_groups.get(&*complete_name) {
+                    None => return CheckLintNameResult::Tool(Err((None, String::new()))),
+                    Some(LintGroup { lint_ids, .. }) => {
+                        return CheckLintNameResult::Tool(Ok(&lint_ids));
+                    }
+                },
+                Some(&Id(ref id)) => return CheckLintNameResult::Tool(Ok(slice::from_ref(id))),
+                // If the lint was registered as removed or renamed by the lint tool, we don't need
+                // to treat tool_lints and rustc lints different and can use the code below.
+                _ => {}
+            }
+        }
+        match self.by_name.get(&complete_name) {
+            Some(&Renamed(ref new_name, _)) => CheckLintNameResult::Warning(
+                format!("lint `{}` has been renamed to `{}`", complete_name, new_name),
+                Some(new_name.to_owned()),
+            ),
+            Some(&Removed(ref reason)) => CheckLintNameResult::Warning(
+                format!("lint `{}` has been removed: `{}`", complete_name, reason),
+                None,
+            ),
+            None => match self.lint_groups.get(&*complete_name) {
+                // If neither the lint, nor the lint group exists check if there is a `clippy::`
+                // variant of this lint
+                None => self.check_tool_name_for_backwards_compat(&complete_name, "clippy"),
+                Some(LintGroup { lint_ids, depr, .. }) => {
+                    // Check if the lint group name is deprecated
+                    if let Some(LintAlias { name, silent }) = depr {
+                        let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap();
+                        return if *silent {
+                            CheckLintNameResult::Ok(&lint_ids)
+                        } else {
+                            CheckLintNameResult::Tool(Err((Some(&lint_ids), (*name).to_string())))
+                        };
+                    }
+                    CheckLintNameResult::Ok(&lint_ids)
+                }
+            },
+            Some(&Id(ref id)) => CheckLintNameResult::Ok(slice::from_ref(id)),
+        }
+    }
+
+    fn check_tool_name_for_backwards_compat(
+        &self,
+        lint_name: &str,
+        tool_name: &str,
+    ) -> CheckLintNameResult<'_> {
+        let complete_name = format!("{}::{}", tool_name, lint_name);
+        match self.by_name.get(&complete_name) {
+            None => match self.lint_groups.get(&*complete_name) {
+                // Now we are sure, that this lint exists nowhere
+                None => {
+                    let symbols =
+                        self.by_name.keys().map(|name| Symbol::intern(&name)).collect::<Vec<_>>();
+
+                    let suggestion = find_best_match_for_name(
+                        symbols.iter(),
+                        Symbol::intern(&lint_name.to_lowercase()),
+                        None,
+                    );
+
+                    CheckLintNameResult::NoLint(suggestion)
+                }
+                Some(LintGroup { lint_ids, depr, .. }) => {
+                    // Reaching this would be weird, but let's cover this case anyway
+                    if let Some(LintAlias { name, silent }) = depr {
+                        let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap();
+                        return if *silent {
+                            CheckLintNameResult::Tool(Err((Some(&lint_ids), complete_name)))
+                        } else {
+                            CheckLintNameResult::Tool(Err((Some(&lint_ids), (*name).to_string())))
+                        };
+                    }
+                    CheckLintNameResult::Tool(Err((Some(&lint_ids), complete_name)))
+                }
+            },
+            Some(&Id(ref id)) => {
+                CheckLintNameResult::Tool(Err((Some(slice::from_ref(id)), complete_name)))
+            }
+            _ => CheckLintNameResult::NoLint(None),
+        }
+    }
+}
+
+/// Context for lint checking after type checking.
+pub struct LateContext<'tcx> {
+    /// Type context we're checking in.
+    pub tcx: TyCtxt<'tcx>,
+
+    /// Current body, or `None` if outside a body.
+    pub enclosing_body: Option<hir::BodyId>,
+
+    /// Type-checking results for the current body. Access using the `typeck_results`
+    /// and `maybe_typeck_results` methods, which handle querying the typeck results on demand.
+    // FIXME(eddyb) move all the code accessing internal fields like this,
+    // to this module, to avoid exposing it to lint logic.
+    pub(super) cached_typeck_results: Cell<Option<&'tcx ty::TypeckResults<'tcx>>>,
+
+    /// Parameter environment for the item we are in.
+    pub param_env: ty::ParamEnv<'tcx>,
+
+    /// Items accessible from the crate being checked.
+    pub access_levels: &'tcx AccessLevels,
+
+    /// The store of registered lints and the lint levels.
+    pub lint_store: &'tcx LintStore,
+
+    pub last_node_with_lint_attrs: hir::HirId,
+
+    /// Generic type parameters in scope for the item we are in.
+    pub generics: Option<&'tcx hir::Generics<'tcx>>,
+
+    /// We are only looking at one module
+    pub only_module: bool,
+}
+
+/// Context for lint checking of the AST, after expansion, before lowering to
+/// HIR.
+pub struct EarlyContext<'a> {
+    /// Type context we're checking in.
+    pub sess: &'a Session,
+
+    /// The crate being checked.
+    pub krate: &'a ast::Crate,
+
+    pub builder: LintLevelsBuilder<'a>,
+
+    /// The store of registered lints and the lint levels.
+    pub lint_store: &'a LintStore,
+
+    pub buffered: LintBuffer,
+}
+
+pub trait LintPassObject: Sized {}
+
+impl LintPassObject for EarlyLintPassObject {}
+
+impl LintPassObject for LateLintPassObject {}
+
+pub trait LintContext: Sized {
+    type PassObject: LintPassObject;
+
+    fn sess(&self) -> &Session;
+    fn lints(&self) -> &LintStore;
+
+    fn lookup_with_diagnostics(
+        &self,
+        lint: &'static Lint,
+        span: Option<impl Into<MultiSpan>>,
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+        diagnostic: BuiltinLintDiagnostics,
+    ) {
+        self.lookup(lint, span, |lint| {
+            // We first generate a blank diagnostic.
+            let mut db = lint.build("");
+
+            // Now, set up surrounding context.
+            let sess = self.sess();
+            match diagnostic {
+                BuiltinLintDiagnostics::Normal => (),
+                BuiltinLintDiagnostics::BareTraitObject(span, is_global) => {
+                    let (sugg, app) = match sess.source_map().span_to_snippet(span) {
+                        Ok(s) if is_global => {
+                            (format!("dyn ({})", s), Applicability::MachineApplicable)
+                        }
+                        Ok(s) => (format!("dyn {}", s), Applicability::MachineApplicable),
+                        Err(_) => ("dyn <type>".to_string(), Applicability::HasPlaceholders),
+                    };
+                    db.span_suggestion(span, "use `dyn`", sugg, app);
+                }
+                BuiltinLintDiagnostics::AbsPathWithModule(span) => {
+                    let (sugg, app) = match sess.source_map().span_to_snippet(span) {
+                        Ok(ref s) => {
+                            // FIXME(Manishearth) ideally the emitting code
+                            // can tell us whether or not this is global
+                            let opt_colon =
+                                if s.trim_start().starts_with("::") { "" } else { "::" };
+
+                            (format!("crate{}{}", opt_colon, s), Applicability::MachineApplicable)
+                        }
+                        Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders),
+                    };
+                    db.span_suggestion(span, "use `crate`", sugg, app);
+                }
+                BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => {
+                    db.span_label(
+                        span,
+                        "names from parent modules are not accessible without an explicit import",
+                    );
+                }
+                BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths(
+                    span_def,
+                ) => {
+                    db.span_note(span_def, "the macro is defined here");
+                }
+                BuiltinLintDiagnostics::ElidedLifetimesInPaths(
+                    n,
+                    path_span,
+                    incl_angl_brckt,
+                    insertion_span,
+                    anon_lts,
+                ) => {
+                    add_elided_lifetime_in_path_suggestion(
+                        sess,
+                        &mut db,
+                        n,
+                        path_span,
+                        incl_angl_brckt,
+                        insertion_span,
+                        anon_lts,
+                    );
+                }
+                BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => {
+                    db.span_suggestion(span, &note, sugg, Applicability::MaybeIncorrect);
+                }
+                BuiltinLintDiagnostics::UnusedImports(message, replaces) => {
+                    if !replaces.is_empty() {
+                        db.tool_only_multipart_suggestion(
+                            &message,
+                            replaces,
+                            Applicability::MachineApplicable,
+                        );
+                    }
+                }
+                BuiltinLintDiagnostics::RedundantImport(spans, ident) => {
+                    for (span, is_imported) in spans {
+                        let introduced = if is_imported { "imported" } else { "defined" };
+                        db.span_label(
+                            span,
+                            format!("the item `{}` is already {} here", ident, introduced),
+                        );
+                    }
+                }
+                BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => {
+                    stability::deprecation_suggestion(&mut db, "macro", suggestion, span)
+                }
+                BuiltinLintDiagnostics::UnusedDocComment(span) => {
+                    db.span_label(span, "rustdoc does not generate documentation for macro invocations");
+                    db.help("to document an item produced by a macro, \
+                                  the macro must produce the documentation as part of its expansion");
+                }
+            }
+            // Rewrap `db`, and pass control to the user.
+            decorate(LintDiagnosticBuilder::new(db));
+        });
+    }
+
+    // FIXME: These methods should not take an Into<MultiSpan> -- instead, callers should need to
+    // set the span in their `decorate` function (preferably using set_span).
+    fn lookup<S: Into<MultiSpan>>(
+        &self,
+        lint: &'static Lint,
+        span: Option<S>,
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    );
+
+    fn struct_span_lint<S: Into<MultiSpan>>(
+        &self,
+        lint: &'static Lint,
+        span: S,
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    ) {
+        self.lookup(lint, Some(span), decorate);
+    }
+    /// Emit a lint at the appropriate level, with no associated span.
+    fn lint(&self, lint: &'static Lint, decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>)) {
+        self.lookup(lint, None as Option<Span>, decorate);
+    }
+}
+
+impl<'a> EarlyContext<'a> {
+    pub fn new(
+        sess: &'a Session,
+        lint_store: &'a LintStore,
+        krate: &'a ast::Crate,
+        buffered: LintBuffer,
+        warn_about_weird_lints: bool,
+    ) -> EarlyContext<'a> {
+        EarlyContext {
+            sess,
+            krate,
+            lint_store,
+            builder: LintLevelsBuilder::new(sess, warn_about_weird_lints, lint_store),
+            buffered,
+        }
+    }
+}
+
+impl LintContext for LateContext<'_> {
+    type PassObject = LateLintPassObject;
+
+    /// Gets the overall compiler `Session` object.
+    fn sess(&self) -> &Session {
+        &self.tcx.sess
+    }
+
+    fn lints(&self) -> &LintStore {
+        &*self.lint_store
+    }
+
+    fn lookup<S: Into<MultiSpan>>(
+        &self,
+        lint: &'static Lint,
+        span: Option<S>,
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    ) {
+        let hir_id = self.last_node_with_lint_attrs;
+
+        match span {
+            Some(s) => self.tcx.struct_span_lint_hir(lint, hir_id, s, decorate),
+            None => self.tcx.struct_lint_node(lint, hir_id, decorate),
+        }
+    }
+}
+
+impl LintContext for EarlyContext<'_> {
+    type PassObject = EarlyLintPassObject;
+
+    /// Gets the overall compiler `Session` object.
+    fn sess(&self) -> &Session {
+        &self.sess
+    }
+
+    fn lints(&self) -> &LintStore {
+        &*self.lint_store
+    }
+
+    fn lookup<S: Into<MultiSpan>>(
+        &self,
+        lint: &'static Lint,
+        span: Option<S>,
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    ) {
+        self.builder.struct_lint(lint, span.map(|s| s.into()), decorate)
+    }
+}
+
+impl<'tcx> LateContext<'tcx> {
+    /// Gets the type-checking results for the current body,
+    /// or `None` if outside a body.
+    pub fn maybe_typeck_results(&self) -> Option<&'tcx ty::TypeckResults<'tcx>> {
+        self.cached_typeck_results.get().or_else(|| {
+            self.enclosing_body.map(|body| {
+                let typeck_results = self.tcx.typeck_body(body);
+                self.cached_typeck_results.set(Some(typeck_results));
+                typeck_results
+            })
+        })
+    }
+
+    /// Gets the type-checking results for the current body.
+    /// As this will ICE if called outside bodies, only call when working with
+    /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies).
+    #[track_caller]
+    pub fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> {
+        self.maybe_typeck_results().expect("`LateContext::typeck_results` called outside of body")
+    }
+
+    /// Returns the final resolution of a `QPath`, or `Res::Err` if unavailable.
+    /// Unlike `.typeck_results().qpath_res(qpath, id)`, this can be used even outside
+    /// bodies (e.g. for paths in `hir::Ty`), without any risk of ICE-ing.
+    pub fn qpath_res(&self, qpath: &hir::QPath<'_>, id: hir::HirId) -> Res {
+        match *qpath {
+            hir::QPath::Resolved(_, ref path) => path.res,
+            hir::QPath::TypeRelative(..) | hir::QPath::LangItem(..) => self
+                .maybe_typeck_results()
+                .and_then(|typeck_results| typeck_results.type_dependent_def(id))
+                .map_or(Res::Err, |(kind, def_id)| Res::Def(kind, def_id)),
+        }
+    }
+
+    pub fn current_lint_root(&self) -> hir::HirId {
+        self.last_node_with_lint_attrs
+    }
+
+    /// Check if a `DefId`'s path matches the given absolute type path usage.
+    ///
+    /// Anonymous scopes such as `extern` imports are matched with `kw::Invalid`;
+    /// inherent `impl` blocks are matched with the name of the type.
+    ///
+    /// # Examples
+    ///
+    /// ```rust,ignore (no context or def id available)
+    /// if cx.match_def_path(def_id, &[sym::core, sym::option, sym::Option]) {
+    ///     // The given `def_id` is that of an `Option` type
+    /// }
+    /// ```
+    pub fn match_def_path(&self, def_id: DefId, path: &[Symbol]) -> bool {
+        let names = self.get_def_path(def_id);
+
+        names.len() == path.len() && names.into_iter().zip(path.iter()).all(|(a, &b)| a == b)
+    }
+
+    /// Gets the absolute path of `def_id` as a vector of `Symbol`.
+    ///
+    /// # Examples
+    ///
+    /// ```rust,ignore (no context or def id available)
+    /// let def_path = cx.get_def_path(def_id);
+    /// if let &[sym::core, sym::option, sym::Option] = &def_path[..] {
+    ///     // The given `def_id` is that of an `Option` type
+    /// }
+    /// ```
+    pub fn get_def_path(&self, def_id: DefId) -> Vec<Symbol> {
+        pub struct AbsolutePathPrinter<'tcx> {
+            pub tcx: TyCtxt<'tcx>,
+        }
+
+        impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
+            type Error = !;
+
+            type Path = Vec<Symbol>;
+            type Region = ();
+            type Type = ();
+            type DynExistential = ();
+            type Const = ();
+
+            fn tcx(&self) -> TyCtxt<'tcx> {
+                self.tcx
+            }
+
+            fn print_region(self, _region: ty::Region<'_>) -> Result<Self::Region, Self::Error> {
+                Ok(())
+            }
+
+            fn print_type(self, _ty: Ty<'tcx>) -> Result<Self::Type, Self::Error> {
+                Ok(())
+            }
+
+            fn print_dyn_existential(
+                self,
+                _predicates: &'tcx ty::List<ty::ExistentialPredicate<'tcx>>,
+            ) -> Result<Self::DynExistential, Self::Error> {
+                Ok(())
+            }
+
+            fn print_const(self, _ct: &'tcx ty::Const<'tcx>) -> Result<Self::Const, Self::Error> {
+                Ok(())
+            }
+
+            fn path_crate(self, cnum: CrateNum) -> Result<Self::Path, Self::Error> {
+                Ok(vec![self.tcx.original_crate_name(cnum)])
+            }
+
+            fn path_qualified(
+                self,
+                self_ty: Ty<'tcx>,
+                trait_ref: Option<ty::TraitRef<'tcx>>,
+            ) -> Result<Self::Path, Self::Error> {
+                if trait_ref.is_none() {
+                    if let ty::Adt(def, substs) = self_ty.kind {
+                        return self.print_def_path(def.did, substs);
+                    }
+                }
+
+                // This shouldn't ever be needed, but just in case:
+                Ok(vec![match trait_ref {
+                    Some(trait_ref) => Symbol::intern(&format!("{:?}", trait_ref)),
+                    None => Symbol::intern(&format!("<{}>", self_ty)),
+                }])
+            }
+
+            fn path_append_impl(
+                self,
+                print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>,
+                _disambiguated_data: &DisambiguatedDefPathData,
+                self_ty: Ty<'tcx>,
+                trait_ref: Option<ty::TraitRef<'tcx>>,
+            ) -> Result<Self::Path, Self::Error> {
+                let mut path = print_prefix(self)?;
+
+                // This shouldn't ever be needed, but just in case:
+                path.push(match trait_ref {
+                    Some(trait_ref) => Symbol::intern(&format!(
+                        "<impl {} for {}>",
+                        trait_ref.print_only_trait_path(),
+                        self_ty
+                    )),
+                    None => Symbol::intern(&format!("<impl {}>", self_ty)),
+                });
+
+                Ok(path)
+            }
+
+            fn path_append(
+                self,
+                print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>,
+                disambiguated_data: &DisambiguatedDefPathData,
+            ) -> Result<Self::Path, Self::Error> {
+                let mut path = print_prefix(self)?;
+
+                // Skip `::{{constructor}}` on tuple/unit structs.
+                if let DefPathData::Ctor = disambiguated_data.data {
+                    return Ok(path);
+                }
+
+                path.push(disambiguated_data.data.as_symbol());
+                Ok(path)
+            }
+
+            fn path_generic_args(
+                self,
+                print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>,
+                _args: &[GenericArg<'tcx>],
+            ) -> Result<Self::Path, Self::Error> {
+                print_prefix(self)
+            }
+        }
+
+        AbsolutePathPrinter { tcx: self.tcx }.print_def_path(def_id, &[]).unwrap()
+    }
+}
+
+impl<'tcx> LayoutOf for LateContext<'tcx> {
+    type Ty = Ty<'tcx>;
+    type TyAndLayout = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
+
+    fn layout_of(&self, ty: Ty<'tcx>) -> Self::TyAndLayout {
+        self.tcx.layout_of(self.param_env.and(ty))
+    }
+}
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
new file mode 100644
index 00000000000..998676d44d2
--- /dev/null
+++ b/compiler/rustc_lint/src/early.rs
@@ -0,0 +1,381 @@
+//! Implementation of lint checking.
+//!
+//! The lint checking is mostly consolidated into one pass which runs
+//! after all other analyses. Throughout compilation, lint warnings
+//! can be added via the `add_lint` method on the Session structure. This
+//! requires a span and an ID of the node that the lint is being added to. The
+//! lint isn't actually emitted at that time because it is unknown what the
+//! actual lint level at that location is.
+//!
+//! To actually emit lint warnings/errors, a separate pass is used.
+//! A context keeps track of the current state of all lint levels.
+//! Upon entering a node of the ast which can modify the lint settings, the
+//! previous lint state is pushed onto a stack and the ast is then recursed
+//! upon. As the ast is traversed, this keeps track of the current lint level
+//! for all lint attributes.
+
+use crate::context::{EarlyContext, LintContext, LintStore};
+use crate::passes::{EarlyLintPass, EarlyLintPassObject};
+use rustc_ast as ast;
+use rustc_ast::visit as ast_visit;
+use rustc_session::lint::{BufferedEarlyLint, LintBuffer, LintPass};
+use rustc_session::Session;
+use rustc_span::symbol::Ident;
+use rustc_span::Span;
+
+use std::slice;
+use tracing::debug;
+
+macro_rules! run_early_pass { ($cx:expr, $f:ident, $($args:expr),*) => ({
+    $cx.pass.$f(&$cx.context, $($args),*);
+}) }
+
+struct EarlyContextAndPass<'a, T: EarlyLintPass> {
+    context: EarlyContext<'a>,
+    pass: T,
+}
+
+impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
+    fn check_id(&mut self, id: ast::NodeId) {
+        for early_lint in self.context.buffered.take(id) {
+            let BufferedEarlyLint { span, msg, node_id: _, lint_id, diagnostic } = early_lint;
+            self.context.lookup_with_diagnostics(
+                lint_id.lint,
+                Some(span),
+                |lint| lint.build(&msg).emit(),
+                diagnostic,
+            );
+        }
+    }
+
+    /// Merge the lints specified by any lint attributes into the
+    /// current lint context, call the provided function, then reset the
+    /// lints in effect to their previous state.
+    fn with_lint_attrs<F>(&mut self, id: ast::NodeId, attrs: &'a [ast::Attribute], f: F)
+    where
+        F: FnOnce(&mut Self),
+    {
+        let is_crate_node = id == ast::CRATE_NODE_ID;
+        let push = self.context.builder.push(attrs, &self.context.lint_store, is_crate_node);
+        self.check_id(id);
+        self.enter_attrs(attrs);
+        f(self);
+        self.exit_attrs(attrs);
+        self.context.builder.pop(push);
+    }
+
+    fn enter_attrs(&mut self, attrs: &'a [ast::Attribute]) {
+        debug!("early context: enter_attrs({:?})", attrs);
+        run_early_pass!(self, enter_lint_attrs, attrs);
+    }
+
+    fn exit_attrs(&mut self, attrs: &'a [ast::Attribute]) {
+        debug!("early context: exit_attrs({:?})", attrs);
+        run_early_pass!(self, exit_lint_attrs, attrs);
+    }
+}
+
+impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T> {
+    fn visit_param(&mut self, param: &'a ast::Param) {
+        self.with_lint_attrs(param.id, &param.attrs, |cx| {
+            run_early_pass!(cx, check_param, param);
+            ast_visit::walk_param(cx, param);
+        });
+    }
+
+    fn visit_item(&mut self, it: &'a ast::Item) {
+        self.with_lint_attrs(it.id, &it.attrs, |cx| {
+            run_early_pass!(cx, check_item, it);
+            ast_visit::walk_item(cx, it);
+            run_early_pass!(cx, check_item_post, it);
+        })
+    }
+
+    fn visit_foreign_item(&mut self, it: &'a ast::ForeignItem) {
+        self.with_lint_attrs(it.id, &it.attrs, |cx| {
+            run_early_pass!(cx, check_foreign_item, it);
+            ast_visit::walk_foreign_item(cx, it);
+            run_early_pass!(cx, check_foreign_item_post, it);
+        })
+    }
+
+    fn visit_pat(&mut self, p: &'a ast::Pat) {
+        run_early_pass!(self, check_pat, p);
+        self.check_id(p.id);
+        ast_visit::walk_pat(self, p);
+        run_early_pass!(self, check_pat_post, p);
+    }
+
+    fn visit_anon_const(&mut self, c: &'a ast::AnonConst) {
+        run_early_pass!(self, check_anon_const, c);
+        ast_visit::walk_anon_const(self, c);
+    }
+
+    fn visit_expr(&mut self, e: &'a ast::Expr) {
+        self.with_lint_attrs(e.id, &e.attrs, |cx| {
+            run_early_pass!(cx, check_expr, e);
+            ast_visit::walk_expr(cx, e);
+        })
+    }
+
+    fn visit_stmt(&mut self, s: &'a ast::Stmt) {
+        run_early_pass!(self, check_stmt, s);
+        self.check_id(s.id);
+        ast_visit::walk_stmt(self, s);
+    }
+
+    fn visit_fn(&mut self, fk: ast_visit::FnKind<'a>, span: Span, id: ast::NodeId) {
+        run_early_pass!(self, check_fn, fk, span, id);
+        self.check_id(id);
+        ast_visit::walk_fn(self, fk, span);
+        run_early_pass!(self, check_fn_post, fk, span, id);
+    }
+
+    fn visit_variant_data(&mut self, s: &'a ast::VariantData) {
+        run_early_pass!(self, check_struct_def, s);
+        if let Some(ctor_hir_id) = s.ctor_id() {
+            self.check_id(ctor_hir_id);
+        }
+        ast_visit::walk_struct_def(self, s);
+        run_early_pass!(self, check_struct_def_post, s);
+    }
+
+    fn visit_struct_field(&mut self, s: &'a ast::StructField) {
+        self.with_lint_attrs(s.id, &s.attrs, |cx| {
+            run_early_pass!(cx, check_struct_field, s);
+            ast_visit::walk_struct_field(cx, s);
+        })
+    }
+
+    fn visit_variant(&mut self, v: &'a ast::Variant) {
+        self.with_lint_attrs(v.id, &v.attrs, |cx| {
+            run_early_pass!(cx, check_variant, v);
+            ast_visit::walk_variant(cx, v);
+            run_early_pass!(cx, check_variant_post, v);
+        })
+    }
+
+    fn visit_ty(&mut self, t: &'a ast::Ty) {
+        run_early_pass!(self, check_ty, t);
+        self.check_id(t.id);
+        ast_visit::walk_ty(self, t);
+    }
+
+    fn visit_ident(&mut self, ident: Ident) {
+        run_early_pass!(self, check_ident, ident);
+    }
+
+    fn visit_mod(&mut self, m: &'a ast::Mod, s: Span, _a: &[ast::Attribute], n: ast::NodeId) {
+        run_early_pass!(self, check_mod, m, s, n);
+        self.check_id(n);
+        ast_visit::walk_mod(self, m);
+        run_early_pass!(self, check_mod_post, m, s, n);
+    }
+
+    fn visit_local(&mut self, l: &'a ast::Local) {
+        self.with_lint_attrs(l.id, &l.attrs, |cx| {
+            run_early_pass!(cx, check_local, l);
+            ast_visit::walk_local(cx, l);
+        })
+    }
+
+    fn visit_block(&mut self, b: &'a ast::Block) {
+        run_early_pass!(self, check_block, b);
+        self.check_id(b.id);
+        ast_visit::walk_block(self, b);
+        run_early_pass!(self, check_block_post, b);
+    }
+
+    fn visit_arm(&mut self, a: &'a ast::Arm) {
+        run_early_pass!(self, check_arm, a);
+        ast_visit::walk_arm(self, a);
+    }
+
+    fn visit_expr_post(&mut self, e: &'a ast::Expr) {
+        run_early_pass!(self, check_expr_post, e);
+    }
+
+    fn visit_generic_param(&mut self, param: &'a ast::GenericParam) {
+        run_early_pass!(self, check_generic_param, param);
+        ast_visit::walk_generic_param(self, param);
+    }
+
+    fn visit_generics(&mut self, g: &'a ast::Generics) {
+        run_early_pass!(self, check_generics, g);
+        ast_visit::walk_generics(self, g);
+    }
+
+    fn visit_where_predicate(&mut self, p: &'a ast::WherePredicate) {
+        run_early_pass!(self, check_where_predicate, p);
+        ast_visit::walk_where_predicate(self, p);
+    }
+
+    fn visit_poly_trait_ref(&mut self, t: &'a ast::PolyTraitRef, m: &'a ast::TraitBoundModifier) {
+        run_early_pass!(self, check_poly_trait_ref, t, m);
+        ast_visit::walk_poly_trait_ref(self, t, m);
+    }
+
+    fn visit_assoc_item(&mut self, item: &'a ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
+        self.with_lint_attrs(item.id, &item.attrs, |cx| match ctxt {
+            ast_visit::AssocCtxt::Trait => {
+                run_early_pass!(cx, check_trait_item, item);
+                ast_visit::walk_assoc_item(cx, item, ctxt);
+                run_early_pass!(cx, check_trait_item_post, item);
+            }
+            ast_visit::AssocCtxt::Impl => {
+                run_early_pass!(cx, check_impl_item, item);
+                ast_visit::walk_assoc_item(cx, item, ctxt);
+                run_early_pass!(cx, check_impl_item_post, item);
+            }
+        });
+    }
+
+    fn visit_lifetime(&mut self, lt: &'a ast::Lifetime) {
+        run_early_pass!(self, check_lifetime, lt);
+        self.check_id(lt.id);
+    }
+
+    fn visit_path(&mut self, p: &'a ast::Path, id: ast::NodeId) {
+        run_early_pass!(self, check_path, p, id);
+        self.check_id(id);
+        ast_visit::walk_path(self, p);
+    }
+
+    fn visit_attribute(&mut self, attr: &'a ast::Attribute) {
+        run_early_pass!(self, check_attribute, attr);
+    }
+
+    fn visit_mac_def(&mut self, mac: &'a ast::MacroDef, id: ast::NodeId) {
+        run_early_pass!(self, check_mac_def, mac, id);
+        self.check_id(id);
+    }
+
+    fn visit_mac(&mut self, mac: &'a ast::MacCall) {
+        // FIXME(#54110): So, this setup isn't really right. I think
+        // that (a) the librustc_ast visitor ought to be doing this as
+        // part of `walk_mac`, and (b) we should be calling
+        // `visit_path`, *but* that would require a `NodeId`, and I
+        // want to get #53686 fixed quickly. -nmatsakis
+        ast_visit::walk_path(self, &mac.path);
+
+        run_early_pass!(self, check_mac, mac);
+    }
+}
+
+struct EarlyLintPassObjects<'a> {
+    lints: &'a mut [EarlyLintPassObject],
+}
+
+#[allow(rustc::lint_pass_impl_without_macro)]
+impl LintPass for EarlyLintPassObjects<'_> {
+    fn name(&self) -> &'static str {
+        panic!()
+    }
+}
+
+macro_rules! expand_early_lint_pass_impl_methods {
+    ([$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+        $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) {
+            for obj in self.lints.iter_mut() {
+                obj.$name(context, $($param),*);
+            }
+        })*
+    )
+}
+
+macro_rules! early_lint_pass_impl {
+    ([], [$($methods:tt)*]) => (
+        impl EarlyLintPass for EarlyLintPassObjects<'_> {
+            expand_early_lint_pass_impl_methods!([$($methods)*]);
+        }
+    )
+}
+
+crate::early_lint_methods!(early_lint_pass_impl, []);
+
+fn early_lint_crate<T: EarlyLintPass>(
+    sess: &Session,
+    lint_store: &LintStore,
+    krate: &ast::Crate,
+    pass: T,
+    buffered: LintBuffer,
+    warn_about_weird_lints: bool,
+) -> LintBuffer {
+    let mut cx = EarlyContextAndPass {
+        context: EarlyContext::new(sess, lint_store, krate, buffered, warn_about_weird_lints),
+        pass,
+    };
+
+    // Visit the whole crate.
+    cx.with_lint_attrs(ast::CRATE_NODE_ID, &krate.attrs, |cx| {
+        // since the root module isn't visited as an item (because it isn't an
+        // item), warn for it here.
+        run_early_pass!(cx, check_crate, krate);
+
+        ast_visit::walk_crate(cx, krate);
+
+        run_early_pass!(cx, check_crate_post, krate);
+    });
+    cx.context.buffered
+}
+
+pub fn check_ast_crate<T: EarlyLintPass>(
+    sess: &Session,
+    lint_store: &LintStore,
+    krate: &ast::Crate,
+    pre_expansion: bool,
+    lint_buffer: Option<LintBuffer>,
+    builtin_lints: T,
+) {
+    let passes =
+        if pre_expansion { &lint_store.pre_expansion_passes } else { &lint_store.early_passes };
+    let mut passes: Vec<_> = passes.iter().map(|p| (p)()).collect();
+    let mut buffered = lint_buffer.unwrap_or_default();
+
+    if !sess.opts.debugging_opts.no_interleave_lints {
+        buffered =
+            early_lint_crate(sess, lint_store, krate, builtin_lints, buffered, pre_expansion);
+
+        if !passes.is_empty() {
+            buffered = early_lint_crate(
+                sess,
+                lint_store,
+                krate,
+                EarlyLintPassObjects { lints: &mut passes[..] },
+                buffered,
+                pre_expansion,
+            );
+        }
+    } else {
+        for pass in &mut passes {
+            buffered =
+                sess.prof.extra_verbose_generic_activity("run_lint", pass.name()).run(|| {
+                    early_lint_crate(
+                        sess,
+                        lint_store,
+                        krate,
+                        EarlyLintPassObjects { lints: slice::from_mut(pass) },
+                        buffered,
+                        pre_expansion,
+                    )
+                });
+        }
+    }
+
+    // All of the buffered lints should have been emitted at this point.
+    // If not, that means that we somehow buffered a lint for a node id
+    // that was not lint-checked (perhaps it doesn't exist?). This is a bug.
+    //
+    // Rustdoc runs everybody-loops before the early lints and removes
+    // function bodies, so it's totally possible for linted
+    // node ids to not exist (e.g., macros defined within functions for the
+    // unused_macro lint) anymore. So we only run this check
+    // when we're not in rustdoc mode. (see issue #47639)
+    if !sess.opts.actually_rustdoc {
+        for (_id, lints) in buffered.map {
+            for early_lint in lints {
+                sess.delay_span_bug(early_lint.span, "failed to process buffered lint here");
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs
new file mode 100644
index 00000000000..100e555f299
--- /dev/null
+++ b/compiler/rustc_lint/src/internal.rs
@@ -0,0 +1,247 @@
+//! Some lints that are only useful in the compiler or crates that use compiler internals, such as
+//! Clippy.
+
+use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_ast::{Item, ItemKind};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_hir::{GenericArg, HirId, MutTy, Mutability, Path, PathSegment, QPath, Ty, TyKind};
+use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
+use rustc_span::hygiene::{ExpnKind, MacroKind};
+use rustc_span::symbol::{sym, Ident, Symbol};
+
+declare_tool_lint! {
+    pub rustc::DEFAULT_HASH_TYPES,
+    Allow,
+    "forbid HashMap and HashSet and suggest the FxHash* variants",
+    report_in_external_macro: true
+}
+
+pub struct DefaultHashTypes {
+    map: FxHashMap<Symbol, Symbol>,
+}
+
+impl DefaultHashTypes {
+    // we are allowed to use `HashMap` and `HashSet` as identifiers for implementing the lint itself
+    #[allow(rustc::default_hash_types)]
+    pub fn new() -> Self {
+        let mut map = FxHashMap::default();
+        map.insert(sym::HashMap, sym::FxHashMap);
+        map.insert(sym::HashSet, sym::FxHashSet);
+        Self { map }
+    }
+}
+
+impl_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
+
+impl EarlyLintPass for DefaultHashTypes {
+    fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) {
+        if let Some(replace) = self.map.get(&ident.name) {
+            cx.struct_span_lint(DEFAULT_HASH_TYPES, ident.span, |lint| {
+                // FIXME: We can avoid a copy here. Would require us to take String instead of &str.
+                let msg = format!("Prefer {} over {}, it has better performance", replace, ident);
+                lint.build(&msg)
+                    .span_suggestion(
+                        ident.span,
+                        "use",
+                        replace.to_string(),
+                        Applicability::MaybeIncorrect, // FxHashMap, ... needs another import
+                    )
+                    .note(&format!(
+                        "a `use rustc_data_structures::fx::{}` may be necessary",
+                        replace
+                    ))
+                    .emit();
+            });
+        }
+    }
+}
+
+declare_tool_lint! {
+    pub rustc::USAGE_OF_TY_TYKIND,
+    Allow,
+    "usage of `ty::TyKind` outside of the `ty::sty` module",
+    report_in_external_macro: true
+}
+
+declare_tool_lint! {
+    pub rustc::TY_PASS_BY_REFERENCE,
+    Allow,
+    "passing `Ty` or `TyCtxt` by reference",
+    report_in_external_macro: true
+}
+
+declare_tool_lint! {
+    pub rustc::USAGE_OF_QUALIFIED_TY,
+    Allow,
+    "using `ty::{Ty,TyCtxt}` instead of importing it",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(TyTyKind => [
+    USAGE_OF_TY_TYKIND,
+    TY_PASS_BY_REFERENCE,
+    USAGE_OF_QUALIFIED_TY,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for TyTyKind {
+    fn check_path(&mut self, cx: &LateContext<'_>, path: &'tcx Path<'tcx>, _: HirId) {
+        let segments = path.segments.iter().rev().skip(1).rev();
+
+        if let Some(last) = segments.last() {
+            let span = path.span.with_hi(last.ident.span.hi());
+            if lint_ty_kind_usage(cx, last) {
+                cx.struct_span_lint(USAGE_OF_TY_TYKIND, span, |lint| {
+                    lint.build("usage of `ty::TyKind::<kind>`")
+                        .span_suggestion(
+                            span,
+                            "try using ty::<kind> directly",
+                            "ty".to_string(),
+                            Applicability::MaybeIncorrect, // ty maybe needs an import
+                        )
+                        .emit();
+                })
+            }
+        }
+    }
+
+    fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
+        match &ty.kind {
+            TyKind::Path(qpath) => {
+                if let QPath::Resolved(_, path) = qpath {
+                    if let Some(last) = path.segments.iter().last() {
+                        if lint_ty_kind_usage(cx, last) {
+                            cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
+                                lint.build("usage of `ty::TyKind`")
+                                    .help("try using `Ty` instead")
+                                    .emit();
+                            })
+                        } else {
+                            if ty.span.from_expansion() {
+                                return;
+                            }
+                            if let Some(t) = is_ty_or_ty_ctxt(cx, ty) {
+                                if path.segments.len() > 1 {
+                                    cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
+                                        lint.build(&format!("usage of qualified `ty::{}`", t))
+                                            .span_suggestion(
+                                                path.span,
+                                                "try using it unqualified",
+                                                t,
+                                                // The import probably needs to be changed
+                                                Applicability::MaybeIncorrect,
+                                            )
+                                            .emit();
+                                    })
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            TyKind::Rptr(_, MutTy { ty: inner_ty, mutbl: Mutability::Not }) => {
+                if let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) {
+                    if cx.tcx.impl_trait_ref(impl_did).is_some() {
+                        return;
+                    }
+                }
+                if let Some(t) = is_ty_or_ty_ctxt(cx, &inner_ty) {
+                    cx.struct_span_lint(TY_PASS_BY_REFERENCE, ty.span, |lint| {
+                        lint.build(&format!("passing `{}` by reference", t))
+                            .span_suggestion(
+                                ty.span,
+                                "try passing by value",
+                                t,
+                                // Changing type of function argument
+                                Applicability::MaybeIncorrect,
+                            )
+                            .emit();
+                    })
+                }
+            }
+            _ => {}
+        }
+    }
+}
+
+fn lint_ty_kind_usage(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> bool {
+    if let Some(res) = segment.res {
+        if let Some(did) = res.opt_def_id() {
+            return cx.tcx.is_diagnostic_item(sym::TyKind, did);
+        }
+    }
+
+    false
+}
+
+fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, ty: &Ty<'_>) -> Option<String> {
+    if let TyKind::Path(qpath) = &ty.kind {
+        if let QPath::Resolved(_, path) = qpath {
+            let did = path.res.opt_def_id()?;
+            if cx.tcx.is_diagnostic_item(sym::Ty, did) {
+                return Some(format!("Ty{}", gen_args(path.segments.last().unwrap())));
+            } else if cx.tcx.is_diagnostic_item(sym::TyCtxt, did) {
+                return Some(format!("TyCtxt{}", gen_args(path.segments.last().unwrap())));
+            }
+        }
+    }
+
+    None
+}
+
+fn gen_args(segment: &PathSegment<'_>) -> String {
+    if let Some(args) = &segment.args {
+        let lifetimes = args
+            .args
+            .iter()
+            .filter_map(|arg| {
+                if let GenericArg::Lifetime(lt) = arg {
+                    Some(lt.name.ident().to_string())
+                } else {
+                    None
+                }
+            })
+            .collect::<Vec<_>>();
+
+        if !lifetimes.is_empty() {
+            return format!("<{}>", lifetimes.join(", "));
+        }
+    }
+
+    String::new()
+}
+
+declare_tool_lint! {
+    pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
+    Allow,
+    "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
+}
+
+declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
+
+impl EarlyLintPass for LintPassImpl {
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+        if let ItemKind::Impl { of_trait: Some(lint_pass), .. } = &item.kind {
+            if let Some(last) = lint_pass.path.segments.last() {
+                if last.ident.name == sym::LintPass {
+                    let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
+                    let call_site = expn_data.call_site;
+                    if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
+                        && call_site.ctxt().outer_expn_data().kind
+                            != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
+                    {
+                        cx.struct_span_lint(
+                            LINT_PASS_IMPL_WITHOUT_MACRO,
+                            lint_pass.path.span,
+                            |lint| {
+                                lint.build("implementing `LintPass` by hand")
+                                    .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead")
+                                    .emit();
+                            },
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs
new file mode 100644
index 00000000000..a6c04fb0b4c
--- /dev/null
+++ b/compiler/rustc_lint/src/late.rs
@@ -0,0 +1,499 @@
+//! Implementation of lint checking.
+//!
+//! The lint checking is mostly consolidated into one pass which runs
+//! after all other analyses. Throughout compilation, lint warnings
+//! can be added via the `add_lint` method on the Session structure. This
+//! requires a span and an ID of the node that the lint is being added to. The
+//! lint isn't actually emitted at that time because it is unknown what the
+//! actual lint level at that location is.
+//!
+//! To actually emit lint warnings/errors, a separate pass is used.
+//! A context keeps track of the current state of all lint levels.
+//! Upon entering a node of the ast which can modify the lint settings, the
+//! previous lint state is pushed onto a stack and the ast is then recursed
+//! upon. As the ast is traversed, this keeps track of the current lint level
+//! for all lint attributes.
+
+use crate::{passes::LateLintPassObject, LateContext, LateLintPass, LintStore};
+use rustc_ast as ast;
+use rustc_ast::walk_list;
+use rustc_data_structures::sync::{join, par_iter, ParallelIterator};
+use rustc_hir as hir;
+use rustc_hir::def_id::{LocalDefId, LOCAL_CRATE};
+use rustc_hir::intravisit as hir_visit;
+use rustc_hir::intravisit::Visitor;
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_session::lint::LintPass;
+use rustc_span::symbol::Symbol;
+use rustc_span::Span;
+
+use std::any::Any;
+use std::cell::Cell;
+use std::slice;
+use tracing::debug;
+
+/// Extract the `LintStore` from the query context.
+/// This function exists because we've erased `LintStore` as `dyn Any` in the context.
+crate fn unerased_lint_store(tcx: TyCtxt<'_>) -> &LintStore {
+    let store: &dyn Any = &*tcx.lint_store;
+    store.downcast_ref().unwrap()
+}
+
+macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({
+    $cx.pass.$f(&$cx.context, $($args),*);
+}) }
+
+struct LateContextAndPass<'tcx, T: LateLintPass<'tcx>> {
+    context: LateContext<'tcx>,
+    pass: T,
+}
+
+impl<'tcx, T: LateLintPass<'tcx>> LateContextAndPass<'tcx, T> {
+    /// Merge the lints specified by any lint attributes into the
+    /// current lint context, call the provided function, then reset the
+    /// lints in effect to their previous state.
+    fn with_lint_attrs<F>(&mut self, id: hir::HirId, attrs: &'tcx [ast::Attribute], f: F)
+    where
+        F: FnOnce(&mut Self),
+    {
+        let prev = self.context.last_node_with_lint_attrs;
+        self.context.last_node_with_lint_attrs = id;
+        self.enter_attrs(attrs);
+        f(self);
+        self.exit_attrs(attrs);
+        self.context.last_node_with_lint_attrs = prev;
+    }
+
+    fn with_param_env<F>(&mut self, id: hir::HirId, f: F)
+    where
+        F: FnOnce(&mut Self),
+    {
+        let old_param_env = self.context.param_env;
+        self.context.param_env =
+            self.context.tcx.param_env(self.context.tcx.hir().local_def_id(id));
+        f(self);
+        self.context.param_env = old_param_env;
+    }
+
+    fn process_mod(&mut self, m: &'tcx hir::Mod<'tcx>, s: Span, n: hir::HirId) {
+        lint_callback!(self, check_mod, m, s, n);
+        hir_visit::walk_mod(self, m, n);
+        lint_callback!(self, check_mod_post, m, s, n);
+    }
+
+    fn enter_attrs(&mut self, attrs: &'tcx [ast::Attribute]) {
+        debug!("late context: enter_attrs({:?})", attrs);
+        lint_callback!(self, enter_lint_attrs, attrs);
+    }
+
+    fn exit_attrs(&mut self, attrs: &'tcx [ast::Attribute]) {
+        debug!("late context: exit_attrs({:?})", attrs);
+        lint_callback!(self, exit_lint_attrs, attrs);
+    }
+}
+
+impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPass<'tcx, T> {
+    type Map = Map<'tcx>;
+
+    /// Because lints are scoped lexically, we want to walk nested
+    /// items in the context of the outer item, so enable
+    /// deep-walking.
+    fn nested_visit_map(&mut self) -> hir_visit::NestedVisitorMap<Self::Map> {
+        hir_visit::NestedVisitorMap::All(self.context.tcx.hir())
+    }
+
+    fn visit_nested_body(&mut self, body_id: hir::BodyId) {
+        let old_enclosing_body = self.context.enclosing_body.replace(body_id);
+        let old_cached_typeck_results = self.context.cached_typeck_results.get();
+
+        // HACK(eddyb) avoid trashing `cached_typeck_results` when we're
+        // nested in `visit_fn`, which may have already resulted in them
+        // being queried.
+        if old_enclosing_body != Some(body_id) {
+            self.context.cached_typeck_results.set(None);
+        }
+
+        let body = self.context.tcx.hir().body(body_id);
+        self.visit_body(body);
+        self.context.enclosing_body = old_enclosing_body;
+
+        // See HACK comment above.
+        if old_enclosing_body != Some(body_id) {
+            self.context.cached_typeck_results.set(old_cached_typeck_results);
+        }
+    }
+
+    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+        self.with_lint_attrs(param.hir_id, &param.attrs, |cx| {
+            lint_callback!(cx, check_param, param);
+            hir_visit::walk_param(cx, param);
+        });
+    }
+
+    fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) {
+        lint_callback!(self, check_body, body);
+        hir_visit::walk_body(self, body);
+        lint_callback!(self, check_body_post, body);
+    }
+
+    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
+        let generics = self.context.generics.take();
+        self.context.generics = it.kind.generics();
+        self.with_lint_attrs(it.hir_id, &it.attrs, |cx| {
+            cx.with_param_env(it.hir_id, |cx| {
+                lint_callback!(cx, check_item, it);
+                hir_visit::walk_item(cx, it);
+                lint_callback!(cx, check_item_post, it);
+            });
+        });
+        self.context.generics = generics;
+    }
+
+    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
+        self.with_lint_attrs(it.hir_id, &it.attrs, |cx| {
+            cx.with_param_env(it.hir_id, |cx| {
+                lint_callback!(cx, check_foreign_item, it);
+                hir_visit::walk_foreign_item(cx, it);
+                lint_callback!(cx, check_foreign_item_post, it);
+            });
+        })
+    }
+
+    fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {
+        lint_callback!(self, check_pat, p);
+        hir_visit::walk_pat(self, p);
+    }
+
+    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+        self.with_lint_attrs(e.hir_id, &e.attrs, |cx| {
+            lint_callback!(cx, check_expr, e);
+            hir_visit::walk_expr(cx, e);
+            lint_callback!(cx, check_expr_post, e);
+        })
+    }
+
+    fn visit_stmt(&mut self, s: &'tcx hir::Stmt<'tcx>) {
+        // statement attributes are actually just attributes on one of
+        // - item
+        // - local
+        // - expression
+        // so we keep track of lint levels there
+        lint_callback!(self, check_stmt, s);
+        hir_visit::walk_stmt(self, s);
+    }
+
+    fn visit_fn(
+        &mut self,
+        fk: hir_visit::FnKind<'tcx>,
+        decl: &'tcx hir::FnDecl<'tcx>,
+        body_id: hir::BodyId,
+        span: Span,
+        id: hir::HirId,
+    ) {
+        // Wrap in typeck results here, not just in visit_nested_body,
+        // in order for `check_fn` to be able to use them.
+        let old_enclosing_body = self.context.enclosing_body.replace(body_id);
+        let old_cached_typeck_results = self.context.cached_typeck_results.take();
+        let body = self.context.tcx.hir().body(body_id);
+        lint_callback!(self, check_fn, fk, decl, body, span, id);
+        hir_visit::walk_fn(self, fk, decl, body_id, span, id);
+        lint_callback!(self, check_fn_post, fk, decl, body, span, id);
+        self.context.enclosing_body = old_enclosing_body;
+        self.context.cached_typeck_results.set(old_cached_typeck_results);
+    }
+
+    fn visit_variant_data(
+        &mut self,
+        s: &'tcx hir::VariantData<'tcx>,
+        _: Symbol,
+        _: &'tcx hir::Generics<'tcx>,
+        _: hir::HirId,
+        _: Span,
+    ) {
+        lint_callback!(self, check_struct_def, s);
+        hir_visit::walk_struct_def(self, s);
+        lint_callback!(self, check_struct_def_post, s);
+    }
+
+    fn visit_struct_field(&mut self, s: &'tcx hir::StructField<'tcx>) {
+        self.with_lint_attrs(s.hir_id, &s.attrs, |cx| {
+            lint_callback!(cx, check_struct_field, s);
+            hir_visit::walk_struct_field(cx, s);
+        })
+    }
+
+    fn visit_variant(
+        &mut self,
+        v: &'tcx hir::Variant<'tcx>,
+        g: &'tcx hir::Generics<'tcx>,
+        item_id: hir::HirId,
+    ) {
+        self.with_lint_attrs(v.id, &v.attrs, |cx| {
+            lint_callback!(cx, check_variant, v);
+            hir_visit::walk_variant(cx, v, g, item_id);
+            lint_callback!(cx, check_variant_post, v);
+        })
+    }
+
+    fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx>) {
+        lint_callback!(self, check_ty, t);
+        hir_visit::walk_ty(self, t);
+    }
+
+    fn visit_name(&mut self, sp: Span, name: Symbol) {
+        lint_callback!(self, check_name, sp, name);
+    }
+
+    fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, s: Span, n: hir::HirId) {
+        if !self.context.only_module {
+            self.process_mod(m, s, n);
+        }
+    }
+
+    fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
+        self.with_lint_attrs(l.hir_id, &l.attrs, |cx| {
+            lint_callback!(cx, check_local, l);
+            hir_visit::walk_local(cx, l);
+        })
+    }
+
+    fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) {
+        lint_callback!(self, check_block, b);
+        hir_visit::walk_block(self, b);
+        lint_callback!(self, check_block_post, b);
+    }
+
+    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
+        lint_callback!(self, check_arm, a);
+        hir_visit::walk_arm(self, a);
+    }
+
+    fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
+        lint_callback!(self, check_generic_param, p);
+        hir_visit::walk_generic_param(self, p);
+    }
+
+    fn visit_generics(&mut self, g: &'tcx hir::Generics<'tcx>) {
+        lint_callback!(self, check_generics, g);
+        hir_visit::walk_generics(self, g);
+    }
+
+    fn visit_where_predicate(&mut self, p: &'tcx hir::WherePredicate<'tcx>) {
+        lint_callback!(self, check_where_predicate, p);
+        hir_visit::walk_where_predicate(self, p);
+    }
+
+    fn visit_poly_trait_ref(
+        &mut self,
+        t: &'tcx hir::PolyTraitRef<'tcx>,
+        m: hir::TraitBoundModifier,
+    ) {
+        lint_callback!(self, check_poly_trait_ref, t, m);
+        hir_visit::walk_poly_trait_ref(self, t, m);
+    }
+
+    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
+        let generics = self.context.generics.take();
+        self.context.generics = Some(&trait_item.generics);
+        self.with_lint_attrs(trait_item.hir_id, &trait_item.attrs, |cx| {
+            cx.with_param_env(trait_item.hir_id, |cx| {
+                lint_callback!(cx, check_trait_item, trait_item);
+                hir_visit::walk_trait_item(cx, trait_item);
+                lint_callback!(cx, check_trait_item_post, trait_item);
+            });
+        });
+        self.context.generics = generics;
+    }
+
+    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
+        let generics = self.context.generics.take();
+        self.context.generics = Some(&impl_item.generics);
+        self.with_lint_attrs(impl_item.hir_id, &impl_item.attrs, |cx| {
+            cx.with_param_env(impl_item.hir_id, |cx| {
+                lint_callback!(cx, check_impl_item, impl_item);
+                hir_visit::walk_impl_item(cx, impl_item);
+                lint_callback!(cx, check_impl_item_post, impl_item);
+            });
+        });
+        self.context.generics = generics;
+    }
+
+    fn visit_lifetime(&mut self, lt: &'tcx hir::Lifetime) {
+        lint_callback!(self, check_lifetime, lt);
+        hir_visit::walk_lifetime(self, lt);
+    }
+
+    fn visit_path(&mut self, p: &'tcx hir::Path<'tcx>, id: hir::HirId) {
+        lint_callback!(self, check_path, p, id);
+        hir_visit::walk_path(self, p);
+    }
+
+    fn visit_attribute(&mut self, attr: &'tcx ast::Attribute) {
+        lint_callback!(self, check_attribute, attr);
+    }
+}
+
+struct LateLintPassObjects<'a> {
+    lints: &'a mut [LateLintPassObject],
+}
+
+#[allow(rustc::lint_pass_impl_without_macro)]
+impl LintPass for LateLintPassObjects<'_> {
+    fn name(&self) -> &'static str {
+        panic!()
+    }
+}
+
+macro_rules! expand_late_lint_pass_impl_methods {
+    ([$hir:tt], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+        $(fn $name(&mut self, context: &LateContext<$hir>, $($param: $arg),*) {
+            for obj in self.lints.iter_mut() {
+                obj.$name(context, $($param),*);
+            }
+        })*
+    )
+}
+
+macro_rules! late_lint_pass_impl {
+    ([], [$hir:tt], $methods:tt) => {
+        impl<$hir> LateLintPass<$hir> for LateLintPassObjects<'_> {
+            expand_late_lint_pass_impl_methods!([$hir], $methods);
+        }
+    };
+}
+
+crate::late_lint_methods!(late_lint_pass_impl, [], ['tcx]);
+
+fn late_lint_mod_pass<'tcx, T: LateLintPass<'tcx>>(
+    tcx: TyCtxt<'tcx>,
+    module_def_id: LocalDefId,
+    pass: T,
+) {
+    let access_levels = &tcx.privacy_access_levels(LOCAL_CRATE);
+
+    let context = LateContext {
+        tcx,
+        enclosing_body: None,
+        cached_typeck_results: Cell::new(None),
+        param_env: ty::ParamEnv::empty(),
+        access_levels,
+        lint_store: unerased_lint_store(tcx),
+        last_node_with_lint_attrs: tcx.hir().local_def_id_to_hir_id(module_def_id),
+        generics: None,
+        only_module: true,
+    };
+
+    let mut cx = LateContextAndPass { context, pass };
+
+    let (module, span, hir_id) = tcx.hir().get_module(module_def_id);
+    cx.process_mod(module, span, hir_id);
+
+    // Visit the crate attributes
+    if hir_id == hir::CRATE_HIR_ID {
+        walk_list!(cx, visit_attribute, tcx.hir().attrs(hir::CRATE_HIR_ID));
+    }
+}
+
+pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx>>(
+    tcx: TyCtxt<'tcx>,
+    module_def_id: LocalDefId,
+    builtin_lints: T,
+) {
+    if tcx.sess.opts.debugging_opts.no_interleave_lints {
+        // These passes runs in late_lint_crate with -Z no_interleave_lints
+        return;
+    }
+
+    late_lint_mod_pass(tcx, module_def_id, builtin_lints);
+
+    let mut passes: Vec<_> =
+        unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)()).collect();
+
+    if !passes.is_empty() {
+        late_lint_mod_pass(tcx, module_def_id, LateLintPassObjects { lints: &mut passes[..] });
+    }
+}
+
+fn late_lint_pass_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, pass: T) {
+    let access_levels = &tcx.privacy_access_levels(LOCAL_CRATE);
+
+    let krate = tcx.hir().krate();
+
+    let context = LateContext {
+        tcx,
+        enclosing_body: None,
+        cached_typeck_results: Cell::new(None),
+        param_env: ty::ParamEnv::empty(),
+        access_levels,
+        lint_store: unerased_lint_store(tcx),
+        last_node_with_lint_attrs: hir::CRATE_HIR_ID,
+        generics: None,
+        only_module: false,
+    };
+
+    let mut cx = LateContextAndPass { context, pass };
+
+    // Visit the whole crate.
+    cx.with_lint_attrs(hir::CRATE_HIR_ID, &krate.item.attrs, |cx| {
+        // since the root module isn't visited as an item (because it isn't an
+        // item), warn for it here.
+        lint_callback!(cx, check_crate, krate);
+
+        hir_visit::walk_crate(cx, krate);
+
+        lint_callback!(cx, check_crate_post, krate);
+    })
+}
+
+fn late_lint_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, builtin_lints: T) {
+    let mut passes = unerased_lint_store(tcx).late_passes.iter().map(|p| (p)()).collect::<Vec<_>>();
+
+    if !tcx.sess.opts.debugging_opts.no_interleave_lints {
+        if !passes.is_empty() {
+            late_lint_pass_crate(tcx, LateLintPassObjects { lints: &mut passes[..] });
+        }
+
+        late_lint_pass_crate(tcx, builtin_lints);
+    } else {
+        for pass in &mut passes {
+            tcx.sess.prof.extra_verbose_generic_activity("run_late_lint", pass.name()).run(|| {
+                late_lint_pass_crate(tcx, LateLintPassObjects { lints: slice::from_mut(pass) });
+            });
+        }
+
+        let mut passes: Vec<_> =
+            unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)()).collect();
+
+        for pass in &mut passes {
+            tcx.sess.prof.extra_verbose_generic_activity("run_late_module_lint", pass.name()).run(
+                || {
+                    late_lint_pass_crate(tcx, LateLintPassObjects { lints: slice::from_mut(pass) });
+                },
+            );
+        }
+    }
+}
+
+/// Performs lint checking on a crate.
+pub fn check_crate<'tcx, T: LateLintPass<'tcx>>(
+    tcx: TyCtxt<'tcx>,
+    builtin_lints: impl FnOnce() -> T + Send,
+) {
+    join(
+        || {
+            tcx.sess.time("crate_lints", || {
+                // Run whole crate non-incremental lints
+                late_lint_crate(tcx, builtin_lints());
+            });
+        },
+        || {
+            tcx.sess.time("module_lints", || {
+                // Run per-module lints
+                par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| {
+                    tcx.ensure().lint_mod(tcx.hir().local_def_id(module));
+                });
+            });
+        },
+    );
+}
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
new file mode 100644
index 00000000000..48254dcee82
--- /dev/null
+++ b/compiler/rustc_lint/src/levels.rs
@@ -0,0 +1,576 @@
+use crate::context::{CheckLintNameResult, LintStore};
+use crate::late::unerased_lint_store;
+use rustc_ast as ast;
+use rustc_ast::attr;
+use rustc_ast::unwrap_or;
+use rustc_ast_pretty::pprust;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::{struct_span_err, Applicability};
+use rustc_hir as hir;
+use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
+use rustc_hir::{intravisit, HirId};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::LintDiagnosticBuilder;
+use rustc_middle::lint::{struct_lint_level, LintLevelMap, LintLevelSets, LintSet, LintSource};
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::lint::{builtin, Level, Lint, LintId};
+use rustc_session::parse::feature_err;
+use rustc_session::Session;
+use rustc_span::symbol::{sym, Symbol};
+use rustc_span::{source_map::MultiSpan, Span, DUMMY_SP};
+
+use std::cmp;
+
+fn lint_levels(tcx: TyCtxt<'_>, cnum: CrateNum) -> LintLevelMap {
+    assert_eq!(cnum, LOCAL_CRATE);
+    let store = unerased_lint_store(tcx);
+    let levels = LintLevelsBuilder::new(tcx.sess, false, &store);
+    let mut builder = LintLevelMapBuilder { levels, tcx, store };
+    let krate = tcx.hir().krate();
+
+    let push = builder.levels.push(&krate.item.attrs, &store, true);
+    builder.levels.register_id(hir::CRATE_HIR_ID);
+    for macro_def in krate.exported_macros {
+        builder.levels.register_id(macro_def.hir_id);
+    }
+    intravisit::walk_crate(&mut builder, krate);
+    builder.levels.pop(push);
+
+    builder.levels.build_map()
+}
+
+pub struct LintLevelsBuilder<'s> {
+    sess: &'s Session,
+    sets: LintLevelSets,
+    id_to_set: FxHashMap<HirId, u32>,
+    cur: u32,
+    warn_about_weird_lints: bool,
+}
+
+pub struct BuilderPush {
+    prev: u32,
+    pub changed: bool,
+}
+
+impl<'s> LintLevelsBuilder<'s> {
+    pub fn new(sess: &'s Session, warn_about_weird_lints: bool, store: &LintStore) -> Self {
+        let mut builder = LintLevelsBuilder {
+            sess,
+            sets: LintLevelSets::new(),
+            cur: 0,
+            id_to_set: Default::default(),
+            warn_about_weird_lints,
+        };
+        builder.process_command_line(sess, store);
+        assert_eq!(builder.sets.list.len(), 1);
+        builder
+    }
+
+    fn process_command_line(&mut self, sess: &Session, store: &LintStore) {
+        let mut specs = FxHashMap::default();
+        self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
+
+        for &(ref lint_name, level) in &sess.opts.lint_opts {
+            store.check_lint_name_cmdline(sess, &lint_name, level);
+
+            // If the cap is less than this specified level, e.g., if we've got
+            // `--cap-lints allow` but we've also got `-D foo` then we ignore
+            // this specification as the lint cap will set it to allow anyway.
+            let level = cmp::min(level, self.sets.lint_cap);
+
+            let lint_flag_val = Symbol::intern(lint_name);
+
+            let ids = match store.find_lints(&lint_name) {
+                Ok(ids) => ids,
+                Err(_) => continue, // errors handled in check_lint_name_cmdline above
+            };
+            for id in ids {
+                self.check_gated_lint(id, DUMMY_SP);
+                let src = LintSource::CommandLine(lint_flag_val);
+                specs.insert(id, (level, src));
+            }
+        }
+
+        self.sets.list.push(LintSet::CommandLine { specs });
+    }
+
+    /// Pushes a list of AST lint attributes onto this context.
+    ///
+    /// This function will return a `BuilderPush` object which should be passed
+    /// to `pop` when this scope for the attributes provided is exited.
+    ///
+    /// This function will perform a number of tasks:
+    ///
+    /// * It'll validate all lint-related attributes in `attrs`
+    /// * It'll mark all lint-related attributes as used
+    /// * Lint levels will be updated based on the attributes provided
+    /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
+    ///   `#[allow]`
+    ///
+    /// Don't forget to call `pop`!
+    pub fn push(
+        &mut self,
+        attrs: &[ast::Attribute],
+        store: &LintStore,
+        is_crate_node: bool,
+    ) -> BuilderPush {
+        let mut specs = FxHashMap::default();
+        let sess = self.sess;
+        let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
+        for attr in attrs {
+            let level = match Level::from_symbol(attr.name_or_empty()) {
+                None => continue,
+                Some(lvl) => lvl,
+            };
+
+            let meta = unwrap_or!(attr.meta(), continue);
+            self.sess.mark_attr_used(attr);
+
+            let mut metas = unwrap_or!(meta.meta_item_list(), continue);
+
+            if metas.is_empty() {
+                // FIXME (#55112): issue unused-attributes lint for `#[level()]`
+                continue;
+            }
+
+            // Before processing the lint names, look for a reason (RFC 2383)
+            // at the end.
+            let mut reason = None;
+            let tail_li = &metas[metas.len() - 1];
+            if let Some(item) = tail_li.meta_item() {
+                match item.kind {
+                    ast::MetaItemKind::Word => {} // actual lint names handled later
+                    ast::MetaItemKind::NameValue(ref name_value) => {
+                        if item.path == sym::reason {
+                            // found reason, reslice meta list to exclude it
+                            metas = &metas[0..metas.len() - 1];
+                            // FIXME (#55112): issue unused-attributes lint if we thereby
+                            // don't have any lint names (`#[level(reason = "foo")]`)
+                            if let ast::LitKind::Str(rationale, _) = name_value.kind {
+                                if !self.sess.features_untracked().lint_reasons {
+                                    feature_err(
+                                        &self.sess.parse_sess,
+                                        sym::lint_reasons,
+                                        item.span,
+                                        "lint reasons are experimental",
+                                    )
+                                    .emit();
+                                }
+                                reason = Some(rationale);
+                            } else {
+                                bad_attr(name_value.span)
+                                    .span_label(name_value.span, "reason must be a string literal")
+                                    .emit();
+                            }
+                        } else {
+                            bad_attr(item.span)
+                                .span_label(item.span, "bad attribute argument")
+                                .emit();
+                        }
+                    }
+                    ast::MetaItemKind::List(_) => {
+                        bad_attr(item.span).span_label(item.span, "bad attribute argument").emit();
+                    }
+                }
+            }
+
+            for li in metas {
+                let meta_item = match li.meta_item() {
+                    Some(meta_item) if meta_item.is_word() => meta_item,
+                    _ => {
+                        let sp = li.span();
+                        let mut err = bad_attr(sp);
+                        let mut add_label = true;
+                        if let Some(item) = li.meta_item() {
+                            if let ast::MetaItemKind::NameValue(_) = item.kind {
+                                if item.path == sym::reason {
+                                    err.span_label(sp, "reason in lint attribute must come last");
+                                    add_label = false;
+                                }
+                            }
+                        }
+                        if add_label {
+                            err.span_label(sp, "bad attribute argument");
+                        }
+                        err.emit();
+                        continue;
+                    }
+                };
+                let tool_name = if meta_item.path.segments.len() > 1 {
+                    let tool_ident = meta_item.path.segments[0].ident;
+                    if !attr::is_known_lint_tool(tool_ident) {
+                        struct_span_err!(
+                            sess,
+                            tool_ident.span,
+                            E0710,
+                            "an unknown tool name found in scoped lint: `{}`",
+                            pprust::path_to_string(&meta_item.path),
+                        )
+                        .emit();
+                        continue;
+                    }
+
+                    Some(tool_ident.name)
+                } else {
+                    None
+                };
+                let name = meta_item.path.segments.last().expect("empty lint name").ident.name;
+                match store.check_lint_name(&name.as_str(), tool_name) {
+                    CheckLintNameResult::Ok(ids) => {
+                        let src = LintSource::Node(name, li.span(), reason);
+                        for &id in ids {
+                            self.check_gated_lint(id, attr.span);
+                            specs.insert(id, (level, src));
+                        }
+                    }
+
+                    CheckLintNameResult::Tool(result) => {
+                        match result {
+                            Ok(ids) => {
+                                let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
+                                let src = LintSource::Node(
+                                    Symbol::intern(complete_name),
+                                    li.span(),
+                                    reason,
+                                );
+                                for id in ids {
+                                    specs.insert(*id, (level, src));
+                                }
+                            }
+                            Err((Some(ids), new_lint_name)) => {
+                                let lint = builtin::RENAMED_AND_REMOVED_LINTS;
+                                let (lvl, src) =
+                                    self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
+                                struct_lint_level(
+                                    self.sess,
+                                    lint,
+                                    lvl,
+                                    src,
+                                    Some(li.span().into()),
+                                    |lint| {
+                                        let msg = format!(
+                                            "lint name `{}` is deprecated \
+                                             and may not have an effect in the future. \
+                                             Also `cfg_attr(cargo-clippy)` won't be necessary anymore",
+                                            name
+                                        );
+                                        lint.build(&msg)
+                                            .span_suggestion(
+                                                li.span(),
+                                                "change it to",
+                                                new_lint_name.to_string(),
+                                                Applicability::MachineApplicable,
+                                            )
+                                            .emit();
+                                    },
+                                );
+
+                                let src = LintSource::Node(
+                                    Symbol::intern(&new_lint_name),
+                                    li.span(),
+                                    reason,
+                                );
+                                for id in ids {
+                                    specs.insert(*id, (level, src));
+                                }
+                            }
+                            Err((None, _)) => {
+                                // If Tool(Err(None, _)) is returned, then either the lint does not
+                                // exist in the tool or the code was not compiled with the tool and
+                                // therefore the lint was never added to the `LintStore`. To detect
+                                // this is the responsibility of the lint tool.
+                            }
+                        }
+                    }
+
+                    _ if !self.warn_about_weird_lints => {}
+
+                    CheckLintNameResult::Warning(msg, renamed) => {
+                        let lint = builtin::RENAMED_AND_REMOVED_LINTS;
+                        let (level, src) =
+                            self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
+                        struct_lint_level(
+                            self.sess,
+                            lint,
+                            level,
+                            src,
+                            Some(li.span().into()),
+                            |lint| {
+                                let mut err = lint.build(&msg);
+                                if let Some(new_name) = renamed {
+                                    err.span_suggestion(
+                                        li.span(),
+                                        "use the new name",
+                                        new_name,
+                                        Applicability::MachineApplicable,
+                                    );
+                                }
+                                err.emit();
+                            },
+                        );
+                    }
+                    CheckLintNameResult::NoLint(suggestion) => {
+                        let lint = builtin::UNKNOWN_LINTS;
+                        let (level, src) =
+                            self.sets.get_lint_level(lint, self.cur, Some(&specs), self.sess);
+                        struct_lint_level(
+                            self.sess,
+                            lint,
+                            level,
+                            src,
+                            Some(li.span().into()),
+                            |lint| {
+                                let mut db = lint.build(&format!("unknown lint: `{}`", name));
+                                if let Some(suggestion) = suggestion {
+                                    db.span_suggestion(
+                                        li.span(),
+                                        "did you mean",
+                                        suggestion.to_string(),
+                                        Applicability::MachineApplicable,
+                                    );
+                                }
+                                db.emit();
+                            },
+                        );
+                    }
+                }
+            }
+        }
+
+        if !is_crate_node {
+            for (id, &(level, ref src)) in specs.iter() {
+                if !id.lint.crate_level_only {
+                    continue;
+                }
+
+                let (lint_attr_name, lint_attr_span) = match *src {
+                    LintSource::Node(name, span, _) => (name, span),
+                    _ => continue,
+                };
+
+                let lint = builtin::UNUSED_ATTRIBUTES;
+                let (lint_level, lint_src) =
+                    self.sets.get_lint_level(lint, self.cur, Some(&specs), self.sess);
+                struct_lint_level(
+                    self.sess,
+                    lint,
+                    lint_level,
+                    lint_src,
+                    Some(lint_attr_span.into()),
+                    |lint| {
+                        let mut db = lint.build(&format!(
+                            "{}({}) is ignored unless specified at crate level",
+                            level.as_str(),
+                            lint_attr_name
+                        ));
+                        db.emit();
+                    },
+                );
+                // don't set a separate error for every lint in the group
+                break;
+            }
+        }
+
+        for (id, &(level, ref src)) in specs.iter() {
+            if level == Level::Forbid {
+                continue;
+            }
+            let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) {
+                (Some(Level::Forbid), src) => src,
+                _ => continue,
+            };
+            let forbidden_lint_name = match forbid_src {
+                LintSource::Default => id.to_string(),
+                LintSource::Node(name, _, _) => name.to_string(),
+                LintSource::CommandLine(name) => name.to_string(),
+            };
+            let (lint_attr_name, lint_attr_span) = match *src {
+                LintSource::Node(name, span, _) => (name, span),
+                _ => continue,
+            };
+            let mut diag_builder = struct_span_err!(
+                self.sess,
+                lint_attr_span,
+                E0453,
+                "{}({}) overruled by outer forbid({})",
+                level.as_str(),
+                lint_attr_name,
+                forbidden_lint_name
+            );
+            diag_builder.span_label(lint_attr_span, "overruled by previous forbid");
+            match forbid_src {
+                LintSource::Default => {}
+                LintSource::Node(_, forbid_source_span, reason) => {
+                    diag_builder.span_label(forbid_source_span, "`forbid` level set here");
+                    if let Some(rationale) = reason {
+                        diag_builder.note(&rationale.as_str());
+                    }
+                }
+                LintSource::CommandLine(_) => {
+                    diag_builder.note("`forbid` lint level was set on command line");
+                }
+            }
+            diag_builder.emit();
+            // don't set a separate error for every lint in the group
+            break;
+        }
+
+        let prev = self.cur;
+        if !specs.is_empty() {
+            self.cur = self.sets.list.len() as u32;
+            self.sets.list.push(LintSet::Node { specs, parent: prev });
+        }
+
+        BuilderPush { prev, changed: prev != self.cur }
+    }
+
+    /// Checks if the lint is gated on a feature that is not enabled.
+    fn check_gated_lint(&self, lint_id: LintId, span: Span) {
+        if let Some(feature) = lint_id.lint.feature_gate {
+            if !self.sess.features_untracked().enabled(feature) {
+                feature_err(
+                    &self.sess.parse_sess,
+                    feature,
+                    span,
+                    &format!("the `{}` lint is unstable", lint_id.lint.name_lower()),
+                )
+                .emit();
+            }
+        }
+    }
+
+    /// Called after `push` when the scope of a set of attributes are exited.
+    pub fn pop(&mut self, push: BuilderPush) {
+        self.cur = push.prev;
+    }
+
+    /// Find the lint level for a lint.
+    pub fn lint_level(&self, lint: &'static Lint) -> (Level, LintSource) {
+        self.sets.get_lint_level(lint, self.cur, None, self.sess)
+    }
+
+    /// Used to emit a lint-related diagnostic based on the current state of
+    /// this lint context.
+    pub fn struct_lint(
+        &self,
+        lint: &'static Lint,
+        span: Option<MultiSpan>,
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    ) {
+        let (level, src) = self.lint_level(lint);
+        struct_lint_level(self.sess, lint, level, src, span, decorate)
+    }
+
+    /// Registers the ID provided with the current set of lints stored in
+    /// this context.
+    pub fn register_id(&mut self, id: HirId) {
+        self.id_to_set.insert(id, self.cur);
+    }
+
+    pub fn build(self) -> LintLevelSets {
+        self.sets
+    }
+
+    pub fn build_map(self) -> LintLevelMap {
+        LintLevelMap { sets: self.sets, id_to_set: self.id_to_set }
+    }
+}
+
+struct LintLevelMapBuilder<'a, 'tcx> {
+    levels: LintLevelsBuilder<'tcx>,
+    tcx: TyCtxt<'tcx>,
+    store: &'a LintStore,
+}
+
+impl LintLevelMapBuilder<'_, '_> {
+    fn with_lint_attrs<F>(&mut self, id: hir::HirId, attrs: &[ast::Attribute], f: F)
+    where
+        F: FnOnce(&mut Self),
+    {
+        let is_crate_hir = id == hir::CRATE_HIR_ID;
+        let push = self.levels.push(attrs, self.store, is_crate_hir);
+        if push.changed {
+            self.levels.register_id(id);
+        }
+        f(self);
+        self.levels.pop(push);
+    }
+}
+
+impl<'tcx> intravisit::Visitor<'tcx> for LintLevelMapBuilder<'_, 'tcx> {
+    type Map = Map<'tcx>;
+
+    fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+        intravisit::NestedVisitorMap::All(self.tcx.hir())
+    }
+
+    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+        self.with_lint_attrs(param.hir_id, &param.attrs, |builder| {
+            intravisit::walk_param(builder, param);
+        });
+    }
+
+    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
+        self.with_lint_attrs(it.hir_id, &it.attrs, |builder| {
+            intravisit::walk_item(builder, it);
+        });
+    }
+
+    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
+        self.with_lint_attrs(it.hir_id, &it.attrs, |builder| {
+            intravisit::walk_foreign_item(builder, it);
+        })
+    }
+
+    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+        self.with_lint_attrs(e.hir_id, &e.attrs, |builder| {
+            intravisit::walk_expr(builder, e);
+        })
+    }
+
+    fn visit_struct_field(&mut self, s: &'tcx hir::StructField<'tcx>) {
+        self.with_lint_attrs(s.hir_id, &s.attrs, |builder| {
+            intravisit::walk_struct_field(builder, s);
+        })
+    }
+
+    fn visit_variant(
+        &mut self,
+        v: &'tcx hir::Variant<'tcx>,
+        g: &'tcx hir::Generics<'tcx>,
+        item_id: hir::HirId,
+    ) {
+        self.with_lint_attrs(v.id, &v.attrs, |builder| {
+            intravisit::walk_variant(builder, v, g, item_id);
+        })
+    }
+
+    fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
+        self.with_lint_attrs(l.hir_id, &l.attrs, |builder| {
+            intravisit::walk_local(builder, l);
+        })
+    }
+
+    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
+        self.with_lint_attrs(a.hir_id, &a.attrs, |builder| {
+            intravisit::walk_arm(builder, a);
+        })
+    }
+
+    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
+        self.with_lint_attrs(trait_item.hir_id, &trait_item.attrs, |builder| {
+            intravisit::walk_trait_item(builder, trait_item);
+        });
+    }
+
+    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
+        self.with_lint_attrs(impl_item.hir_id, &impl_item.attrs, |builder| {
+            intravisit::walk_impl_item(builder, impl_item);
+        });
+    }
+}
+
+pub fn provide(providers: &mut Providers) {
+    providers.lint_levels = lint_levels;
+}
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
new file mode 100644
index 00000000000..0a14b16e274
--- /dev/null
+++ b/compiler/rustc_lint/src/lib.rs
@@ -0,0 +1,464 @@
+//! Lints, aka compiler warnings.
+//!
+//! A 'lint' check is a kind of miscellaneous constraint that a user _might_
+//! want to enforce, but might reasonably want to permit as well, on a
+//! module-by-module basis. They contrast with static constraints enforced by
+//! other phases of the compiler, which are generally required to hold in order
+//! to compile the program at all.
+//!
+//! Most lints can be written as [LintPass] instances. These run after
+//! all other analyses. The `LintPass`es built into rustc are defined
+//! within [rustc_session::lint::builtin],
+//! which has further comments on how to add such a lint.
+//! rustc can also load user-defined lint plugins via the plugin mechanism.
+//!
+//! Some of rustc's lints are defined elsewhere in the compiler and work by
+//! calling `add_lint()` on the overall `Session` object. This works when
+//! it happens before the main lint pass, which emits the lints stored by
+//! `add_lint()`. To emit lints after the main lint pass (from codegen, for
+//! example) requires more effort. See `emit_lint` and `GatherNodeLevels`
+//! in `context.rs`.
+//!
+//! Some code also exists in [rustc_session::lint], [rustc_middle::lint].
+//!
+//! ## Note
+//!
+//! This API is completely unstable and subject to change.
+
+#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")]
+#![cfg_attr(test, feature(test))]
+#![feature(bool_to_option)]
+#![feature(box_syntax)]
+#![feature(crate_visibility_modifier)]
+#![feature(iter_order_by)]
+#![feature(never_type)]
+#![feature(nll)]
+#![feature(or_patterns)]
+#![recursion_limit = "256"]
+
+#[macro_use]
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_session;
+
+mod array_into_iter;
+pub mod builtin;
+mod context;
+mod early;
+mod internal;
+mod late;
+mod levels;
+mod non_ascii_idents;
+mod nonstandard_style;
+mod passes;
+mod redundant_semicolon;
+mod types;
+mod unused;
+
+use rustc_ast as ast;
+use rustc_hir as hir;
+use rustc_hir::def_id::LocalDefId;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::lint::builtin::{
+    BARE_TRAIT_OBJECTS, BROKEN_INTRA_DOC_LINKS, ELIDED_LIFETIMES_IN_PATHS,
+    EXPLICIT_OUTLIVES_REQUIREMENTS, INVALID_CODEBLOCK_ATTRIBUTES, MISSING_DOC_CODE_EXAMPLES,
+    PRIVATE_DOC_TESTS,
+};
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
+
+use array_into_iter::ArrayIntoIter;
+use builtin::*;
+use internal::*;
+use non_ascii_idents::*;
+use nonstandard_style::*;
+use redundant_semicolon::*;
+use types::*;
+use unused::*;
+
+/// Useful for other parts of the compiler / Clippy.
+pub use builtin::SoftLints;
+pub use context::{CheckLintNameResult, EarlyContext, LateContext, LintContext, LintStore};
+pub use early::check_ast_crate;
+pub use late::check_crate;
+pub use passes::{EarlyLintPass, LateLintPass};
+pub use rustc_session::lint::Level::{self, *};
+pub use rustc_session::lint::{BufferedEarlyLint, FutureIncompatibleInfo, Lint, LintId};
+pub use rustc_session::lint::{LintArray, LintPass};
+
+pub fn provide(providers: &mut Providers) {
+    levels::provide(providers);
+    *providers = Providers { lint_mod, ..*providers };
+}
+
+fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
+    late::late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new());
+}
+
+macro_rules! pre_expansion_lint_passes {
+    ($macro:path, $args:tt) => {
+        $macro!($args, [KeywordIdents: KeywordIdents,]);
+    };
+}
+
+macro_rules! early_lint_passes {
+    ($macro:path, $args:tt) => {
+        $macro!(
+            $args,
+            [
+                UnusedParens: UnusedParens,
+                UnusedBraces: UnusedBraces,
+                UnusedImportBraces: UnusedImportBraces,
+                UnsafeCode: UnsafeCode,
+                AnonymousParameters: AnonymousParameters,
+                EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(),
+                NonCamelCaseTypes: NonCamelCaseTypes,
+                DeprecatedAttr: DeprecatedAttr::new(),
+                WhileTrue: WhileTrue,
+                NonAsciiIdents: NonAsciiIdents,
+                IncompleteFeatures: IncompleteFeatures,
+                RedundantSemicolons: RedundantSemicolons,
+                UnusedDocComment: UnusedDocComment,
+            ]
+        );
+    };
+}
+
+macro_rules! declare_combined_early_pass {
+    ([$name:ident], $passes:tt) => (
+        early_lint_methods!(declare_combined_early_lint_pass, [pub $name, $passes]);
+    )
+}
+
+pre_expansion_lint_passes!(declare_combined_early_pass, [BuiltinCombinedPreExpansionLintPass]);
+early_lint_passes!(declare_combined_early_pass, [BuiltinCombinedEarlyLintPass]);
+
+macro_rules! late_lint_passes {
+    ($macro:path, $args:tt) => {
+        $macro!(
+            $args,
+            [
+                // FIXME: Look into regression when this is used as a module lint
+                // May Depend on constants elsewhere
+                UnusedBrokenConst: UnusedBrokenConst,
+                // Uses attr::is_used which is untracked, can't be an incremental module pass.
+                UnusedAttributes: UnusedAttributes::new(),
+                // Needs to run after UnusedAttributes as it marks all `feature` attributes as used.
+                UnstableFeatures: UnstableFeatures,
+                // Tracks state across modules
+                UnnameableTestItems: UnnameableTestItems::new(),
+                // Tracks attributes of parents
+                MissingDoc: MissingDoc::new(),
+                // Depends on access levels
+                // FIXME: Turn the computation of types which implement Debug into a query
+                // and change this to a module lint pass
+                MissingDebugImplementations: MissingDebugImplementations::default(),
+                ArrayIntoIter: ArrayIntoIter,
+                ClashingExternDeclarations: ClashingExternDeclarations::new(),
+            ]
+        );
+    };
+}
+
+macro_rules! late_lint_mod_passes {
+    ($macro:path, $args:tt) => {
+        $macro!(
+            $args,
+            [
+                HardwiredLints: HardwiredLints,
+                ImproperCTypesDeclarations: ImproperCTypesDeclarations,
+                ImproperCTypesDefinitions: ImproperCTypesDefinitions,
+                VariantSizeDifferences: VariantSizeDifferences,
+                BoxPointers: BoxPointers,
+                PathStatements: PathStatements,
+                // Depends on referenced function signatures in expressions
+                UnusedResults: UnusedResults,
+                NonUpperCaseGlobals: NonUpperCaseGlobals,
+                NonShorthandFieldPatterns: NonShorthandFieldPatterns,
+                UnusedAllocation: UnusedAllocation,
+                // Depends on types used in type definitions
+                MissingCopyImplementations: MissingCopyImplementations,
+                // Depends on referenced function signatures in expressions
+                MutableTransmutes: MutableTransmutes,
+                TypeAliasBounds: TypeAliasBounds,
+                TrivialConstraints: TrivialConstraints,
+                TypeLimits: TypeLimits::new(),
+                NonSnakeCase: NonSnakeCase,
+                InvalidNoMangleItems: InvalidNoMangleItems,
+                // Depends on access levels
+                UnreachablePub: UnreachablePub,
+                ExplicitOutlivesRequirements: ExplicitOutlivesRequirements,
+                InvalidValue: InvalidValue,
+            ]
+        );
+    };
+}
+
+macro_rules! declare_combined_late_pass {
+    ([$v:vis $name:ident], $passes:tt) => (
+        late_lint_methods!(declare_combined_late_lint_pass, [$v $name, $passes], ['tcx]);
+    )
+}
+
+// FIXME: Make a separate lint type which do not require typeck tables
+late_lint_passes!(declare_combined_late_pass, [pub BuiltinCombinedLateLintPass]);
+
+late_lint_mod_passes!(declare_combined_late_pass, [BuiltinCombinedModuleLateLintPass]);
+
+pub fn new_lint_store(no_interleave_lints: bool, internal_lints: bool) -> LintStore {
+    let mut lint_store = LintStore::new();
+
+    register_builtins(&mut lint_store, no_interleave_lints);
+    if internal_lints {
+        register_internals(&mut lint_store);
+    }
+
+    lint_store
+}
+
+/// Tell the `LintStore` about all the built-in lints (the ones
+/// defined in this crate and the ones defined in
+/// `rustc_session::lint::builtin`).
+fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
+    macro_rules! add_lint_group {
+        ($name:expr, $($lint:ident),*) => (
+            store.register_group(false, $name, None, vec![$(LintId::of($lint)),*]);
+        )
+    }
+
+    macro_rules! register_pass {
+        ($method:ident, $ty:ident, $constructor:expr) => {
+            store.register_lints(&$ty::get_lints());
+            store.$method(|| box $constructor);
+        };
+    }
+
+    macro_rules! register_passes {
+        ($method:ident, [$($passes:ident: $constructor:expr,)*]) => (
+            $(
+                register_pass!($method, $passes, $constructor);
+            )*
+        )
+    }
+
+    if no_interleave_lints {
+        pre_expansion_lint_passes!(register_passes, register_pre_expansion_pass);
+        early_lint_passes!(register_passes, register_early_pass);
+        late_lint_passes!(register_passes, register_late_pass);
+        late_lint_mod_passes!(register_passes, register_late_mod_pass);
+    } else {
+        store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints());
+        store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints());
+        store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints());
+        store.register_lints(&BuiltinCombinedLateLintPass::get_lints());
+    }
+
+    add_lint_group!(
+        "nonstandard_style",
+        NON_CAMEL_CASE_TYPES,
+        NON_SNAKE_CASE,
+        NON_UPPER_CASE_GLOBALS
+    );
+
+    add_lint_group!(
+        "unused",
+        UNUSED_IMPORTS,
+        UNUSED_VARIABLES,
+        UNUSED_ASSIGNMENTS,
+        DEAD_CODE,
+        UNUSED_MUT,
+        UNREACHABLE_CODE,
+        UNREACHABLE_PATTERNS,
+        OVERLAPPING_PATTERNS,
+        UNUSED_MUST_USE,
+        UNUSED_UNSAFE,
+        PATH_STATEMENTS,
+        UNUSED_ATTRIBUTES,
+        UNUSED_MACROS,
+        UNUSED_ALLOCATION,
+        UNUSED_DOC_COMMENTS,
+        UNUSED_EXTERN_CRATES,
+        UNUSED_FEATURES,
+        UNUSED_LABELS,
+        UNUSED_PARENS,
+        UNUSED_BRACES,
+        REDUNDANT_SEMICOLONS
+    );
+
+    add_lint_group!(
+        "rust_2018_idioms",
+        BARE_TRAIT_OBJECTS,
+        UNUSED_EXTERN_CRATES,
+        ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
+        ELIDED_LIFETIMES_IN_PATHS,
+        EXPLICIT_OUTLIVES_REQUIREMENTS // FIXME(#52665, #47816) not always applicable and not all
+                                       // macros are ready for this yet.
+                                       // UNREACHABLE_PUB,
+
+                                       // FIXME macro crates are not up for this yet, too much
+                                       // breakage is seen if we try to encourage this lint.
+                                       // MACRO_USE_EXTERN_CRATE
+    );
+
+    add_lint_group!(
+        "rustdoc",
+        BROKEN_INTRA_DOC_LINKS,
+        INVALID_CODEBLOCK_ATTRIBUTES,
+        MISSING_DOC_CODE_EXAMPLES,
+        PRIVATE_DOC_TESTS
+    );
+
+    // Register renamed and removed lints.
+    store.register_renamed("single_use_lifetime", "single_use_lifetimes");
+    store.register_renamed("elided_lifetime_in_path", "elided_lifetimes_in_paths");
+    store.register_renamed("bare_trait_object", "bare_trait_objects");
+    store.register_renamed("unstable_name_collision", "unstable_name_collisions");
+    store.register_renamed("unused_doc_comment", "unused_doc_comments");
+    store.register_renamed("async_idents", "keyword_idents");
+    store.register_renamed("exceeding_bitshifts", "arithmetic_overflow");
+    store.register_renamed("redundant_semicolon", "redundant_semicolons");
+    store.register_renamed("intra_doc_link_resolution_failure", "broken_intra_doc_links");
+    store.register_removed("unknown_features", "replaced by an error");
+    store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate");
+    store.register_removed("negate_unsigned", "cast a signed value instead");
+    store.register_removed("raw_pointer_derive", "using derive with raw pointers is ok");
+    // Register lint group aliases.
+    store.register_group_alias("nonstandard_style", "bad_style");
+    // This was renamed to `raw_pointer_derive`, which was then removed,
+    // so it is also considered removed.
+    store.register_removed("raw_pointer_deriving", "using derive with raw pointers is ok");
+    store.register_removed("drop_with_repr_extern", "drop flags have been removed");
+    store.register_removed("fat_ptr_transmutes", "was accidentally removed back in 2014");
+    store.register_removed("deprecated_attr", "use `deprecated` instead");
+    store.register_removed(
+        "transmute_from_fn_item_types",
+        "always cast functions before transmuting them",
+    );
+    store.register_removed(
+        "hr_lifetime_in_assoc_type",
+        "converted into hard error, see issue #33685 \
+         <https://github.com/rust-lang/rust/issues/33685> for more information",
+    );
+    store.register_removed(
+        "inaccessible_extern_crate",
+        "converted into hard error, see issue #36886 \
+         <https://github.com/rust-lang/rust/issues/36886> for more information",
+    );
+    store.register_removed(
+        "super_or_self_in_global_path",
+        "converted into hard error, see issue #36888 \
+         <https://github.com/rust-lang/rust/issues/36888> for more information",
+    );
+    store.register_removed(
+        "overlapping_inherent_impls",
+        "converted into hard error, see issue #36889 \
+         <https://github.com/rust-lang/rust/issues/36889> for more information",
+    );
+    store.register_removed(
+        "illegal_floating_point_constant_pattern",
+        "converted into hard error, see issue #36890 \
+         <https://github.com/rust-lang/rust/issues/36890> for more information",
+    );
+    store.register_removed(
+        "illegal_struct_or_enum_constant_pattern",
+        "converted into hard error, see issue #36891 \
+         <https://github.com/rust-lang/rust/issues/36891> for more information",
+    );
+    store.register_removed(
+        "lifetime_underscore",
+        "converted into hard error, see issue #36892 \
+         <https://github.com/rust-lang/rust/issues/36892> for more information",
+    );
+    store.register_removed(
+        "extra_requirement_in_impl",
+        "converted into hard error, see issue #37166 \
+         <https://github.com/rust-lang/rust/issues/37166> for more information",
+    );
+    store.register_removed(
+        "legacy_imports",
+        "converted into hard error, see issue #38260 \
+         <https://github.com/rust-lang/rust/issues/38260> for more information",
+    );
+    store.register_removed(
+        "coerce_never",
+        "converted into hard error, see issue #48950 \
+         <https://github.com/rust-lang/rust/issues/48950> for more information",
+    );
+    store.register_removed(
+        "resolve_trait_on_defaulted_unit",
+        "converted into hard error, see issue #48950 \
+         <https://github.com/rust-lang/rust/issues/48950> for more information",
+    );
+    store.register_removed(
+        "private_no_mangle_fns",
+        "no longer a warning, `#[no_mangle]` functions always exported",
+    );
+    store.register_removed(
+        "private_no_mangle_statics",
+        "no longer a warning, `#[no_mangle]` statics always exported",
+    );
+    store.register_removed("bad_repr", "replaced with a generic attribute input check");
+    store.register_removed(
+        "duplicate_matcher_binding_name",
+        "converted into hard error, see issue #57742 \
+         <https://github.com/rust-lang/rust/issues/57742> for more information",
+    );
+    store.register_removed(
+        "incoherent_fundamental_impls",
+        "converted into hard error, see issue #46205 \
+         <https://github.com/rust-lang/rust/issues/46205> for more information",
+    );
+    store.register_removed(
+        "legacy_constructor_visibility",
+        "converted into hard error, see issue #39207 \
+         <https://github.com/rust-lang/rust/issues/39207> for more information",
+    );
+    store.register_removed(
+        "legacy_directory_ownership",
+        "converted into hard error, see issue #37872 \
+         <https://github.com/rust-lang/rust/issues/37872> for more information",
+    );
+    store.register_removed(
+        "safe_extern_statics",
+        "converted into hard error, see issue #36247 \
+         <https://github.com/rust-lang/rust/issues/36247> for more information",
+    );
+    store.register_removed(
+        "parenthesized_params_in_types_and_modules",
+        "converted into hard error, see issue #42238 \
+         <https://github.com/rust-lang/rust/issues/42238> for more information",
+    );
+    store.register_removed(
+        "duplicate_macro_exports",
+        "converted into hard error, see issue #35896 \
+         <https://github.com/rust-lang/rust/issues/35896> for more information",
+    );
+    store.register_removed(
+        "nested_impl_trait",
+        "converted into hard error, see issue #59014 \
+         <https://github.com/rust-lang/rust/issues/59014> for more information",
+    );
+    store.register_removed("plugin_as_library", "plugins have been deprecated and retired");
+}
+
+fn register_internals(store: &mut LintStore) {
+    store.register_lints(&DefaultHashTypes::get_lints());
+    store.register_early_pass(|| box DefaultHashTypes::new());
+    store.register_lints(&LintPassImpl::get_lints());
+    store.register_early_pass(|| box LintPassImpl);
+    store.register_lints(&TyTyKind::get_lints());
+    store.register_late_pass(|| box TyTyKind);
+    store.register_group(
+        false,
+        "rustc::internal",
+        None,
+        vec![
+            LintId::of(DEFAULT_HASH_TYPES),
+            LintId::of(USAGE_OF_TY_TYKIND),
+            LintId::of(LINT_PASS_IMPL_WITHOUT_MACRO),
+            LintId::of(TY_PASS_BY_REFERENCE),
+            LintId::of(USAGE_OF_QUALIFIED_TY),
+        ],
+    );
+}
diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs
new file mode 100644
index 00000000000..2f0b2a8d680
--- /dev/null
+++ b/compiler/rustc_lint/src/non_ascii_idents.rs
@@ -0,0 +1,240 @@
+use crate::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_span::symbol::Symbol;
+
+declare_lint! {
+    pub NON_ASCII_IDENTS,
+    Allow,
+    "detects non-ASCII identifiers",
+    crate_level_only
+}
+
+declare_lint! {
+    pub UNCOMMON_CODEPOINTS,
+    Warn,
+    "detects uncommon Unicode codepoints in identifiers",
+    crate_level_only
+}
+
+declare_lint! {
+    pub CONFUSABLE_IDENTS,
+    Warn,
+    "detects visually confusable pairs between identifiers",
+    crate_level_only
+}
+
+declare_lint! {
+    pub MIXED_SCRIPT_CONFUSABLES,
+    Warn,
+    "detects Unicode scripts whose mixed script confusables codepoints are solely used",
+    crate_level_only
+}
+
+declare_lint_pass!(NonAsciiIdents => [NON_ASCII_IDENTS, UNCOMMON_CODEPOINTS, CONFUSABLE_IDENTS, MIXED_SCRIPT_CONFUSABLES]);
+
+impl EarlyLintPass for NonAsciiIdents {
+    fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
+        use rustc_session::lint::Level;
+        use rustc_span::Span;
+        use std::collections::BTreeMap;
+        use unicode_security::GeneralSecurityProfile;
+
+        let check_non_ascii_idents = cx.builder.lint_level(NON_ASCII_IDENTS).0 != Level::Allow;
+        let check_uncommon_codepoints =
+            cx.builder.lint_level(UNCOMMON_CODEPOINTS).0 != Level::Allow;
+        let check_confusable_idents = cx.builder.lint_level(CONFUSABLE_IDENTS).0 != Level::Allow;
+        let check_mixed_script_confusables =
+            cx.builder.lint_level(MIXED_SCRIPT_CONFUSABLES).0 != Level::Allow;
+
+        if !check_non_ascii_idents
+            && !check_uncommon_codepoints
+            && !check_confusable_idents
+            && !check_mixed_script_confusables
+        {
+            return;
+        }
+
+        let mut has_non_ascii_idents = false;
+        let symbols = cx.sess.parse_sess.symbol_gallery.symbols.lock();
+
+        // Sort by `Span` so that error messages make sense with respect to the
+        // order of identifier locations in the code.
+        let mut symbols: Vec<_> = symbols.iter().collect();
+        symbols.sort_by_key(|k| k.1);
+
+        for (symbol, &sp) in symbols.iter() {
+            let symbol_str = symbol.as_str();
+            if symbol_str.is_ascii() {
+                continue;
+            }
+            has_non_ascii_idents = true;
+            cx.struct_span_lint(NON_ASCII_IDENTS, sp, |lint| {
+                lint.build("identifier contains non-ASCII characters").emit()
+            });
+            if check_uncommon_codepoints
+                && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed)
+            {
+                cx.struct_span_lint(UNCOMMON_CODEPOINTS, sp, |lint| {
+                    lint.build("identifier contains uncommon Unicode codepoints").emit()
+                })
+            }
+        }
+
+        if has_non_ascii_idents && check_confusable_idents {
+            let mut skeleton_map: FxHashMap<Symbol, (Symbol, Span, bool)> =
+                FxHashMap::with_capacity_and_hasher(symbols.len(), Default::default());
+            let mut skeleton_buf = String::new();
+
+            for (&symbol, &sp) in symbols.iter() {
+                use unicode_security::confusable_detection::skeleton;
+
+                let symbol_str = symbol.as_str();
+                let is_ascii = symbol_str.is_ascii();
+
+                // Get the skeleton as a `Symbol`.
+                skeleton_buf.clear();
+                skeleton_buf.extend(skeleton(&symbol_str));
+                let skeleton_sym = if *symbol_str == *skeleton_buf {
+                    symbol
+                } else {
+                    Symbol::intern(&skeleton_buf)
+                };
+
+                skeleton_map
+                    .entry(skeleton_sym)
+                    .and_modify(|(existing_symbol, existing_span, existing_is_ascii)| {
+                        if !*existing_is_ascii || !is_ascii {
+                            cx.struct_span_lint(CONFUSABLE_IDENTS, sp, |lint| {
+                                lint.build(&format!(
+                                    "identifier pair considered confusable between `{}` and `{}`",
+                                    existing_symbol.as_str(),
+                                    symbol.as_str()
+                                ))
+                                .span_label(
+                                    *existing_span,
+                                    "this is where the previous identifier occurred",
+                                )
+                                .emit();
+                            });
+                        }
+                        if *existing_is_ascii && !is_ascii {
+                            *existing_symbol = symbol;
+                            *existing_span = sp;
+                            *existing_is_ascii = is_ascii;
+                        }
+                    })
+                    .or_insert((symbol, sp, is_ascii));
+            }
+        }
+
+        if has_non_ascii_idents && check_mixed_script_confusables {
+            use unicode_security::is_potential_mixed_script_confusable_char;
+            use unicode_security::mixed_script::AugmentedScriptSet;
+
+            #[derive(Clone)]
+            enum ScriptSetUsage {
+                Suspicious(Vec<char>, Span),
+                Verified,
+            }
+
+            let mut script_states: FxHashMap<AugmentedScriptSet, ScriptSetUsage> =
+                FxHashMap::default();
+            let latin_augmented_script_set = AugmentedScriptSet::for_char('A');
+            script_states.insert(latin_augmented_script_set, ScriptSetUsage::Verified);
+
+            let mut has_suspicous = false;
+            for (symbol, &sp) in symbols.iter() {
+                let symbol_str = symbol.as_str();
+                for ch in symbol_str.chars() {
+                    if ch.is_ascii() {
+                        // all ascii characters are covered by exception.
+                        continue;
+                    }
+                    if !GeneralSecurityProfile::identifier_allowed(ch) {
+                        // this character is covered by `uncommon_codepoints` lint.
+                        continue;
+                    }
+                    let augmented_script_set = AugmentedScriptSet::for_char(ch);
+                    script_states
+                        .entry(augmented_script_set)
+                        .and_modify(|existing_state| {
+                            if let ScriptSetUsage::Suspicious(ch_list, _) = existing_state {
+                                if is_potential_mixed_script_confusable_char(ch) {
+                                    ch_list.push(ch);
+                                } else {
+                                    *existing_state = ScriptSetUsage::Verified;
+                                }
+                            }
+                        })
+                        .or_insert_with(|| {
+                            if !is_potential_mixed_script_confusable_char(ch) {
+                                ScriptSetUsage::Verified
+                            } else {
+                                has_suspicous = true;
+                                ScriptSetUsage::Suspicious(vec![ch], sp)
+                            }
+                        });
+                }
+            }
+
+            if has_suspicous {
+                let verified_augmented_script_sets = script_states
+                    .iter()
+                    .flat_map(|(k, v)| match v {
+                        ScriptSetUsage::Verified => Some(*k),
+                        _ => None,
+                    })
+                    .collect::<Vec<_>>();
+
+                // we're sorting the output here.
+                let mut lint_reports: BTreeMap<(Span, Vec<char>), AugmentedScriptSet> =
+                    BTreeMap::new();
+
+                'outerloop: for (augment_script_set, usage) in script_states {
+                    let (mut ch_list, sp) = match usage {
+                        ScriptSetUsage::Verified => continue,
+                        ScriptSetUsage::Suspicious(ch_list, sp) => (ch_list, sp),
+                    };
+
+                    if augment_script_set.is_all() {
+                        continue;
+                    }
+
+                    for existing in verified_augmented_script_sets.iter() {
+                        if existing.is_all() {
+                            continue;
+                        }
+                        let mut intersect = *existing;
+                        intersect.intersect_with(augment_script_set);
+                        if !intersect.is_empty() && !intersect.is_all() {
+                            continue 'outerloop;
+                        }
+                    }
+
+                    ch_list.sort();
+                    ch_list.dedup();
+                    lint_reports.insert((sp, ch_list), augment_script_set);
+                }
+
+                for ((sp, ch_list), script_set) in lint_reports {
+                    cx.struct_span_lint(MIXED_SCRIPT_CONFUSABLES, sp, |lint| {
+                        let message = format!(
+                            "The usage of Script Group `{}` in this crate consists solely of mixed script confusables",
+                            script_set);
+                        let mut note = "The usage includes ".to_string();
+                        for (idx, ch) in ch_list.into_iter().enumerate() {
+                            if idx != 0 {
+                                note += ", ";
+                            }
+                            let char_info = format!("'{}' (U+{:04X})", ch, ch as u32);
+                            note += &char_info;
+                        }
+                        note += ".";
+                        lint.build(&message).note(&note).note("Please recheck to make sure their usages are indeed what you want.").emit()
+                    });
+                }
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs
new file mode 100644
index 00000000000..f23e8c5e208
--- /dev/null
+++ b/compiler/rustc_lint/src/nonstandard_style.rs
@@ -0,0 +1,456 @@
+use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_attr as attr;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{GenericParamKind, PatKind};
+use rustc_middle::ty;
+use rustc_span::symbol::sym;
+use rustc_span::{symbol::Ident, BytePos, Span};
+use rustc_target::spec::abi::Abi;
+
+#[derive(PartialEq)]
+pub enum MethodLateContext {
+    TraitAutoImpl,
+    TraitImpl,
+    PlainImpl,
+}
+
+pub fn method_context(cx: &LateContext<'_>, id: hir::HirId) -> MethodLateContext {
+    let def_id = cx.tcx.hir().local_def_id(id);
+    let item = cx.tcx.associated_item(def_id);
+    match item.container {
+        ty::TraitContainer(..) => MethodLateContext::TraitAutoImpl,
+        ty::ImplContainer(cid) => match cx.tcx.impl_trait_ref(cid) {
+            Some(_) => MethodLateContext::TraitImpl,
+            None => MethodLateContext::PlainImpl,
+        },
+    }
+}
+
+declare_lint! {
+    pub NON_CAMEL_CASE_TYPES,
+    Warn,
+    "types, variants, traits and type parameters should have camel case names"
+}
+
+declare_lint_pass!(NonCamelCaseTypes => [NON_CAMEL_CASE_TYPES]);
+
+fn char_has_case(c: char) -> bool {
+    c.is_lowercase() || c.is_uppercase()
+}
+
+fn is_camel_case(name: &str) -> bool {
+    let name = name.trim_matches('_');
+    if name.is_empty() {
+        return true;
+    }
+
+    // start with a non-lowercase letter rather than non-uppercase
+    // ones (some scripts don't have a concept of upper/lowercase)
+    !name.chars().next().unwrap().is_lowercase()
+        && !name.contains("__")
+        && !name.chars().collect::<Vec<_>>().windows(2).any(|pair| {
+            // contains a capitalisable character followed by, or preceded by, an underscore
+            char_has_case(pair[0]) && pair[1] == '_' || char_has_case(pair[1]) && pair[0] == '_'
+        })
+}
+
+fn to_camel_case(s: &str) -> String {
+    s.trim_matches('_')
+        .split('_')
+        .filter(|component| !component.is_empty())
+        .map(|component| {
+            let mut camel_cased_component = String::new();
+
+            let mut new_word = true;
+            let mut prev_is_lower_case = true;
+
+            for c in component.chars() {
+                // Preserve the case if an uppercase letter follows a lowercase letter, so that
+                // `camelCase` is converted to `CamelCase`.
+                if prev_is_lower_case && c.is_uppercase() {
+                    new_word = true;
+                }
+
+                if new_word {
+                    camel_cased_component.push_str(&c.to_uppercase().to_string());
+                } else {
+                    camel_cased_component.push_str(&c.to_lowercase().to_string());
+                }
+
+                prev_is_lower_case = c.is_lowercase();
+                new_word = false;
+            }
+
+            camel_cased_component
+        })
+        .fold((String::new(), None), |(acc, prev): (String, Option<String>), next| {
+            // separate two components with an underscore if their boundary cannot
+            // be distinguished using a uppercase/lowercase case distinction
+            let join = if let Some(prev) = prev {
+                let l = prev.chars().last().unwrap();
+                let f = next.chars().next().unwrap();
+                !char_has_case(l) && !char_has_case(f)
+            } else {
+                false
+            };
+            (acc + if join { "_" } else { "" } + &next, Some(next))
+        })
+        .0
+}
+
+impl NonCamelCaseTypes {
+    fn check_case(&self, cx: &EarlyContext<'_>, sort: &str, ident: &Ident) {
+        let name = &ident.name.as_str();
+
+        if !is_camel_case(name) {
+            cx.struct_span_lint(NON_CAMEL_CASE_TYPES, ident.span, |lint| {
+                let msg = format!("{} `{}` should have an upper camel case name", sort, name);
+                lint.build(&msg)
+                    .span_suggestion(
+                        ident.span,
+                        "convert the identifier to upper camel case",
+                        to_camel_case(name),
+                        Applicability::MaybeIncorrect,
+                    )
+                    .emit()
+            })
+        }
+    }
+}
+
+impl EarlyLintPass for NonCamelCaseTypes {
+    fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
+        let has_repr_c = it
+            .attrs
+            .iter()
+            .any(|attr| attr::find_repr_attrs(&cx.sess, attr).contains(&attr::ReprC));
+
+        if has_repr_c {
+            return;
+        }
+
+        match it.kind {
+            ast::ItemKind::TyAlias(..)
+            | ast::ItemKind::Enum(..)
+            | ast::ItemKind::Struct(..)
+            | ast::ItemKind::Union(..) => self.check_case(cx, "type", &it.ident),
+            ast::ItemKind::Trait(..) => self.check_case(cx, "trait", &it.ident),
+            _ => (),
+        }
+    }
+
+    fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
+        if let ast::AssocItemKind::TyAlias(..) = it.kind {
+            self.check_case(cx, "associated type", &it.ident);
+        }
+    }
+
+    fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) {
+        self.check_case(cx, "variant", &v.ident);
+    }
+
+    fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) {
+        if let ast::GenericParamKind::Type { .. } = param.kind {
+            self.check_case(cx, "type parameter", &param.ident);
+        }
+    }
+}
+
+declare_lint! {
+    pub NON_SNAKE_CASE,
+    Warn,
+    "variables, methods, functions, lifetime parameters and modules should have snake case names"
+}
+
+declare_lint_pass!(NonSnakeCase => [NON_SNAKE_CASE]);
+
+impl NonSnakeCase {
+    fn to_snake_case(mut str: &str) -> String {
+        let mut words = vec![];
+        // Preserve leading underscores
+        str = str.trim_start_matches(|c: char| {
+            if c == '_' {
+                words.push(String::new());
+                true
+            } else {
+                false
+            }
+        });
+        for s in str.split('_') {
+            let mut last_upper = false;
+            let mut buf = String::new();
+            if s.is_empty() {
+                continue;
+            }
+            for ch in s.chars() {
+                if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
+                    words.push(buf);
+                    buf = String::new();
+                }
+                last_upper = ch.is_uppercase();
+                buf.extend(ch.to_lowercase());
+            }
+            words.push(buf);
+        }
+        words.join("_")
+    }
+
+    /// Checks if a given identifier is snake case, and reports a diagnostic if not.
+    fn check_snake_case(&self, cx: &LateContext<'_>, sort: &str, ident: &Ident) {
+        fn is_snake_case(ident: &str) -> bool {
+            if ident.is_empty() {
+                return true;
+            }
+            let ident = ident.trim_start_matches('\'');
+            let ident = ident.trim_matches('_');
+
+            let mut allow_underscore = true;
+            ident.chars().all(|c| {
+                allow_underscore = match c {
+                    '_' if !allow_underscore => return false,
+                    '_' => false,
+                    // It would be more obvious to use `c.is_lowercase()`,
+                    // but some characters do not have a lowercase form
+                    c if !c.is_uppercase() => true,
+                    _ => return false,
+                };
+                true
+            })
+        }
+
+        let name = &ident.name.as_str();
+
+        if !is_snake_case(name) {
+            cx.struct_span_lint(NON_SNAKE_CASE, ident.span, |lint| {
+                let sc = NonSnakeCase::to_snake_case(name);
+                let msg = format!("{} `{}` should have a snake case name", sort, name);
+                let mut err = lint.build(&msg);
+                // We have a valid span in almost all cases, but we don't have one when linting a crate
+                // name provided via the command line.
+                if !ident.span.is_dummy() {
+                    err.span_suggestion(
+                        ident.span,
+                        "convert the identifier to snake case",
+                        sc,
+                        Applicability::MaybeIncorrect,
+                    );
+                } else {
+                    err.help(&format!("convert the identifier to snake case: `{}`", sc));
+                }
+
+                err.emit();
+            });
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for NonSnakeCase {
+    fn check_mod(
+        &mut self,
+        cx: &LateContext<'_>,
+        _: &'tcx hir::Mod<'tcx>,
+        _: Span,
+        id: hir::HirId,
+    ) {
+        if id != hir::CRATE_HIR_ID {
+            return;
+        }
+
+        let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name {
+            Some(Ident::from_str(name))
+        } else {
+            cx.sess()
+                .find_by_name(&cx.tcx.hir().attrs(hir::CRATE_HIR_ID), sym::crate_name)
+                .and_then(|attr| attr.meta())
+                .and_then(|meta| {
+                    meta.name_value_literal().and_then(|lit| {
+                        if let ast::LitKind::Str(name, ..) = lit.kind {
+                            // Discard the double quotes surrounding the literal.
+                            let sp = cx
+                                .sess()
+                                .source_map()
+                                .span_to_snippet(lit.span)
+                                .ok()
+                                .and_then(|snippet| {
+                                    let left = snippet.find('"')?;
+                                    let right =
+                                        snippet.rfind('"').map(|pos| snippet.len() - pos)?;
+
+                                    Some(
+                                        lit.span
+                                            .with_lo(lit.span.lo() + BytePos(left as u32 + 1))
+                                            .with_hi(lit.span.hi() - BytePos(right as u32)),
+                                    )
+                                })
+                                .unwrap_or_else(|| lit.span);
+
+                            Some(Ident::new(name, sp))
+                        } else {
+                            None
+                        }
+                    })
+                })
+        };
+
+        if let Some(ident) = &crate_ident {
+            self.check_snake_case(cx, "crate", ident);
+        }
+    }
+
+    fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
+        if let GenericParamKind::Lifetime { .. } = param.kind {
+            self.check_snake_case(cx, "lifetime", &param.name.ident());
+        }
+    }
+
+    fn check_fn(
+        &mut self,
+        cx: &LateContext<'_>,
+        fk: FnKind<'_>,
+        _: &hir::FnDecl<'_>,
+        _: &hir::Body<'_>,
+        _: Span,
+        id: hir::HirId,
+    ) {
+        match &fk {
+            FnKind::Method(ident, ..) => match method_context(cx, id) {
+                MethodLateContext::PlainImpl => {
+                    self.check_snake_case(cx, "method", ident);
+                }
+                MethodLateContext::TraitAutoImpl => {
+                    self.check_snake_case(cx, "trait method", ident);
+                }
+                _ => (),
+            },
+            FnKind::ItemFn(ident, _, header, _, attrs) => {
+                // Skip foreign-ABI #[no_mangle] functions (Issue #31924)
+                if header.abi != Abi::Rust && cx.sess().contains_name(attrs, sym::no_mangle) {
+                    return;
+                }
+                self.check_snake_case(cx, "function", ident);
+            }
+            FnKind::Closure(_) => (),
+        }
+    }
+
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        if let hir::ItemKind::Mod(_) = it.kind {
+            self.check_snake_case(cx, "module", &it.ident);
+        }
+    }
+
+    fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) {
+        if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(pnames)) = item.kind {
+            self.check_snake_case(cx, "trait method", &item.ident);
+            for param_name in pnames {
+                self.check_snake_case(cx, "variable", param_name);
+            }
+        }
+    }
+
+    fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
+        if let &PatKind::Binding(_, hid, ident, _) = &p.kind {
+            if let hir::Node::Pat(parent_pat) = cx.tcx.hir().get(cx.tcx.hir().get_parent_node(hid))
+            {
+                if let PatKind::Struct(_, field_pats, _) = &parent_pat.kind {
+                    for field in field_pats.iter() {
+                        if field.ident != ident {
+                            // Only check if a new name has been introduced, to avoid warning
+                            // on both the struct definition and this pattern.
+                            self.check_snake_case(cx, "variable", &ident);
+                        }
+                    }
+                    return;
+                }
+            }
+            self.check_snake_case(cx, "variable", &ident);
+        }
+    }
+
+    fn check_struct_def(&mut self, cx: &LateContext<'_>, s: &hir::VariantData<'_>) {
+        for sf in s.fields() {
+            self.check_snake_case(cx, "structure field", &sf.ident);
+        }
+    }
+}
+
+declare_lint! {
+    pub NON_UPPER_CASE_GLOBALS,
+    Warn,
+    "static constants should have uppercase identifiers"
+}
+
+declare_lint_pass!(NonUpperCaseGlobals => [NON_UPPER_CASE_GLOBALS]);
+
+impl NonUpperCaseGlobals {
+    fn check_upper_case(cx: &LateContext<'_>, sort: &str, ident: &Ident) {
+        let name = &ident.name.as_str();
+        if name.chars().any(|c| c.is_lowercase()) {
+            cx.struct_span_lint(NON_UPPER_CASE_GLOBALS, ident.span, |lint| {
+                let uc = NonSnakeCase::to_snake_case(&name).to_uppercase();
+                lint.build(&format!("{} `{}` should have an upper case name", sort, name))
+                    .span_suggestion(
+                        ident.span,
+                        "convert the identifier to upper case",
+                        uc,
+                        Applicability::MaybeIncorrect,
+                    )
+                    .emit();
+            })
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals {
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        match it.kind {
+            hir::ItemKind::Static(..) if !cx.sess().contains_name(&it.attrs, sym::no_mangle) => {
+                NonUpperCaseGlobals::check_upper_case(cx, "static variable", &it.ident);
+            }
+            hir::ItemKind::Const(..) => {
+                NonUpperCaseGlobals::check_upper_case(cx, "constant", &it.ident);
+            }
+            _ => {}
+        }
+    }
+
+    fn check_trait_item(&mut self, cx: &LateContext<'_>, ti: &hir::TraitItem<'_>) {
+        if let hir::TraitItemKind::Const(..) = ti.kind {
+            NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ti.ident);
+        }
+    }
+
+    fn check_impl_item(&mut self, cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) {
+        if let hir::ImplItemKind::Const(..) = ii.kind {
+            NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ii.ident);
+        }
+    }
+
+    fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
+        // Lint for constants that look like binding identifiers (#7526)
+        if let PatKind::Path(hir::QPath::Resolved(None, ref path)) = p.kind {
+            if let Res::Def(DefKind::Const, _) = path.res {
+                if path.segments.len() == 1 {
+                    NonUpperCaseGlobals::check_upper_case(
+                        cx,
+                        "constant in pattern",
+                        &path.segments[0].ident,
+                    );
+                }
+            }
+        }
+    }
+
+    fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
+        if let GenericParamKind::Const { .. } = param.kind {
+            NonUpperCaseGlobals::check_upper_case(cx, "const parameter", &param.name.ident());
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/compiler/rustc_lint/src/nonstandard_style/tests.rs b/compiler/rustc_lint/src/nonstandard_style/tests.rs
new file mode 100644
index 00000000000..39c525b8623
--- /dev/null
+++ b/compiler/rustc_lint/src/nonstandard_style/tests.rs
@@ -0,0 +1,21 @@
+use super::{is_camel_case, to_camel_case};
+
+#[test]
+fn camel_case() {
+    assert!(!is_camel_case("userData"));
+    assert_eq!(to_camel_case("userData"), "UserData");
+
+    assert!(is_camel_case("X86_64"));
+
+    assert!(!is_camel_case("X86__64"));
+    assert_eq!(to_camel_case("X86__64"), "X86_64");
+
+    assert!(!is_camel_case("Abc_123"));
+    assert_eq!(to_camel_case("Abc_123"), "Abc123");
+
+    assert!(!is_camel_case("A1_b2_c3"));
+    assert_eq!(to_camel_case("A1_b2_c3"), "A1B2C3");
+
+    assert!(!is_camel_case("ONE_TWO_THREE"));
+    assert_eq!(to_camel_case("ONE_TWO_THREE"), "OneTwoThree");
+}
diff --git a/compiler/rustc_lint/src/passes.rs b/compiler/rustc_lint/src/passes.rs
new file mode 100644
index 00000000000..159286c13a4
--- /dev/null
+++ b/compiler/rustc_lint/src/passes.rs
@@ -0,0 +1,285 @@
+use crate::context::{EarlyContext, LateContext};
+
+use rustc_ast as ast;
+use rustc_data_structures::sync;
+use rustc_hir as hir;
+use rustc_session::lint::builtin::HardwiredLints;
+use rustc_session::lint::LintPass;
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
+
+#[macro_export]
+macro_rules! late_lint_methods {
+    ($macro:path, $args:tt, [$hir:tt]) => (
+        $macro!($args, [$hir], [
+            fn check_param(a: &$hir hir::Param<$hir>);
+            fn check_body(a: &$hir hir::Body<$hir>);
+            fn check_body_post(a: &$hir hir::Body<$hir>);
+            fn check_name(a: Span, b: Symbol);
+            fn check_crate(a: &$hir hir::Crate<$hir>);
+            fn check_crate_post(a: &$hir hir::Crate<$hir>);
+            fn check_mod(a: &$hir hir::Mod<$hir>, b: Span, c: hir::HirId);
+            fn check_mod_post(a: &$hir hir::Mod<$hir>, b: Span, c: hir::HirId);
+            fn check_foreign_item(a: &$hir hir::ForeignItem<$hir>);
+            fn check_foreign_item_post(a: &$hir hir::ForeignItem<$hir>);
+            fn check_item(a: &$hir hir::Item<$hir>);
+            fn check_item_post(a: &$hir hir::Item<$hir>);
+            fn check_local(a: &$hir hir::Local<$hir>);
+            fn check_block(a: &$hir hir::Block<$hir>);
+            fn check_block_post(a: &$hir hir::Block<$hir>);
+            fn check_stmt(a: &$hir hir::Stmt<$hir>);
+            fn check_arm(a: &$hir hir::Arm<$hir>);
+            fn check_pat(a: &$hir hir::Pat<$hir>);
+            fn check_expr(a: &$hir hir::Expr<$hir>);
+            fn check_expr_post(a: &$hir hir::Expr<$hir>);
+            fn check_ty(a: &$hir hir::Ty<$hir>);
+            fn check_generic_param(a: &$hir hir::GenericParam<$hir>);
+            fn check_generics(a: &$hir hir::Generics<$hir>);
+            fn check_where_predicate(a: &$hir hir::WherePredicate<$hir>);
+            fn check_poly_trait_ref(a: &$hir hir::PolyTraitRef<$hir>, b: hir::TraitBoundModifier);
+            fn check_fn(
+                a: rustc_hir::intravisit::FnKind<$hir>,
+                b: &$hir hir::FnDecl<$hir>,
+                c: &$hir hir::Body<$hir>,
+                d: Span,
+                e: hir::HirId);
+            fn check_fn_post(
+                a: rustc_hir::intravisit::FnKind<$hir>,
+                b: &$hir hir::FnDecl<$hir>,
+                c: &$hir hir::Body<$hir>,
+                d: Span,
+                e: hir::HirId
+            );
+            fn check_trait_item(a: &$hir hir::TraitItem<$hir>);
+            fn check_trait_item_post(a: &$hir hir::TraitItem<$hir>);
+            fn check_impl_item(a: &$hir hir::ImplItem<$hir>);
+            fn check_impl_item_post(a: &$hir hir::ImplItem<$hir>);
+            fn check_struct_def(a: &$hir hir::VariantData<$hir>);
+            fn check_struct_def_post(a: &$hir hir::VariantData<$hir>);
+            fn check_struct_field(a: &$hir hir::StructField<$hir>);
+            fn check_variant(a: &$hir hir::Variant<$hir>);
+            fn check_variant_post(a: &$hir hir::Variant<$hir>);
+            fn check_lifetime(a: &$hir hir::Lifetime);
+            fn check_path(a: &$hir hir::Path<$hir>, b: hir::HirId);
+            fn check_attribute(a: &$hir ast::Attribute);
+
+            /// Called when entering a syntax node that can have lint attributes such
+            /// as `#[allow(...)]`. Called with *all* the attributes of that node.
+            fn enter_lint_attrs(a: &$hir [ast::Attribute]);
+
+            /// Counterpart to `enter_lint_attrs`.
+            fn exit_lint_attrs(a: &$hir [ast::Attribute]);
+        ]);
+    )
+}
+
+/// Trait for types providing lint checks.
+///
+/// Each `check` method checks a single syntax node, and should not
+/// invoke methods recursively (unlike `Visitor`). By default they
+/// do nothing.
+//
+// FIXME: eliminate the duplication with `Visitor`. But this also
+// contains a few lint-specific methods with no equivalent in `Visitor`.
+
+macro_rules! expand_lint_pass_methods {
+    ($context:ty, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+        $(#[inline(always)] fn $name(&mut self, _: $context, $(_: $arg),*) {})*
+    )
+}
+
+macro_rules! declare_late_lint_pass {
+    ([], [$hir:tt], [$($methods:tt)*]) => (
+        pub trait LateLintPass<$hir>: LintPass {
+            expand_lint_pass_methods!(&LateContext<$hir>, [$($methods)*]);
+        }
+    )
+}
+
+late_lint_methods!(declare_late_lint_pass, [], ['tcx]);
+
+impl LateLintPass<'_> for HardwiredLints {}
+
+#[macro_export]
+macro_rules! expand_combined_late_lint_pass_method {
+    ([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({
+        $($self.$passes.$name $params;)*
+    })
+}
+
+#[macro_export]
+macro_rules! expand_combined_late_lint_pass_methods {
+    ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+        $(fn $name(&mut self, context: &LateContext<'tcx>, $($param: $arg),*) {
+            expand_combined_late_lint_pass_method!($passes, self, $name, (context, $($param),*));
+        })*
+    )
+}
+
+#[macro_export]
+macro_rules! declare_combined_late_lint_pass {
+    ([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], [$hir:tt], $methods:tt) => (
+        #[allow(non_snake_case)]
+        $v struct $name {
+            $($passes: $passes,)*
+        }
+
+        impl $name {
+            $v fn new() -> Self {
+                Self {
+                    $($passes: $constructor,)*
+                }
+            }
+
+            $v fn get_lints() -> LintArray {
+                let mut lints = Vec::new();
+                $(lints.extend_from_slice(&$passes::get_lints());)*
+                lints
+            }
+        }
+
+        impl<'tcx> LateLintPass<'tcx> for $name {
+            expand_combined_late_lint_pass_methods!([$($passes),*], $methods);
+        }
+
+        #[allow(rustc::lint_pass_impl_without_macro)]
+        impl LintPass for $name {
+            fn name(&self) -> &'static str {
+                panic!()
+            }
+        }
+    )
+}
+
+#[macro_export]
+macro_rules! early_lint_methods {
+    ($macro:path, $args:tt) => (
+        $macro!($args, [
+            fn check_param(a: &ast::Param);
+            fn check_ident(a: Ident);
+            fn check_crate(a: &ast::Crate);
+            fn check_crate_post(a: &ast::Crate);
+            fn check_mod(a: &ast::Mod, b: Span, c: ast::NodeId);
+            fn check_mod_post(a: &ast::Mod, b: Span, c: ast::NodeId);
+            fn check_foreign_item(a: &ast::ForeignItem);
+            fn check_foreign_item_post(a: &ast::ForeignItem);
+            fn check_item(a: &ast::Item);
+            fn check_item_post(a: &ast::Item);
+            fn check_local(a: &ast::Local);
+            fn check_block(a: &ast::Block);
+            fn check_block_post(a: &ast::Block);
+            fn check_stmt(a: &ast::Stmt);
+            fn check_arm(a: &ast::Arm);
+            fn check_pat(a: &ast::Pat);
+            fn check_anon_const(a: &ast::AnonConst);
+            fn check_pat_post(a: &ast::Pat);
+            fn check_expr(a: &ast::Expr);
+            fn check_expr_post(a: &ast::Expr);
+            fn check_ty(a: &ast::Ty);
+            fn check_generic_param(a: &ast::GenericParam);
+            fn check_generics(a: &ast::Generics);
+            fn check_where_predicate(a: &ast::WherePredicate);
+            fn check_poly_trait_ref(a: &ast::PolyTraitRef,
+                                    b: &ast::TraitBoundModifier);
+            fn check_fn(a: rustc_ast::visit::FnKind<'_>, c: Span, d_: ast::NodeId);
+            fn check_fn_post(
+                a: rustc_ast::visit::FnKind<'_>,
+                c: Span,
+                d: ast::NodeId
+            );
+            fn check_trait_item(a: &ast::AssocItem);
+            fn check_trait_item_post(a: &ast::AssocItem);
+            fn check_impl_item(a: &ast::AssocItem);
+            fn check_impl_item_post(a: &ast::AssocItem);
+            fn check_struct_def(a: &ast::VariantData);
+            fn check_struct_def_post(a: &ast::VariantData);
+            fn check_struct_field(a: &ast::StructField);
+            fn check_variant(a: &ast::Variant);
+            fn check_variant_post(a: &ast::Variant);
+            fn check_lifetime(a: &ast::Lifetime);
+            fn check_path(a: &ast::Path, b: ast::NodeId);
+            fn check_attribute(a: &ast::Attribute);
+            fn check_mac_def(a: &ast::MacroDef, b: ast::NodeId);
+            fn check_mac(a: &ast::MacCall);
+
+            /// Called when entering a syntax node that can have lint attributes such
+            /// as `#[allow(...)]`. Called with *all* the attributes of that node.
+            fn enter_lint_attrs(a: &[ast::Attribute]);
+
+            /// Counterpart to `enter_lint_attrs`.
+            fn exit_lint_attrs(a: &[ast::Attribute]);
+        ]);
+    )
+}
+
+macro_rules! expand_early_lint_pass_methods {
+    ($context:ty, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+        $(#[inline(always)] fn $name(&mut self, _: $context, $(_: $arg),*) {})*
+    )
+}
+
+macro_rules! declare_early_lint_pass {
+    ([], [$($methods:tt)*]) => (
+        pub trait EarlyLintPass: LintPass {
+            expand_early_lint_pass_methods!(&EarlyContext<'_>, [$($methods)*]);
+        }
+    )
+}
+
+early_lint_methods!(declare_early_lint_pass, []);
+
+#[macro_export]
+macro_rules! expand_combined_early_lint_pass_method {
+    ([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({
+        $($self.$passes.$name $params;)*
+    })
+}
+
+#[macro_export]
+macro_rules! expand_combined_early_lint_pass_methods {
+    ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+        $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) {
+            expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*));
+        })*
+    )
+}
+
+#[macro_export]
+macro_rules! declare_combined_early_lint_pass {
+    ([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], $methods:tt) => (
+        #[allow(non_snake_case)]
+        $v struct $name {
+            $($passes: $passes,)*
+        }
+
+        impl $name {
+            $v fn new() -> Self {
+                Self {
+                    $($passes: $constructor,)*
+                }
+            }
+
+            $v fn get_lints() -> LintArray {
+                let mut lints = Vec::new();
+                $(lints.extend_from_slice(&$passes::get_lints());)*
+                lints
+            }
+        }
+
+        impl EarlyLintPass for $name {
+            expand_combined_early_lint_pass_methods!([$($passes),*], $methods);
+        }
+
+        #[allow(rustc::lint_pass_impl_without_macro)]
+        impl LintPass for $name {
+            fn name(&self) -> &'static str {
+                panic!()
+            }
+        }
+    )
+}
+
+/// A lint pass boxed up as a trait object.
+pub type EarlyLintPassObject = Box<dyn EarlyLintPass + sync::Send + sync::Sync + 'static>;
+pub type LateLintPassObject =
+    Box<dyn for<'tcx> LateLintPass<'tcx> + sync::Send + sync::Sync + 'static>;
diff --git a/compiler/rustc_lint/src/redundant_semicolon.rs b/compiler/rustc_lint/src/redundant_semicolon.rs
new file mode 100644
index 00000000000..d4aa4968f25
--- /dev/null
+++ b/compiler/rustc_lint/src/redundant_semicolon.rs
@@ -0,0 +1,41 @@
+use crate::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_ast::{Block, StmtKind};
+use rustc_errors::Applicability;
+use rustc_span::Span;
+
+declare_lint! {
+    pub REDUNDANT_SEMICOLONS,
+    Warn,
+    "detects unnecessary trailing semicolons"
+}
+
+declare_lint_pass!(RedundantSemicolons => [REDUNDANT_SEMICOLONS]);
+
+impl EarlyLintPass for RedundantSemicolons {
+    fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
+        let mut seq = None;
+        for stmt in block.stmts.iter() {
+            match (&stmt.kind, &mut seq) {
+                (StmtKind::Empty, None) => seq = Some((stmt.span, false)),
+                (StmtKind::Empty, Some(seq)) => *seq = (seq.0.to(stmt.span), true),
+                (_, seq) => maybe_lint_redundant_semis(cx, seq),
+            }
+        }
+        maybe_lint_redundant_semis(cx, &mut seq);
+    }
+}
+
+fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, bool)>) {
+    if let Some((span, multiple)) = seq.take() {
+        cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| {
+            let (msg, rem) = if multiple {
+                ("unnecessary trailing semicolons", "remove these semicolons")
+            } else {
+                ("unnecessary trailing semicolon", "remove this semicolon")
+            };
+            lint.build(msg)
+                .span_suggestion(span, rem, String::new(), Applicability::MaybeIncorrect)
+                .emit();
+        });
+    }
+}
diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
new file mode 100644
index 00000000000..35c462c24c8
--- /dev/null
+++ b/compiler/rustc_lint/src/types.rs
@@ -0,0 +1,1240 @@
+#![allow(non_snake_case)]
+
+use crate::{LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_attr as attr;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::{is_range_literal, ExprKind, Node};
+use rustc_index::vec::Idx;
+use rustc_middle::mir::interpret::{sign_extend, truncate};
+use rustc_middle::ty::layout::{IntegerExt, SizeSkeleton};
+use rustc_middle::ty::subst::SubstsRef;
+use rustc_middle::ty::{self, AdtKind, Ty, TyCtxt, TypeFoldable};
+use rustc_span::source_map;
+use rustc_span::symbol::sym;
+use rustc_span::{Span, DUMMY_SP};
+use rustc_target::abi::Abi;
+use rustc_target::abi::{Integer, LayoutOf, TagEncoding, VariantIdx, Variants};
+use rustc_target::spec::abi::Abi as SpecAbi;
+
+use std::cmp;
+use tracing::debug;
+
+declare_lint! {
+    UNUSED_COMPARISONS,
+    Warn,
+    "comparisons made useless by limits of the types involved"
+}
+
+declare_lint! {
+    OVERFLOWING_LITERALS,
+    Deny,
+    "literal out of range for its type"
+}
+
+declare_lint! {
+    VARIANT_SIZE_DIFFERENCES,
+    Allow,
+    "detects enums with widely varying variant sizes"
+}
+
+#[derive(Copy, Clone)]
+pub struct TypeLimits {
+    /// Id of the last visited negated expression
+    negated_expr_id: Option<hir::HirId>,
+}
+
+impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS]);
+
+impl TypeLimits {
+    pub fn new() -> TypeLimits {
+        TypeLimits { negated_expr_id: None }
+    }
+}
+
+/// Attempts to special-case the overflowing literal lint when it occurs as a range endpoint.
+/// Returns `true` iff the lint was overridden.
+fn lint_overflowing_range_endpoint<'tcx>(
+    cx: &LateContext<'tcx>,
+    lit: &hir::Lit,
+    lit_val: u128,
+    max: u128,
+    expr: &'tcx hir::Expr<'tcx>,
+    parent_expr: &'tcx hir::Expr<'tcx>,
+    ty: &str,
+) -> bool {
+    // We only want to handle exclusive (`..`) ranges,
+    // which are represented as `ExprKind::Struct`.
+    let mut overwritten = false;
+    if let ExprKind::Struct(_, eps, _) = &parent_expr.kind {
+        if eps.len() != 2 {
+            return false;
+        }
+        // We can suggest using an inclusive range
+        // (`..=`) instead only if it is the `end` that is
+        // overflowing and only by 1.
+        if eps[1].expr.hir_id == expr.hir_id && lit_val - 1 == max {
+            cx.struct_span_lint(OVERFLOWING_LITERALS, parent_expr.span, |lint| {
+                let mut err = lint.build(&format!("range endpoint is out of range for `{}`", ty));
+                if let Ok(start) = cx.sess().source_map().span_to_snippet(eps[0].span) {
+                    use ast::{LitIntType, LitKind};
+                    // We need to preserve the literal's suffix,
+                    // as it may determine typing information.
+                    let suffix = match lit.node {
+                        LitKind::Int(_, LitIntType::Signed(s)) => s.name_str().to_string(),
+                        LitKind::Int(_, LitIntType::Unsigned(s)) => s.name_str().to_string(),
+                        LitKind::Int(_, LitIntType::Unsuffixed) => "".to_string(),
+                        _ => bug!(),
+                    };
+                    let suggestion = format!("{}..={}{}", start, lit_val - 1, suffix);
+                    err.span_suggestion(
+                        parent_expr.span,
+                        &"use an inclusive range instead",
+                        suggestion,
+                        Applicability::MachineApplicable,
+                    );
+                    err.emit();
+                    overwritten = true;
+                }
+            });
+        }
+    }
+    overwritten
+}
+
+// For `isize` & `usize`, be conservative with the warnings, so that the
+// warnings are consistent between 32- and 64-bit platforms.
+fn int_ty_range(int_ty: ast::IntTy) -> (i128, i128) {
+    match int_ty {
+        ast::IntTy::Isize => (i64::MIN as i128, i64::MAX as i128),
+        ast::IntTy::I8 => (i8::MIN as i64 as i128, i8::MAX as i128),
+        ast::IntTy::I16 => (i16::MIN as i64 as i128, i16::MAX as i128),
+        ast::IntTy::I32 => (i32::MIN as i64 as i128, i32::MAX as i128),
+        ast::IntTy::I64 => (i64::MIN as i128, i64::MAX as i128),
+        ast::IntTy::I128 => (i128::MIN as i128, i128::MAX),
+    }
+}
+
+fn uint_ty_range(uint_ty: ast::UintTy) -> (u128, u128) {
+    match uint_ty {
+        ast::UintTy::Usize => (u64::MIN as u128, u64::MAX as u128),
+        ast::UintTy::U8 => (u8::MIN as u128, u8::MAX as u128),
+        ast::UintTy::U16 => (u16::MIN as u128, u16::MAX as u128),
+        ast::UintTy::U32 => (u32::MIN as u128, u32::MAX as u128),
+        ast::UintTy::U64 => (u64::MIN as u128, u64::MAX as u128),
+        ast::UintTy::U128 => (u128::MIN, u128::MAX),
+    }
+}
+
+fn get_bin_hex_repr(cx: &LateContext<'_>, lit: &hir::Lit) -> Option<String> {
+    let src = cx.sess().source_map().span_to_snippet(lit.span).ok()?;
+    let firstch = src.chars().next()?;
+
+    if firstch == '0' {
+        match src.chars().nth(1) {
+            Some('x' | 'b') => return Some(src),
+            _ => return None,
+        }
+    }
+
+    None
+}
+
+fn report_bin_hex_error(
+    cx: &LateContext<'_>,
+    expr: &hir::Expr<'_>,
+    ty: attr::IntType,
+    repr_str: String,
+    val: u128,
+    negative: bool,
+) {
+    let size = Integer::from_attr(&cx.tcx, ty).size();
+    cx.struct_span_lint(OVERFLOWING_LITERALS, expr.span, |lint| {
+        let (t, actually) = match ty {
+            attr::IntType::SignedInt(t) => {
+                let actually = sign_extend(val, size) as i128;
+                (t.name_str(), actually.to_string())
+            }
+            attr::IntType::UnsignedInt(t) => {
+                let actually = truncate(val, size);
+                (t.name_str(), actually.to_string())
+            }
+        };
+        let mut err = lint.build(&format!("literal out of range for {}", t));
+        err.note(&format!(
+            "the literal `{}` (decimal `{}`) does not fit into \
+             the type `{}` and will become `{}{}`",
+            repr_str, val, t, actually, t
+        ));
+        if let Some(sugg_ty) =
+            get_type_suggestion(&cx.typeck_results().node_type(expr.hir_id), val, negative)
+        {
+            if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
+                let (sans_suffix, _) = repr_str.split_at(pos);
+                err.span_suggestion(
+                    expr.span,
+                    &format!("consider using `{}` instead", sugg_ty),
+                    format!("{}{}", sans_suffix, sugg_ty),
+                    Applicability::MachineApplicable,
+                );
+            } else {
+                err.help(&format!("consider using `{}` instead", sugg_ty));
+            }
+        }
+        err.emit();
+    });
+}
+
+// This function finds the next fitting type and generates a suggestion string.
+// It searches for fitting types in the following way (`X < Y`):
+//  - `iX`: if literal fits in `uX` => `uX`, else => `iY`
+//  - `-iX` => `iY`
+//  - `uX` => `uY`
+//
+// No suggestion for: `isize`, `usize`.
+fn get_type_suggestion(t: Ty<'_>, val: u128, negative: bool) -> Option<&'static str> {
+    use rustc_ast::IntTy::*;
+    use rustc_ast::UintTy::*;
+    macro_rules! find_fit {
+        ($ty:expr, $val:expr, $negative:expr,
+         $($type:ident => [$($utypes:expr),*] => [$($itypes:expr),*]),+) => {
+            {
+                let _neg = if negative { 1 } else { 0 };
+                match $ty {
+                    $($type => {
+                        $(if !negative && val <= uint_ty_range($utypes).1 {
+                            return Some($utypes.name_str())
+                        })*
+                        $(if val <= int_ty_range($itypes).1 as u128 + _neg {
+                            return Some($itypes.name_str())
+                        })*
+                        None
+                    },)+
+                    _ => None
+                }
+            }
+        }
+    }
+    match t.kind {
+        ty::Int(i) => find_fit!(i, val, negative,
+                      I8 => [U8] => [I16, I32, I64, I128],
+                      I16 => [U16] => [I32, I64, I128],
+                      I32 => [U32] => [I64, I128],
+                      I64 => [U64] => [I128],
+                      I128 => [U128] => []),
+        ty::Uint(u) => find_fit!(u, val, negative,
+                      U8 => [U8, U16, U32, U64, U128] => [],
+                      U16 => [U16, U32, U64, U128] => [],
+                      U32 => [U32, U64, U128] => [],
+                      U64 => [U64, U128] => [],
+                      U128 => [U128] => []),
+        _ => None,
+    }
+}
+
+fn lint_int_literal<'tcx>(
+    cx: &LateContext<'tcx>,
+    type_limits: &TypeLimits,
+    e: &'tcx hir::Expr<'tcx>,
+    lit: &hir::Lit,
+    t: ast::IntTy,
+    v: u128,
+) {
+    let int_type = t.normalize(cx.sess().target.ptr_width);
+    let (min, max) = int_ty_range(int_type);
+    let max = max as u128;
+    let negative = type_limits.negated_expr_id == Some(e.hir_id);
+
+    // Detect literal value out of range [min, max] inclusive
+    // avoiding use of -min to prevent overflow/panic
+    if (negative && v > max + 1) || (!negative && v > max) {
+        if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
+            report_bin_hex_error(cx, e, attr::IntType::SignedInt(t), repr_str, v, negative);
+            return;
+        }
+
+        let par_id = cx.tcx.hir().get_parent_node(e.hir_id);
+        if let Node::Expr(par_e) = cx.tcx.hir().get(par_id) {
+            if let hir::ExprKind::Struct(..) = par_e.kind {
+                if is_range_literal(par_e)
+                    && lint_overflowing_range_endpoint(cx, lit, v, max, e, par_e, t.name_str())
+                {
+                    // The overflowing literal lint was overridden.
+                    return;
+                }
+            }
+        }
+
+        cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
+            lint.build(&format!("literal out of range for `{}`", t.name_str()))
+                .note(&format!(
+                    "the literal `{}` does not fit into the type `{}` whose range is `{}..={}`",
+                    cx.sess()
+                        .source_map()
+                        .span_to_snippet(lit.span)
+                        .expect("must get snippet from literal"),
+                    t.name_str(),
+                    min,
+                    max,
+                ))
+                .emit();
+        });
+    }
+}
+
+fn lint_uint_literal<'tcx>(
+    cx: &LateContext<'tcx>,
+    e: &'tcx hir::Expr<'tcx>,
+    lit: &hir::Lit,
+    t: ast::UintTy,
+) {
+    let uint_type = t.normalize(cx.sess().target.ptr_width);
+    let (min, max) = uint_ty_range(uint_type);
+    let lit_val: u128 = match lit.node {
+        // _v is u8, within range by definition
+        ast::LitKind::Byte(_v) => return,
+        ast::LitKind::Int(v, _) => v,
+        _ => bug!(),
+    };
+    if lit_val < min || lit_val > max {
+        let parent_id = cx.tcx.hir().get_parent_node(e.hir_id);
+        if let Node::Expr(par_e) = cx.tcx.hir().get(parent_id) {
+            match par_e.kind {
+                hir::ExprKind::Cast(..) => {
+                    if let ty::Char = cx.typeck_results().expr_ty(par_e).kind {
+                        cx.struct_span_lint(OVERFLOWING_LITERALS, par_e.span, |lint| {
+                            lint.build("only `u8` can be cast into `char`")
+                                .span_suggestion(
+                                    par_e.span,
+                                    &"use a `char` literal instead",
+                                    format!("'\\u{{{:X}}}'", lit_val),
+                                    Applicability::MachineApplicable,
+                                )
+                                .emit();
+                        });
+                        return;
+                    }
+                }
+                hir::ExprKind::Struct(..) if is_range_literal(par_e) => {
+                    let t = t.name_str();
+                    if lint_overflowing_range_endpoint(cx, lit, lit_val, max, e, par_e, t) {
+                        // The overflowing literal lint was overridden.
+                        return;
+                    }
+                }
+                _ => {}
+            }
+        }
+        if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
+            report_bin_hex_error(cx, e, attr::IntType::UnsignedInt(t), repr_str, lit_val, false);
+            return;
+        }
+        cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
+            lint.build(&format!("literal out of range for `{}`", t.name_str()))
+                .note(&format!(
+                    "the literal `{}` does not fit into the type `{}` whose range is `{}..={}`",
+                    cx.sess()
+                        .source_map()
+                        .span_to_snippet(lit.span)
+                        .expect("must get snippet from literal"),
+                    t.name_str(),
+                    min,
+                    max,
+                ))
+                .emit()
+        });
+    }
+}
+
+fn lint_literal<'tcx>(
+    cx: &LateContext<'tcx>,
+    type_limits: &TypeLimits,
+    e: &'tcx hir::Expr<'tcx>,
+    lit: &hir::Lit,
+) {
+    match cx.typeck_results().node_type(e.hir_id).kind {
+        ty::Int(t) => {
+            match lit.node {
+                ast::LitKind::Int(v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed) => {
+                    lint_int_literal(cx, type_limits, e, lit, t, v)
+                }
+                _ => bug!(),
+            };
+        }
+        ty::Uint(t) => lint_uint_literal(cx, e, lit, t),
+        ty::Float(t) => {
+            let is_infinite = match lit.node {
+                ast::LitKind::Float(v, _) => match t {
+                    ast::FloatTy::F32 => v.as_str().parse().map(f32::is_infinite),
+                    ast::FloatTy::F64 => v.as_str().parse().map(f64::is_infinite),
+                },
+                _ => bug!(),
+            };
+            if is_infinite == Ok(true) {
+                cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
+                    lint.build(&format!("literal out of range for `{}`", t.name_str()))
+                        .note(&format!(
+                            "the literal `{}` does not fit into the type `{}` and will be converted to `std::{}::INFINITY`",
+                            cx.sess()
+                                .source_map()
+                                .span_to_snippet(lit.span)
+                                .expect("must get snippet from literal"),
+                            t.name_str(),
+                            t.name_str(),
+                        ))
+                        .emit();
+                });
+            }
+        }
+        _ => {}
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for TypeLimits {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) {
+        match e.kind {
+            hir::ExprKind::Unary(hir::UnOp::UnNeg, ref expr) => {
+                // propagate negation, if the negation itself isn't negated
+                if self.negated_expr_id != Some(e.hir_id) {
+                    self.negated_expr_id = Some(expr.hir_id);
+                }
+            }
+            hir::ExprKind::Binary(binop, ref l, ref r) => {
+                if is_comparison(binop) && !check_limits(cx, binop, &l, &r) {
+                    cx.struct_span_lint(UNUSED_COMPARISONS, e.span, |lint| {
+                        lint.build("comparison is useless due to type limits").emit()
+                    });
+                }
+            }
+            hir::ExprKind::Lit(ref lit) => lint_literal(cx, self, e, lit),
+            _ => {}
+        };
+
+        fn is_valid<T: cmp::PartialOrd>(binop: hir::BinOp, v: T, min: T, max: T) -> bool {
+            match binop.node {
+                hir::BinOpKind::Lt => v > min && v <= max,
+                hir::BinOpKind::Le => v >= min && v < max,
+                hir::BinOpKind::Gt => v >= min && v < max,
+                hir::BinOpKind::Ge => v > min && v <= max,
+                hir::BinOpKind::Eq | hir::BinOpKind::Ne => v >= min && v <= max,
+                _ => bug!(),
+            }
+        }
+
+        fn rev_binop(binop: hir::BinOp) -> hir::BinOp {
+            source_map::respan(
+                binop.span,
+                match binop.node {
+                    hir::BinOpKind::Lt => hir::BinOpKind::Gt,
+                    hir::BinOpKind::Le => hir::BinOpKind::Ge,
+                    hir::BinOpKind::Gt => hir::BinOpKind::Lt,
+                    hir::BinOpKind::Ge => hir::BinOpKind::Le,
+                    _ => return binop,
+                },
+            )
+        }
+
+        fn check_limits(
+            cx: &LateContext<'_>,
+            binop: hir::BinOp,
+            l: &hir::Expr<'_>,
+            r: &hir::Expr<'_>,
+        ) -> bool {
+            let (lit, expr, swap) = match (&l.kind, &r.kind) {
+                (&hir::ExprKind::Lit(_), _) => (l, r, true),
+                (_, &hir::ExprKind::Lit(_)) => (r, l, false),
+                _ => return true,
+            };
+            // Normalize the binop so that the literal is always on the RHS in
+            // the comparison
+            let norm_binop = if swap { rev_binop(binop) } else { binop };
+            match cx.typeck_results().node_type(expr.hir_id).kind {
+                ty::Int(int_ty) => {
+                    let (min, max) = int_ty_range(int_ty);
+                    let lit_val: i128 = match lit.kind {
+                        hir::ExprKind::Lit(ref li) => match li.node {
+                            ast::LitKind::Int(
+                                v,
+                                ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed,
+                            ) => v as i128,
+                            _ => return true,
+                        },
+                        _ => bug!(),
+                    };
+                    is_valid(norm_binop, lit_val, min, max)
+                }
+                ty::Uint(uint_ty) => {
+                    let (min, max): (u128, u128) = uint_ty_range(uint_ty);
+                    let lit_val: u128 = match lit.kind {
+                        hir::ExprKind::Lit(ref li) => match li.node {
+                            ast::LitKind::Int(v, _) => v,
+                            _ => return true,
+                        },
+                        _ => bug!(),
+                    };
+                    is_valid(norm_binop, lit_val, min, max)
+                }
+                _ => true,
+            }
+        }
+
+        fn is_comparison(binop: hir::BinOp) -> bool {
+            match binop.node {
+                hir::BinOpKind::Eq
+                | hir::BinOpKind::Lt
+                | hir::BinOpKind::Le
+                | hir::BinOpKind::Ne
+                | hir::BinOpKind::Ge
+                | hir::BinOpKind::Gt => true,
+                _ => false,
+            }
+        }
+    }
+}
+
+declare_lint! {
+    IMPROPER_CTYPES,
+    Warn,
+    "proper use of libc types in foreign modules"
+}
+
+declare_lint_pass!(ImproperCTypesDeclarations => [IMPROPER_CTYPES]);
+
+declare_lint! {
+    IMPROPER_CTYPES_DEFINITIONS,
+    Warn,
+    "proper use of libc types in foreign item definitions"
+}
+
+declare_lint_pass!(ImproperCTypesDefinitions => [IMPROPER_CTYPES_DEFINITIONS]);
+
+#[derive(Clone, Copy)]
+crate enum CItemKind {
+    Declaration,
+    Definition,
+}
+
+struct ImproperCTypesVisitor<'a, 'tcx> {
+    cx: &'a LateContext<'tcx>,
+    mode: CItemKind,
+}
+
+enum FfiResult<'tcx> {
+    FfiSafe,
+    FfiPhantom(Ty<'tcx>),
+    FfiUnsafe { ty: Ty<'tcx>, reason: String, help: Option<String> },
+}
+
+crate fn nonnull_optimization_guaranteed<'tcx>(tcx: TyCtxt<'tcx>, def: &ty::AdtDef) -> bool {
+    tcx.get_attrs(def.did)
+        .iter()
+        .any(|a| tcx.sess.check_name(a, sym::rustc_nonnull_optimization_guaranteed))
+}
+
+/// Is type known to be non-null?
+crate fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool {
+    let tcx = cx.tcx;
+    match ty.kind {
+        ty::FnPtr(_) => true,
+        ty::Ref(..) => true,
+        ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true,
+        ty::Adt(def, substs) if def.repr.transparent() && !def.is_union() => {
+            let marked_non_null = nonnull_optimization_guaranteed(tcx, &def);
+
+            if marked_non_null {
+                return true;
+            }
+
+            for variant in &def.variants {
+                if let Some(field) = variant.transparent_newtype_field(tcx) {
+                    if ty_is_known_nonnull(cx, field.ty(tcx, substs), mode) {
+                        return true;
+                    }
+                }
+            }
+
+            false
+        }
+        _ => false,
+    }
+}
+
+/// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type.
+/// If the type passed in was not scalar, returns None.
+fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+    let tcx = cx.tcx;
+    Some(match ty.kind {
+        ty::Adt(field_def, field_substs) => {
+            let inner_field_ty = {
+                let first_non_zst_ty =
+                    field_def.variants.iter().filter_map(|v| v.transparent_newtype_field(tcx));
+                debug_assert_eq!(
+                    first_non_zst_ty.clone().count(),
+                    1,
+                    "Wrong number of fields for transparent type"
+                );
+                first_non_zst_ty
+                    .last()
+                    .expect("No non-zst fields in transparent type.")
+                    .ty(tcx, field_substs)
+            };
+            return get_nullable_type(cx, inner_field_ty);
+        }
+        ty::Int(ty) => tcx.mk_mach_int(ty),
+        ty::Uint(ty) => tcx.mk_mach_uint(ty),
+        ty::RawPtr(ty_mut) => tcx.mk_ptr(ty_mut),
+        // As these types are always non-null, the nullable equivalent of
+        // Option<T> of these types are their raw pointer counterparts.
+        ty::Ref(_region, ty, mutbl) => tcx.mk_ptr(ty::TypeAndMut { ty, mutbl }),
+        ty::FnPtr(..) => {
+            // There is no nullable equivalent for Rust's function pointers -- you
+            // must use an Option<fn(..) -> _> to represent it.
+            ty
+        }
+
+        // We should only ever reach this case if ty_is_known_nonnull is extended
+        // to other types.
+        ref unhandled => {
+            debug!(
+                "get_nullable_type: Unhandled scalar kind: {:?} while checking {:?}",
+                unhandled, ty
+            );
+            return None;
+        }
+    })
+}
+
+/// Check if this enum can be safely exported based on the "nullable pointer optimization". If it
+/// can, return the the type that `ty` can be safely converted to, otherwise return `None`.
+/// Currently restricted to function pointers, boxes, references, `core::num::NonZero*`,
+/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes.
+/// FIXME: This duplicates code in codegen.
+crate fn repr_nullable_ptr<'tcx>(
+    cx: &LateContext<'tcx>,
+    ty: Ty<'tcx>,
+    ckind: CItemKind,
+) -> Option<Ty<'tcx>> {
+    debug!("is_repr_nullable_ptr(cx, ty = {:?})", ty);
+    if let ty::Adt(ty_def, substs) = ty.kind {
+        if ty_def.variants.len() != 2 {
+            return None;
+        }
+
+        let get_variant_fields = |index| &ty_def.variants[VariantIdx::new(index)].fields;
+        let variant_fields = [get_variant_fields(0), get_variant_fields(1)];
+        let fields = if variant_fields[0].is_empty() {
+            &variant_fields[1]
+        } else if variant_fields[1].is_empty() {
+            &variant_fields[0]
+        } else {
+            return None;
+        };
+
+        if fields.len() != 1 {
+            return None;
+        }
+
+        let field_ty = fields[0].ty(cx.tcx, substs);
+        if !ty_is_known_nonnull(cx, field_ty, ckind) {
+            return None;
+        }
+
+        // At this point, the field's type is known to be nonnull and the parent enum is Option-like.
+        // If the computed size for the field and the enum are different, the nonnull optimization isn't
+        // being applied (and we've got a problem somewhere).
+        let compute_size_skeleton = |t| SizeSkeleton::compute(t, cx.tcx, cx.param_env).unwrap();
+        if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) {
+            bug!("improper_ctypes: Option nonnull optimization not applied?");
+        }
+
+        // Return the nullable type this Option-like enum can be safely represented with.
+        let field_ty_abi = &cx.layout_of(field_ty).unwrap().abi;
+        if let Abi::Scalar(field_ty_scalar) = field_ty_abi {
+            match (field_ty_scalar.valid_range.start(), field_ty_scalar.valid_range.end()) {
+                (0, _) => unreachable!("Non-null optimisation extended to a non-zero value."),
+                (1, _) => {
+                    return Some(get_nullable_type(cx, field_ty).unwrap());
+                }
+                (start, end) => unreachable!("Unhandled start and end range: ({}, {})", start, end),
+            };
+        }
+    }
+    None
+}
+
+impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
+    /// Check if the type is array and emit an unsafe type lint.
+    fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
+        if let ty::Array(..) = ty.kind {
+            self.emit_ffi_unsafe_type_lint(
+                ty,
+                sp,
+                "passing raw arrays by value is not FFI-safe",
+                Some("consider passing a pointer to the array"),
+            );
+            true
+        } else {
+            false
+        }
+    }
+
+    /// Checks if the given field's type is "ffi-safe".
+    fn check_field_type_for_ffi(
+        &self,
+        cache: &mut FxHashSet<Ty<'tcx>>,
+        field: &ty::FieldDef,
+        substs: SubstsRef<'tcx>,
+    ) -> FfiResult<'tcx> {
+        let field_ty = field.ty(self.cx.tcx, substs);
+        if field_ty.has_opaque_types() {
+            self.check_type_for_ffi(cache, field_ty)
+        } else {
+            let field_ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, field_ty);
+            self.check_type_for_ffi(cache, field_ty)
+        }
+    }
+
+    /// Checks if the given `VariantDef`'s field types are "ffi-safe".
+    fn check_variant_for_ffi(
+        &self,
+        cache: &mut FxHashSet<Ty<'tcx>>,
+        ty: Ty<'tcx>,
+        def: &ty::AdtDef,
+        variant: &ty::VariantDef,
+        substs: SubstsRef<'tcx>,
+    ) -> FfiResult<'tcx> {
+        use FfiResult::*;
+
+        if def.repr.transparent() {
+            // Can assume that only one field is not a ZST, so only check
+            // that field's type for FFI-safety.
+            if let Some(field) = variant.transparent_newtype_field(self.cx.tcx) {
+                self.check_field_type_for_ffi(cache, field, substs)
+            } else {
+                bug!("malformed transparent type");
+            }
+        } else {
+            // We can't completely trust repr(C) markings; make sure the fields are
+            // actually safe.
+            let mut all_phantom = !variant.fields.is_empty();
+            for field in &variant.fields {
+                match self.check_field_type_for_ffi(cache, &field, substs) {
+                    FfiSafe => {
+                        all_phantom = false;
+                    }
+                    FfiPhantom(..) if def.is_enum() => {
+                        return FfiUnsafe {
+                            ty,
+                            reason: "this enum contains a PhantomData field".into(),
+                            help: None,
+                        };
+                    }
+                    FfiPhantom(..) => {}
+                    r => return r,
+                }
+            }
+
+            if all_phantom { FfiPhantom(ty) } else { FfiSafe }
+        }
+    }
+
+    /// Checks if the given type is "ffi-safe" (has a stable, well-defined
+    /// representation which can be exported to C code).
+    fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> {
+        use FfiResult::*;
+
+        let tcx = self.cx.tcx;
+
+        // Protect against infinite recursion, for example
+        // `struct S(*mut S);`.
+        // FIXME: A recursion limit is necessary as well, for irregular
+        // recursive types.
+        if !cache.insert(ty) {
+            return FfiSafe;
+        }
+
+        match ty.kind {
+            ty::Adt(def, _) if def.is_box() && matches!(self.mode, CItemKind::Definition) => {
+                FfiSafe
+            }
+
+            ty::Adt(def, substs) => {
+                if def.is_phantom_data() {
+                    return FfiPhantom(ty);
+                }
+                match def.adt_kind() {
+                    AdtKind::Struct | AdtKind::Union => {
+                        let kind = if def.is_struct() { "struct" } else { "union" };
+
+                        if !def.repr.c() && !def.repr.transparent() {
+                            return FfiUnsafe {
+                                ty,
+                                reason: format!("this {} has unspecified layout", kind),
+                                help: Some(format!(
+                                    "consider adding a `#[repr(C)]` or \
+                                             `#[repr(transparent)]` attribute to this {}",
+                                    kind
+                                )),
+                            };
+                        }
+
+                        let is_non_exhaustive =
+                            def.non_enum_variant().is_field_list_non_exhaustive();
+                        if is_non_exhaustive && !def.did.is_local() {
+                            return FfiUnsafe {
+                                ty,
+                                reason: format!("this {} is non-exhaustive", kind),
+                                help: None,
+                            };
+                        }
+
+                        if def.non_enum_variant().fields.is_empty() {
+                            return FfiUnsafe {
+                                ty,
+                                reason: format!("this {} has no fields", kind),
+                                help: Some(format!("consider adding a member to this {}", kind)),
+                            };
+                        }
+
+                        self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), substs)
+                    }
+                    AdtKind::Enum => {
+                        if def.variants.is_empty() {
+                            // Empty enums are okay... although sort of useless.
+                            return FfiSafe;
+                        }
+
+                        // Check for a repr() attribute to specify the size of the
+                        // discriminant.
+                        if !def.repr.c() && !def.repr.transparent() && def.repr.int.is_none() {
+                            // Special-case types like `Option<extern fn()>`.
+                            if repr_nullable_ptr(self.cx, ty, self.mode).is_none() {
+                                return FfiUnsafe {
+                                    ty,
+                                    reason: "enum has no representation hint".into(),
+                                    help: Some(
+                                        "consider adding a `#[repr(C)]`, \
+                                                `#[repr(transparent)]`, or integer `#[repr(...)]` \
+                                                attribute to this enum"
+                                            .into(),
+                                    ),
+                                };
+                            }
+                        }
+
+                        if def.is_variant_list_non_exhaustive() && !def.did.is_local() {
+                            return FfiUnsafe {
+                                ty,
+                                reason: "this enum is non-exhaustive".into(),
+                                help: None,
+                            };
+                        }
+
+                        // Check the contained variants.
+                        for variant in &def.variants {
+                            let is_non_exhaustive = variant.is_field_list_non_exhaustive();
+                            if is_non_exhaustive && !variant.def_id.is_local() {
+                                return FfiUnsafe {
+                                    ty,
+                                    reason: "this enum has non-exhaustive variants".into(),
+                                    help: None,
+                                };
+                            }
+
+                            match self.check_variant_for_ffi(cache, ty, def, variant, substs) {
+                                FfiSafe => (),
+                                r => return r,
+                            }
+                        }
+
+                        FfiSafe
+                    }
+                }
+            }
+
+            ty::Char => FfiUnsafe {
+                ty,
+                reason: "the `char` type has no C equivalent".into(),
+                help: Some("consider using `u32` or `libc::wchar_t` instead".into()),
+            },
+
+            ty::Int(ast::IntTy::I128) | ty::Uint(ast::UintTy::U128) => FfiUnsafe {
+                ty,
+                reason: "128-bit integers don't currently have a known stable ABI".into(),
+                help: None,
+            },
+
+            // Primitive types with a stable representation.
+            ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe,
+
+            ty::Slice(_) => FfiUnsafe {
+                ty,
+                reason: "slices have no C equivalent".into(),
+                help: Some("consider using a raw pointer instead".into()),
+            },
+
+            ty::Dynamic(..) => {
+                FfiUnsafe { ty, reason: "trait objects have no C equivalent".into(), help: None }
+            }
+
+            ty::Str => FfiUnsafe {
+                ty,
+                reason: "string slices have no C equivalent".into(),
+                help: Some("consider using `*const u8` and a length instead".into()),
+            },
+
+            ty::Tuple(..) => FfiUnsafe {
+                ty,
+                reason: "tuples have unspecified layout".into(),
+                help: Some("consider using a struct instead".into()),
+            },
+
+            ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _)
+                if {
+                    matches!(self.mode, CItemKind::Definition)
+                        && ty.is_sized(self.cx.tcx.at(DUMMY_SP), self.cx.param_env)
+                } =>
+            {
+                FfiSafe
+            }
+
+            ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => {
+                self.check_type_for_ffi(cache, ty)
+            }
+
+            ty::Array(inner_ty, _) => self.check_type_for_ffi(cache, inner_ty),
+
+            ty::FnPtr(sig) => {
+                if self.is_internal_abi(sig.abi()) {
+                    return FfiUnsafe {
+                        ty,
+                        reason: "this function pointer has Rust-specific calling convention".into(),
+                        help: Some(
+                            "consider using an `extern fn(...) -> ...` \
+                                    function pointer instead"
+                                .into(),
+                        ),
+                    };
+                }
+
+                let sig = tcx.erase_late_bound_regions(&sig);
+                if !sig.output().is_unit() {
+                    let r = self.check_type_for_ffi(cache, sig.output());
+                    match r {
+                        FfiSafe => {}
+                        _ => {
+                            return r;
+                        }
+                    }
+                }
+                for arg in sig.inputs() {
+                    let r = self.check_type_for_ffi(cache, arg);
+                    match r {
+                        FfiSafe => {}
+                        _ => {
+                            return r;
+                        }
+                    }
+                }
+                FfiSafe
+            }
+
+            ty::Foreign(..) => FfiSafe,
+
+            // While opaque types are checked for earlier, if a projection in a struct field
+            // normalizes to an opaque type, then it will reach this branch.
+            ty::Opaque(..) => {
+                FfiUnsafe { ty, reason: "opaque types have no C equivalent".into(), help: None }
+            }
+
+            // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe,
+            //  so they are currently ignored for the purposes of this lint.
+            ty::Param(..) | ty::Projection(..) if matches!(self.mode, CItemKind::Definition) => {
+                FfiSafe
+            }
+
+            ty::Param(..)
+            | ty::Projection(..)
+            | ty::Infer(..)
+            | ty::Bound(..)
+            | ty::Error(_)
+            | ty::Closure(..)
+            | ty::Generator(..)
+            | ty::GeneratorWitness(..)
+            | ty::Placeholder(..)
+            | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty),
+        }
+    }
+
+    fn emit_ffi_unsafe_type_lint(
+        &mut self,
+        ty: Ty<'tcx>,
+        sp: Span,
+        note: &str,
+        help: Option<&str>,
+    ) {
+        let lint = match self.mode {
+            CItemKind::Declaration => IMPROPER_CTYPES,
+            CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS,
+        };
+
+        self.cx.struct_span_lint(lint, sp, |lint| {
+            let item_description = match self.mode {
+                CItemKind::Declaration => "block",
+                CItemKind::Definition => "fn",
+            };
+            let mut diag = lint.build(&format!(
+                "`extern` {} uses type `{}`, which is not FFI-safe",
+                item_description, ty
+            ));
+            diag.span_label(sp, "not FFI-safe");
+            if let Some(help) = help {
+                diag.help(help);
+            }
+            diag.note(note);
+            if let ty::Adt(def, _) = ty.kind {
+                if let Some(sp) = self.cx.tcx.hir().span_if_local(def.did) {
+                    diag.span_note(sp, "the type is defined here");
+                }
+            }
+            diag.emit();
+        });
+    }
+
+    fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
+        struct ProhibitOpaqueTypes<'a, 'tcx> {
+            cx: &'a LateContext<'tcx>,
+            ty: Option<Ty<'tcx>>,
+        };
+
+        impl<'a, 'tcx> ty::fold::TypeVisitor<'tcx> for ProhibitOpaqueTypes<'a, 'tcx> {
+            fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
+                match ty.kind {
+                    ty::Opaque(..) => {
+                        self.ty = Some(ty);
+                        true
+                    }
+                    // Consider opaque types within projections FFI-safe if they do not normalize
+                    // to more opaque types.
+                    ty::Projection(..) => {
+                        let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty);
+
+                        // If `ty` is a opaque type directly then `super_visit_with` won't invoke
+                        // this function again.
+                        if ty.has_opaque_types() { self.visit_ty(ty) } else { false }
+                    }
+                    _ => ty.super_visit_with(self),
+                }
+            }
+        }
+
+        let mut visitor = ProhibitOpaqueTypes { cx: self.cx, ty: None };
+        ty.visit_with(&mut visitor);
+        if let Some(ty) = visitor.ty {
+            self.emit_ffi_unsafe_type_lint(ty, sp, "opaque types have no C equivalent", None);
+            true
+        } else {
+            false
+        }
+    }
+
+    fn check_type_for_ffi_and_report_errors(
+        &mut self,
+        sp: Span,
+        ty: Ty<'tcx>,
+        is_static: bool,
+        is_return_type: bool,
+    ) {
+        // We have to check for opaque types before `normalize_erasing_regions`,
+        // which will replace opaque types with their underlying concrete type.
+        if self.check_for_opaque_ty(sp, ty) {
+            // We've already emitted an error due to an opaque type.
+            return;
+        }
+
+        // it is only OK to use this function because extern fns cannot have
+        // any generic types right now:
+        let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty);
+
+        // C doesn't really support passing arrays by value - the only way to pass an array by value
+        // is through a struct. So, first test that the top level isn't an array, and then
+        // recursively check the types inside.
+        if !is_static && self.check_for_array_ty(sp, ty) {
+            return;
+        }
+
+        // Don't report FFI errors for unit return types. This check exists here, and not in
+        // `check_foreign_fn` (where it would make more sense) so that normalization has definitely
+        // happened.
+        if is_return_type && ty.is_unit() {
+            return;
+        }
+
+        match self.check_type_for_ffi(&mut FxHashSet::default(), ty) {
+            FfiResult::FfiSafe => {}
+            FfiResult::FfiPhantom(ty) => {
+                self.emit_ffi_unsafe_type_lint(ty, sp, "composed only of `PhantomData`", None);
+            }
+            // If `ty` is a `repr(transparent)` newtype, and the non-zero-sized type is a generic
+            // argument, which after substitution, is `()`, then this branch can be hit.
+            FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => {}
+            FfiResult::FfiUnsafe { ty, reason, help } => {
+                self.emit_ffi_unsafe_type_lint(ty, sp, &reason, help.as_deref());
+            }
+        }
+    }
+
+    fn check_foreign_fn(&mut self, id: hir::HirId, decl: &hir::FnDecl<'_>) {
+        let def_id = self.cx.tcx.hir().local_def_id(id);
+        let sig = self.cx.tcx.fn_sig(def_id);
+        let sig = self.cx.tcx.erase_late_bound_regions(&sig);
+
+        for (input_ty, input_hir) in sig.inputs().iter().zip(decl.inputs) {
+            self.check_type_for_ffi_and_report_errors(input_hir.span, input_ty, false, false);
+        }
+
+        if let hir::FnRetTy::Return(ref ret_hir) = decl.output {
+            let ret_ty = sig.output();
+            self.check_type_for_ffi_and_report_errors(ret_hir.span, ret_ty, false, true);
+        }
+    }
+
+    fn check_foreign_static(&mut self, id: hir::HirId, span: Span) {
+        let def_id = self.cx.tcx.hir().local_def_id(id);
+        let ty = self.cx.tcx.type_of(def_id);
+        self.check_type_for_ffi_and_report_errors(span, ty, true, false);
+    }
+
+    fn is_internal_abi(&self, abi: SpecAbi) -> bool {
+        if let SpecAbi::Rust
+        | SpecAbi::RustCall
+        | SpecAbi::RustIntrinsic
+        | SpecAbi::PlatformIntrinsic = abi
+        {
+            true
+        } else {
+            false
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations {
+    fn check_foreign_item(&mut self, cx: &LateContext<'_>, it: &hir::ForeignItem<'_>) {
+        let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration };
+        let abi = cx.tcx.hir().get_foreign_abi(it.hir_id);
+
+        if !vis.is_internal_abi(abi) {
+            match it.kind {
+                hir::ForeignItemKind::Fn(ref decl, _, _) => {
+                    vis.check_foreign_fn(it.hir_id, decl);
+                }
+                hir::ForeignItemKind::Static(ref ty, _) => {
+                    vis.check_foreign_static(it.hir_id, ty.span);
+                }
+                hir::ForeignItemKind::Type => (),
+            }
+        }
+    }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions {
+    fn check_fn(
+        &mut self,
+        cx: &LateContext<'tcx>,
+        kind: hir::intravisit::FnKind<'tcx>,
+        decl: &'tcx hir::FnDecl<'_>,
+        _: &'tcx hir::Body<'_>,
+        _: Span,
+        hir_id: hir::HirId,
+    ) {
+        use hir::intravisit::FnKind;
+
+        let abi = match kind {
+            FnKind::ItemFn(_, _, header, ..) => header.abi,
+            FnKind::Method(_, sig, ..) => sig.header.abi,
+            _ => return,
+        };
+
+        let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition };
+        if !vis.is_internal_abi(abi) {
+            vis.check_foreign_fn(hir_id, decl);
+        }
+    }
+}
+
+declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]);
+
+impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences {
+    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+        if let hir::ItemKind::Enum(ref enum_definition, _) = it.kind {
+            let item_def_id = cx.tcx.hir().local_def_id(it.hir_id);
+            let t = cx.tcx.type_of(item_def_id);
+            let ty = cx.tcx.erase_regions(&t);
+            let layout = match cx.layout_of(ty) {
+                Ok(layout) => layout,
+                Err(
+                    ty::layout::LayoutError::Unknown(_) | ty::layout::LayoutError::SizeOverflow(_),
+                ) => return,
+            };
+            let (variants, tag) = match layout.variants {
+                Variants::Multiple {
+                    tag_encoding: TagEncoding::Direct,
+                    ref tag,
+                    ref variants,
+                    ..
+                } => (variants, tag),
+                _ => return,
+            };
+
+            let tag_size = tag.value.size(&cx.tcx).bytes();
+
+            debug!(
+                "enum `{}` is {} bytes large with layout:\n{:#?}",
+                t,
+                layout.size.bytes(),
+                layout
+            );
+
+            let (largest, slargest, largest_index) = enum_definition
+                .variants
+                .iter()
+                .zip(variants)
+                .map(|(variant, variant_layout)| {
+                    // Subtract the size of the enum tag.
+                    let bytes = variant_layout.size.bytes().saturating_sub(tag_size);
+
+                    debug!("- variant `{}` is {} bytes large", variant.ident, bytes);
+                    bytes
+                })
+                .enumerate()
+                .fold((0, 0, 0), |(l, s, li), (idx, size)| {
+                    if size > l {
+                        (size, l, idx)
+                    } else if size > s {
+                        (l, size, li)
+                    } else {
+                        (l, s, li)
+                    }
+                });
+
+            // We only warn if the largest variant is at least thrice as large as
+            // the second-largest.
+            if largest > slargest * 3 && slargest > 0 {
+                cx.struct_span_lint(
+                    VARIANT_SIZE_DIFFERENCES,
+                    enum_definition.variants[largest_index].span,
+                    |lint| {
+                        lint.build(&format!(
+                            "enum variant is more than three times \
+                                          larger ({} bytes) than the next largest",
+                            largest
+                        ))
+                        .emit()
+                    },
+                );
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
new file mode 100644
index 00000000000..c793e81ebe3
--- /dev/null
+++ b/compiler/rustc_lint/src/unused.rs
@@ -0,0 +1,1011 @@
+use crate::Lint;
+use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_ast::util::parser;
+use rustc_ast::{ExprKind, StmtKind};
+use rustc_ast_pretty::pprust;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::{pluralize, Applicability};
+use rustc_feature::{AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_middle::ty::adjustment;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::lint::builtin::UNUSED_ATTRIBUTES;
+use rustc_span::symbol::Symbol;
+use rustc_span::symbol::{kw, sym};
+use rustc_span::{BytePos, Span, DUMMY_SP};
+
+use tracing::debug;
+
+declare_lint! {
+    pub UNUSED_MUST_USE,
+    Warn,
+    "unused result of a type flagged as `#[must_use]`",
+    report_in_external_macro
+}
+
+declare_lint! {
+    pub UNUSED_RESULTS,
+    Allow,
+    "unused result of an expression in a statement"
+}
+
+declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedResults {
+    fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
+        let expr = match s.kind {
+            hir::StmtKind::Semi(ref expr) => &**expr,
+            _ => return,
+        };
+
+        if let hir::ExprKind::Ret(..) = expr.kind {
+            return;
+        }
+
+        let ty = cx.typeck_results().expr_ty(&expr);
+        let type_permits_lack_of_use = check_must_use_ty(cx, ty, &expr, s.span, "", "", 1);
+
+        let mut fn_warned = false;
+        let mut op_warned = false;
+        let maybe_def_id = match expr.kind {
+            hir::ExprKind::Call(ref callee, _) => {
+                match callee.kind {
+                    hir::ExprKind::Path(ref qpath) => {
+                        match cx.qpath_res(qpath, callee.hir_id) {
+                            Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id),
+                            // `Res::Local` if it was a closure, for which we
+                            // do not currently support must-use linting
+                            _ => None,
+                        }
+                    }
+                    _ => None,
+                }
+            }
+            hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
+            _ => None,
+        };
+        if let Some(def_id) = maybe_def_id {
+            fn_warned = check_must_use_def(cx, def_id, s.span, "return value of ", "");
+        } else if type_permits_lack_of_use {
+            // We don't warn about unused unit or uninhabited types.
+            // (See https://github.com/rust-lang/rust/issues/43806 for details.)
+            return;
+        }
+
+        let must_use_op = match expr.kind {
+            // Hardcoding operators here seemed more expedient than the
+            // refactoring that would be needed to look up the `#[must_use]`
+            // attribute which does exist on the comparison trait methods
+            hir::ExprKind::Binary(bin_op, ..) => match bin_op.node {
+                hir::BinOpKind::Eq
+                | hir::BinOpKind::Lt
+                | hir::BinOpKind::Le
+                | hir::BinOpKind::Ne
+                | hir::BinOpKind::Ge
+                | hir::BinOpKind::Gt => Some("comparison"),
+                hir::BinOpKind::Add
+                | hir::BinOpKind::Sub
+                | hir::BinOpKind::Div
+                | hir::BinOpKind::Mul
+                | hir::BinOpKind::Rem => Some("arithmetic operation"),
+                hir::BinOpKind::And | hir::BinOpKind::Or => Some("logical operation"),
+                hir::BinOpKind::BitXor
+                | hir::BinOpKind::BitAnd
+                | hir::BinOpKind::BitOr
+                | hir::BinOpKind::Shl
+                | hir::BinOpKind::Shr => Some("bitwise operation"),
+            },
+            hir::ExprKind::Unary(..) => Some("unary operation"),
+            _ => None,
+        };
+
+        if let Some(must_use_op) = must_use_op {
+            cx.struct_span_lint(UNUSED_MUST_USE, expr.span, |lint| {
+                lint.build(&format!("unused {} that must be used", must_use_op)).emit()
+            });
+            op_warned = true;
+        }
+
+        if !(type_permits_lack_of_use || fn_warned || op_warned) {
+            cx.struct_span_lint(UNUSED_RESULTS, s.span, |lint| lint.build("unused result").emit());
+        }
+
+        // Returns whether an error has been emitted (and thus another does not need to be later).
+        fn check_must_use_ty<'tcx>(
+            cx: &LateContext<'tcx>,
+            ty: Ty<'tcx>,
+            expr: &hir::Expr<'_>,
+            span: Span,
+            descr_pre: &str,
+            descr_post: &str,
+            plural_len: usize,
+        ) -> bool {
+            if ty.is_unit()
+                || cx.tcx.is_ty_uninhabited_from(
+                    cx.tcx.parent_module(expr.hir_id).to_def_id(),
+                    ty,
+                    cx.param_env,
+                )
+            {
+                return true;
+            }
+
+            let plural_suffix = pluralize!(plural_len);
+
+            match ty.kind {
+                ty::Adt(..) if ty.is_box() => {
+                    let boxed_ty = ty.boxed_ty();
+                    let descr_pre = &format!("{}boxed ", descr_pre);
+                    check_must_use_ty(cx, boxed_ty, expr, span, descr_pre, descr_post, plural_len)
+                }
+                ty::Adt(def, _) => check_must_use_def(cx, def.did, span, descr_pre, descr_post),
+                ty::Opaque(def, _) => {
+                    let mut has_emitted = false;
+                    for (predicate, _) in cx.tcx.predicates_of(def).predicates {
+                        // We only look at the `DefId`, so it is safe to skip the binder here.
+                        if let ty::PredicateAtom::Trait(ref poly_trait_predicate, _) =
+                            predicate.skip_binders()
+                        {
+                            let def_id = poly_trait_predicate.trait_ref.def_id;
+                            let descr_pre =
+                                &format!("{}implementer{} of ", descr_pre, plural_suffix,);
+                            if check_must_use_def(cx, def_id, span, descr_pre, descr_post) {
+                                has_emitted = true;
+                                break;
+                            }
+                        }
+                    }
+                    has_emitted
+                }
+                ty::Dynamic(binder, _) => {
+                    let mut has_emitted = false;
+                    for predicate in binder.skip_binder().iter() {
+                        if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate {
+                            let def_id = trait_ref.def_id;
+                            let descr_post =
+                                &format!(" trait object{}{}", plural_suffix, descr_post,);
+                            if check_must_use_def(cx, def_id, span, descr_pre, descr_post) {
+                                has_emitted = true;
+                                break;
+                            }
+                        }
+                    }
+                    has_emitted
+                }
+                ty::Tuple(ref tys) => {
+                    let mut has_emitted = false;
+                    let spans = if let hir::ExprKind::Tup(comps) = &expr.kind {
+                        debug_assert_eq!(comps.len(), tys.len());
+                        comps.iter().map(|e| e.span).collect()
+                    } else {
+                        vec![]
+                    };
+                    for (i, ty) in tys.iter().map(|k| k.expect_ty()).enumerate() {
+                        let descr_post = &format!(" in tuple element {}", i);
+                        let span = *spans.get(i).unwrap_or(&span);
+                        if check_must_use_ty(cx, ty, expr, span, descr_pre, descr_post, plural_len)
+                        {
+                            has_emitted = true;
+                        }
+                    }
+                    has_emitted
+                }
+                ty::Array(ty, len) => match len.try_eval_usize(cx.tcx, cx.param_env) {
+                    // If the array is definitely non-empty, we can do `#[must_use]` checking.
+                    Some(n) if n != 0 => {
+                        let descr_pre = &format!("{}array{} of ", descr_pre, plural_suffix,);
+                        check_must_use_ty(cx, ty, expr, span, descr_pre, descr_post, n as usize + 1)
+                    }
+                    // Otherwise, we don't lint, to avoid false positives.
+                    _ => false,
+                },
+                ty::Closure(..) => {
+                    cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
+                        let mut err = lint.build(&format!(
+                            "unused {}closure{}{} that must be used",
+                            descr_pre, plural_suffix, descr_post,
+                        ));
+                        err.note("closures are lazy and do nothing unless called");
+                        err.emit();
+                    });
+                    true
+                }
+                ty::Generator(..) => {
+                    cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
+                        let mut err = lint.build(&format!(
+                            "unused {}generator{}{} that must be used",
+                            descr_pre, plural_suffix, descr_post,
+                        ));
+                        err.note("generators are lazy and do nothing unless resumed");
+                        err.emit();
+                    });
+                    true
+                }
+                _ => false,
+            }
+        }
+
+        // Returns whether an error has been emitted (and thus another does not need to be later).
+        // FIXME: Args desc_{pre,post}_path could be made lazy by taking Fn() -> &str, but this
+        // would make calling it a big awkward. Could also take String (so args are moved), but
+        // this would still require a copy into the format string, which would only be executed
+        // when needed.
+        fn check_must_use_def(
+            cx: &LateContext<'_>,
+            def_id: DefId,
+            span: Span,
+            descr_pre_path: &str,
+            descr_post_path: &str,
+        ) -> bool {
+            for attr in cx.tcx.get_attrs(def_id).iter() {
+                if cx.sess().check_name(attr, sym::must_use) {
+                    cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
+                        let msg = format!(
+                            "unused {}`{}`{} that must be used",
+                            descr_pre_path,
+                            cx.tcx.def_path_str(def_id),
+                            descr_post_path
+                        );
+                        let mut err = lint.build(&msg);
+                        // check for #[must_use = "..."]
+                        if let Some(note) = attr.value_str() {
+                            err.note(&note.as_str());
+                        }
+                        err.emit();
+                    });
+                    return true;
+                }
+            }
+            false
+        }
+    }
+}
+
+declare_lint! {
+    pub PATH_STATEMENTS,
+    Warn,
+    "path statements with no effect"
+}
+
+declare_lint_pass!(PathStatements => [PATH_STATEMENTS]);
+
+impl<'tcx> LateLintPass<'tcx> for PathStatements {
+    fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
+        if let hir::StmtKind::Semi(expr) = s.kind {
+            if let hir::ExprKind::Path(_) = expr.kind {
+                cx.struct_span_lint(PATH_STATEMENTS, s.span, |lint| {
+                    let ty = cx.typeck_results().expr_ty(expr);
+                    if ty.needs_drop(cx.tcx, cx.param_env) {
+                        let mut lint = lint.build("path statement drops value");
+                        if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span) {
+                            lint.span_suggestion(
+                                s.span,
+                                "use `drop` to clarify the intent",
+                                format!("drop({});", snippet),
+                                Applicability::MachineApplicable,
+                            );
+                        } else {
+                            lint.span_help(s.span, "use `drop` to clarify the intent");
+                        }
+                        lint.emit()
+                    } else {
+                        lint.build("path statement with no effect").emit()
+                    }
+                });
+            }
+        }
+    }
+}
+
+#[derive(Copy, Clone)]
+pub struct UnusedAttributes {
+    builtin_attributes: &'static FxHashMap<Symbol, &'static BuiltinAttribute>,
+}
+
+impl UnusedAttributes {
+    pub fn new() -> Self {
+        UnusedAttributes { builtin_attributes: &*BUILTIN_ATTRIBUTE_MAP }
+    }
+}
+
+impl_lint_pass!(UnusedAttributes => [UNUSED_ATTRIBUTES]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedAttributes {
+    fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
+        debug!("checking attribute: {:?}", attr);
+
+        if attr.is_doc_comment() {
+            return;
+        }
+
+        let attr_info = attr.ident().and_then(|ident| self.builtin_attributes.get(&ident.name));
+
+        if let Some(&&(name, ty, ..)) = attr_info {
+            if let AttributeType::AssumedUsed = ty {
+                debug!("{:?} is AssumedUsed", name);
+                return;
+            }
+        }
+
+        if !cx.sess().is_attr_used(attr) {
+            debug!("emitting warning for: {:?}", attr);
+            cx.struct_span_lint(UNUSED_ATTRIBUTES, attr.span, |lint| {
+                lint.build("unused attribute").emit()
+            });
+            // Is it a builtin attribute that must be used at the crate level?
+            if attr_info.map_or(false, |(_, ty, ..)| ty == &AttributeType::CrateLevel) {
+                cx.struct_span_lint(UNUSED_ATTRIBUTES, attr.span, |lint| {
+                    let msg = match attr.style {
+                        ast::AttrStyle::Outer => {
+                            "crate-level attribute should be an inner attribute: add an exclamation \
+                             mark: `#![foo]`"
+                        }
+                        ast::AttrStyle::Inner => "crate-level attribute should be in the root module",
+                    };
+                    lint.build(msg).emit()
+                });
+            }
+        } else {
+            debug!("Attr was used: {:?}", attr);
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum UnusedDelimsCtx {
+    FunctionArg,
+    MethodArg,
+    AssignedValue,
+    IfCond,
+    WhileCond,
+    ForIterExpr,
+    MatchScrutineeExpr,
+    ReturnValue,
+    BlockRetValue,
+    LetScrutineeExpr,
+    ArrayLenExpr,
+    AnonConst,
+}
+
+impl From<UnusedDelimsCtx> for &'static str {
+    fn from(ctx: UnusedDelimsCtx) -> &'static str {
+        match ctx {
+            UnusedDelimsCtx::FunctionArg => "function argument",
+            UnusedDelimsCtx::MethodArg => "method argument",
+            UnusedDelimsCtx::AssignedValue => "assigned value",
+            UnusedDelimsCtx::IfCond => "`if` condition",
+            UnusedDelimsCtx::WhileCond => "`while` condition",
+            UnusedDelimsCtx::ForIterExpr => "`for` iterator expression",
+            UnusedDelimsCtx::MatchScrutineeExpr => "`match` scrutinee expression",
+            UnusedDelimsCtx::ReturnValue => "`return` value",
+            UnusedDelimsCtx::BlockRetValue => "block return value",
+            UnusedDelimsCtx::LetScrutineeExpr => "`let` scrutinee expression",
+            UnusedDelimsCtx::ArrayLenExpr | UnusedDelimsCtx::AnonConst => "const expression",
+        }
+    }
+}
+
+/// Used by both `UnusedParens` and `UnusedBraces` to prevent code duplication.
+trait UnusedDelimLint {
+    const DELIM_STR: &'static str;
+
+    /// Due to `ref` pattern, there can be a difference between using
+    /// `{ expr }` and `expr` in pattern-matching contexts. This means
+    /// that we should only lint `unused_parens` and not `unused_braces`
+    /// in this case.
+    ///
+    /// ```rust
+    /// let mut a = 7;
+    /// let ref b = { a }; // We actually borrow a copy of `a` here.
+    /// a += 1; // By mutating `a` we invalidate any borrows of `a`.
+    /// assert_eq!(b + 1, a); // `b` does not borrow `a`, so we can still use it here.
+    /// ```
+    const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool;
+
+    // this cannot be a constant is it refers to a static.
+    fn lint(&self) -> &'static Lint;
+
+    fn check_unused_delims_expr(
+        &self,
+        cx: &EarlyContext<'_>,
+        value: &ast::Expr,
+        ctx: UnusedDelimsCtx,
+        followed_by_block: bool,
+        left_pos: Option<BytePos>,
+        right_pos: Option<BytePos>,
+    );
+
+    fn is_expr_delims_necessary(inner: &ast::Expr, followed_by_block: bool) -> bool {
+        // Prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }`
+        let lhs_needs_parens = {
+            let mut innermost = inner;
+            loop {
+                if let ExprKind::Binary(_, lhs, _rhs) = &innermost.kind {
+                    innermost = lhs;
+                    if !rustc_ast::util::classify::expr_requires_semi_to_be_stmt(innermost) {
+                        break true;
+                    }
+                } else {
+                    break false;
+                }
+            }
+        };
+
+        lhs_needs_parens
+            || (followed_by_block
+                && match inner.kind {
+                    ExprKind::Ret(_) | ExprKind::Break(..) | ExprKind::Yield(..) => true,
+                    _ => parser::contains_exterior_struct_lit(&inner),
+                })
+    }
+
+    fn emit_unused_delims_expr(
+        &self,
+        cx: &EarlyContext<'_>,
+        value: &ast::Expr,
+        ctx: UnusedDelimsCtx,
+        left_pos: Option<BytePos>,
+        right_pos: Option<BytePos>,
+    ) {
+        let expr_text = if let Ok(snippet) = cx.sess().source_map().span_to_snippet(value.span) {
+            snippet
+        } else {
+            pprust::expr_to_string(value)
+        };
+        let keep_space = (
+            left_pos.map(|s| s >= value.span.lo()).unwrap_or(false),
+            right_pos.map(|s| s <= value.span.hi()).unwrap_or(false),
+        );
+        self.emit_unused_delims(cx, value.span, &expr_text, ctx.into(), keep_space);
+    }
+
+    fn emit_unused_delims(
+        &self,
+        cx: &EarlyContext<'_>,
+        span: Span,
+        pattern: &str,
+        msg: &str,
+        keep_space: (bool, bool),
+    ) {
+        // FIXME(flip1995): Quick and dirty fix for #70814. This should be fixed in rustdoc
+        // properly.
+        if span == DUMMY_SP {
+            return;
+        }
+
+        cx.struct_span_lint(self.lint(), span, |lint| {
+            let span_msg = format!("unnecessary {} around {}", Self::DELIM_STR, msg);
+            let mut err = lint.build(&span_msg);
+            let mut ate_left_paren = false;
+            let mut ate_right_paren = false;
+            let parens_removed = pattern
+                .trim_matches(|c| match c {
+                    '(' | '{' => {
+                        if ate_left_paren {
+                            false
+                        } else {
+                            ate_left_paren = true;
+                            true
+                        }
+                    }
+                    ')' | '}' => {
+                        if ate_right_paren {
+                            false
+                        } else {
+                            ate_right_paren = true;
+                            true
+                        }
+                    }
+                    _ => false,
+                })
+                .trim();
+
+            let replace = {
+                let mut replace = if keep_space.0 {
+                    let mut s = String::from(" ");
+                    s.push_str(parens_removed);
+                    s
+                } else {
+                    String::from(parens_removed)
+                };
+
+                if keep_space.1 {
+                    replace.push(' ');
+                }
+                replace
+            };
+
+            let suggestion = format!("remove these {}", Self::DELIM_STR);
+
+            err.span_suggestion_short(span, &suggestion, replace, Applicability::MachineApplicable);
+            err.emit();
+        });
+    }
+
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+        use rustc_ast::ExprKind::*;
+        let (value, ctx, followed_by_block, left_pos, right_pos) = match e.kind {
+            // Do not lint `unused_braces` in `if let` expressions.
+            If(ref cond, ref block, ..)
+                if !matches!(cond.kind, Let(_, _)) || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
+            {
+                let left = e.span.lo() + rustc_span::BytePos(2);
+                let right = block.span.lo();
+                (cond, UnusedDelimsCtx::IfCond, true, Some(left), Some(right))
+            }
+
+            // Do not lint `unused_braces` in `while let` expressions.
+            While(ref cond, ref block, ..)
+                if !matches!(cond.kind, Let(_, _)) || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
+            {
+                let left = e.span.lo() + rustc_span::BytePos(5);
+                let right = block.span.lo();
+                (cond, UnusedDelimsCtx::WhileCond, true, Some(left), Some(right))
+            }
+
+            ForLoop(_, ref cond, ref block, ..) => {
+                (cond, UnusedDelimsCtx::ForIterExpr, true, None, Some(block.span.lo()))
+            }
+
+            Match(ref head, _) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
+                let left = e.span.lo() + rustc_span::BytePos(5);
+                (head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None)
+            }
+
+            Ret(Some(ref value)) => {
+                let left = e.span.lo() + rustc_span::BytePos(3);
+                (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None)
+            }
+
+            Assign(_, ref value, _) | AssignOp(.., ref value) => {
+                (value, UnusedDelimsCtx::AssignedValue, false, None, None)
+            }
+            // either function/method call, or something this lint doesn't care about
+            ref call_or_other => {
+                let (args_to_check, ctx) = match *call_or_other {
+                    Call(_, ref args) => (&args[..], UnusedDelimsCtx::FunctionArg),
+                    // first "argument" is self (which sometimes needs delims)
+                    MethodCall(_, ref args, _) => (&args[1..], UnusedDelimsCtx::MethodArg),
+                    // actual catch-all arm
+                    _ => {
+                        return;
+                    }
+                };
+                // Don't lint if this is a nested macro expansion: otherwise, the lint could
+                // trigger in situations that macro authors shouldn't have to care about, e.g.,
+                // when a parenthesized token tree matched in one macro expansion is matched as
+                // an expression in another and used as a fn/method argument (Issue #47775)
+                if e.span.ctxt().outer_expn_data().call_site.from_expansion() {
+                    return;
+                }
+                for arg in args_to_check {
+                    self.check_unused_delims_expr(cx, arg, ctx, false, None, None);
+                }
+                return;
+            }
+        };
+        self.check_unused_delims_expr(cx, &value, ctx, followed_by_block, left_pos, right_pos);
+    }
+
+    fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
+        match s.kind {
+            StmtKind::Local(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
+                if let Some(ref value) = local.init {
+                    self.check_unused_delims_expr(
+                        cx,
+                        &value,
+                        UnusedDelimsCtx::AssignedValue,
+                        false,
+                        None,
+                        None,
+                    );
+                }
+            }
+            StmtKind::Expr(ref expr) => {
+                self.check_unused_delims_expr(
+                    cx,
+                    &expr,
+                    UnusedDelimsCtx::BlockRetValue,
+                    false,
+                    None,
+                    None,
+                );
+            }
+            _ => {}
+        }
+    }
+
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        use ast::ItemKind::*;
+
+        if let Const(.., Some(expr)) | Static(.., Some(expr)) = &item.kind {
+            self.check_unused_delims_expr(
+                cx,
+                expr,
+                UnusedDelimsCtx::AssignedValue,
+                false,
+                None,
+                None,
+            );
+        }
+    }
+}
+
+declare_lint! {
+    pub(super) UNUSED_PARENS,
+    Warn,
+    "`if`, `match`, `while` and `return` do not need parentheses"
+}
+
+declare_lint_pass!(UnusedParens => [UNUSED_PARENS]);
+
+impl UnusedDelimLint for UnusedParens {
+    const DELIM_STR: &'static str = "parentheses";
+
+    const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = true;
+
+    fn lint(&self) -> &'static Lint {
+        UNUSED_PARENS
+    }
+
+    fn check_unused_delims_expr(
+        &self,
+        cx: &EarlyContext<'_>,
+        value: &ast::Expr,
+        ctx: UnusedDelimsCtx,
+        followed_by_block: bool,
+        left_pos: Option<BytePos>,
+        right_pos: Option<BytePos>,
+    ) {
+        match value.kind {
+            ast::ExprKind::Paren(ref inner) => {
+                if !Self::is_expr_delims_necessary(inner, followed_by_block)
+                    && value.attrs.is_empty()
+                    && !value.span.from_expansion()
+                {
+                    self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos)
+                }
+            }
+            ast::ExprKind::Let(_, ref expr) => {
+                // FIXME(#60336): Properly handle `let true = (false && true)`
+                // actually needing the parenthesis.
+                self.check_unused_delims_expr(
+                    cx,
+                    expr,
+                    UnusedDelimsCtx::LetScrutineeExpr,
+                    followed_by_block,
+                    None,
+                    None,
+                );
+            }
+            _ => {}
+        }
+    }
+}
+
+impl UnusedParens {
+    fn check_unused_parens_pat(
+        &self,
+        cx: &EarlyContext<'_>,
+        value: &ast::Pat,
+        avoid_or: bool,
+        avoid_mut: bool,
+    ) {
+        use ast::{BindingMode, Mutability, PatKind};
+
+        if let PatKind::Paren(inner) = &value.kind {
+            match inner.kind {
+                // The lint visitor will visit each subpattern of `p`. We do not want to lint
+                // any range pattern no matter where it occurs in the pattern. For something like
+                // `&(a..=b)`, there is a recursive `check_pat` on `a` and `b`, but we will assume
+                // that if there are unnecessary parens they serve a purpose of readability.
+                PatKind::Range(..) => return,
+                // Avoid `p0 | .. | pn` if we should.
+                PatKind::Or(..) if avoid_or => return,
+                // Avoid `mut x` and `mut x @ p` if we should:
+                PatKind::Ident(BindingMode::ByValue(Mutability::Mut), ..) if avoid_mut => return,
+                // Otherwise proceed with linting.
+                _ => {}
+            }
+
+            let pattern_text =
+                if let Ok(snippet) = cx.sess().source_map().span_to_snippet(value.span) {
+                    snippet
+                } else {
+                    pprust::pat_to_string(value)
+                };
+            self.emit_unused_delims(cx, value.span, &pattern_text, "pattern", (false, false));
+        }
+    }
+}
+
+impl EarlyLintPass for UnusedParens {
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+        if let ExprKind::Let(ref pat, ..) | ExprKind::ForLoop(ref pat, ..) = e.kind {
+            self.check_unused_parens_pat(cx, pat, false, false);
+        }
+
+        <Self as UnusedDelimLint>::check_expr(self, cx, e)
+    }
+
+    fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &ast::Pat) {
+        use ast::{Mutability, PatKind::*};
+        match &p.kind {
+            // Do not lint on `(..)` as that will result in the other arms being useless.
+            Paren(_)
+            // The other cases do not contain sub-patterns.
+            | Wild | Rest | Lit(..) | MacCall(..) | Range(..) | Ident(.., None) | Path(..) => {},
+            // These are list-like patterns; parens can always be removed.
+            TupleStruct(_, ps) | Tuple(ps) | Slice(ps) | Or(ps) => for p in ps {
+                self.check_unused_parens_pat(cx, p, false, false);
+            },
+            Struct(_, fps, _) => for f in fps {
+                self.check_unused_parens_pat(cx, &f.pat, false, false);
+            },
+            // Avoid linting on `i @ (p0 | .. | pn)` and `box (p0 | .. | pn)`, #64106.
+            Ident(.., Some(p)) | Box(p) => self.check_unused_parens_pat(cx, p, true, false),
+            // Avoid linting on `&(mut x)` as `&mut x` has a different meaning, #55342.
+            // Also avoid linting on `& mut? (p0 | .. | pn)`, #64106.
+            Ref(p, m) => self.check_unused_parens_pat(cx, p, true, *m == Mutability::Not),
+        }
+    }
+
+    fn check_anon_const(&mut self, cx: &EarlyContext<'_>, c: &ast::AnonConst) {
+        self.check_unused_delims_expr(cx, &c.value, UnusedDelimsCtx::AnonConst, false, None, None);
+    }
+
+    fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
+        if let StmtKind::Local(ref local) = s.kind {
+            self.check_unused_parens_pat(cx, &local.pat, false, false);
+        }
+
+        <Self as UnusedDelimLint>::check_stmt(self, cx, s)
+    }
+
+    fn check_param(&mut self, cx: &EarlyContext<'_>, param: &ast::Param) {
+        self.check_unused_parens_pat(cx, &param.pat, true, false);
+    }
+
+    fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
+        self.check_unused_parens_pat(cx, &arm.pat, false, false);
+    }
+
+    fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
+        if let &ast::TyKind::Paren(ref r) = &ty.kind {
+            match &r.kind {
+                &ast::TyKind::TraitObject(..) => {}
+                &ast::TyKind::ImplTrait(_, ref bounds) if bounds.len() > 1 => {}
+                &ast::TyKind::Array(_, ref len) => {
+                    self.check_unused_delims_expr(
+                        cx,
+                        &len.value,
+                        UnusedDelimsCtx::ArrayLenExpr,
+                        false,
+                        None,
+                        None,
+                    );
+                }
+                _ => {
+                    let pattern_text =
+                        if let Ok(snippet) = cx.sess().source_map().span_to_snippet(ty.span) {
+                            snippet
+                        } else {
+                            pprust::ty_to_string(ty)
+                        };
+
+                    self.emit_unused_delims(cx, ty.span, &pattern_text, "type", (false, false));
+                }
+            }
+        }
+    }
+
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        <Self as UnusedDelimLint>::check_item(self, cx, item)
+    }
+}
+
+declare_lint! {
+    pub(super) UNUSED_BRACES,
+    Warn,
+    "unnecessary braces around an expression"
+}
+
+declare_lint_pass!(UnusedBraces => [UNUSED_BRACES]);
+
+impl UnusedDelimLint for UnusedBraces {
+    const DELIM_STR: &'static str = "braces";
+
+    const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = false;
+
+    fn lint(&self) -> &'static Lint {
+        UNUSED_BRACES
+    }
+
+    fn check_unused_delims_expr(
+        &self,
+        cx: &EarlyContext<'_>,
+        value: &ast::Expr,
+        ctx: UnusedDelimsCtx,
+        followed_by_block: bool,
+        left_pos: Option<BytePos>,
+        right_pos: Option<BytePos>,
+    ) {
+        match value.kind {
+            ast::ExprKind::Block(ref inner, None)
+                if inner.rules == ast::BlockCheckMode::Default =>
+            {
+                // emit a warning under the following conditions:
+                //
+                // - the block does not have a label
+                // - the block is not `unsafe`
+                // - the block contains exactly one expression (do not lint `{ expr; }`)
+                // - `followed_by_block` is true and the internal expr may contain a `{`
+                // - the block is not multiline (do not lint multiline match arms)
+                //      ```
+                //      match expr {
+                //          Pattern => {
+                //              somewhat_long_expression
+                //          }
+                //          // ...
+                //      }
+                //      ```
+                // - the block has no attribute and was not created inside a macro
+                // - if the block is an `anon_const`, the inner expr must be a literal
+                //      (do not lint `struct A<const N: usize>; let _: A<{ 2 + 3 }>;`)
+                //
+                // FIXME(const_generics): handle paths when #67075 is fixed.
+                if let [stmt] = inner.stmts.as_slice() {
+                    if let ast::StmtKind::Expr(ref expr) = stmt.kind {
+                        if !Self::is_expr_delims_necessary(expr, followed_by_block)
+                            && (ctx != UnusedDelimsCtx::AnonConst
+                                || matches!(expr.kind, ast::ExprKind::Lit(_)))
+                            // array length expressions are checked during `check_anon_const` and `check_ty`,
+                            // once as `ArrayLenExpr` and once as `AnonConst`.
+                            //
+                            // As we do not want to lint this twice, we do not emit an error for
+                            // `ArrayLenExpr` if `AnonConst` would do the same.
+                            && (ctx != UnusedDelimsCtx::ArrayLenExpr
+                                || !matches!(expr.kind, ast::ExprKind::Lit(_)))
+                            && !cx.sess().source_map().is_multiline(value.span)
+                            && value.attrs.is_empty()
+                            && !value.span.from_expansion()
+                        {
+                            self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos)
+                        }
+                    }
+                }
+            }
+            ast::ExprKind::Let(_, ref expr) => {
+                // FIXME(#60336): Properly handle `let true = (false && true)`
+                // actually needing the parenthesis.
+                self.check_unused_delims_expr(
+                    cx,
+                    expr,
+                    UnusedDelimsCtx::LetScrutineeExpr,
+                    followed_by_block,
+                    None,
+                    None,
+                );
+            }
+            _ => {}
+        }
+    }
+}
+
+impl EarlyLintPass for UnusedBraces {
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+        <Self as UnusedDelimLint>::check_expr(self, cx, e)
+    }
+
+    fn check_anon_const(&mut self, cx: &EarlyContext<'_>, c: &ast::AnonConst) {
+        self.check_unused_delims_expr(cx, &c.value, UnusedDelimsCtx::AnonConst, false, None, None);
+    }
+
+    fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
+        <Self as UnusedDelimLint>::check_stmt(self, cx, s)
+    }
+
+    fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
+        if let &ast::TyKind::Paren(ref r) = &ty.kind {
+            if let ast::TyKind::Array(_, ref len) = r.kind {
+                self.check_unused_delims_expr(
+                    cx,
+                    &len.value,
+                    UnusedDelimsCtx::ArrayLenExpr,
+                    false,
+                    None,
+                    None,
+                );
+            }
+        }
+    }
+
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        <Self as UnusedDelimLint>::check_item(self, cx, item)
+    }
+}
+
+declare_lint! {
+    UNUSED_IMPORT_BRACES,
+    Allow,
+    "unnecessary braces around an imported item"
+}
+
+declare_lint_pass!(UnusedImportBraces => [UNUSED_IMPORT_BRACES]);
+
+impl UnusedImportBraces {
+    fn check_use_tree(&self, cx: &EarlyContext<'_>, use_tree: &ast::UseTree, item: &ast::Item) {
+        if let ast::UseTreeKind::Nested(ref items) = use_tree.kind {
+            // Recursively check nested UseTrees
+            for &(ref tree, _) in items {
+                self.check_use_tree(cx, tree, item);
+            }
+
+            // Trigger the lint only if there is one nested item
+            if items.len() != 1 {
+                return;
+            }
+
+            // Trigger the lint if the nested item is a non-self single item
+            let node_name = match items[0].0.kind {
+                ast::UseTreeKind::Simple(rename, ..) => {
+                    let orig_ident = items[0].0.prefix.segments.last().unwrap().ident;
+                    if orig_ident.name == kw::SelfLower {
+                        return;
+                    }
+                    rename.unwrap_or(orig_ident).name
+                }
+                ast::UseTreeKind::Glob => Symbol::intern("*"),
+                ast::UseTreeKind::Nested(_) => return,
+            };
+
+            cx.struct_span_lint(UNUSED_IMPORT_BRACES, item.span, |lint| {
+                lint.build(&format!("braces around {} is unnecessary", node_name)).emit()
+            });
+        }
+    }
+}
+
+impl EarlyLintPass for UnusedImportBraces {
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+        if let ast::ItemKind::Use(ref use_tree) = item.kind {
+            self.check_use_tree(cx, use_tree, item);
+        }
+    }
+}
+
+declare_lint! {
+    pub(super) UNUSED_ALLOCATION,
+    Warn,
+    "detects unnecessary allocations that can be eliminated"
+}
+
+declare_lint_pass!(UnusedAllocation => [UNUSED_ALLOCATION]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedAllocation {
+    fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) {
+        match e.kind {
+            hir::ExprKind::Box(_) => {}
+            _ => return,
+        }
+
+        for adj in cx.typeck_results().expr_adjustments(e) {
+            if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind {
+                cx.struct_span_lint(UNUSED_ALLOCATION, e.span, |lint| {
+                    let msg = match m {
+                        adjustment::AutoBorrowMutability::Not => {
+                            "unnecessary allocation, use `&` instead"
+                        }
+                        adjustment::AutoBorrowMutability::Mut { .. } => {
+                            "unnecessary allocation, use `&mut` instead"
+                        }
+                    };
+                    lint.build(msg).emit()
+                });
+            }
+        }
+    }
+}