diff --git a/src/librustc/infer/error_reporting/mod.rs b/src/librustc/infer/error_reporting/mod.rs
index 57a52a991edc8..01390f2c7195b 100644
--- a/src/librustc/infer/error_reporting/mod.rs
+++ b/src/librustc/infer/error_reporting/mod.rs
@@ -1701,7 +1701,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
         sub: Region<'tcx>,
     ) {
         self.construct_generic_bound_failure(region_scope_tree, span, origin, bound_kind, sub)
-            .emit()
+            .emit();
     }
 
     pub fn construct_generic_bound_failure(
diff --git a/src/librustc/lint.rs b/src/librustc/lint.rs
index 8c18b1368a9d8..004835b230ab4 100644
--- a/src/librustc/lint.rs
+++ b/src/librustc/lint.rs
@@ -174,132 +174,164 @@ impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
     }
 }
 
-pub fn struct_lint_level<'a>(
-    sess: &'a Session,
+pub struct LintDiagnosticBuilder<'a>(DiagnosticBuilder<'a>);
+
+impl<'a> LintDiagnosticBuilder<'a> {
+    /// Return the inner DiagnosticBuilder, first setting the primary message to `msg`.
+    pub fn build(mut self, msg: &str) -> DiagnosticBuilder<'a> {
+        self.0.set_primary_message(msg);
+        self.0
+    }
+
+    /// Create a LintDiagnosticBuilder from some existing DiagnosticBuilder.
+    pub fn new(err: DiagnosticBuilder<'a>) -> LintDiagnosticBuilder<'a> {
+        LintDiagnosticBuilder(err)
+    }
+}
+
+pub fn struct_lint_level<'s, 'd>(
+    sess: &'s Session,
     lint: &'static Lint,
     level: Level,
     src: LintSource,
     span: Option<MultiSpan>,
-    msg: &str,
-) -> DiagnosticBuilder<'a> {
-    let mut err = match (level, span) {
-        (Level::Allow, _) => return sess.diagnostic().struct_dummy(),
-        (Level::Warn, Some(span)) => sess.struct_span_warn(span, msg),
-        (Level::Warn, None) => sess.struct_warn(msg),
-        (Level::Deny, Some(span)) | (Level::Forbid, Some(span)) => sess.struct_span_err(span, msg),
-        (Level::Deny, None) | (Level::Forbid, None) => sess.struct_err(msg),
-    };
+    decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>) + 'd,
+) {
+    // Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to
+    // the "real" work.
+    fn struct_lint_level_impl(
+        sess: &'s Session,
+        lint: &'static Lint,
+        level: Level,
+        src: LintSource,
+        span: Option<MultiSpan>,
+        decorate: Box<dyn for<'b> FnOnce(LintDiagnosticBuilder<'b>) + 'd>,
+    ) {
+        let mut err = match (level, span) {
+            (Level::Allow, _) => {
+                return;
+            }
+            (Level::Warn, Some(span)) => sess.struct_span_warn(span, ""),
+            (Level::Warn, None) => sess.struct_warn(""),
+            (Level::Deny, Some(span)) | (Level::Forbid, Some(span)) => {
+                sess.struct_span_err(span, "")
+            }
+            (Level::Deny, None) | (Level::Forbid, None) => sess.struct_err(""),
+        };
 
-    // Check for future incompatibility lints and issue a stronger warning.
-    let lint_id = LintId::of(lint);
-    let future_incompatible = lint.future_incompatible;
-
-    // If this code originates in a foreign macro, aka something that this crate
-    // did not itself author, then it's likely that there's nothing this crate
-    // can do about it. We probably want to skip the lint entirely.
-    if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) {
-        // Any suggestions made here are likely to be incorrect, so anything we
-        // emit shouldn't be automatically fixed by rustfix.
-        err.allow_suggestions(false);
-
-        // If this is a future incompatible lint it'll become a hard error, so
-        // we have to emit *something*. Also allow lints to whitelist themselves
-        // on a case-by-case basis for emission in a foreign macro.
-        if future_incompatible.is_none() && !lint.report_in_external_macro {
-            err.cancel();
-            // Don't continue further, since we don't want to have
-            // `diag_span_note_once` called for a diagnostic that isn't emitted.
-            return err;
+        // Check for future incompatibility lints and issue a stronger warning.
+        let lint_id = LintId::of(lint);
+        let future_incompatible = lint.future_incompatible;
+
+        // If this code originates in a foreign macro, aka something that this crate
+        // did not itself author, then it's likely that there's nothing this crate
+        // can do about it. We probably want to skip the lint entirely.
+        if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) {
+            // Any suggestions made here are likely to be incorrect, so anything we
+            // emit shouldn't be automatically fixed by rustfix.
+            err.allow_suggestions(false);
+
+            // If this is a future incompatible lint it'll become a hard error, so
+            // we have to emit *something*. Also allow lints to whitelist themselves
+            // on a case-by-case basis for emission in a foreign macro.
+            if future_incompatible.is_none() && !lint.report_in_external_macro {
+                err.cancel();
+                // Don't continue further, since we don't want to have
+                // `diag_span_note_once` called for a diagnostic that isn't emitted.
+                return;
+            }
         }
-    }
 
-    let name = lint.name_lower();
-    match src {
-        LintSource::Default => {
-            sess.diag_note_once(
-                &mut err,
-                DiagnosticMessageId::from(lint),
-                &format!("`#[{}({})]` on by default", level.as_str(), name),
-            );
-        }
-        LintSource::CommandLine(lint_flag_val) => {
-            let flag = match level {
-                Level::Warn => "-W",
-                Level::Deny => "-D",
-                Level::Forbid => "-F",
-                Level::Allow => panic!(),
-            };
-            let hyphen_case_lint_name = name.replace("_", "-");
-            if lint_flag_val.as_str() == name {
+        let name = lint.name_lower();
+        match src {
+            LintSource::Default => {
                 sess.diag_note_once(
                     &mut err,
                     DiagnosticMessageId::from(lint),
-                    &format!(
-                        "requested on the command line with `{} {}`",
-                        flag, hyphen_case_lint_name
-                    ),
-                );
-            } else {
-                let hyphen_case_flag_val = lint_flag_val.as_str().replace("_", "-");
-                sess.diag_note_once(
-                    &mut err,
-                    DiagnosticMessageId::from(lint),
-                    &format!(
-                        "`{} {}` implied by `{} {}`",
-                        flag, hyphen_case_lint_name, flag, hyphen_case_flag_val
-                    ),
+                    &format!("`#[{}({})]` on by default", level.as_str(), name),
                 );
             }
-        }
-        LintSource::Node(lint_attr_name, src, reason) => {
-            if let Some(rationale) = reason {
-                err.note(&rationale.as_str());
+            LintSource::CommandLine(lint_flag_val) => {
+                let flag = match level {
+                    Level::Warn => "-W",
+                    Level::Deny => "-D",
+                    Level::Forbid => "-F",
+                    Level::Allow => panic!(),
+                };
+                let hyphen_case_lint_name = name.replace("_", "-");
+                if lint_flag_val.as_str() == name {
+                    sess.diag_note_once(
+                        &mut err,
+                        DiagnosticMessageId::from(lint),
+                        &format!(
+                            "requested on the command line with `{} {}`",
+                            flag, hyphen_case_lint_name
+                        ),
+                    );
+                } else {
+                    let hyphen_case_flag_val = lint_flag_val.as_str().replace("_", "-");
+                    sess.diag_note_once(
+                        &mut err,
+                        DiagnosticMessageId::from(lint),
+                        &format!(
+                            "`{} {}` implied by `{} {}`",
+                            flag, hyphen_case_lint_name, flag, hyphen_case_flag_val
+                        ),
+                    );
+                }
             }
-            sess.diag_span_note_once(
-                &mut err,
-                DiagnosticMessageId::from(lint),
-                src,
-                "the lint level is defined here",
-            );
-            if lint_attr_name.as_str() != name {
-                let level_str = level.as_str();
-                sess.diag_note_once(
+            LintSource::Node(lint_attr_name, src, reason) => {
+                if let Some(rationale) = reason {
+                    err.note(&rationale.as_str());
+                }
+                sess.diag_span_note_once(
                     &mut err,
                     DiagnosticMessageId::from(lint),
-                    &format!(
-                        "`#[{}({})]` implied by `#[{}({})]`",
-                        level_str, name, level_str, lint_attr_name
-                    ),
+                    src,
+                    "the lint level is defined here",
                 );
+                if lint_attr_name.as_str() != name {
+                    let level_str = level.as_str();
+                    sess.diag_note_once(
+                        &mut err,
+                        DiagnosticMessageId::from(lint),
+                        &format!(
+                            "`#[{}({})]` implied by `#[{}({})]`",
+                            level_str, name, level_str, lint_attr_name
+                        ),
+                    );
+                }
             }
         }
-    }
 
-    err.code(DiagnosticId::Lint(name));
-
-    if let Some(future_incompatible) = future_incompatible {
-        const STANDARD_MESSAGE: &str = "this was previously accepted by the compiler but is being phased out; \
-             it will become a hard error";
-
-        let explanation = if lint_id == LintId::of(builtin::UNSTABLE_NAME_COLLISIONS) {
-            "once this method is added to the standard library, \
-             the ambiguity may cause an error or change in behavior!"
-                .to_owned()
-        } else if lint_id == LintId::of(builtin::MUTABLE_BORROW_RESERVATION_CONFLICT) {
-            "this borrowing pattern was not meant to be accepted, \
-             and may become a hard error in the future"
-                .to_owned()
-        } else if let Some(edition) = future_incompatible.edition {
-            format!("{} in the {} edition!", STANDARD_MESSAGE, edition)
-        } else {
-            format!("{} in a future release!", STANDARD_MESSAGE)
-        };
-        let citation = format!("for more information, see {}", future_incompatible.reference);
-        err.warn(&explanation);
-        err.note(&citation);
-    }
+        err.code(DiagnosticId::Lint(name));
+
+        if let Some(future_incompatible) = future_incompatible {
+            const STANDARD_MESSAGE: &str = "this was previously accepted by the compiler but is being phased out; \
+                 it will become a hard error";
+
+            let explanation = if lint_id == LintId::of(builtin::UNSTABLE_NAME_COLLISIONS) {
+                "once this method is added to the standard library, \
+                 the ambiguity may cause an error or change in behavior!"
+                    .to_owned()
+            } else if lint_id == LintId::of(builtin::MUTABLE_BORROW_RESERVATION_CONFLICT) {
+                "this borrowing pattern was not meant to be accepted, \
+                 and may become a hard error in the future"
+                    .to_owned()
+            } else if let Some(edition) = future_incompatible.edition {
+                format!("{} in the {} edition!", STANDARD_MESSAGE, edition)
+            } else {
+                format!("{} in a future release!", STANDARD_MESSAGE)
+            };
+            let citation = format!("for more information, see {}", future_incompatible.reference);
+            err.warn(&explanation);
+            err.note(&citation);
+        }
 
-    return err;
+        // Finally, run `decorate`. This function is also responsible for emitting the diagnostic.
+        decorate(LintDiagnosticBuilder::new(err));
+    }
+    struct_lint_level_impl(sess, lint, level, src, span, Box::new(decorate))
 }
 
 /// Returns whether `span` originates in a foreign crate's external macro.
diff --git a/src/librustc/middle/stability.rs b/src/librustc/middle/stability.rs
index 7cbe77b9e82f0..dd30fb23c2e76 100644
--- a/src/librustc/middle/stability.rs
+++ b/src/librustc/middle/stability.rs
@@ -222,11 +222,13 @@ fn late_report_deprecation(
         return;
     }
 
-    let mut diag = tcx.struct_span_lint_hir(lint, hir_id, span, message);
-    if let hir::Node::Expr(_) = tcx.hir().get(hir_id) {
-        deprecation_suggestion(&mut diag, suggestion, span);
-    }
-    diag.emit();
+    tcx.struct_span_lint_hir(lint, hir_id, span, |lint| {
+        let mut diag = lint.build(message);
+        if let hir::Node::Expr(_) = tcx.hir().get(hir_id) {
+            deprecation_suggestion(&mut diag, suggestion, span);
+        }
+        diag.emit()
+    });
     if hir_id == hir::DUMMY_HIR_ID {
         span_bug!(span, "emitted a {} lint with dummy HIR id: {:?}", lint.name, def_id);
     }
@@ -387,8 +389,11 @@ impl<'tcx> TyCtxt<'tcx> {
     /// Additionally, this function will also check if the item is deprecated. If so, and `id` is
     /// not `None`, a deprecated lint attached to `id` will be emitted.
     pub fn check_stability(self, def_id: DefId, id: Option<HirId>, span: Span) {
-        let soft_handler =
-            |lint, span, msg: &_| self.lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, msg);
+        let soft_handler = |lint, span, msg: &_| {
+            self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
+                lint.build(msg).emit()
+            })
+        };
         match self.eval_stability(def_id, id, span) {
             EvalResult::Allow => {}
             EvalResult::Deny { feature, reason, issue, is_soft } => {
diff --git a/src/librustc/mir/interpret/error.rs b/src/librustc/mir/interpret/error.rs
index 349dbd74ad1c9..df3971a5ac3f1 100644
--- a/src/librustc/mir/interpret/error.rs
+++ b/src/librustc/mir/interpret/error.rs
@@ -83,18 +83,15 @@ impl<'tcx> ConstEvalErr<'tcx> {
         &self,
         tcx: TyCtxtAt<'tcx>,
         message: &str,
-    ) -> Result<DiagnosticBuilder<'tcx>, ErrorHandled> {
-        self.struct_generic(tcx, message, None)
+        emit: impl FnOnce(DiagnosticBuilder<'_>),
+    ) -> Result<(), ErrorHandled> {
+        self.struct_generic(tcx, message, emit, None)
     }
 
     pub fn report_as_error(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled {
-        let err = self.struct_error(tcx, message);
-        match err {
-            Ok(mut err) => {
-                err.emit();
-                ErrorHandled::Reported
-            }
-            Err(err) => err,
+        match self.struct_error(tcx, message, |mut e| e.emit()) {
+            Ok(_) => ErrorHandled::Reported,
+            Err(x) => x,
         }
     }
 
@@ -105,9 +102,11 @@ impl<'tcx> ConstEvalErr<'tcx> {
         lint_root: hir::HirId,
         span: Option<Span>,
     ) -> ErrorHandled {
-        let lint = self.struct_generic(tcx, message, Some(lint_root));
-        match lint {
-            Ok(mut lint) => {
+        match self.struct_generic(
+            tcx,
+            message,
+            |mut lint: DiagnosticBuilder<'_>| {
+                // Apply the span.
                 if let Some(span) = span {
                     let primary_spans = lint.span.primary_spans().to_vec();
                     // point at the actual error as the primary span
@@ -121,18 +120,25 @@ impl<'tcx> ConstEvalErr<'tcx> {
                     }
                 }
                 lint.emit();
-                ErrorHandled::Reported
-            }
+            },
+            Some(lint_root),
+        ) {
+            Ok(_) => ErrorHandled::Reported,
             Err(err) => err,
         }
     }
 
+    /// Sets the message passed in via `message`, then adds the span labels for you, before applying
+    /// further modifications in `emit`. It's up to you to call emit(), stash(..), etc. within the
+    /// `emit` method. If you don't need to do any additional processing, just use
+    /// struct_generic.
     fn struct_generic(
         &self,
         tcx: TyCtxtAt<'tcx>,
         message: &str,
+        emit: impl FnOnce(DiagnosticBuilder<'_>),
         lint_root: Option<hir::HirId>,
-    ) -> Result<DiagnosticBuilder<'tcx>, ErrorHandled> {
+    ) -> Result<(), ErrorHandled> {
         let must_error = match self.error {
             InterpError::MachineStop(_) => bug!("CTFE does not stop"),
             err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
@@ -143,7 +149,22 @@ impl<'tcx> ConstEvalErr<'tcx> {
             _ => false,
         };
         trace!("reporting const eval failure at {:?}", self.span);
-        let mut err = if let (Some(lint_root), false) = (lint_root, must_error) {
+
+        let add_span_labels = |err: &mut DiagnosticBuilder<'_>| {
+            if !must_error {
+                err.span_label(self.span, self.error.to_string());
+            }
+            // Skip the last, which is just the environment of the constant.  The stacktrace
+            // is sometimes empty because we create "fake" eval contexts in CTFE to do work
+            // on constant values.
+            if self.stacktrace.len() > 0 {
+                for frame_info in &self.stacktrace[..self.stacktrace.len() - 1] {
+                    err.span_label(frame_info.call_site, frame_info.to_string());
+                }
+            }
+        };
+
+        if let (Some(lint_root), false) = (lint_root, must_error) {
             let hir_id = self
                 .stacktrace
                 .iter()
@@ -155,25 +176,22 @@ impl<'tcx> ConstEvalErr<'tcx> {
                 rustc_session::lint::builtin::CONST_ERR,
                 hir_id,
                 tcx.span,
-                message,
-            )
-        } else if must_error {
-            struct_error(tcx, &self.error.to_string())
+                |lint| {
+                    let mut err = lint.build(message);
+                    add_span_labels(&mut err);
+                    emit(err);
+                },
+            );
         } else {
-            struct_error(tcx, message)
+            let mut err = if must_error {
+                struct_error(tcx, &self.error.to_string())
+            } else {
+                struct_error(tcx, message)
+            };
+            add_span_labels(&mut err);
+            emit(err);
         };
-        if !must_error {
-            err.span_label(self.span, self.error.to_string());
-        }
-        // Skip the last, which is just the environment of the constant.  The stacktrace
-        // is sometimes empty because we create "fake" eval contexts in CTFE to do work
-        // on constant values.
-        if self.stacktrace.len() > 0 {
-            for frame_info in &self.stacktrace[..self.stacktrace.len() - 1] {
-                err.span_label(frame_info.call_site, frame_info.to_string());
-            }
-        }
-        Ok(err)
+        Ok(())
     }
 }
 
diff --git a/src/librustc/traits/object_safety.rs b/src/librustc/traits/object_safety.rs
index 3c886ce7f3eb7..4c5cd866b4a01 100644
--- a/src/librustc/traits/object_safety.rs
+++ b/src/librustc/traits/object_safety.rs
@@ -227,37 +227,47 @@ fn object_safety_violations_for_trait(
             {
                 // Using `CRATE_NODE_ID` is wrong, but it's hard to get a more precise id.
                 // It's also hard to get a use site span, so we use the method definition span.
-                let mut err = tcx.struct_span_lint_hir(
+                tcx.struct_span_lint_hir(
                     WHERE_CLAUSES_OBJECT_SAFETY,
                     hir::CRATE_HIR_ID,
                     *span,
-                    &format!(
-                        "the trait `{}` cannot be made into an object",
-                        tcx.def_path_str(trait_def_id)
-                    ),
+                    |lint| {
+                        let mut err = lint.build(&format!(
+                            "the trait `{}` cannot be made into an object",
+                            tcx.def_path_str(trait_def_id)
+                        ));
+                        let node = tcx.hir().get_if_local(trait_def_id);
+                        let msg = if let Some(hir::Node::Item(item)) = node {
+                            err.span_label(
+                                item.ident.span,
+                                "this trait cannot be made into an object...",
+                            );
+                            format!("...because {}", violation.error_msg())
+                        } else {
+                            format!(
+                                "the trait cannot be made into an object because {}",
+                                violation.error_msg()
+                            )
+                        };
+                        err.span_label(*span, &msg);
+                        match (node, violation.solution()) {
+                            (Some(_), Some((note, None))) => {
+                                err.help(&note);
+                            }
+                            (Some(_), Some((note, Some((sugg, span))))) => {
+                                err.span_suggestion(
+                                    span,
+                                    &note,
+                                    sugg,
+                                    Applicability::MachineApplicable,
+                                );
+                            }
+                            // Only provide the help if its a local trait, otherwise it's not actionable.
+                            _ => {}
+                        }
+                        err.emit();
+                    },
                 );
-                let node = tcx.hir().get_if_local(trait_def_id);
-                let msg = if let Some(hir::Node::Item(item)) = node {
-                    err.span_label(item.ident.span, "this trait cannot be made into an object...");
-                    format!("...because {}", violation.error_msg())
-                } else {
-                    format!(
-                        "the trait cannot be made into an object because {}",
-                        violation.error_msg()
-                    )
-                };
-                err.span_label(*span, &msg);
-                match (node, violation.solution()) {
-                    (Some(_), Some((note, None))) => {
-                        err.help(&note);
-                    }
-                    (Some(_), Some((note, Some((sugg, span))))) => {
-                        err.span_suggestion(span, &note, sugg, Applicability::MachineApplicable);
-                    }
-                    // Only provide the help if its a local trait, otherwise it's not actionable.
-                    _ => {}
-                }
-                err.emit();
                 false
             } else {
                 true
diff --git a/src/librustc/traits/specialize/mod.rs b/src/librustc/traits/specialize/mod.rs
index 071b5277dd95d..7c93a35158b08 100644
--- a/src/librustc/traits/specialize/mod.rs
+++ b/src/librustc/traits/specialize/mod.rs
@@ -16,6 +16,7 @@ use crate::traits::select::IntercrateAmbiguityCause;
 use crate::traits::{self, coherence, FutureCompatOverlapErrorKind, ObligationCause, TraitEngine};
 use crate::ty::subst::{InternalSubsts, Subst, SubstsRef};
 use crate::ty::{self, TyCtxt, TypeFoldable};
+use rustc::lint::LintDiagnosticBuilder;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::struct_span_err;
 use rustc_hir::def_id::DefId;
@@ -317,22 +318,70 @@ pub(super) fn specialization_graph_provider(
             };
 
             if let Some(overlap) = overlap {
-                let msg = format!(
-                    "conflicting implementations of trait `{}`{}:{}",
-                    overlap.trait_desc,
-                    overlap
-                        .self_desc
-                        .clone()
-                        .map_or(String::new(), |ty| { format!(" for type `{}`", ty) }),
-                    match used_to_be_allowed {
-                        Some(FutureCompatOverlapErrorKind::Issue33140) => " (E0119)",
-                        _ => "",
-                    }
-                );
                 let impl_span =
                     tcx.sess.source_map().def_span(tcx.span_of_impl(impl_def_id).unwrap());
-                let mut err = match used_to_be_allowed {
-                    None => struct_span_err!(tcx.sess, impl_span, E0119, "{}", msg),
+
+                // Work to be done after we've built the DiagnosticBuilder. We have to define it
+                // now because the struct_lint methods don't return back the DiagnosticBuilder
+                // that's passed in.
+                let decorate = |err: LintDiagnosticBuilder<'_>| {
+                    let msg = format!(
+                        "conflicting implementations of trait `{}`{}:{}",
+                        overlap.trait_desc,
+                        overlap
+                            .self_desc
+                            .clone()
+                            .map_or(String::new(), |ty| { format!(" for type `{}`", ty) }),
+                        match used_to_be_allowed {
+                            Some(FutureCompatOverlapErrorKind::Issue33140) => " (E0119)",
+                            _ => "",
+                        }
+                    );
+                    let mut err = err.build(&msg);
+                    match tcx.span_of_impl(overlap.with_impl) {
+                        Ok(span) => {
+                            err.span_label(
+                                tcx.sess.source_map().def_span(span),
+                                "first implementation here".to_string(),
+                            );
+
+                            err.span_label(
+                                impl_span,
+                                format!(
+                                    "conflicting implementation{}",
+                                    overlap
+                                        .self_desc
+                                        .map_or(String::new(), |ty| format!(" for `{}`", ty))
+                                ),
+                            );
+                        }
+                        Err(cname) => {
+                            let msg = match to_pretty_impl_header(tcx, overlap.with_impl) {
+                                Some(s) => format!(
+                                    "conflicting implementation in crate `{}`:\n- {}",
+                                    cname, s
+                                ),
+                                None => format!("conflicting implementation in crate `{}`", cname),
+                            };
+                            err.note(&msg);
+                        }
+                    }
+
+                    for cause in &overlap.intercrate_ambiguity_causes {
+                        cause.add_intercrate_ambiguity_hint(&mut err);
+                    }
+
+                    if overlap.involves_placeholder {
+                        coherence::add_placeholder_note(&mut err);
+                    }
+                    err.emit()
+                };
+
+                match used_to_be_allowed {
+                    None => {
+                        let err = struct_span_err!(tcx.sess, impl_span, E0119, "");
+                        decorate(LintDiagnosticBuilder::new(err));
+                    }
                     Some(kind) => {
                         let lint = match kind {
                             FutureCompatOverlapErrorKind::Issue33140 => {
@@ -344,47 +393,10 @@ pub(super) fn specialization_graph_provider(
                             lint,
                             tcx.hir().as_local_hir_id(impl_def_id).unwrap(),
                             impl_span,
-                            &msg,
+                            decorate,
                         )
                     }
                 };
-
-                match tcx.span_of_impl(overlap.with_impl) {
-                    Ok(span) => {
-                        err.span_label(
-                            tcx.sess.source_map().def_span(span),
-                            "first implementation here".to_string(),
-                        );
-                        err.span_label(
-                            impl_span,
-                            format!(
-                                "conflicting implementation{}",
-                                overlap
-                                    .self_desc
-                                    .map_or(String::new(), |ty| format!(" for `{}`", ty))
-                            ),
-                        );
-                    }
-                    Err(cname) => {
-                        let msg = match to_pretty_impl_header(tcx, overlap.with_impl) {
-                            Some(s) => {
-                                format!("conflicting implementation in crate `{}`:\n- {}", cname, s)
-                            }
-                            None => format!("conflicting implementation in crate `{}`", cname),
-                        };
-                        err.note(&msg);
-                    }
-                }
-
-                for cause in &overlap.intercrate_ambiguity_causes {
-                    cause.add_intercrate_ambiguity_hint(&mut err);
-                }
-
-                if overlap.involves_placeholder {
-                    coherence::add_placeholder_note(&mut err);
-                }
-
-                err.emit();
             }
         } else {
             let parent = tcx.impl_parent(impl_def_id).unwrap_or(trait_id);
diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs
index 92c5600362e01..f2ad01b3d5961 100644
--- a/src/librustc/ty/context.rs
+++ b/src/librustc/ty/context.rs
@@ -41,6 +41,7 @@ use crate::ty::{ExistentialPredicate, InferTy, ParamTy, PolyFnSig, Predicate, Pr
 use crate::ty::{InferConst, ParamConst};
 use crate::ty::{List, TyKind, TyS};
 use crate::util::common::ErrorReported;
+use rustc::lint::LintDiagnosticBuilder;
 use rustc_attr as attr;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::profiling::SelfProfilerRef;
@@ -49,7 +50,6 @@ use rustc_data_structures::stable_hasher::{
     hash_stable_hashmap, HashStable, StableHasher, StableVec,
 };
 use rustc_data_structures::sync::{self, Lock, Lrc, WorkerLocal};
-use rustc_errors::DiagnosticBuilder;
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet, DefIndex, LOCAL_CRATE};
@@ -2551,16 +2551,6 @@ impl<'tcx> TyCtxt<'tcx> {
         iter.intern_with(|xs| self.intern_goals(xs))
     }
 
-    pub fn lint_hir(
-        self,
-        lint: &'static Lint,
-        hir_id: HirId,
-        span: impl Into<MultiSpan>,
-        msg: &str,
-    ) {
-        self.struct_span_lint_hir(lint, hir_id, span.into(), msg).emit()
-    }
-
     /// Walks upwards from `id` to find a node which might change lint levels with attributes.
     /// It stops at `bound` and just returns it if reached.
     pub fn maybe_lint_level_root_bounded(self, mut id: HirId, bound: HirId) -> HirId {
@@ -2604,20 +2594,20 @@ impl<'tcx> TyCtxt<'tcx> {
         lint: &'static Lint,
         hir_id: HirId,
         span: impl Into<MultiSpan>,
-        msg: &str,
-    ) -> DiagnosticBuilder<'tcx> {
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    ) {
         let (level, src) = self.lint_level_at_node(lint, hir_id);
-        struct_lint_level(self.sess, lint, level, src, Some(span.into()), msg)
+        struct_lint_level(self.sess, lint, level, src, Some(span.into()), decorate);
     }
 
     pub fn struct_lint_node(
         self,
         lint: &'static Lint,
         id: HirId,
-        msg: &str,
-    ) -> DiagnosticBuilder<'tcx> {
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    ) {
         let (level, src) = self.lint_level_at_node(lint, id);
-        struct_lint_level(self.sess, lint, level, src, None, msg)
+        struct_lint_level(self.sess, lint, level, src, None, decorate);
     }
 
     pub fn in_scope_traits(self, id: HirId) -> Option<&'tcx StableVec<TraitCandidate>> {
diff --git a/src/librustc_attr/builtin.rs b/src/librustc_attr/builtin.rs
index ab03297fffe84..3c13735bbf41f 100644
--- a/src/librustc_attr/builtin.rs
+++ b/src/librustc_attr/builtin.rs
@@ -37,7 +37,9 @@ fn handle_errors(sess: &ParseSess, span: Span, error: AttrError) {
                 .span_label(span, format!("expected one of {}", expected.join(", ")))
                 .emit();
         }
-        AttrError::MissingSince => struct_span_err!(diag, span, E0542, "missing 'since'").emit(),
+        AttrError::MissingSince => {
+            struct_span_err!(diag, span, E0542, "missing 'since'").emit();
+        }
         AttrError::MissingFeature => {
             struct_span_err!(diag, span, E0546, "missing 'feature'").emit();
         }
@@ -639,7 +641,7 @@ fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &ParseSess, features: &F
     let (cfg, feature, has_feature) = gated_cfg;
     if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
         let explain = format!("`cfg({})` is experimental and subject to change", cfg);
-        feature_err(sess, *feature, cfg_span, &explain).emit()
+        feature_err(sess, *feature, cfg_span, &explain).emit();
     }
 }
 
diff --git a/src/librustc_errors/diagnostic.rs b/src/librustc_errors/diagnostic.rs
index 5286e8c19b462..189b5bd0f9e87 100644
--- a/src/librustc_errors/diagnostic.rs
+++ b/src/librustc_errors/diagnostic.rs
@@ -194,6 +194,7 @@ impl Diagnostic {
         found_extra: &dyn fmt::Display,
     ) -> &mut Self {
         let expected_label = format!("expected {}", expected_label);
+
         let found_label = format!("found {}", found_label);
         let (found_padding, expected_padding) = if expected_label.len() > found_label.len() {
             (expected_label.len() - found_label.len(), 0)
diff --git a/src/librustc_errors/diagnostic_builder.rs b/src/librustc_errors/diagnostic_builder.rs
index 82bbae18a9c0b..39f585231eea4 100644
--- a/src/librustc_errors/diagnostic_builder.rs
+++ b/src/librustc_errors/diagnostic_builder.rs
@@ -106,7 +106,11 @@ impl<'a> DiagnosticBuilder<'a> {
     ///
     /// See `emit` and `delay_as_bug` for details.
     pub fn emit_unless(&mut self, delay: bool) {
-        if delay { self.delay_as_bug() } else { self.emit() }
+        if delay {
+            self.delay_as_bug();
+        } else {
+            self.emit();
+        }
     }
 
     /// Stashes diagnostic for possible later improvement in a different,
@@ -369,6 +373,7 @@ impl<'a> DiagnosticBuilder<'a> {
     /// Creates a new `DiagnosticBuilder` with an already constructed
     /// diagnostic.
     crate fn new_diagnostic(handler: &'a Handler, diagnostic: Diagnostic) -> DiagnosticBuilder<'a> {
+        debug!("Created new diagnostic");
         DiagnosticBuilder(Box::new(DiagnosticBuilderInner {
             handler,
             diagnostic,
diff --git a/src/librustc_expand/base.rs b/src/librustc_expand/base.rs
index e167089b93a35..a5614f900b6fb 100644
--- a/src/librustc_expand/base.rs
+++ b/src/librustc_expand/base.rs
@@ -1113,7 +1113,11 @@ pub fn expr_to_string(
     err_msg: &str,
 ) -> Option<(Symbol, ast::StrStyle)> {
     expr_to_spanned_string(cx, expr, err_msg)
-        .map_err(|err| err.map(|mut err| err.emit()))
+        .map_err(|err| {
+            err.map(|mut err| {
+                err.emit();
+            })
+        })
         .ok()
         .map(|(symbol, style, _)| (symbol, style))
 }
diff --git a/src/librustc_lint/array_into_iter.rs b/src/librustc_lint/array_into_iter.rs
index fb11b6771e9ca..a91d735622f4e 100644
--- a/src/librustc_lint/array_into_iter.rs
+++ b/src/librustc_lint/array_into_iter.rs
@@ -77,13 +77,13 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ArrayIntoIter {
                 // to an array or to a slice.
                 _ => bug!("array type coerced to something other than array or slice"),
             };
-            let msg = format!(
+            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,
-            );
-            cx.struct_span_lint(ARRAY_INTO_ITER, *span, &msg)
+                ))
                 .span_suggestion(
                     call.ident.span,
                     "use `.iter()` instead of `.into_iter()` to avoid ambiguity",
@@ -91,6 +91,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ArrayIntoIter {
                     Applicability::MachineApplicable,
                 )
                 .emit();
+            })
         }
     }
 }
diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs
index ca66717ac5eba..c827a7f3d5283 100644
--- a/src/librustc_lint/builtin.rs
+++ b/src/librustc_lint/builtin.rs
@@ -23,6 +23,7 @@
 
 use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
 use rustc::hir::map::Map;
+use rustc::lint::LintDiagnosticBuilder;
 use rustc::traits::misc::can_type_implement_copy;
 use rustc::ty::{self, layout::VariantIdx, Ty, TyCtxt};
 use rustc_ast_pretty::pprust::{self, expr_to_string};
@@ -77,14 +78,16 @@ impl EarlyLintPass for WhileTrue {
                     if !lit.span.from_expansion() {
                         let msg = "denote infinite loops with `loop { ... }`";
                         let condition_span = cx.sess.source_map().def_span(e.span);
-                        cx.struct_span_lint(WHILE_TRUE, condition_span, msg)
-                            .span_suggestion_short(
-                                condition_span,
-                                "use `loop`",
-                                "loop".to_owned(),
-                                Applicability::MachineApplicable,
-                            )
-                            .emit();
+                        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();
+                        })
                     }
                 }
             }
@@ -104,8 +107,9 @@ impl BoxPointers {
     fn check_heap_type(&self, cx: &LateContext<'_, '_>, span: Span, ty: Ty<'_>) {
         for leaf_ty in ty.walk() {
             if leaf_ty.is_box() {
-                let m = format!("type uses owned (Box type) pointers: {}", ty);
-                cx.span_lint(BOX_POINTERS, span, &m);
+                cx.struct_span_lint(BOX_POINTERS, span, |lint| {
+                    lint.build(&format!("type uses owned (Box type) pointers: {}", ty)).emit()
+                });
             }
         }
     }
@@ -174,29 +178,28 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NonShorthandFieldPatterns {
                     if cx.tcx.find_field_index(ident, &variant)
                         == Some(cx.tcx.field_index(fieldpat.hir_id, cx.tables))
                     {
-                        let mut err = cx.struct_span_lint(
-                            NON_SHORTHAND_FIELD_PATTERNS,
-                            fieldpat.span,
-                            &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();
+                        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();
+                        });
                     }
                 }
             }
@@ -213,26 +216,32 @@ declare_lint! {
 declare_lint_pass!(UnsafeCode => [UNSAFE_CODE]);
 
 impl UnsafeCode {
-    fn report_unsafe(&self, cx: &EarlyContext<'_>, span: Span, desc: &'static str) {
+    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.span_lint(UNSAFE_CODE, span, desc);
+        cx.struct_span_lint(UNSAFE_CODE, span, decorate);
     }
 }
 
 impl EarlyLintPass for UnsafeCode {
     fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
         if attr.check_name(sym::allow_internal_unsafe) {
-            self.report_unsafe(
-                cx,
-                attr.span,
-                "`allow_internal_unsafe` allows defining \
+            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()
+            });
         }
     }
 
@@ -240,7 +249,9 @@ impl EarlyLintPass for UnsafeCode {
         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, "usage of an `unsafe` block");
+                self.report_unsafe(cx, blk.span, |lint| {
+                    lint.build("usage of an `unsafe` block").emit()
+                });
             }
         }
     }
@@ -248,11 +259,15 @@ impl EarlyLintPass for UnsafeCode {
     fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
         match it.kind {
             ast::ItemKind::Trait(_, ast::Unsafety::Unsafe, ..) => {
-                self.report_unsafe(cx, it.span, "declaration of an `unsafe` trait")
+                self.report_unsafe(cx, it.span, |lint| {
+                    lint.build("declaration of an `unsafe` trait").emit()
+                })
             }
 
             ast::ItemKind::Impl { unsafety: ast::Unsafety::Unsafe, .. } => {
-                self.report_unsafe(cx, it.span, "implementation of an `unsafe` trait")
+                self.report_unsafe(cx, it.span, |lint| {
+                    lint.build("implementation of an `unsafe` trait").emit()
+                })
             }
 
             _ => return,
@@ -274,7 +289,7 @@ impl EarlyLintPass for UnsafeCode {
                 FnCtxt::Assoc(_) if body.is_none() => "declaration of an `unsafe` method",
                 FnCtxt::Assoc(_) => "implementation of an `unsafe` method",
             };
-            self.report_unsafe(cx, span, msg);
+            self.report_unsafe(cx, span, |lint| lint.build(msg).emit());
         }
     }
 }
@@ -359,11 +374,9 @@ impl MissingDoc {
 
         let has_doc = attrs.iter().any(|a| has_doc(a));
         if !has_doc {
-            cx.span_lint(
-                MISSING_DOCS,
-                cx.tcx.sess.source_map().def_span(sp),
-                &format!("missing documentation for {}", desc),
-            );
+            cx.struct_span_lint(MISSING_DOCS, cx.tcx.sess.source_map().def_span(sp), |lint| {
+                lint.build(&format!("missing documentation for {}", desc)).emit()
+            });
         }
     }
 }
@@ -391,10 +404,10 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingDoc {
         for macro_def in krate.exported_macros {
             let has_doc = macro_def.attrs.iter().any(|a| has_doc(a));
             if !has_doc {
-                cx.span_lint(
+                cx.struct_span_lint(
                     MISSING_DOCS,
                     cx.tcx.sess.source_map().def_span(macro_def.span),
-                    "missing documentation for macro",
+                    |lint| lint.build("missing documentation for macro").emit(),
                 );
             }
         }
@@ -542,12 +555,13 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingCopyImplementations {
             return;
         }
         if can_type_implement_copy(cx.tcx, param_env, ty).is_ok() {
-            cx.span_lint(
-                MISSING_COPY_IMPLEMENTATIONS,
-                item.span,
-                "type could implement `Copy`; consider adding `impl \
+            cx.struct_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, |lint| {
+                lint.build(
+                    "type could implement `Copy`; consider adding `impl \
                           Copy`",
-            )
+                )
+                .emit()
+            })
         }
     }
 }
@@ -596,15 +610,14 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingDebugImplementations {
         }
 
         if !self.impling_types.as_ref().unwrap().contains(&item.hir_id) {
-            cx.span_lint(
-                MISSING_DEBUG_IMPLEMENTATIONS,
-                item.span,
-                &format!(
+            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()
+            });
         }
     }
 }
@@ -632,28 +645,28 @@ impl EarlyLintPass for AnonymousParameters {
                     match arg.pat.kind {
                         ast::PatKind::Ident(_, ident, None) => {
                             if ident.name == kw::Invalid {
-                                let ty_snip = cx.sess.source_map().span_to_snippet(arg.ty.span);
+                                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(snip) = ty_snip {
-                                    (snip, Applicability::MachineApplicable)
-                                } else {
-                                    ("<type>".to_owned(), Applicability::HasPlaceholders)
-                                };
+                                    let (ty_snip, appl) = if let Ok(ref snip) = ty_snip {
+                                        (snip.as_str(), Applicability::MachineApplicable)
+                                    } else {
+                                        ("<type>", Applicability::HasPlaceholders)
+                                    };
 
-                                cx.struct_span_lint(
-                                    ANONYMOUS_PARAMETERS,
-                                    arg.pat.span,
-                                    "anonymous parameters are deprecated and will be \
+                                    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();
+                                    )
+                                    .span_suggestion(
+                                        arg.pat.span,
+                                        "try naming the parameter or explicitly \
+                                            ignoring it",
+                                        format!("_: {}", ty_snip),
+                                        appl,
+                                    )
+                                    .emit();
+                                })
                             }
                         }
                         _ => (),
@@ -687,14 +700,16 @@ fn lint_deprecated_attr(
     msg: &str,
     suggestion: Option<&str>,
 ) {
-    cx.struct_span_lint(DEPRECATED, attr.span, &msg)
-        .span_suggestion_short(
-            attr.span,
-            suggestion.unwrap_or("remove this attribute"),
-            String::new(),
-            Applicability::MachineApplicable,
-        )
-        .emit();
+    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 {
@@ -759,21 +774,20 @@ impl UnusedDocComment {
             let span = sugared_span.take().unwrap_or_else(|| attr.span);
 
             if attr.is_doc_comment() || attr.check_name(sym::doc) {
-                let mut err = cx.struct_span_lint(UNUSED_DOC_COMMENTS, span, "unused doc comment");
-
-                err.span_label(
-                    node_span,
-                    format!("rustdoc does not generate documentation for {}", node_kind),
-                );
-
-                if is_macro_expansion {
-                    err.help(
-                        "to document an item produced by a macro, \
-                              the macro must produce the documentation as part of its expansion",
+                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();
+                    if is_macro_expansion {
+                        err.help(
+                            "to document an item produced by a macro, \
+                                  the macro must produce the documentation as part of its expansion",
+                        );
+                    }
+                    err.emit();
+                });
             }
         }
     }
@@ -831,20 +845,20 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidNoMangleItems {
                         match param.kind {
                             GenericParamKind::Lifetime { .. } => {}
                             GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => {
-                                let mut err = cx.struct_span_lint(
-                                    NO_MANGLE_GENERIC_ITEMS,
-                                    it.span,
-                                    "functions generic over types or consts must be mangled",
-                                );
-                                err.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,
-                                );
-                                err.emit();
+                                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;
                             }
                         }
@@ -855,26 +869,28 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidNoMangleItems {
                 if attr::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
-                    let msg = "const items should never be `#[no_mangle]`";
-                    let mut err = cx.struct_span_lint(NO_MANGLE_CONST_ITEMS, it.span, 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();
+                    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();
+                    });
                 }
             }
             _ => {}
@@ -894,12 +910,14 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MutableTransmutes {
     fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) {
         use rustc_target::spec::abi::Abi::RustIntrinsic;
 
-        let msg = "mutating transmuted &mut T from &T may cause undefined behavior, \
-                   consider instead using an UnsafeCell";
         match get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (&ty1.kind, &ty2.kind)) {
             Some((&ty::Ref(_, _, from_mt), &ty::Ref(_, _, to_mt))) => {
                 if to_mt == hir::Mutability::Mut && from_mt == hir::Mutability::Not {
-                    cx.span_lint(MUTABLE_TRANSMUTES, expr.span, msg);
+                    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()
+                    });
                 }
             }
             _ => (),
@@ -949,7 +967,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnstableFeatures {
         if attr.check_name(sym::feature) {
             if let Some(items) = attr.meta_item_list() {
                 for item in items {
-                    ctx.span_lint(UNSTABLE_FEATURES, item.span(), "unstable feature");
+                    ctx.struct_span_lint(UNSTABLE_FEATURES, item.span(), |lint| {
+                        lint.build("unstable feature").emit()
+                    });
                 }
             }
         }
@@ -984,28 +1004,26 @@ impl UnreachablePub {
                     applicability = Applicability::MaybeIncorrect;
                 }
                 let def_span = cx.tcx.sess.source_map().def_span(span);
-                let mut err = cx.struct_span_lint(
-                    UNREACHABLE_PUB,
-                    def_span,
-                    &format!("unreachable `pub` {}", what),
-                );
-                let replacement = if cx.tcx.features().crate_visibility_modifier {
-                    "crate"
-                } else {
-                    "pub(crate)"
-                }
-                .to_owned();
+                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();
+                    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();
+                });
             }
             _ => {}
         }
@@ -1114,28 +1132,30 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TypeAliasBounds {
         let mut suggested_changing_assoc_types = false;
         // There must not be a where clause
         if !type_alias_generics.where_clause.predicates.is_empty() {
-            let spans: Vec<_> = type_alias_generics
-                .where_clause
-                .predicates
-                .iter()
-                .map(|pred| pred.span())
-                .collect();
-            let mut err = cx.struct_span_lint(
+            cx.lint(
                 TYPE_ALIAS_BOUNDS,
-                spans,
-                "where clauses are not enforced in type aliases",
-            );
-            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,
+                |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();
+                },
             );
-            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() {
@@ -1148,19 +1168,18 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TypeAliasBounds {
                 })
                 .collect();
             if !spans.is_empty() {
-                let mut err = cx.struct_span_lint(
-                    TYPE_ALIAS_BOUNDS,
-                    spans,
-                    "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();
+                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();
+                });
             }
         }
     }
@@ -1232,15 +1251,14 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TrivialConstraints {
                     ConstEvaluatable(..) => continue,
                 };
                 if predicate.is_global() {
-                    cx.span_lint(
-                        TRIVIAL_BOUNDS,
-                        span,
-                        &format!(
+                    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()
+                    });
                 }
             }
         }
@@ -1317,28 +1335,32 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
             let suggestion = "use `..=` for an inclusive range";
             if parenthesise {
                 self.node_id = Some(pat.id);
-                let end = expr_to_string(&end);
-                let replace = match start {
-                    Some(start) => format!("&({}..={})", expr_to_string(&start), end),
-                    None => format!("&(..={})", end),
-                };
-                let mut err = cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, pat.span, msg);
-                err.span_suggestion(
-                    pat.span,
-                    suggestion,
-                    replace,
-                    Applicability::MachineApplicable,
-                );
-                err.emit();
+                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 {
-                let mut err = cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, join, msg);
-                err.span_suggestion_short(
-                    join,
-                    suggestion,
-                    "..=".to_owned(),
-                    Applicability::MachineApplicable,
-                );
-                err.emit();
+                cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, join, |lint| {
+                    lint.build(msg)
+                        .span_suggestion_short(
+                            join,
+                            suggestion,
+                            "..=".to_owned(),
+                            Applicability::MachineApplicable,
+                        )
+                        .emit();
+                });
             };
         }
     }
@@ -1384,7 +1406,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestItems {
         }
 
         if let Some(attr) = attr::find_by_name(&it.attrs, sym::rustc_test_marker) {
-            cx.struct_span_lint(UNNAMEABLE_TEST_ITEMS, attr.span, "cannot test inner items").emit();
+            cx.struct_span_lint(UNNAMEABLE_TEST_ITEMS, attr.span, |lint| {
+                lint.build("cannot test inner items").emit()
+            });
         }
     }
 
@@ -1465,18 +1489,16 @@ impl KeywordIdents {
             return;
         }
 
-        let mut lint = cx.struct_span_lint(
-            KEYWORD_IDENTS,
-            ident.span,
-            &format!("`{}` is a keyword in the {} edition", ident, next_edition),
-        );
-        lint.span_suggestion(
-            ident.span,
-            "you can use a raw identifier to stay compatible",
-            format!("r#{}", ident),
-            Applicability::MachineApplicable,
-        );
-        lint.emit()
+        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()
+        });
     }
 }
 
@@ -1780,17 +1802,22 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExplicitOutlivesRequirements {
             }
 
             if !lint_spans.is_empty() {
-                let mut err = cx.struct_span_lint(
-                    EXPLICIT_OUTLIVES_REQUIREMENTS,
-                    lint_spans.clone(),
-                    "outlives requirements can be inferred",
-                );
-                err.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,
-                );
-                err.emit();
+                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();
+                });
             }
         }
     }
@@ -1817,15 +1844,13 @@ impl EarlyLintPass for IncompleteFeatures {
             .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,
-                    &format!(
+                cx.struct_span_lint(INCOMPLETE_FEATURES, span, |lint| {
+                    lint.build(&format!(
                         "the feature `{}` is incomplete and may cause the compiler to crash",
                         name,
-                    ),
-                )
-                .emit();
+                    ))
+                    .emit()
+                })
             });
     }
 }
@@ -2015,30 +2040,28 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
             // We are extremely conservative with what we warn about.
             let conjured_ty = cx.tables.expr_ty(expr);
             if let Some((msg, span)) = ty_find_init_error(cx.tcx, conjured_ty, init) {
-                let mut err = cx.struct_span_lint(
-                    INVALID_VALUE,
-                    expr.span,
-                    &format!(
+                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();
+                    ));
+                    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();
+                });
             }
         }
     }
diff --git a/src/librustc_lint/context.rs b/src/librustc_lint/context.rs
index 3b8cce5635d07..8e8beefa72f13 100644
--- a/src/librustc_lint/context.rs
+++ b/src/librustc_lint/context.rs
@@ -20,13 +20,14 @@ use crate::levels::LintLevelsBuilder;
 use crate::passes::{EarlyLintPassObject, LateLintPassObject};
 use rustc::hir::map::definitions::{DefPathData, DisambiguatedDefPathData};
 use rustc::lint::add_elided_lifetime_in_path_suggestion;
+use rustc::lint::LintDiagnosticBuilder;
 use rustc::middle::privacy::AccessLevels;
 use rustc::middle::stability;
 use rustc::ty::layout::{LayoutError, LayoutOf, TyLayout};
 use rustc::ty::{self, print::Printer, subst::GenericArg, Ty, TyCtxt};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync;
-use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
+use rustc_errors::{struct_span_err, Applicability};
 use rustc_hir as hir;
 use rustc_hir::def_id::{CrateNum, DefId};
 use rustc_session::lint::BuiltinLintDiagnostics;
@@ -473,150 +474,123 @@ pub trait LintContext: Sized {
     fn sess(&self) -> &Session;
     fn lints(&self) -> &LintStore;
 
-    fn lookup_and_emit<S: Into<MultiSpan>>(&self, lint: &'static Lint, span: Option<S>, msg: &str) {
-        self.lookup(lint, span, msg).emit();
-    }
-
-    fn lookup_and_emit_with_diagnostics<S: Into<MultiSpan>>(
+    fn lookup_with_diagnostics(
         &self,
         lint: &'static Lint,
-        span: Option<S>,
-        msg: &str,
+        span: Option<impl Into<MultiSpan>>,
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
         diagnostic: BuiltinLintDiagnostics,
     ) {
-        let mut db = self.lookup(lint, span, msg);
-
-        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,
+        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,
-                );
-            }
-            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,
+                ) => {
+                    add_elided_lifetime_in_path_suggestion(
+                        sess,
+                        &mut db,
+                        n,
+                        path_span,
+                        incl_angl_brckt,
+                        insertion_span,
+                        anon_lts,
                     );
                 }
-            }
-            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::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, suggestion, span)
                 }
             }
-            BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => {
-                stability::deprecation_suggestion(&mut db, suggestion, span)
-            }
-        }
-
-        db.emit();
+            // 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>,
-        msg: &str,
-    ) -> DiagnosticBuilder<'_>;
-
-    /// Emit a lint at the appropriate level, for a particular span.
-    fn span_lint<S: Into<MultiSpan>>(&self, lint: &'static Lint, span: S, msg: &str) {
-        self.lookup_and_emit(lint, Some(span), msg);
-    }
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    );
 
     fn struct_span_lint<S: Into<MultiSpan>>(
         &self,
         lint: &'static Lint,
         span: S,
-        msg: &str,
-    ) -> DiagnosticBuilder<'_> {
-        self.lookup(lint, Some(span), msg)
-    }
-
-    /// Emit a lint and note at the appropriate level, for a particular span.
-    fn span_lint_note(
-        &self,
-        lint: &'static Lint,
-        span: Span,
-        msg: &str,
-        note_span: Span,
-        note: &str,
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
     ) {
-        let mut err = self.lookup(lint, Some(span), msg);
-        if note_span == span {
-            err.note(note);
-        } else {
-            err.span_note(note_span, note);
-        }
-        err.emit();
+        self.lookup(lint, Some(span), decorate);
     }
-
-    /// Emit a lint and help at the appropriate level, for a particular span.
-    fn span_lint_help(&self, lint: &'static Lint, span: Span, msg: &str, help: &str) {
-        let mut err = self.lookup(lint, Some(span), msg);
-        self.span_lint(lint, span, msg);
-        err.span_help(span, help);
-        err.emit();
-    }
-
     /// Emit a lint at the appropriate level, with no associated span.
-    fn lint(&self, lint: &'static Lint, msg: &str) {
-        self.lookup_and_emit(lint, None as Option<Span>, msg);
+    fn lint(&self, lint: &'static Lint, decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>)) {
+        self.lookup(lint, None as Option<Span>, decorate);
     }
 }
 
@@ -654,13 +628,13 @@ impl LintContext for LateContext<'_, '_> {
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        msg: &str,
-    ) -> DiagnosticBuilder<'_> {
+        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, msg),
-            None => self.tcx.struct_lint_node(lint, hir_id, msg),
+            Some(s) => self.tcx.struct_span_lint_hir(lint, hir_id, s, decorate),
+            None => self.tcx.struct_lint_node(lint, hir_id, decorate),
         }
     }
 }
@@ -681,9 +655,9 @@ impl LintContext for EarlyContext<'_> {
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        msg: &str,
-    ) -> DiagnosticBuilder<'_> {
-        self.builder.struct_lint(lint, span.map(|s| s.into()), msg)
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    ) {
+        self.builder.struct_lint(lint, span.map(|s| s.into()), decorate)
     }
 }
 
diff --git a/src/librustc_lint/early.rs b/src/librustc_lint/early.rs
index 27781eb41d28d..c351159067387 100644
--- a/src/librustc_lint/early.rs
+++ b/src/librustc_lint/early.rs
@@ -37,11 +37,18 @@ struct EarlyContextAndPass<'a, T: EarlyLintPass> {
 impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
     fn check_id(&mut self, id: ast::NodeId) {
         for early_lint in self.context.buffered.take(id) {
-            self.context.lookup_and_emit_with_diagnostics(
-                early_lint.lint_id.lint,
-                Some(early_lint.span.clone()),
-                &early_lint.msg,
-                early_lint.diagnostic,
+            let rustc_session::lint::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,
             );
         }
     }
diff --git a/src/librustc_lint/internal.rs b/src/librustc_lint/internal.rs
index 8480c85075dd4..7875261911201 100644
--- a/src/librustc_lint/internal.rs
+++ b/src/librustc_lint/internal.rs
@@ -37,16 +37,22 @@ 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) {
-            let msg = format!("Prefer {} over {}, it has better performance", replace, ident);
-            let mut db = cx.struct_span_lint(DEFAULT_HASH_TYPES, ident.span, &msg);
-            db.span_suggestion(
-                ident.span,
-                "use",
-                replace.to_string(),
-                Applicability::MaybeIncorrect, // FxHashMap, ... needs another import
-            );
-            db.note(&format!("a `use rustc_data_structures::fx::{}` may be necessary", replace))
-                .emit();
+            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();
+            });
         }
     }
 }
@@ -85,14 +91,16 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TyTyKind {
         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, "usage of `ty::TyKind::<kind>`")
-                    .span_suggestion(
-                        span,
-                        "try using ty::<kind> directly",
-                        "ty".to_string(),
-                        Applicability::MaybeIncorrect, // ty maybe needs an import
-                    )
-                    .emit();
+                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();
+                })
             }
         }
     }
@@ -103,32 +111,28 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TyTyKind {
                 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,
-                                "usage of `ty::TyKind`",
-                            )
-                            .help("try using `Ty` instead")
-                            .emit();
+                            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,
-                                        &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();
+                                    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();
+                                    })
                                 }
                             }
                         }
@@ -142,19 +146,17 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TyTyKind {
                     }
                 }
                 if let Some(t) = is_ty_or_ty_ctxt(cx, &inner_ty) {
-                    cx.struct_span_lint(
-                        TY_PASS_BY_REFERENCE,
-                        ty.span,
-                        &format!("passing `{}` by reference", t),
-                    )
-                    .span_suggestion(
-                        ty.span,
-                        "try passing by value",
-                        t,
-                        // Changing type of function argument
-                        Applicability::MaybeIncorrect,
-                    )
-                    .emit();
+                    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();
+                    })
                 }
             }
             _ => {}
@@ -234,10 +236,12 @@ impl EarlyLintPass for LintPassImpl {
                         cx.struct_span_lint(
                             LINT_PASS_IMPL_WITHOUT_MACRO,
                             lint_pass.path.span,
-                            "implementing `LintPass` by hand",
+                            |lint| {
+                                lint.build("implementing `LintPass` by hand")
+                                    .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead")
+                                    .emit();
+                            },
                         )
-                        .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead")
-                        .emit();
                     }
                 }
             }
diff --git a/src/librustc_lint/levels.rs b/src/librustc_lint/levels.rs
index 4f30d2b222684..da449ed42fd00 100644
--- a/src/librustc_lint/levels.rs
+++ b/src/librustc_lint/levels.rs
@@ -1,12 +1,13 @@
 use crate::context::{CheckLintNameResult, LintStore};
 use crate::late::unerased_lint_store;
 use rustc::hir::map::Map;
+use rustc::lint::LintDiagnosticBuilder;
 use rustc::lint::{struct_lint_level, LintLevelMap, LintLevelSets, LintSet, LintSource};
 use rustc::ty::query::Providers;
 use rustc::ty::TyCtxt;
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
+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};
@@ -39,8 +40,8 @@ fn lint_levels(tcx: TyCtxt<'_>, cnum: CrateNum) -> &LintLevelMap {
     tcx.arena.alloc(builder.levels.build_map())
 }
 
-pub struct LintLevelsBuilder<'a> {
-    sess: &'a Session,
+pub struct LintLevelsBuilder<'s> {
+    sess: &'s Session,
     sets: LintLevelSets,
     id_to_set: FxHashMap<HirId, u32>,
     cur: u32,
@@ -52,8 +53,8 @@ pub struct BuilderPush {
     pub changed: bool,
 }
 
-impl<'a> LintLevelsBuilder<'a> {
-    pub fn new(sess: &'a Session, warn_about_weird_lints: bool, store: &LintStore) -> Self {
+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(),
@@ -233,27 +234,29 @@ impl<'a> LintLevelsBuilder<'a> {
                                 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
                                 let (lvl, src) =
                                     self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
-                                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
-                                );
                                 struct_lint_level(
                                     self.sess,
                                     lint,
                                     lvl,
                                     src,
                                     Some(li.span().into()),
-                                    &msg,
-                                )
-                                .span_suggestion(
-                                    li.span(),
-                                    "change it to",
-                                    new_lint_name.to_string(),
-                                    Applicability::MachineApplicable,
-                                )
-                                .emit();
+                                    |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),
@@ -279,48 +282,49 @@ impl<'a> LintLevelsBuilder<'a> {
                         let lint = builtin::RENAMED_AND_REMOVED_LINTS;
                         let (level, src) =
                             self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
-                        let mut err = struct_lint_level(
+                        struct_lint_level(
                             self.sess,
                             lint,
                             level,
                             src,
                             Some(li.span().into()),
-                            &msg,
+                            |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();
+                            },
                         );
-                        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);
-                        let msg = format!("unknown lint: `{}`", name);
-                        let mut db = struct_lint_level(
+                        struct_lint_level(
                             self.sess,
                             lint,
                             level,
                             src,
                             Some(li.span().into()),
-                            &msg,
+                            |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 let Some(suggestion) = suggestion {
-                            db.span_suggestion(
-                                li.span(),
-                                "did you mean",
-                                suggestion.to_string(),
-                                Applicability::MachineApplicable,
-                            );
-                        }
-
-                        db.emit();
                     }
                 }
             }
@@ -390,10 +394,10 @@ impl<'a> LintLevelsBuilder<'a> {
         &self,
         lint: &'static Lint,
         span: Option<MultiSpan>,
-        msg: &str,
-    ) -> DiagnosticBuilder<'a> {
+        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
+    ) {
         let (level, src) = self.sets.get_lint_level(lint, self.cur, None, self.sess);
-        struct_lint_level(self.sess, lint, level, src, span, msg)
+        struct_lint_level(self.sess, lint, level, src, span, decorate)
     }
 
     /// Registers the ID provided with the current set of lints stored in
diff --git a/src/librustc_lint/non_ascii_idents.rs b/src/librustc_lint/non_ascii_idents.rs
index 3c85a1b31b244..a0ca7ad1860e8 100644
--- a/src/librustc_lint/non_ascii_idents.rs
+++ b/src/librustc_lint/non_ascii_idents.rs
@@ -22,19 +22,13 @@ impl EarlyLintPass for NonAsciiIdents {
         if name_str.is_ascii() {
             return;
         }
-        cx.struct_span_lint(
-            NON_ASCII_IDENTS,
-            ident.span,
-            "identifier contains non-ASCII characters",
-        )
-        .emit();
+        cx.struct_span_lint(NON_ASCII_IDENTS, ident.span, |lint| {
+            lint.build("identifier contains non-ASCII characters").emit()
+        });
         if !name_str.chars().all(GeneralSecurityProfile::identifier_allowed) {
-            cx.struct_span_lint(
-                UNCOMMON_CODEPOINTS,
-                ident.span,
-                "identifier contains uncommon Unicode codepoints",
-            )
-            .emit();
+            cx.struct_span_lint(UNCOMMON_CODEPOINTS, ident.span, |lint| {
+                lint.build("identifier contains uncommon Unicode codepoints").emit()
+            })
         }
     }
 }
diff --git a/src/librustc_lint/nonstandard_style.rs b/src/librustc_lint/nonstandard_style.rs
index 6fdbfea7f03b3..8c58f2ba4c07a 100644
--- a/src/librustc_lint/nonstandard_style.rs
+++ b/src/librustc_lint/nonstandard_style.rs
@@ -107,15 +107,17 @@ impl NonCamelCaseTypes {
         let name = &ident.name.as_str();
 
         if !is_camel_case(name) {
-            let msg = format!("{} `{}` should have an upper camel case name", sort, name);
-            cx.struct_span_lint(NON_CAMEL_CASE_TYPES, ident.span, &msg)
-                .span_suggestion(
-                    ident.span,
-                    "convert the identifier to upper camel case",
-                    to_camel_case(name),
-                    Applicability::MaybeIncorrect,
-                )
-                .emit();
+            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()
+            })
         }
     }
 }
@@ -223,25 +225,25 @@ impl NonSnakeCase {
         let name = &ident.name.as_str();
 
         if !is_snake_case(name) {
-            let sc = NonSnakeCase::to_snake_case(name);
-
-            let msg = format!("{} `{}` should have a snake case name", sort, name);
-            let mut err = cx.struct_span_lint(NON_SNAKE_CASE, ident.span, &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));
-            }
+            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();
+                err.emit();
+            });
         }
     }
 }
@@ -386,19 +388,18 @@ 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()) {
-            let uc = NonSnakeCase::to_snake_case(&name).to_uppercase();
-
-            let msg = format!("{} `{}` should have an upper case name", sort, name);
-            cx.struct_span_lint(NON_UPPER_CASE_GLOBALS, ident.span, &msg)
-                .span_suggestion(
-                    ident.span,
-                    "convert the identifier to upper case",
-                    uc,
-                    Applicability::MaybeIncorrect,
-                )
-                .emit();
+            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();
+            })
         }
     }
 }
diff --git a/src/librustc_lint/redundant_semicolon.rs b/src/librustc_lint/redundant_semicolon.rs
index 21b244ad75d4e..68cb0b54f56c5 100644
--- a/src/librustc_lint/redundant_semicolon.rs
+++ b/src/librustc_lint/redundant_semicolon.rs
@@ -26,19 +26,21 @@ impl EarlyLintPass for RedundantSemicolon {
                             } else {
                                 "unnecessary trailing semicolon"
                             };
-                            let mut err = cx.struct_span_lint(REDUNDANT_SEMICOLON, stmt.span, &msg);
-                            let suggest_msg = if multiple {
-                                "remove these semicolons"
-                            } else {
-                                "remove this semicolon"
-                            };
-                            err.span_suggestion(
-                                stmt.span,
-                                &suggest_msg,
-                                String::new(),
-                                Applicability::MaybeIncorrect,
-                            );
-                            err.emit();
+                            cx.struct_span_lint(REDUNDANT_SEMICOLON, stmt.span, |lint| {
+                                let mut err = lint.build(&msg);
+                                let suggest_msg = if multiple {
+                                    "remove these semicolons"
+                                } else {
+                                    "remove this semicolon"
+                                };
+                                err.span_suggestion(
+                                    stmt.span,
+                                    &suggest_msg,
+                                    String::new(),
+                                    Applicability::MaybeIncorrect,
+                                );
+                                err.emit();
+                            });
                         }
                     }
                 }
diff --git a/src/librustc_lint/types.rs b/src/librustc_lint/types.rs
index 6bc6f58f3e7fd..5ffa9c1747fa9 100644
--- a/src/librustc_lint/types.rs
+++ b/src/librustc_lint/types.rs
@@ -67,6 +67,7 @@ fn lint_overflowing_range_endpoint<'a, 'tcx>(
 ) -> 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;
@@ -75,35 +76,32 @@ fn lint_overflowing_range_endpoint<'a, 'tcx>(
         // (`..=`) 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 {
-            let mut err = cx.struct_span_lint(
-                OVERFLOWING_LITERALS,
-                parent_expr.span,
-                &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)) => format!("{}", s.name_str()),
-                    LitKind::Int(_, LitIntType::Unsigned(s)) => format!("{}", s.name_str()),
-                    LitKind::Int(_, LitIntType::Unsuffixed) => "".to_owned(),
-                    _ => 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();
-                return true;
-            }
+            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)) => format!("{}", s.name_str()),
+                        LitKind::Int(_, LitIntType::Unsigned(s)) => format!("{}", s.name_str()),
+                        LitKind::Int(_, LitIntType::Unsuffixed) => "".to_owned(),
+                        _ => 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;
+                }
+            });
         }
     }
-
-    false
+    overwritten
 }
 
 // For `isize` & `usize`, be conservative with the warnings, so that the
@@ -153,41 +151,39 @@ fn report_bin_hex_error(
     negative: bool,
 ) {
     let size = layout::Integer::from_attr(&cx.tcx, ty).size();
-    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 = cx.struct_span_lint(
-        OVERFLOWING_LITERALS,
-        expr.span,
-        &format!("literal out of range for {}", t),
-    );
-    err.note(&format!(
-        "the literal `{}` (decimal `{}`) does not fit into \
-            an `{}` and will become `{}{}`",
-        repr_str, val, t, actually, t
-    ));
-    if let Some(sugg_ty) = get_type_suggestion(&cx.tables.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));
+    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 \
+                    an `{}` and will become `{}{}`",
+            repr_str, val, t, actually, t
+        ));
+        if let Some(sugg_ty) = get_type_suggestion(&cx.tables.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();
+        err.emit();
+    });
 }
 
 // This function finds the next fitting type and generates a suggestion string.
@@ -270,11 +266,9 @@ fn lint_int_literal<'a, 'tcx>(
             }
         }
 
-        cx.span_lint(
-            OVERFLOWING_LITERALS,
-            e.span,
-            &format!("literal out of range for `{}`", t.name_str()),
-        );
+        cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
+            lint.build(&format!("literal out of range for `{}`", t.name_str())).emit()
+        });
     }
 }
 
@@ -298,18 +292,16 @@ fn lint_uint_literal<'a, 'tcx>(
             match par_e.kind {
                 hir::ExprKind::Cast(..) => {
                     if let ty::Char = cx.tables.expr_ty(par_e).kind {
-                        let mut err = cx.struct_span_lint(
-                            OVERFLOWING_LITERALS,
-                            par_e.span,
-                            "only `u8` can be cast into `char`",
-                        );
-                        err.span_suggestion(
-                            par_e.span,
-                            &"use a `char` literal instead",
-                            format!("'\\u{{{:X}}}'", lit_val),
-                            Applicability::MachineApplicable,
-                        );
-                        err.emit();
+                        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;
                     }
                 }
@@ -327,11 +319,9 @@ fn lint_uint_literal<'a, 'tcx>(
             report_bin_hex_error(cx, e, attr::IntType::UnsignedInt(t), repr_str, lit_val, false);
             return;
         }
-        cx.span_lint(
-            OVERFLOWING_LITERALS,
-            e.span,
-            &format!("literal out of range for `{}`", t.name_str()),
-        );
+        cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
+            lint.build(&format!("literal out of range for `{}`", t.name_str())).emit()
+        });
     }
 }
 
@@ -361,11 +351,9 @@ fn lint_literal<'a, 'tcx>(
                 _ => bug!(),
             };
             if is_infinite == Ok(true) {
-                cx.span_lint(
-                    OVERFLOWING_LITERALS,
-                    e.span,
-                    &format!("literal out of range for `{}`", t.name_str()),
-                );
+                cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
+                    lint.build(&format!("literal out of range for `{}`", t.name_str())).emit()
+                });
             }
         }
         _ => {}
@@ -383,11 +371,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TypeLimits {
             }
             hir::ExprKind::Binary(binop, ref l, ref r) => {
                 if is_comparison(binop) && !check_limits(cx, binop, &l, &r) {
-                    cx.span_lint(
-                        UNUSED_COMPARISONS,
-                        e.span,
-                        "comparison is useless due to type limits",
-                    );
+                    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),
@@ -883,22 +869,21 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
         note: &str,
         help: Option<&str>,
     ) {
-        let mut diag = self.cx.struct_span_lint(
-            IMPROPER_CTYPES,
-            sp,
-            &format!("`extern` block uses type `{}`, which is not FFI-safe", 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");
+        self.cx.struct_span_lint(IMPROPER_CTYPES, sp, |lint| {
+            let mut diag =
+                lint.build(&format!("`extern` block uses type `{}`, which is not FFI-safe", ty));
+            diag.span_label(sp, "not FFI-safe");
+            if let Some(help) = help {
+                diag.help(help);
             }
-        }
-        diag.emit();
+            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 {
@@ -1062,14 +1047,17 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for VariantSizeDifferences {
             // We only warn if the largest variant is at least thrice as large as
             // the second-largest.
             if largest > slargest * 3 && slargest > 0 {
-                cx.span_lint(
+                cx.struct_span_lint(
                     VARIANT_SIZE_DIFFERENCES,
                     enum_definition.variants[largest_index].span,
-                    &format!(
-                        "enum variant is more than three times \
+                    |lint| {
+                        lint.build(&format!(
+                            "enum variant is more than three times \
                                           larger ({} bytes) than the next largest",
-                        largest
-                    ),
+                            largest
+                        ))
+                        .emit()
+                    },
                 );
             }
         }
diff --git a/src/librustc_lint/unused.rs b/src/librustc_lint/unused.rs
index 5490e5e2b4d55..480df99a01eed 100644
--- a/src/librustc_lint/unused.rs
+++ b/src/librustc_lint/unused.rs
@@ -104,16 +104,14 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedResults {
         };
 
         if let Some(must_use_op) = must_use_op {
-            cx.span_lint(
-                UNUSED_MUST_USE,
-                expr.span,
-                &format!("unused {} that must be used", 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.span_lint(UNUSED_RESULTS, s.span, "unused result");
+            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).
@@ -204,6 +202,10 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedResults {
         }
 
         // 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,
@@ -213,18 +215,20 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedResults {
         ) -> bool {
             for attr in cx.tcx.get_attrs(def_id).iter() {
                 if attr.check_name(sym::must_use) {
-                    let msg = format!(
-                        "unused {}`{}`{} that must be used",
-                        descr_pre_path,
-                        cx.tcx.def_path_str(def_id),
-                        descr_post_path
-                    );
-                    let mut err = cx.struct_span_lint(UNUSED_MUST_USE, span, &msg);
-                    // check for #[must_use = "..."]
-                    if let Some(note) = attr.value_str() {
-                        err.note(&note.as_str());
-                    }
-                    err.emit();
+                    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;
                 }
             }
@@ -245,7 +249,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PathStatements {
     fn check_stmt(&mut self, cx: &LateContext<'_, '_>, s: &hir::Stmt<'_>) {
         if let hir::StmtKind::Semi(ref expr) = s.kind {
             if let hir::ExprKind::Path(_) = expr.kind {
-                cx.span_lint(PATH_STATEMENTS, s.span, "path statement with no effect");
+                cx.struct_span_lint(PATH_STATEMENTS, s.span, |lint| {
+                    lint.build("path statement with no effect").emit()
+                });
             }
         }
     }
@@ -286,17 +292,21 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedAttributes {
 
         if !attr::is_used(attr) {
             debug!("emitting warning for: {:?}", attr);
-            cx.span_lint(UNUSED_ATTRIBUTES, attr.span, "unused attribute");
+            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) {
-                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",
-                };
-                cx.span_lint(UNUSED_ATTRIBUTES, attr.span, msg);
+                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);
@@ -406,52 +416,54 @@ impl UnusedParens {
         msg: &str,
         keep_space: (bool, bool),
     ) {
-        let span_msg = format!("unnecessary parentheses around {}", msg);
-        let mut err = cx.struct_span_lint(UNUSED_PARENS, span, &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
+        cx.struct_span_lint(UNUSED_PARENS, span, |lint| {
+            let span_msg = format!("unnecessary parentheses around {}", 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
+                ')' => {
+                    if ate_right_paren {
+                        false
+                    } else {
+                        ate_right_paren = true;
+                        true
+                    }
                 }
-            }
-            _ => false,
-        });
+                _ => false,
+            });
 
-        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)
-            };
+            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
-        };
+                if keep_space.1 {
+                    replace.push(' ');
+                }
+                replace
+            };
 
-        err.span_suggestion_short(
-            span,
-            "remove these parentheses",
-            replace,
-            Applicability::MachineApplicable,
-        );
-        err.emit();
+            err.span_suggestion_short(
+                span,
+                "remove these parentheses",
+                replace,
+                Applicability::MachineApplicable,
+            );
+            err.emit();
+        });
     }
 }
 
@@ -631,8 +643,9 @@ impl UnusedImportBraces {
                 ast::UseTreeKind::Nested(_) => return,
             };
 
-            let msg = format!("braces around {} is unnecessary", node_name);
-            cx.span_lint(UNUSED_IMPORT_BRACES, item.span, &msg);
+            cx.struct_span_lint(UNUSED_IMPORT_BRACES, item.span, |lint| {
+                lint.build(&format!("braces around {} is unnecessary", node_name)).emit()
+            });
         }
     }
 }
@@ -662,15 +675,17 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedAllocation {
 
         for adj in cx.tables.expr_adjustments(e) {
             if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind {
-                let msg = match m {
-                    adjustment::AutoBorrowMutability::Not => {
-                        "unnecessary allocation, use `&` instead"
-                    }
-                    adjustment::AutoBorrowMutability::Mut { .. } => {
-                        "unnecessary allocation, use `&mut` instead"
-                    }
-                };
-                cx.span_lint(UNUSED_ALLOCATION, e.span, msg);
+                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()
+                });
             }
         }
     }
diff --git a/src/librustc_mir/borrow_check/mod.rs b/src/librustc_mir/borrow_check/mod.rs
index e528159fcef17..82a9c631b727e 100644
--- a/src/librustc_mir/borrow_check/mod.rs
+++ b/src/librustc_mir/borrow_check/mod.rs
@@ -319,18 +319,20 @@ fn do_mir_borrowck<'a, 'tcx>(
         };
 
         // Span and message don't matter; we overwrite them below anyway
-        let mut diag = mbcx.infcx.tcx.struct_span_lint_hir(
+        mbcx.infcx.tcx.struct_span_lint_hir(
             MUTABLE_BORROW_RESERVATION_CONFLICT,
             lint_root,
             DUMMY_SP,
-            "",
-        );
+            |lint| {
+                let mut diag = lint.build("");
 
-        diag.message = initial_diag.styled_message().clone();
-        diag.span = initial_diag.span.clone();
+                diag.message = initial_diag.styled_message().clone();
+                diag.span = initial_diag.span.clone();
 
+                diag.buffer(&mut mbcx.errors_buffer);
+            },
+        );
         initial_diag.cancel();
-        diag.buffer(&mut mbcx.errors_buffer);
     }
 
     // For each non-user used mutable variable, check if it's been assigned from
@@ -376,20 +378,17 @@ fn do_mir_borrowck<'a, 'tcx>(
             continue;
         }
 
-        let mut_span = tcx.sess.source_map().span_until_non_whitespace(span);
-        tcx.struct_span_lint_hir(
-            UNUSED_MUT,
-            lint_root,
-            span,
-            "variable does not need to be mutable",
-        )
-        .span_suggestion_short(
-            mut_span,
-            "remove this `mut`",
-            String::new(),
-            Applicability::MachineApplicable,
-        )
-        .emit();
+        tcx.struct_span_lint_hir(UNUSED_MUT, lint_root, span, |lint| {
+            let mut_span = tcx.sess.source_map().span_until_non_whitespace(span);
+            lint.build("variable does not need to be mutable")
+                .span_suggestion_short(
+                    mut_span,
+                    "remove this `mut`",
+                    String::new(),
+                    Applicability::MachineApplicable,
+                )
+                .emit();
+        })
     }
 
     // Buffer any move errors that we collected and de-duplicated.
diff --git a/src/librustc_mir/const_eval/eval_queries.rs b/src/librustc_mir/const_eval/eval_queries.rs
index 428db8356b122..2e8e4dac237bc 100644
--- a/src/librustc_mir/const_eval/eval_queries.rs
+++ b/src/librustc_mir/const_eval/eval_queries.rs
@@ -209,12 +209,11 @@ fn validate_and_turn_into_const<'tcx>(
 
     val.map_err(|error| {
         let err = error_to_const_error(&ecx, error);
-        match err.struct_error(ecx.tcx, "it is undefined behavior to use this value") {
-            Ok(mut diag) => {
-                diag.note(note_on_undefined_behavior_error());
-                diag.emit();
-                ErrorHandled::Reported
-            }
+        match err.struct_error(ecx.tcx, "it is undefined behavior to use this value", |mut diag| {
+            diag.note(note_on_undefined_behavior_error());
+            diag.emit();
+        }) {
+            Ok(_) => ErrorHandled::Reported,
             Err(err) => err,
         }
     })
diff --git a/src/librustc_mir/interpret/intern.rs b/src/librustc_mir/interpret/intern.rs
index 0c65b77a38240..9fea82d78c94f 100644
--- a/src/librustc_mir/interpret/intern.rs
+++ b/src/librustc_mir/interpret/intern.rs
@@ -326,12 +326,15 @@ pub fn intern_const_alloc_recursive<M: CompileTimeMachine<'mir, 'tcx>>(
             // to read enum discriminants in order to find references in enum variant fields.
             if let err_unsup!(ValidationFailure(_)) = error.kind {
                 let err = crate::const_eval::error_to_const_error(&ecx, error);
-                match err.struct_error(ecx.tcx, "it is undefined behavior to use this value") {
-                    Ok(mut diag) => {
+                match err.struct_error(
+                    ecx.tcx,
+                    "it is undefined behavior to use this value",
+                    |mut diag| {
                         diag.note(crate::const_eval::note_on_undefined_behavior_error());
                         diag.emit();
-                    }
-                    Err(ErrorHandled::TooGeneric) | Err(ErrorHandled::Reported) => {}
+                    },
+                ) {
+                    Ok(()) | Err(ErrorHandled::TooGeneric) | Err(ErrorHandled::Reported) => {}
                 }
             }
         }
diff --git a/src/librustc_mir/transform/check_unsafety.rs b/src/librustc_mir/transform/check_unsafety.rs
index 8dc185cd82b3b..6e80338c975ed 100644
--- a/src/librustc_mir/transform/check_unsafety.rs
+++ b/src/librustc_mir/transform/check_unsafety.rs
@@ -516,18 +516,20 @@ fn unsafe_derive_on_repr_packed(tcx: TyCtxt<'_>, def_id: DefId) {
         .as_local_hir_id(def_id)
         .unwrap_or_else(|| bug!("checking unsafety for non-local def id {:?}", def_id));
 
-    // FIXME: when we make this a hard error, this should have its
-    // own error code.
-    let message = if tcx.generics_of(def_id).own_requires_monomorphization() {
-        "`#[derive]` can't be used on a `#[repr(packed)]` struct with \
-         type or const parameters (error E0133)"
-            .to_string()
-    } else {
-        "`#[derive]` can't be used on a `#[repr(packed)]` struct that \
-         does not derive Copy (error E0133)"
-            .to_string()
-    };
-    tcx.lint_hir(SAFE_PACKED_BORROWS, lint_hir_id, tcx.def_span(def_id), &message);
+    tcx.struct_span_lint_hir(SAFE_PACKED_BORROWS, lint_hir_id, tcx.def_span(def_id), |lint| {
+        // FIXME: when we make this a hard error, this should have its
+        // own error code.
+        let message = if tcx.generics_of(def_id).own_requires_monomorphization() {
+            "`#[derive]` can't be used on a `#[repr(packed)]` struct with \
+             type or const parameters (error E0133)"
+                .to_string()
+        } else {
+            "`#[derive]` can't be used on a `#[repr(packed)]` struct that \
+             does not derive Copy (error E0133)"
+                .to_string()
+        };
+        lint.build(&message).emit()
+    });
 }
 
 /// Returns the `HirId` for an enclosing scope that is also `unsafe`.
@@ -558,16 +560,18 @@ fn is_enclosed(
 
 fn report_unused_unsafe(tcx: TyCtxt<'_>, used_unsafe: &FxHashSet<hir::HirId>, id: hir::HirId) {
     let span = tcx.sess.source_map().def_span(tcx.hir().span(id));
-    let msg = "unnecessary `unsafe` block";
-    let mut db = tcx.struct_span_lint_hir(UNUSED_UNSAFE, id, span, msg);
-    db.span_label(span, msg);
-    if let Some((kind, id)) = is_enclosed(tcx, used_unsafe, id) {
-        db.span_label(
-            tcx.sess.source_map().def_span(tcx.hir().span(id)),
-            format!("because it's nested under this `unsafe` {}", kind),
-        );
-    }
-    db.emit();
+    tcx.struct_span_lint_hir(UNUSED_UNSAFE, id, span, |lint| {
+        let msg = "unnecessary `unsafe` block";
+        let mut db = lint.build(msg);
+        db.span_label(span, msg);
+        if let Some((kind, id)) = is_enclosed(tcx, used_unsafe, id) {
+            db.span_label(
+                tcx.sess.source_map().def_span(tcx.hir().span(id)),
+                format!("because it's nested under this `unsafe` {}", kind),
+            );
+        }
+        db.emit();
+    });
 }
 
 fn builtin_derive_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
@@ -619,13 +623,15 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: DefId) {
                         SAFE_PACKED_BORROWS,
                         lint_hir_id,
                         source_info.span,
-                        &format!(
-                            "{} is unsafe and requires unsafe function or block (error E0133)",
-                            description
-                        ),
+                        |lint| {
+                            lint.build(&format!(
+                                "{} is unsafe and requires unsafe function or block (error E0133)",
+                                description
+                            ))
+                            .note(&details.as_str())
+                            .emit()
+                        },
                     )
-                    .note(&details.as_str())
-                    .emit();
                 }
             }
         }
diff --git a/src/librustc_mir/transform/const_prop.rs b/src/librustc_mir/transform/const_prop.rs
index d645f6cf183b4..14c0db2def285 100644
--- a/src/librustc_mir/transform/const_prop.rs
+++ b/src/librustc_mir/transform/const_prop.rs
@@ -556,12 +556,14 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
             let r_bits = r.to_scalar().and_then(|r| r.to_bits(right_size));
             if r_bits.map_or(false, |b| b >= left_bits as u128) {
                 let lint_root = self.lint_root(source_info)?;
-                let dir = if op == BinOp::Shr { "right" } else { "left" };
-                self.tcx.lint_hir(
+                self.tcx.struct_span_lint_hir(
                     ::rustc::lint::builtin::EXCEEDING_BITSHIFTS,
                     lint_root,
                     source_info.span,
-                    &format!("attempt to shift {} with overflow", dir),
+                    |lint| {
+                        let dir = if op == BinOp::Shr { "right" } else { "left" };
+                        lint.build(&format!("attempt to shift {} with overflow", dir)).emit()
+                    },
                 );
                 return None;
             }
@@ -912,35 +914,49 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
                             .hir()
                             .as_local_hir_id(self.source.def_id())
                             .expect("some part of a failing const eval must be local");
-                        let msg = match msg {
-                            PanicInfo::Overflow(_)
-                            | PanicInfo::OverflowNeg
-                            | PanicInfo::DivisionByZero
-                            | PanicInfo::RemainderByZero => msg.description().to_owned(),
-                            PanicInfo::BoundsCheck { ref len, ref index } => {
-                                let len =
-                                    self.eval_operand(len, source_info).expect("len must be const");
-                                let len = match self.ecx.read_scalar(len) {
-                                    Ok(ScalarMaybeUndef::Scalar(Scalar::Raw { data, .. })) => data,
-                                    other => bug!("const len not primitive: {:?}", other),
-                                };
-                                let index = self
-                                    .eval_operand(index, source_info)
-                                    .expect("index must be const");
-                                let index = match self.ecx.read_scalar(index) {
-                                    Ok(ScalarMaybeUndef::Scalar(Scalar::Raw { data, .. })) => data,
-                                    other => bug!("const index not primitive: {:?}", other),
+                        self.tcx.struct_span_lint_hir(
+                            ::rustc::lint::builtin::CONST_ERR,
+                            hir_id,
+                            span,
+                            |lint| {
+                                let msg = match msg {
+                                    PanicInfo::Overflow(_)
+                                    | PanicInfo::OverflowNeg
+                                    | PanicInfo::DivisionByZero
+                                    | PanicInfo::RemainderByZero => msg.description().to_owned(),
+                                    PanicInfo::BoundsCheck { ref len, ref index } => {
+                                        let len = self
+                                            .eval_operand(len, source_info)
+                                            .expect("len must be const");
+                                        let len = match self.ecx.read_scalar(len) {
+                                            Ok(ScalarMaybeUndef::Scalar(Scalar::Raw {
+                                                data,
+                                                ..
+                                            })) => data,
+                                            other => bug!("const len not primitive: {:?}", other),
+                                        };
+                                        let index = self
+                                            .eval_operand(index, source_info)
+                                            .expect("index must be const");
+                                        let index = match self.ecx.read_scalar(index) {
+                                            Ok(ScalarMaybeUndef::Scalar(Scalar::Raw {
+                                                data,
+                                                ..
+                                            })) => data,
+                                            other => bug!("const index not primitive: {:?}", other),
+                                        };
+                                        format!(
+                                            "index out of bounds: \
+                                            the len is {} but the index is {}",
+                                            len, index,
+                                        )
+                                    }
+                                    // Need proper const propagator for these
+                                    _ => return,
                                 };
-                                format!(
-                                    "index out of bounds: \
-                                    the len is {} but the index is {}",
-                                    len, index,
-                                )
-                            }
-                            // Need proper const propagator for these
-                            _ => return,
-                        };
-                        self.tcx.lint_hir(::rustc::lint::builtin::CONST_ERR, hir_id, span, &msg);
+                                lint.build(&msg).emit()
+                            },
+                        );
                     } else {
                         if self.should_const_prop(value) {
                             if let ScalarMaybeUndef::Scalar(scalar) = value_const {
diff --git a/src/librustc_mir_build/hair/pattern/_match.rs b/src/librustc_mir_build/hair/pattern/_match.rs
index 4f0e5bb45822b..7ed6c81eb63bd 100644
--- a/src/librustc_mir_build/hair/pattern/_match.rs
+++ b/src/librustc_mir_build/hair/pattern/_match.rs
@@ -2235,24 +2235,26 @@ fn lint_overlapping_patterns<'tcx>(
     overlaps: Vec<IntRange<'tcx>>,
 ) {
     if let (true, Some(hir_id)) = (!overlaps.is_empty(), hir_id) {
-        let mut err = tcx.struct_span_lint_hir(
+        tcx.struct_span_lint_hir(
             lint::builtin::OVERLAPPING_PATTERNS,
             hir_id,
             ctor_range.span,
-            "multiple patterns covering the same range",
+            |lint| {
+                let mut err = lint.build("multiple patterns covering the same range");
+                err.span_label(ctor_range.span, "overlapping patterns");
+                for int_range in overlaps {
+                    // Use the real type for user display of the ranges:
+                    err.span_label(
+                        int_range.span,
+                        &format!(
+                            "this range overlaps on `{}`",
+                            IntRange { range: int_range.range, ty, span: DUMMY_SP }.to_pat(tcx),
+                        ),
+                    );
+                }
+                err.emit();
+            },
         );
-        err.span_label(ctor_range.span, "overlapping patterns");
-        for int_range in overlaps {
-            // Use the real type for user display of the ranges:
-            err.span_label(
-                int_range.span,
-                &format!(
-                    "this range overlaps on `{}`",
-                    IntRange { range: int_range.range, ty, span: DUMMY_SP }.to_pat(tcx),
-                ),
-            );
-        }
-        err.emit();
     }
 }
 
diff --git a/src/librustc_mir_build/hair/pattern/check_match.rs b/src/librustc_mir_build/hair/pattern/check_match.rs
index b77bd4ecb8e1f..651f2f70d9bfb 100644
--- a/src/librustc_mir_build/hair/pattern/check_match.rs
+++ b/src/librustc_mir_build/hair/pattern/check_match.rs
@@ -286,26 +286,27 @@ fn check_for_bindings_named_same_as_variants(cx: &MatchVisitor<'_, '_>, pat: &Pa
                             variant.ident == ident && variant.ctor_kind == CtorKind::Const
                         })
                     {
-                        let ty_path = cx.tcx.def_path_str(edef.did);
-                        cx.tcx
-                            .struct_span_lint_hir(
-                                BINDINGS_WITH_VARIANT_NAME,
-                                p.hir_id,
-                                p.span,
-                                &format!(
+                        cx.tcx.struct_span_lint_hir(
+                            BINDINGS_WITH_VARIANT_NAME,
+                            p.hir_id,
+                            p.span,
+                            |lint| {
+                                let ty_path = cx.tcx.def_path_str(edef.did);
+                                lint.build(&format!(
                                     "pattern binding `{}` is named the same as one \
-                                    of the variants of the type `{}`",
+                                                of the variants of the type `{}`",
                                     ident, ty_path
-                                ),
-                            )
-                            .code(error_code!(E0170))
-                            .span_suggestion(
-                                p.span,
-                                "to match on the variant, qualify the path",
-                                format!("{}::{}", ty_path, ident),
-                                Applicability::MachineApplicable,
-                            )
-                            .emit();
+                                ))
+                                .code(error_code!(E0170))
+                                .span_suggestion(
+                                    p.span,
+                                    "to match on the variant, qualify the path",
+                                    format!("{}::{}", ty_path, ident),
+                                    Applicability::MachineApplicable,
+                                )
+                                .emit();
+                            },
+                        )
                     }
                 }
             }
@@ -325,22 +326,26 @@ fn pat_is_catchall(pat: &super::Pat<'_>) -> bool {
 }
 
 fn unreachable_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, catchall: Option<Span>) {
-    let mut err = tcx.struct_span_lint_hir(UNREACHABLE_PATTERNS, id, span, "unreachable pattern");
-    if let Some(catchall) = catchall {
-        // We had a catchall pattern, hint at that.
-        err.span_label(span, "unreachable pattern");
-        err.span_label(catchall, "matches any value");
-    }
-    err.emit();
+    tcx.struct_span_lint_hir(UNREACHABLE_PATTERNS, id, span, |lint| {
+        let mut err = lint.build("unreachable pattern");
+        if let Some(catchall) = catchall {
+            // We had a catchall pattern, hint at that.
+            err.span_label(span, "unreachable pattern");
+            err.span_label(catchall, "matches any value");
+        }
+        err.emit();
+    });
 }
 
 fn irrefutable_let_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, source: hir::MatchSource) {
-    let msg = match source {
-        hir::MatchSource::IfLetDesugar { .. } => "irrefutable if-let pattern",
-        hir::MatchSource::WhileLetDesugar => "irrefutable while-let pattern",
-        _ => bug!(),
-    };
-    tcx.lint_hir(IRREFUTABLE_LET_PATTERNS, id, span, msg);
+    tcx.struct_span_lint_hir(IRREFUTABLE_LET_PATTERNS, id, span, |lint| {
+        let msg = match source {
+            hir::MatchSource::IfLetDesugar { .. } => "irrefutable if-let pattern",
+            hir::MatchSource::WhileLetDesugar => "irrefutable while-let pattern",
+            _ => bug!(),
+        };
+        lint.build(msg).emit()
+    });
 }
 
 /// Check for unreachable patterns.
diff --git a/src/librustc_mir_build/hair/pattern/const_to_pat.rs b/src/librustc_mir_build/hair/pattern/const_to_pat.rs
index a21a0ee8a1b20..5fbe764430c54 100644
--- a/src/librustc_mir_build/hair/pattern/const_to_pat.rs
+++ b/src/librustc_mir_build/hair/pattern/const_to_pat.rs
@@ -109,11 +109,14 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
                     }
                 };
                 let path = self.tcx().def_path_str(adt_def.did);
-                let msg = format!(
-                    "to use a constant of type `{}` in a pattern, \
-                     `{}` must be annotated with `#[derive(PartialEq, Eq)]`",
-                    path, path,
-                );
+
+                let make_msg = || -> String {
+                    format!(
+                        "to use a constant of type `{}` in a pattern, \
+                         `{}` must be annotated with `#[derive(PartialEq, Eq)]`",
+                        path, path,
+                    )
+                };
 
                 // double-check there even *is* a semantic `PartialEq` to dispatch to.
                 //
@@ -143,13 +146,13 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
 
                 if !ty_is_partial_eq {
                     // span_fatal avoids ICE from resolution of non-existent method (rare case).
-                    self.tcx().sess.span_fatal(self.span, &msg);
+                    self.tcx().sess.span_fatal(self.span, &make_msg());
                 } else {
-                    self.tcx().lint_hir(
+                    self.tcx().struct_span_lint_hir(
                         lint::builtin::INDIRECT_STRUCTURAL_MATCH,
                         self.id,
                         self.span,
-                        &msg,
+                        |lint| lint.build(&make_msg()).emit(),
                     );
                 }
             }
@@ -177,11 +180,11 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
 
         let kind = match cv.ty.kind {
             ty::Float(_) => {
-                tcx.lint_hir(
+                tcx.struct_span_lint_hir(
                     ::rustc::lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
                     id,
                     span,
-                    "floating-point types cannot be used in patterns",
+                    |lint| lint.build("floating-point types cannot be used in patterns").emit(),
                 );
                 PatKind::Constant { value: cv }
             }
diff --git a/src/librustc_mir_build/lints.rs b/src/librustc_mir_build/lints.rs
index 4244e1b8d80a6..0017f800de702 100644
--- a/src/librustc_mir_build/lints.rs
+++ b/src/librustc_mir_build/lints.rs
@@ -124,18 +124,15 @@ fn check_fn_for_unconditional_recursion<'tcx>(
     if !reached_exit_without_self_call && !self_call_locations.is_empty() {
         let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
         let sp = tcx.sess.source_map().def_span(tcx.hir().span(hir_id));
-        let mut db = tcx.struct_span_lint_hir(
-            UNCONDITIONAL_RECURSION,
-            hir_id,
-            sp,
-            "function cannot return without recursing",
-        );
-        db.span_label(sp, "cannot return without recursing");
-        // offer some help to the programmer.
-        for location in &self_call_locations {
-            db.span_label(location.span, "recursive call site");
-        }
-        db.help("a `loop` may express intention better if this is on purpose");
-        db.emit();
+        tcx.struct_span_lint_hir(UNCONDITIONAL_RECURSION, hir_id, sp, |lint| {
+            let mut db = lint.build("function cannot return without recursing");
+            db.span_label(sp, "cannot return without recursing");
+            // offer some help to the programmer.
+            for location in &self_call_locations {
+                db.span_label(location.span, "recursive call site");
+            }
+            db.help("a `loop` may express intention better if this is on purpose");
+            db.emit();
+        });
     }
 }
diff --git a/src/librustc_parse/config.rs b/src/librustc_parse/config.rs
index 0edd56680f933..8dec64c579e88 100644
--- a/src/librustc_parse/config.rs
+++ b/src/librustc_parse/config.rs
@@ -315,10 +315,11 @@ impl<'a> StripUnconfigured<'a> {
                 validate_attr::check_meta_bad_delim(self.sess, dspan, delim, msg);
                 match parse_in(self.sess, tts.clone(), "`cfg_attr` input", |p| p.parse_cfg_attr()) {
                     Ok(r) => return Some(r),
-                    Err(mut e) => e
-                        .help(&format!("the valid syntax is `{}`", CFG_ATTR_GRAMMAR_HELP))
-                        .note(CFG_ATTR_NOTE_REF)
-                        .emit(),
+                    Err(mut e) => {
+                        e.help(&format!("the valid syntax is `{}`", CFG_ATTR_GRAMMAR_HELP))
+                            .note(CFG_ATTR_NOTE_REF)
+                            .emit();
+                    }
                 }
             }
             _ => self.error_malformed_cfg_attr_missing(attr.span),
diff --git a/src/librustc_parse/lexer/unescape_error_reporting.rs b/src/librustc_parse/lexer/unescape_error_reporting.rs
index 88762dabd8a29..d41775a143ad6 100644
--- a/src/librustc_parse/lexer/unescape_error_reporting.rs
+++ b/src/librustc_parse/lexer/unescape_error_reporting.rs
@@ -69,7 +69,7 @@ pub(crate) fn emit_unescape_error(
                     format!("\"{}\"", lit),
                     Applicability::MachineApplicable,
                 )
-                .emit()
+                .emit();
         }
         EscapeError::EscapeOnlyChar => {
             let (c, _span) = last_char();
diff --git a/src/librustc_parse/parser/attr.rs b/src/librustc_parse/parser/attr.rs
index e58eb9ffc51e7..905f6bef58e85 100644
--- a/src/librustc_parse/parser/attr.rs
+++ b/src/librustc_parse/parser/attr.rs
@@ -149,7 +149,7 @@ impl<'a> Parser<'a> {
                                    source files. Outer attributes, like `#[test]`, annotate the \
                                    item following them.",
                             )
-                            .emit()
+                            .emit();
                     }
                 }
 
@@ -239,7 +239,7 @@ impl<'a> Parser<'a> {
                                     (`1u8`, `1.0f32`, etc.), use an unsuffixed version \
                                     (`1`, `1.0`, etc.)",
                 )
-                .emit()
+                .emit();
         }
 
         Ok(lit)
diff --git a/src/librustc_parse/parser/diagnostics.rs b/src/librustc_parse/parser/diagnostics.rs
index 237b3cc13d394..018aef3c13cee 100644
--- a/src/librustc_parse/parser/diagnostics.rs
+++ b/src/librustc_parse/parser/diagnostics.rs
@@ -1014,7 +1014,7 @@ impl<'a> Parser<'a> {
                     String::new(),
                     Applicability::MachineApplicable,
                 )
-                .emit()
+                .emit();
         }
     }
 
diff --git a/src/librustc_parse/parser/mod.rs b/src/librustc_parse/parser/mod.rs
index 7131eb1144e06..21a738c7f7b18 100644
--- a/src/librustc_parse/parser/mod.rs
+++ b/src/librustc_parse/parser/mod.rs
@@ -1407,6 +1407,8 @@ pub fn emit_unclosed_delims(unclosed_delims: &mut Vec<UnmatchedBrace>, sess: &Pa
     *sess.reached_eof.borrow_mut() |=
         unclosed_delims.iter().any(|unmatched_delim| unmatched_delim.found_delim.is_none());
     for unmatched in unclosed_delims.drain(..) {
-        make_unclosed_delims_error(unmatched, sess).map(|mut e| e.emit());
+        make_unclosed_delims_error(unmatched, sess).map(|mut e| {
+            e.emit();
+        });
     }
 }
diff --git a/src/librustc_parse/parser/pat.rs b/src/librustc_parse/parser/pat.rs
index 985185230f26a..ec6d4db610285 100644
--- a/src/librustc_parse/parser/pat.rs
+++ b/src/librustc_parse/parser/pat.rs
@@ -572,7 +572,7 @@ impl<'a> Parser<'a> {
         self.struct_span_err(span, problem)
             .span_suggestion(span, suggestion, fix, Applicability::MachineApplicable)
             .note("`mut` may be followed by `variable` and `variable @ pattern`")
-            .emit()
+            .emit();
     }
 
     /// Eat any extraneous `mut`s and error + recover if we ate any.
diff --git a/src/librustc_parse/validate_attr.rs b/src/librustc_parse/validate_attr.rs
index 84562fbb46ff2..f5e47608d589d 100644
--- a/src/librustc_parse/validate_attr.rs
+++ b/src/librustc_parse/validate_attr.rs
@@ -27,7 +27,11 @@ pub fn check_meta(sess: &ParseSess, attr: &Attribute) {
         _ => {
             if let MacArgs::Eq(..) = attr.get_normal_item().args {
                 // All key-value attributes are restricted to meta-item syntax.
-                parse_meta(sess, attr).map_err(|mut err| err.emit()).ok();
+                parse_meta(sess, attr)
+                    .map_err(|mut err| {
+                        err.emit();
+                    })
+                    .ok();
             }
         }
     }
@@ -152,6 +156,8 @@ pub fn check_builtin_attribute(
                 }
             }
         }
-        Err(mut err) => err.emit(),
+        Err(mut err) => {
+            err.emit();
+        }
     }
 }
diff --git a/src/librustc_passes/check_attr.rs b/src/librustc_passes/check_attr.rs
index a877c1de175e1..ecffd615e71c0 100644
--- a/src/librustc_passes/check_attr.rs
+++ b/src/librustc_passes/check_attr.rs
@@ -92,14 +92,9 @@ impl CheckAttrVisitor<'tcx> {
             | Target::Method(MethodKind::Trait { body: true })
             | Target::Method(MethodKind::Inherent) => true,
             Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
-                self.tcx
-                    .struct_span_lint_hir(
-                        UNUSED_ATTRIBUTES,
-                        hir_id,
-                        attr.span,
-                        "`#[inline]` is ignored on function prototypes",
-                    )
-                    .emit();
+                self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
+                    lint.build("`#[inline]` is ignored on function prototypes").emit()
+                });
                 true
             }
             // FIXME(#65833): We permit associated consts to have an `#[inline]` attribute with
@@ -107,23 +102,19 @@ impl CheckAttrVisitor<'tcx> {
             // accidentally, to to be compatible with crates depending on them, we can't throw an
             // error here.
             Target::AssocConst => {
-                self.tcx
-                    .struct_span_lint_hir(
-                        UNUSED_ATTRIBUTES,
-                        hir_id,
-                        attr.span,
-                        "`#[inline]` is ignored on constants",
-                    )
-                    .warn(
-                        "this was previously accepted by the compiler but is \
-                       being phased out; it will become a hard error in \
-                       a future release!",
-                    )
-                    .note(
-                        "see issue #65833 <https://github.com/rust-lang/rust/issues/65833> \
-                         for more information",
-                    )
-                    .emit();
+                self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
+                    lint.build("`#[inline]` is ignored on constants")
+                        .warn(
+                            "this was previously accepted by the compiler but is \
+                               being phased out; it will become a hard error in \
+                               a future release!",
+                        )
+                        .note(
+                            "see issue #65833 <https://github.com/rust-lang/rust/issues/65833> \
+                                 for more information",
+                        )
+                        .emit();
+                });
                 true
             }
             _ => {
@@ -331,15 +322,16 @@ impl CheckAttrVisitor<'tcx> {
             || (is_simd && is_c)
             || (int_reprs == 1 && is_c && item.map_or(false, |item| is_c_like_enum(item)))
         {
-            self.tcx
-                .struct_span_lint_hir(
-                    CONFLICTING_REPR_HINTS,
-                    hir_id,
-                    hint_spans.collect::<Vec<Span>>(),
-                    "conflicting representation hints",
-                )
-                .code(rustc_errors::error_code!(E0566))
-                .emit();
+            self.tcx.struct_span_lint_hir(
+                CONFLICTING_REPR_HINTS,
+                hir_id,
+                hint_spans.collect::<Vec<Span>>(),
+                |lint| {
+                    lint.build("conflicting representation hints")
+                        .code(rustc_errors::error_code!(E0566))
+                        .emit();
+                },
+            );
         }
     }
 
diff --git a/src/librustc_passes/dead.rs b/src/librustc_passes/dead.rs
index 9367909d10c30..25b8b8fcd444b 100644
--- a/src/librustc_passes/dead.rs
+++ b/src/librustc_passes/dead.rs
@@ -554,12 +554,9 @@ impl DeadVisitor<'tcx> {
         participle: &str,
     ) {
         if !name.as_str().starts_with("_") {
-            self.tcx.lint_hir(
-                lint::builtin::DEAD_CODE,
-                id,
-                span,
-                &format!("{} is never {}: `{}`", node_type, participle, name),
-            );
+            self.tcx.struct_span_lint_hir(lint::builtin::DEAD_CODE, id, span, |lint| {
+                lint.build(&format!("{} is never {}: `{}`", node_type, participle, name)).emit()
+            });
         }
     }
 }
diff --git a/src/librustc_passes/liveness.rs b/src/librustc_passes/liveness.rs
index b355a47c39470..709068d218937 100644
--- a/src/librustc_passes/liveness.rs
+++ b/src/librustc_passes/liveness.rs
@@ -1521,45 +1521,47 @@ impl<'tcx> Liveness<'_, 'tcx> {
                 if ln == self.s.exit_ln { false } else { self.assigned_on_exit(ln, var).is_some() };
 
             if is_assigned {
-                self.ir
-                    .tcx
-                    .struct_span_lint_hir(
-                        lint::builtin::UNUSED_VARIABLES,
-                        hir_id,
-                        spans,
-                        &format!("variable `{}` is assigned to, but never used", name),
-                    )
-                    .note(&format!("consider using `_{}` instead", name))
-                    .emit();
+                self.ir.tcx.struct_span_lint_hir(
+                    lint::builtin::UNUSED_VARIABLES,
+                    hir_id,
+                    spans,
+                    |lint| {
+                        lint.build(&format!("variable `{}` is assigned to, but never used", name))
+                            .note(&format!("consider using `_{}` instead", name))
+                            .emit();
+                    },
+                )
             } else {
-                let mut err = self.ir.tcx.struct_span_lint_hir(
+                self.ir.tcx.struct_span_lint_hir(
                     lint::builtin::UNUSED_VARIABLES,
                     hir_id,
                     spans.clone(),
-                    &format!("unused variable: `{}`", name),
+                    |lint| {
+                        let mut err = lint.build(&format!("unused variable: `{}`", name));
+                        if self.ir.variable_is_shorthand(var) {
+                            if let Node::Binding(pat) = self.ir.tcx.hir().get(hir_id) {
+                                // Handle `ref` and `ref mut`.
+                                let spans = spans
+                                    .iter()
+                                    .map(|_span| (pat.span, format!("{}: _", name)))
+                                    .collect();
+
+                                err.multipart_suggestion(
+                                    "try ignoring the field",
+                                    spans,
+                                    Applicability::MachineApplicable,
+                                );
+                            }
+                        } else {
+                            err.multipart_suggestion(
+                                "consider prefixing with an underscore",
+                                spans.iter().map(|span| (*span, format!("_{}", name))).collect(),
+                                Applicability::MachineApplicable,
+                            );
+                        }
+                        err.emit()
+                    },
                 );
-
-                if self.ir.variable_is_shorthand(var) {
-                    if let Node::Binding(pat) = self.ir.tcx.hir().get(hir_id) {
-                        // Handle `ref` and `ref mut`.
-                        let spans =
-                            spans.iter().map(|_span| (pat.span, format!("{}: _", name))).collect();
-
-                        err.multipart_suggestion(
-                            "try ignoring the field",
-                            spans,
-                            Applicability::MachineApplicable,
-                        );
-                    }
-                } else {
-                    err.multipart_suggestion(
-                        "consider prefixing with an underscore",
-                        spans.iter().map(|span| (*span, format!("_{}", name))).collect(),
-                        Applicability::MachineApplicable,
-                    );
-                }
-
-                err.emit()
             }
         }
     }
@@ -1573,27 +1575,27 @@ impl<'tcx> Liveness<'_, 'tcx> {
     fn report_dead_assign(&self, hir_id: HirId, spans: Vec<Span>, var: Variable, is_param: bool) {
         if let Some(name) = self.should_warn(var) {
             if is_param {
-                self.ir
-                    .tcx
-                    .struct_span_lint_hir(
-                        lint::builtin::UNUSED_ASSIGNMENTS,
-                        hir_id,
-                        spans,
-                        &format!("value passed to `{}` is never read", name),
-                    )
-                    .help("maybe it is overwritten before being read?")
-                    .emit();
+                self.ir.tcx.struct_span_lint_hir(
+                    lint::builtin::UNUSED_ASSIGNMENTS,
+                    hir_id,
+                    spans,
+                    |lint| {
+                        lint.build(&format!("value passed to `{}` is never read", name))
+                            .help("maybe it is overwritten before being read?")
+                            .emit();
+                    },
+                )
             } else {
-                self.ir
-                    .tcx
-                    .struct_span_lint_hir(
-                        lint::builtin::UNUSED_ASSIGNMENTS,
-                        hir_id,
-                        spans,
-                        &format!("value assigned to `{}` is never read", name),
-                    )
-                    .help("maybe it is overwritten before being read?")
-                    .emit();
+                self.ir.tcx.struct_span_lint_hir(
+                    lint::builtin::UNUSED_ASSIGNMENTS,
+                    hir_id,
+                    spans,
+                    |lint| {
+                        lint.build(&format!("value assigned to `{}` is never read", name))
+                            .help("maybe it is overwritten before being read?")
+                            .emit();
+                    },
+                )
             }
         }
     }
diff --git a/src/librustc_passes/stability.rs b/src/librustc_passes/stability.rs
index 12debfb66a431..4e2085d07a39a 100644
--- a/src/librustc_passes/stability.rs
+++ b/src/librustc_passes/stability.rs
@@ -604,16 +604,14 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
 }
 
 fn unnecessary_stable_feature_lint(tcx: TyCtxt<'_>, span: Span, feature: Symbol, since: Symbol) {
-    tcx.lint_hir(
-        lint::builtin::STABLE_FEATURES,
-        hir::CRATE_HIR_ID,
-        span,
-        &format!(
+    tcx.struct_span_lint_hir(lint::builtin::STABLE_FEATURES, hir::CRATE_HIR_ID, span, |lint| {
+        lint.build(&format!(
             "the feature `{}` has been stable since {} and no longer requires \
-                  an attribute to enable",
+                      an attribute to enable",
             feature, since
-        ),
-    );
+        ))
+        .emit();
+    });
 }
 
 fn duplicate_feature_err(sess: &Session, span: Span, feature: Symbol) {
diff --git a/src/librustc_privacy/lib.rs b/src/librustc_privacy/lib.rs
index 74bb72d6fad7f..ef1e99c5a64be 100644
--- a/src/librustc_privacy/lib.rs
+++ b/src/librustc_privacy/lib.rs
@@ -1781,17 +1781,20 @@ impl SearchInterfaceForPrivateItemsVisitor<'tcx> {
 
     fn check_def_id(&mut self, def_id: DefId, kind: &str, descr: &dyn fmt::Display) -> bool {
         if self.leaks_private_dep(def_id) {
-            self.tcx.lint_hir(
+            self.tcx.struct_span_lint_hir(
                 lint::builtin::EXPORTED_PRIVATE_DEPENDENCIES,
                 self.item_id,
                 self.span,
-                &format!(
-                    "{} `{}` from private dependency '{}' in public \
-                                        interface",
-                    kind,
-                    descr,
-                    self.tcx.crate_name(def_id.krate)
-                ),
+                |lint| {
+                    lint.build(&format!(
+                        "{} `{}` from private dependency '{}' in public \
+                                                interface",
+                        kind,
+                        descr,
+                        self.tcx.crate_name(def_id.krate)
+                    ))
+                    .emit()
+                },
             );
         }
 
@@ -1802,23 +1805,23 @@ impl SearchInterfaceForPrivateItemsVisitor<'tcx> {
 
         let (vis, vis_span, vis_descr) = def_id_visibility(self.tcx, def_id);
         if !vis.is_at_least(self.required_visibility, self.tcx) {
-            let msg = format!("{} {} `{}` in public interface", vis_descr, kind, descr);
+            let make_msg = || format!("{} {} `{}` in public interface", vis_descr, kind, descr);
             if self.has_pub_restricted || self.has_old_errors || self.in_assoc_ty {
                 let mut err = if kind == "trait" {
-                    struct_span_err!(self.tcx.sess, self.span, E0445, "{}", msg)
+                    struct_span_err!(self.tcx.sess, self.span, E0445, "{}", make_msg())
                 } else {
-                    struct_span_err!(self.tcx.sess, self.span, E0446, "{}", msg)
+                    struct_span_err!(self.tcx.sess, self.span, E0446, "{}", make_msg())
                 };
                 err.span_label(self.span, format!("can't leak {} {}", vis_descr, kind));
                 err.span_label(vis_span, format!("`{}` declared as {}", descr, vis_descr));
                 err.emit();
             } else {
                 let err_code = if kind == "trait" { "E0445" } else { "E0446" };
-                self.tcx.lint_hir(
+                self.tcx.struct_span_lint_hir(
                     lint::builtin::PRIVATE_IN_PUBLIC,
                     hir_id,
                     self.span,
-                    &format!("{} (error {})", msg, err_code),
+                    |lint| lint.build(&format!("{} (error {})", make_msg(), err_code)).emit(),
                 );
             }
         }
diff --git a/src/librustc_resolve/lifetimes.rs b/src/librustc_resolve/lifetimes.rs
index 87522d28d1e80..fd4d2c718c094 100644
--- a/src/librustc_resolve/lifetimes.rs
+++ b/src/librustc_resolve/lifetimes.rs
@@ -1575,22 +1575,28 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
                             }
                         }
 
-                        let mut err = self.tcx.struct_span_lint_hir(
+                        self.tcx.struct_span_lint_hir(
                             lint::builtin::SINGLE_USE_LIFETIMES,
                             id,
                             span,
-                            &format!("lifetime parameter `{}` only used once", name),
+                            |lint| {
+                                let mut err = lint.build(&format!(
+                                    "lifetime parameter `{}` only used once",
+                                    name
+                                ));
+                                if span == lifetime.span {
+                                    // spans are the same for in-band lifetime declarations
+                                    err.span_label(span, "this lifetime is only used here");
+                                } else {
+                                    err.span_label(span, "this lifetime...");
+                                    err.span_label(lifetime.span, "...is used only here");
+                                }
+                                self.suggest_eliding_single_use_lifetime(
+                                    &mut err, def_id, lifetime,
+                                );
+                                err.emit();
+                            },
                         );
-
-                        if span == lifetime.span {
-                            // spans are the same for in-band lifetime declarations
-                            err.span_label(span, "this lifetime is only used here");
-                        } else {
-                            err.span_label(span, "this lifetime...");
-                            err.span_label(lifetime.span, "...is used only here");
-                        }
-                        self.suggest_eliding_single_use_lifetime(&mut err, def_id, lifetime);
-                        err.emit();
                     }
                 }
                 Some(LifetimeUseSet::Many) => {
@@ -1610,26 +1616,32 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
                         _ => None,
                     } {
                         debug!("id ={:?} span = {:?} name = {:?}", id, span, name);
-                        let mut err = self.tcx.struct_span_lint_hir(
+                        self.tcx.struct_span_lint_hir(
                             lint::builtin::UNUSED_LIFETIMES,
                             id,
                             span,
-                            &format!("lifetime parameter `{}` never used", name),
-                        );
-                        if let Some(parent_def_id) = self.tcx.parent(def_id) {
-                            if let Some(generics) = self.tcx.hir().get_generics(parent_def_id) {
-                                let unused_lt_span = self.lifetime_deletion_span(name, generics);
-                                if let Some(span) = unused_lt_span {
-                                    err.span_suggestion(
-                                        span,
-                                        "elide the unused lifetime",
-                                        String::new(),
-                                        Applicability::MachineApplicable,
-                                    );
+                            |lint| {
+                                let mut err = lint
+                                    .build(&format!("lifetime parameter `{}` never used", name));
+                                if let Some(parent_def_id) = self.tcx.parent(def_id) {
+                                    if let Some(generics) =
+                                        self.tcx.hir().get_generics(parent_def_id)
+                                    {
+                                        let unused_lt_span =
+                                            self.lifetime_deletion_span(name, generics);
+                                        if let Some(span) = unused_lt_span {
+                                            err.span_suggestion(
+                                                span,
+                                                "elide the unused lifetime",
+                                                String::new(),
+                                                Applicability::MachineApplicable,
+                                            );
+                                        }
+                                    }
                                 }
-                            }
-                        }
-                        err.emit();
+                                err.emit();
+                            },
+                        );
                     }
                 }
             }
diff --git a/src/librustc_typeck/astconv.rs b/src/librustc_typeck/astconv.rs
index 2909d0f8c54e7..c26b47c313023 100644
--- a/src/librustc_typeck/astconv.rs
+++ b/src/librustc_typeck/astconv.rs
@@ -331,11 +331,11 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
                 } else {
                     let mut multispan = MultiSpan::from_span(span);
                     multispan.push_span_label(span_late, note.to_string());
-                    tcx.lint_hir(
+                    tcx.struct_span_lint_hir(
                         lint::builtin::LATE_BOUND_LIFETIME_ARGUMENTS,
                         args.args[0].id(),
                         multispan,
-                        msg,
+                        |lint| lint.build(msg).emit(),
                     );
                     reported_late_bound_region_err = Some(false);
                 }
@@ -2216,34 +2216,31 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
         tcx.check_stability(item.def_id, Some(hir_ref_id), span);
 
         if let Some(variant_def_id) = variant_resolution {
-            let mut err = tcx.struct_span_lint_hir(
-                AMBIGUOUS_ASSOCIATED_ITEMS,
-                hir_ref_id,
-                span,
-                "ambiguous associated item",
-            );
+            tcx.struct_span_lint_hir(AMBIGUOUS_ASSOCIATED_ITEMS, hir_ref_id, span, |lint| {
+                let mut err = lint.build("ambiguous associated item");
+                let mut could_refer_to = |kind: DefKind, def_id, also| {
+                    let note_msg = format!(
+                        "`{}` could{} refer to the {} defined here",
+                        assoc_ident,
+                        also,
+                        kind.descr(def_id)
+                    );
+                    err.span_note(tcx.def_span(def_id), &note_msg);
+                };
 
-            let mut could_refer_to = |kind: DefKind, def_id, also| {
-                let note_msg = format!(
-                    "`{}` could{} refer to the {} defined here",
-                    assoc_ident,
-                    also,
-                    kind.descr(def_id)
+                could_refer_to(DefKind::Variant, variant_def_id, "");
+                could_refer_to(kind, item.def_id, " also");
+
+                err.span_suggestion(
+                    span,
+                    "use fully-qualified syntax",
+                    format!("<{} as {}>::{}", qself_ty, tcx.item_name(trait_did), assoc_ident),
+                    Applicability::MachineApplicable,
                 );
-                err.span_note(tcx.def_span(def_id), &note_msg);
-            };
-            could_refer_to(DefKind::Variant, variant_def_id, "");
-            could_refer_to(kind, item.def_id, " also");
 
-            err.span_suggestion(
-                span,
-                "use fully-qualified syntax",
-                format!("<{} as {}>::{}", qself_ty, tcx.item_name(trait_did), assoc_ident),
-                Applicability::MachineApplicable,
-            )
-            .emit();
+                err.emit();
+            });
         }
-
         Ok((ty, kind, item.def_id))
     }
 
diff --git a/src/librustc_typeck/check/cast.rs b/src/librustc_typeck/check/cast.rs
index d254a84df72ce..909f40ee98499 100644
--- a/src/librustc_typeck/check/cast.rs
+++ b/src/librustc_typeck/check/cast.rs
@@ -468,23 +468,20 @@ impl<'a, 'tcx> CastCheck<'tcx> {
         } else {
             ("", lint::builtin::TRIVIAL_CASTS)
         };
-        let mut err = fcx.tcx.struct_span_lint_hir(
-            lint,
-            self.expr.hir_id,
-            self.span,
-            &format!(
+        fcx.tcx.struct_span_lint_hir(lint, self.expr.hir_id, self.span, |err| {
+            err.build(&format!(
                 "trivial {}cast: `{}` as `{}`",
                 adjective,
                 fcx.ty_to_string(t_expr),
                 fcx.ty_to_string(t_cast)
-            ),
-        );
-        err.help(&format!(
-            "cast can be replaced by coercion; this might \
-                           require {}a temporary variable",
-            type_asc_or
-        ));
-        err.emit();
+            ))
+            .help(&format!(
+                "cast can be replaced by coercion; this might \
+                                   require {}a temporary variable",
+                type_asc_or
+            ))
+            .emit();
+        });
     }
 
     pub fn check(mut self, fcx: &FnCtxt<'a, 'tcx>) {
diff --git a/src/librustc_typeck/check/method/probe.rs b/src/librustc_typeck/check/method/probe.rs
index 497a401a031e5..760b6487045d8 100644
--- a/src/librustc_typeck/check/method/probe.rs
+++ b/src/librustc_typeck/check/method/probe.rs
@@ -382,11 +382,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     )
                     .emit();
                 } else {
-                    self.tcx.lint_hir(
+                    self.tcx.struct_span_lint_hir(
                         lint::builtin::TYVAR_BEHIND_RAW_POINTER,
                         scope_expr_id,
                         span,
-                        "type annotations needed",
+                        |lint| lint.build("type annotations needed").emit(),
                     );
                 }
             } else {
@@ -1280,33 +1280,36 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
         stable_pick: &Pick<'_>,
         unstable_candidates: &[(&Candidate<'tcx>, Symbol)],
     ) {
-        let mut diag = self.tcx.struct_span_lint_hir(
+        self.tcx.struct_span_lint_hir(
             lint::builtin::UNSTABLE_NAME_COLLISIONS,
             self.fcx.body_id,
             self.span,
-            "a method with this name may be added to the standard library in the future",
-        );
-
-        // FIXME: This should be a `span_suggestion` instead of `help`
-        // However `self.span` only
-        // highlights the method name, so we can't use it. Also consider reusing the code from
-        // `report_method_error()`.
-        diag.help(&format!(
-            "call with fully qualified syntax `{}(...)` to keep using the current method",
-            self.tcx.def_path_str(stable_pick.item.def_id),
-        ));
-
-        if nightly_options::is_nightly_build() {
-            for (candidate, feature) in unstable_candidates {
+            |lint| {
+                let mut diag = lint.build(
+                    "a method with this name may be added to the standard library in the future",
+                );
+                // FIXME: This should be a `span_suggestion` instead of `help`
+                // However `self.span` only
+                // highlights the method name, so we can't use it. Also consider reusing the code from
+                // `report_method_error()`.
                 diag.help(&format!(
-                    "add `#![feature({})]` to the crate attributes to enable `{}`",
-                    feature,
-                    self.tcx.def_path_str(candidate.item.def_id),
+                    "call with fully qualified syntax `{}(...)` to keep using the current method",
+                    self.tcx.def_path_str(stable_pick.item.def_id),
                 ));
-            }
-        }
 
-        diag.emit();
+                if nightly_options::is_nightly_build() {
+                    for (candidate, feature) in unstable_candidates {
+                        diag.help(&format!(
+                            "add `#![feature({})]` to the crate attributes to enable `{}`",
+                            feature,
+                            self.tcx.def_path_str(candidate.item.def_id),
+                        ));
+                    }
+                }
+
+                diag.emit();
+            },
+        );
     }
 
     fn select_trait_candidate(
diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs
index b82a83171d1eb..be2052dce3c0b 100644
--- a/src/librustc_typeck/check/mod.rs
+++ b/src/librustc_typeck/check/mod.rs
@@ -2895,15 +2895,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
                 debug!("warn_if_unreachable: id={:?} span={:?} kind={}", id, span, kind);
 
-                let msg = format!("unreachable {}", kind);
-                self.tcx()
-                    .struct_span_lint_hir(lint::builtin::UNREACHABLE_CODE, id, span, &msg)
-                    .span_label(span, &msg)
-                    .span_label(
-                        orig_span,
-                        custom_note.unwrap_or("any code following this expression is unreachable"),
-                    )
-                    .emit();
+                self.tcx().struct_span_lint_hir(lint::builtin::UNREACHABLE_CODE, id, span, |lint| {
+                    let msg = format!("unreachable {}", kind);
+                    lint.build(&msg)
+                        .span_label(span, &msg)
+                        .span_label(
+                            orig_span,
+                            custom_note
+                                .unwrap_or("any code following this expression is unreachable"),
+                        )
+                        .emit();
+                })
             }
         }
     }
diff --git a/src/librustc_typeck/check_unused.rs b/src/librustc_typeck/check_unused.rs
index ec098c1d89679..dd833d9751cb9 100644
--- a/src/librustc_typeck/check_unused.rs
+++ b/src/librustc_typeck/check_unused.rs
@@ -55,12 +55,14 @@ impl CheckVisitor<'tcx> {
             return;
         }
 
-        let msg = if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
-            format!("unused import: `{}`", snippet)
-        } else {
-            "unused import".to_owned()
-        };
-        self.tcx.lint_hir(lint::builtin::UNUSED_IMPORTS, id, span, &msg);
+        self.tcx.struct_span_lint_hir(lint::builtin::UNUSED_IMPORTS, id, span, |lint| {
+            let msg = if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
+                format!("unused import: `{}`", snippet)
+            } else {
+                "unused import".to_owned()
+            };
+            lint.build(&msg).emit();
+        });
     }
 }
 
@@ -121,23 +123,23 @@ fn unused_crates_lint(tcx: TyCtxt<'_>) {
         // We do this in any edition.
         if extern_crate.warn_if_unused {
             if let Some(&span) = unused_extern_crates.get(&extern_crate.def_id) {
-                let msg = "unused extern crate";
-
-                // Removal suggestion span needs to include attributes (Issue #54400)
-                let span_with_attrs = tcx
-                    .get_attrs(extern_crate.def_id)
-                    .iter()
-                    .map(|attr| attr.span)
-                    .fold(span, |acc, attr_span| acc.to(attr_span));
-
-                tcx.struct_span_lint_hir(lint, id, span, msg)
-                    .span_suggestion_short(
-                        span_with_attrs,
-                        "remove it",
-                        String::new(),
-                        Applicability::MachineApplicable,
-                    )
-                    .emit();
+                tcx.struct_span_lint_hir(lint, id, span, |lint| {
+                    // Removal suggestion span needs to include attributes (Issue #54400)
+                    let span_with_attrs = tcx
+                        .get_attrs(extern_crate.def_id)
+                        .iter()
+                        .map(|attr| attr.span)
+                        .fold(span, |acc, attr_span| acc.to(attr_span));
+
+                    lint.build("unused extern crate")
+                        .span_suggestion_short(
+                            span_with_attrs,
+                            "remove it",
+                            String::new(),
+                            Applicability::MachineApplicable,
+                        )
+                        .emit();
+                });
                 continue;
             }
         }
@@ -168,23 +170,26 @@ fn unused_crates_lint(tcx: TyCtxt<'_>) {
         if !tcx.get_attrs(extern_crate.def_id).is_empty() {
             continue;
         }
-
-        // Otherwise, we can convert it into a `use` of some kind.
-        let msg = "`extern crate` is not idiomatic in the new edition";
-        let help = format!("convert it to a `{}`", visibility_qualified(&item.vis, "use"));
-        let base_replacement = match extern_crate.orig_name {
-            Some(orig_name) => format!("use {} as {};", orig_name, item.ident.name),
-            None => format!("use {};", item.ident.name),
-        };
-        let replacement = visibility_qualified(&item.vis, base_replacement);
-        tcx.struct_span_lint_hir(lint, id, extern_crate.span, msg)
-            .span_suggestion_short(
-                extern_crate.span,
-                &help,
-                replacement,
-                Applicability::MachineApplicable,
-            )
-            .emit();
+        tcx.struct_span_lint_hir(lint, id, extern_crate.span, |lint| {
+            // Otherwise, we can convert it into a `use` of some kind.
+            let base_replacement = match extern_crate.orig_name {
+                Some(orig_name) => format!("use {} as {};", orig_name, item.ident.name),
+                None => format!("use {};", item.ident.name),
+            };
+
+            let replacement = visibility_qualified(&item.vis, base_replacement);
+            let msg = "`extern crate` is not idiomatic in the new edition";
+            let help = format!("convert it to a `{}`", visibility_qualified(&item.vis, "use"));
+
+            lint.build(msg)
+                .span_suggestion_short(
+                    extern_crate.span,
+                    &help,
+                    replacement,
+                    Applicability::MachineApplicable,
+                )
+                .emit();
+        })
     }
 }
 
diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs
index 1a3016cd9d33b..b661006d1ddd0 100644
--- a/src/librustc_typeck/collect.rs
+++ b/src/librustc_typeck/collect.rs
@@ -1151,14 +1151,17 @@ fn generics_of(tcx: TyCtxt<'_>, def_id: DefId) -> &ty::Generics {
             GenericParamKind::Type { ref default, synthetic, .. } => {
                 if !allow_defaults && default.is_some() {
                     if !tcx.features().default_type_parameter_fallback {
-                        tcx.lint_hir(
+                        tcx.struct_span_lint_hir(
                             lint::builtin::INVALID_TYPE_PARAM_DEFAULT,
                             param.hir_id,
                             param.span,
-                            &format!(
-                                "defaults for type parameters are only allowed in \
+                            |lint| {
+                                lint.build(&format!(
+                                    "defaults for type parameters are only allowed in \
                                         `struct`, `enum`, `type`, or `trait` definitions."
-                            ),
+                                ))
+                                .emit();
+                            },
                         );
                     }
                 }
@@ -2956,10 +2959,12 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
                     lint::builtin::INLINE_NO_SANITIZE,
                     hir_id,
                     no_sanitize_span,
-                    "`no_sanitize` will have no effect after inlining",
+                    |lint| {
+                        lint.build("`no_sanitize` will have no effect after inlining")
+                            .span_note(inline_span, "inlining requested here")
+                            .emit();
+                    },
                 )
-                .span_note(inline_span, "inlining requested here")
-                .emit();
             }
         }
     }
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 9ecf6d531299f..79bcfe7aee744 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -669,39 +669,43 @@ fn build_diagnostic(
     let attrs = &item.attrs;
     let sp = span_of_attrs(attrs).unwrap_or(item.source.span());
 
-    let mut diag = cx.tcx.struct_span_lint_hir(
+    cx.tcx.struct_span_lint_hir(
         lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
         hir_id,
         sp,
-        &format!("`[{}]` {}", path_str, err_msg),
+        |lint| {
+            let mut diag = lint.build(&format!("`[{}]` {}", path_str, err_msg));
+            if let Some(link_range) = link_range {
+                if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs)
+                {
+                    diag.set_span(sp);
+                    diag.span_label(sp, short_err_msg);
+                } else {
+                    // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
+                    //                       ^     ~~~~
+                    //                       |     link_range
+                    //                       last_new_line_offset
+                    let last_new_line_offset =
+                        dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
+                    let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
+
+                    // Print the line containing the `link_range` and manually mark it with '^'s.
+                    diag.note(&format!(
+                        "the link appears in this line:\n\n{line}\n\
+                         {indicator: <before$}{indicator:^<found$}",
+                        line = line,
+                        indicator = "",
+                        before = link_range.start - last_new_line_offset,
+                        found = link_range.len(),
+                    ));
+                }
+            };
+            if let Some(help_msg) = help_msg {
+                diag.help(help_msg);
+            }
+            diag.emit();
+        },
     );
-    if let Some(link_range) = link_range {
-        if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) {
-            diag.set_span(sp);
-            diag.span_label(sp, short_err_msg);
-        } else {
-            // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
-            //                       ^     ~~~~
-            //                       |     link_range
-            //                       last_new_line_offset
-            let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
-            let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
-
-            // Print the line containing the `link_range` and manually mark it with '^'s.
-            diag.note(&format!(
-                "the link appears in this line:\n\n{line}\n\
-                 {indicator: <before$}{indicator:^<found$}",
-                line = line,
-                indicator = "",
-                before = link_range.start - last_new_line_offset,
-                found = link_range.len(),
-            ));
-        }
-    };
-    if let Some(help_msg) = help_msg {
-        diag.help(help_msg);
-    }
-    diag.emit();
 }
 
 /// Reports a resolution failure diagnostic.
@@ -766,105 +770,108 @@ fn ambiguity_error(
     let attrs = &item.attrs;
     let sp = span_of_attrs(attrs).unwrap_or(item.source.span());
 
-    let mut msg = format!("`{}` is ", path_str);
-
-    let candidates = [TypeNS, ValueNS, MacroNS]
-        .iter()
-        .filter_map(|&ns| candidates[ns].map(|res| (res, ns)))
-        .collect::<Vec<_>>();
-    match candidates.as_slice() {
-        [(first_def, _), (second_def, _)] => {
-            msg += &format!(
-                "both {} {} and {} {}",
-                first_def.article(),
-                first_def.descr(),
-                second_def.article(),
-                second_def.descr(),
-            );
-        }
-        _ => {
-            let mut candidates = candidates.iter().peekable();
-            while let Some((res, _)) = candidates.next() {
-                if candidates.peek().is_some() {
-                    msg += &format!("{} {}, ", res.article(), res.descr());
-                } else {
-                    msg += &format!("and {} {}", res.article(), res.descr());
-                }
-            }
-        }
-    }
-
-    let mut diag = cx.tcx.struct_span_lint_hir(
+    cx.tcx.struct_span_lint_hir(
         lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
         hir_id,
         sp,
-        &msg,
-    );
+        |lint| {
+            let mut msg = format!("`{}` is ", path_str);
 
-    if let Some(link_range) = link_range {
-        if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs) {
-            diag.set_span(sp);
-            diag.span_label(sp, "ambiguous link");
-
-            for (res, ns) in candidates {
-                let (action, mut suggestion) = match res {
-                    Res::Def(DefKind::Method, _) | Res::Def(DefKind::Fn, _) => {
-                        ("add parentheses", format!("{}()", path_str))
-                    }
-                    Res::Def(DefKind::Macro(..), _) => {
-                        ("add an exclamation mark", format!("{}!", path_str))
+            let candidates = [TypeNS, ValueNS, MacroNS]
+                .iter()
+                .filter_map(|&ns| candidates[ns].map(|res| (res, ns)))
+                .collect::<Vec<_>>();
+            match candidates.as_slice() {
+                [(first_def, _), (second_def, _)] => {
+                    msg += &format!(
+                        "both {} {} and {} {}",
+                        first_def.article(),
+                        first_def.descr(),
+                        second_def.article(),
+                        second_def.descr(),
+                    );
+                }
+                _ => {
+                    let mut candidates = candidates.iter().peekable();
+                    while let Some((res, _)) = candidates.next() {
+                        if candidates.peek().is_some() {
+                            msg += &format!("{} {}, ", res.article(), res.descr());
+                        } else {
+                            msg += &format!("and {} {}", res.article(), res.descr());
+                        }
                     }
-                    _ => {
-                        let type_ = match (res, ns) {
-                            (Res::Def(DefKind::Const, _), _) => "const",
-                            (Res::Def(DefKind::Static, _), _) => "static",
-                            (Res::Def(DefKind::Struct, _), _) => "struct",
-                            (Res::Def(DefKind::Enum, _), _) => "enum",
-                            (Res::Def(DefKind::Union, _), _) => "union",
-                            (Res::Def(DefKind::Trait, _), _) => "trait",
-                            (Res::Def(DefKind::Mod, _), _) => "module",
-                            (_, TypeNS) => "type",
-                            (_, ValueNS) => "value",
-                            (_, MacroNS) => "macro",
+                }
+            }
+
+            let mut diag = lint.build(&msg);
+
+            if let Some(link_range) = link_range {
+                if let Some(sp) = super::source_span_for_markdown_range(cx, dox, &link_range, attrs)
+                {
+                    diag.set_span(sp);
+                    diag.span_label(sp, "ambiguous link");
+
+                    for (res, ns) in candidates {
+                        let (action, mut suggestion) = match res {
+                            Res::Def(DefKind::Method, _) | Res::Def(DefKind::Fn, _) => {
+                                ("add parentheses", format!("{}()", path_str))
+                            }
+                            Res::Def(DefKind::Macro(..), _) => {
+                                ("add an exclamation mark", format!("{}!", path_str))
+                            }
+                            _ => {
+                                let type_ = match (res, ns) {
+                                    (Res::Def(DefKind::Const, _), _) => "const",
+                                    (Res::Def(DefKind::Static, _), _) => "static",
+                                    (Res::Def(DefKind::Struct, _), _) => "struct",
+                                    (Res::Def(DefKind::Enum, _), _) => "enum",
+                                    (Res::Def(DefKind::Union, _), _) => "union",
+                                    (Res::Def(DefKind::Trait, _), _) => "trait",
+                                    (Res::Def(DefKind::Mod, _), _) => "module",
+                                    (_, TypeNS) => "type",
+                                    (_, ValueNS) => "value",
+                                    (_, MacroNS) => "macro",
+                                };
+
+                                // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
+                                ("prefix with the item type", format!("{}@{}", type_, path_str))
+                            }
                         };
 
-                        // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
-                        ("prefix with the item type", format!("{}@{}", type_, path_str))
-                    }
-                };
+                        if dox.bytes().nth(link_range.start) == Some(b'`') {
+                            suggestion = format!("`{}`", suggestion);
+                        }
 
-                if dox.bytes().nth(link_range.start) == Some(b'`') {
-                    suggestion = format!("`{}`", suggestion);
+                        diag.span_suggestion(
+                            sp,
+                            &format!("to link to the {}, {}", res.descr(), action),
+                            suggestion,
+                            Applicability::MaybeIncorrect,
+                        );
+                    }
+                } else {
+                    // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
+                    //                       ^     ~~~~
+                    //                       |     link_range
+                    //                       last_new_line_offset
+                    let last_new_line_offset =
+                        dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
+                    let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
+
+                    // Print the line containing the `link_range` and manually mark it with '^'s.
+                    diag.note(&format!(
+                        "the link appears in this line:\n\n{line}\n\
+                         {indicator: <before$}{indicator:^<found$}",
+                        line = line,
+                        indicator = "",
+                        before = link_range.start - last_new_line_offset,
+                        found = link_range.len(),
+                    ));
                 }
-
-                diag.span_suggestion(
-                    sp,
-                    &format!("to link to the {}, {}", res.descr(), action),
-                    suggestion,
-                    Applicability::MaybeIncorrect,
-                );
             }
-        } else {
-            // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
-            //                       ^     ~~~~
-            //                       |     link_range
-            //                       last_new_line_offset
-            let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
-            let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
-
-            // Print the line containing the `link_range` and manually mark it with '^'s.
-            diag.note(&format!(
-                "the link appears in this line:\n\n{line}\n\
-                 {indicator: <before$}{indicator:^<found$}",
-                line = line,
-                indicator = "",
-                before = link_range.start - last_new_line_offset,
-                found = link_range.len(),
-            ));
-        }
-    }
-
-    diag.emit();
+            diag.emit();
+        },
+    );
 }
 
 /// Given an enum variant's res, return the res of its enum and the associated fragment.
diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs
index 355ea15223b0c..9e48904a47d33 100644
--- a/src/librustdoc/passes/mod.rs
+++ b/src/librustdoc/passes/mod.rs
@@ -342,24 +342,19 @@ pub fn look_for_tests<'tcx>(
 
     if check_missing_code == true && tests.found_tests == 0 {
         let sp = span_of_attrs(&item.attrs).unwrap_or(item.source.span());
-        let mut diag = cx.tcx.struct_span_lint_hir(
-            lint::builtin::MISSING_DOC_CODE_EXAMPLES,
-            hir_id,
-            sp,
-            "missing code example in this documentation",
-        );
-        diag.emit();
+        cx.tcx.struct_span_lint_hir(lint::builtin::MISSING_DOC_CODE_EXAMPLES, hir_id, sp, |lint| {
+            lint.build("missing code example in this documentation").emit()
+        });
     } else if check_missing_code == false
         && tests.found_tests > 0
         && !cx.renderinfo.borrow().access_levels.is_public(item.def_id)
     {
-        let mut diag = cx.tcx.struct_span_lint_hir(
+        cx.tcx.struct_span_lint_hir(
             lint::builtin::PRIVATE_DOC_TESTS,
             hir_id,
             span_of_attrs(&item.attrs).unwrap_or(item.source.span()),
-            "documentation test in private item",
+            |lint| lint.build("documentation test in private item").emit(),
         );
-        diag.emit();
     }
 }
 
diff --git a/src/test/ui-fulldeps/auxiliary/issue-40001-plugin.rs b/src/test/ui-fulldeps/auxiliary/issue-40001-plugin.rs
index fbdad29d6494f..91b3372e8f415 100644
--- a/src/test/ui-fulldeps/auxiliary/issue-40001-plugin.rs
+++ b/src/test/ui-fulldeps/auxiliary/issue-40001-plugin.rs
@@ -4,17 +4,19 @@
 extern crate rustc_ast_pretty;
 extern crate rustc_driver;
 extern crate rustc_hir;
-#[macro_use] extern crate rustc_lint;
-#[macro_use] extern crate rustc_session;
+#[macro_use]
+extern crate rustc_lint;
+#[macro_use]
+extern crate rustc_session;
 extern crate rustc_span;
 extern crate syntax;
 
 use rustc_ast_pretty::pprust;
-use rustc_hir::intravisit;
+use rustc_driver::plugin::Registry;
 use rustc_hir as hir;
+use rustc_hir::intravisit;
 use rustc_hir::Node;
-use rustc_lint::{LateContext, LintPass, LintArray, LateLintPass, LintContext};
-use rustc_driver::plugin::Registry;
+use rustc_lint::{LateContext, LateLintPass, LintArray, LintContext, LintPass};
 use rustc_span::source_map;
 
 #[plugin_registrar]
@@ -32,14 +34,15 @@ declare_lint! {
 declare_lint_pass!(MissingWhitelistedAttrPass => [MISSING_WHITELISTED_ATTR]);
 
 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingWhitelistedAttrPass {
-    fn check_fn(&mut self,
-                cx: &LateContext<'a, 'tcx>,
-                _: intravisit::FnKind<'tcx>,
-                _: &'tcx hir::FnDecl,
-                _: &'tcx hir::Body,
-                span: source_map::Span,
-                id: hir::HirId) {
-
+    fn check_fn(
+        &mut self,
+        cx: &LateContext<'a, 'tcx>,
+        _: intravisit::FnKind<'tcx>,
+        _: &'tcx hir::FnDecl,
+        _: &'tcx hir::Body,
+        span: source_map::Span,
+        id: hir::HirId,
+    ) {
         let item = match cx.tcx.hir().get(id) {
             Node::Item(item) => item,
             _ => cx.tcx.hir().expect_item(cx.tcx.hir().get_parent_item(id)),
@@ -47,8 +50,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingWhitelistedAttrPass {
 
         let whitelisted = |attr| pprust::attribute_to_string(attr).contains("whitelisted_attr");
         if !item.attrs.iter().any(whitelisted) {
-            cx.span_lint(MISSING_WHITELISTED_ATTR, span,
-                         "Missing 'whitelisted_attr' attribute");
+            cx.lint(MISSING_WHITELISTED_ATTR, |lint| {
+                lint.build("Missing 'whitelisted_attr' attribute").set_span(span).emit()
+            });
         }
     }
 }
diff --git a/src/test/ui-fulldeps/auxiliary/lint-for-crate-rpass.rs b/src/test/ui-fulldeps/auxiliary/lint-for-crate-rpass.rs
index 98963a180c850..eb251a0a3adb8 100644
--- a/src/test/ui-fulldeps/auxiliary/lint-for-crate-rpass.rs
+++ b/src/test/ui-fulldeps/auxiliary/lint-for-crate-rpass.rs
@@ -5,12 +5,14 @@
 extern crate rustc_driver;
 extern crate rustc_hir;
 extern crate rustc_span;
-#[macro_use] extern crate rustc_lint;
-#[macro_use] extern crate rustc_session;
+#[macro_use]
+extern crate rustc_lint;
+#[macro_use]
+extern crate rustc_session;
 extern crate syntax;
 
-use rustc_lint::{LateContext, LintContext, LintPass, LateLintPass};
 use rustc_driver::plugin::Registry;
+use rustc_lint::{LateContext, LateLintPass, LintContext, LintPass};
 use rustc_span::symbol::Symbol;
 use syntax::attr;
 
@@ -28,8 +30,10 @@ macro_rules! fake_lint_pass {
             fn check_crate(&mut self, cx: &LateContext, krate: &rustc_hir::Crate) {
                 $(
                     if !attr::contains_name(&krate.attrs, $attr) {
-                        cx.span_lint(CRATE_NOT_OKAY, krate.span,
-                                     &format!("crate is not marked with #![{}]", $attr));
+                        cx.lint(CRATE_NOT_OKAY, |lint| {
+                             let msg = format!("crate is not marked with #![{}]", $attr);
+                             lint.build(&msg).set_span(krate.span).emit()
+                        });
                     }
                 )*
             }
diff --git a/src/test/ui-fulldeps/auxiliary/lint-for-crate.rs b/src/test/ui-fulldeps/auxiliary/lint-for-crate.rs
index 589477da62527..e5f4bf88d57aa 100644
--- a/src/test/ui-fulldeps/auxiliary/lint-for-crate.rs
+++ b/src/test/ui-fulldeps/auxiliary/lint-for-crate.rs
@@ -5,13 +5,15 @@
 
 extern crate rustc_driver;
 extern crate rustc_hir;
-#[macro_use] extern crate rustc_lint;
-#[macro_use] extern crate rustc_session;
+#[macro_use]
+extern crate rustc_lint;
+#[macro_use]
+extern crate rustc_session;
 extern crate rustc_span;
 extern crate syntax;
 
-use rustc_lint::{LateContext, LintContext, LintPass, LateLintPass, LintArray};
 use rustc_driver::plugin::Registry;
+use rustc_lint::{LateContext, LateLintPass, LintArray, LintContext, LintPass};
 use rustc_span::symbol::Symbol;
 use syntax::attr;
 
@@ -26,8 +28,11 @@ declare_lint_pass!(Pass => [CRATE_NOT_OKAY]);
 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
     fn check_crate(&mut self, cx: &LateContext, krate: &rustc_hir::Crate) {
         if !attr::contains_name(&krate.attrs, Symbol::intern("crate_okay")) {
-            cx.span_lint(CRATE_NOT_OKAY, krate.span,
-                         "crate is not marked with #![crate_okay]");
+            cx.lint(CRATE_NOT_OKAY, |lint| {
+                lint.build("crate is not marked with #![crate_okay]")
+                    .set_span(krate.span)
+                    .emit()
+            });
         }
     }
 }
diff --git a/src/test/ui-fulldeps/auxiliary/lint-group-plugin-test.rs b/src/test/ui-fulldeps/auxiliary/lint-group-plugin-test.rs
index 2cc288c21e697..5a8eaa63db2f8 100644
--- a/src/test/ui-fulldeps/auxiliary/lint-group-plugin-test.rs
+++ b/src/test/ui-fulldeps/auxiliary/lint-group-plugin-test.rs
@@ -6,11 +6,13 @@
 // Load rustc as a plugin to get macros.
 extern crate rustc_driver;
 extern crate rustc_hir;
-#[macro_use] extern crate rustc_lint;
-#[macro_use] extern crate rustc_session;
+#[macro_use]
+extern crate rustc_lint;
+#[macro_use]
+extern crate rustc_session;
 
-use rustc_lint::{LateContext, LintContext, LintPass, LateLintPass, LintArray, LintId};
 use rustc_driver::plugin::Registry;
+use rustc_lint::{LateContext, LateLintPass, LintArray, LintContext, LintId, LintPass};
 
 declare_lint!(TEST_LINT, Warn, "Warn about items named 'lintme'");
 
@@ -21,8 +23,12 @@ declare_lint_pass!(Pass => [TEST_LINT, PLEASE_LINT]);
 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
     fn check_item(&mut self, cx: &LateContext, it: &rustc_hir::Item) {
         match &*it.ident.as_str() {
-            "lintme" => cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'"),
-            "pleaselintme" => cx.span_lint(PLEASE_LINT, it.span, "item is named 'pleaselintme'"),
+            "lintme" => cx.lint(TEST_LINT, |lint| {
+                lint.build("item is named 'lintme'").set_span(it.span).emit()
+            }),
+            "pleaselintme" => cx.lint(PLEASE_LINT, |lint| {
+                lint.build("item is named 'pleaselintme'").set_span(it.span).emit()
+            }),
             _ => {}
         }
     }
@@ -32,6 +38,10 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
 pub fn plugin_registrar(reg: &mut Registry) {
     reg.lint_store.register_lints(&[&TEST_LINT, &PLEASE_LINT]);
     reg.lint_store.register_late_pass(|| box Pass);
-    reg.lint_store.register_group(true, "lint_me", None,
-        vec![LintId::of(&TEST_LINT), LintId::of(&PLEASE_LINT)]);
+    reg.lint_store.register_group(
+        true,
+        "lint_me",
+        None,
+        vec![LintId::of(&TEST_LINT), LintId::of(&PLEASE_LINT)],
+    );
 }
diff --git a/src/test/ui-fulldeps/auxiliary/lint-plugin-test.rs b/src/test/ui-fulldeps/auxiliary/lint-plugin-test.rs
index c704701cc480b..ad5f882c43434 100644
--- a/src/test/ui-fulldeps/auxiliary/lint-plugin-test.rs
+++ b/src/test/ui-fulldeps/auxiliary/lint-plugin-test.rs
@@ -7,11 +7,13 @@ extern crate syntax;
 
 // Load rustc as a plugin to get macros
 extern crate rustc_driver;
-#[macro_use] extern crate rustc_lint;
-#[macro_use] extern crate rustc_session;
+#[macro_use]
+extern crate rustc_lint;
+#[macro_use]
+extern crate rustc_session;
 
-use rustc_lint::{EarlyContext, LintContext, LintPass, EarlyLintPass, LintArray};
 use rustc_driver::plugin::Registry;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass};
 use syntax::ast;
 declare_lint!(TEST_LINT, Warn, "Warn about items named 'lintme'");
 
@@ -20,7 +22,9 @@ declare_lint_pass!(Pass => [TEST_LINT]);
 impl EarlyLintPass for Pass {
     fn check_item(&mut self, cx: &EarlyContext, it: &ast::Item) {
         if it.ident.name.as_str() == "lintme" {
-            cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'");
+            cx.lint(TEST_LINT, |lint| {
+                lint.build("item is named 'lintme'").set_span(it.span).emit()
+            });
         }
     }
 }
diff --git a/src/test/ui-fulldeps/auxiliary/lint-tool-test.rs b/src/test/ui-fulldeps/auxiliary/lint-tool-test.rs
index fa545ddc2bc6b..b14dbdf8f938b 100644
--- a/src/test/ui-fulldeps/auxiliary/lint-tool-test.rs
+++ b/src/test/ui-fulldeps/auxiliary/lint-tool-test.rs
@@ -5,11 +5,13 @@ extern crate syntax;
 
 // Load rustc as a plugin to get macros
 extern crate rustc_driver;
-#[macro_use] extern crate rustc_lint;
-#[macro_use] extern crate rustc_session;
+#[macro_use]
+extern crate rustc_lint;
+#[macro_use]
+extern crate rustc_session;
 
-use rustc_lint::{EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass, LintId};
 use rustc_driver::plugin::Registry;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintArray, LintContext, LintId, LintPass};
 use syntax::ast;
 declare_tool_lint!(pub clippy::TEST_LINT, Warn, "Warn about stuff");
 declare_tool_lint!(
@@ -30,10 +32,14 @@ declare_lint_pass!(Pass => [TEST_LINT, TEST_GROUP, TEST_RUSTC_TOOL_LINT]);
 impl EarlyLintPass for Pass {
     fn check_item(&mut self, cx: &EarlyContext, it: &ast::Item) {
         if it.ident.name.as_str() == "lintme" {
-            cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'");
+            cx.lint(TEST_LINT, |lint| {
+                lint.build("item is named 'lintme'").set_span(it.span).emit()
+            });
         }
         if it.ident.name.as_str() == "lintmetoo" {
-            cx.span_lint(TEST_GROUP, it.span, "item is named 'lintmetoo'");
+            cx.lint(TEST_GROUP, |lint| {
+                lint.build("item is named 'lintmetoo'").set_span(it.span).emit()
+            });
         }
     }
 }
@@ -42,6 +48,10 @@ impl EarlyLintPass for Pass {
 pub fn plugin_registrar(reg: &mut Registry) {
     reg.lint_store.register_lints(&[&TEST_RUSTC_TOOL_LINT, &TEST_LINT, &TEST_GROUP]);
     reg.lint_store.register_early_pass(|| box Pass);
-    reg.lint_store.register_group(true, "clippy::group", Some("clippy_group"),
-        vec![LintId::of(&TEST_LINT), LintId::of(&TEST_GROUP)]);
+    reg.lint_store.register_group(
+        true,
+        "clippy::group",
+        Some("clippy_group"),
+        vec![LintId::of(&TEST_LINT), LintId::of(&TEST_GROUP)],
+    );
 }
diff --git a/src/test/ui/type-alias-enum-variants/enum-variant-priority-lint-ambiguous_associated_items.rs b/src/test/ui/type-alias-enum-variants/enum-variant-priority-lint-ambiguous_associated_items.rs
index 7f69590400b3c..5f3b711b31aa9 100644
--- a/src/test/ui/type-alias-enum-variants/enum-variant-priority-lint-ambiguous_associated_items.rs
+++ b/src/test/ui/type-alias-enum-variants/enum-variant-priority-lint-ambiguous_associated_items.rs
@@ -32,6 +32,7 @@ impl Tr for E {
     fn f() -> Self::V { 0 }
     //~^ ERROR ambiguous associated item
     //~| WARN this was previously accepted
+    //~| HELP use fully-qualified syntax
 }
 
 fn main() {}