about summary refs log tree commit diff
path: root/compiler/rustc_lint/src
diff options
context:
space:
mode:
authorMaybe Waffle <waffle.lapkin@gmail.com>2022-09-16 11:01:02 +0400
committerMaybe Waffle <waffle.lapkin@gmail.com>2022-10-01 10:03:06 +0000
commita8f7e244b785feb1b1d696abf0a7efb5cb7aed30 (patch)
tree494e24f05b9fe3dfe766207727a83d369c218c22 /compiler/rustc_lint/src
parent65445a571c11f07f4ca1ed0f63bad247ae295bd0 (diff)
downloadrust-a8f7e244b785feb1b1d696abf0a7efb5cb7aed30.tar.gz
rust-a8f7e244b785feb1b1d696abf0a7efb5cb7aed30.zip
Refactor rustc lint API
Diffstat (limited to 'compiler/rustc_lint/src')
-rw-r--r--compiler/rustc_lint/src/array_into_iter.rs62
-rw-r--r--compiler/rustc_lint/src/builtin.rs623
-rw-r--r--compiler/rustc_lint/src/context.rs64
-rw-r--r--compiler/rustc_lint/src/early.rs5
-rw-r--r--compiler/rustc_lint/src/enum_intrinsics_non_enums.rs26
-rw-r--r--compiler/rustc_lint/src/expect.rs10
-rw-r--r--compiler/rustc_lint/src/hidden_unicode_codepoints.rs94
-rw-r--r--compiler/rustc_lint/src/internal.rs205
-rw-r--r--compiler/rustc_lint/src/let_underscore.rs67
-rw-r--r--compiler/rustc_lint/src/levels.rs132
-rw-r--r--compiler/rustc_lint/src/methods.rs19
-rw-r--r--compiler/rustc_lint/src/non_ascii_idents.rs68
-rw-r--r--compiler/rustc_lint/src/non_fmt_panic.rs104
-rw-r--r--compiler/rustc_lint/src/nonstandard_style.rs103
-rw-r--r--compiler/rustc_lint/src/noop_method_call.rs6
-rw-r--r--compiler/rustc_lint/src/pass_by_value.rs14
-rw-r--r--compiler/rustc_lint/src/redundant_semicolon.rs19
-rw-r--r--compiler/rustc_lint/src/traits.rs34
-rw-r--r--compiler/rustc_lint/src/types.rs257
-rw-r--r--compiler/rustc_lint/src/unused.rs148
20 files changed, 1105 insertions, 955 deletions
diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs
index b97f8acb37f..bd6b637f76f 100644
--- a/compiler/rustc_lint/src/array_into_iter.rs
+++ b/compiler/rustc_lint/src/array_into_iter.rs
@@ -118,37 +118,41 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
                 // to an array or to a slice.
                 _ => bug!("array type coerced to something other than array or slice"),
             };
-            cx.struct_span_lint(ARRAY_INTO_ITER, call.ident.span, |lint| {
-                let mut diag = lint.build(fluent::lint::array_into_iter);
-                diag.set_arg("target", target);
-                diag.span_suggestion(
-                    call.ident.span,
-                    fluent::lint::use_iter_suggestion,
-                    "iter",
-                    Applicability::MachineApplicable,
-                );
-                if self.for_expr_span == expr.span {
+            cx.struct_span_lint(
+                ARRAY_INTO_ITER,
+                call.ident.span,
+                fluent::lint::array_into_iter,
+                |diag| {
+                    diag.set_arg("target", target);
                     diag.span_suggestion(
-                        receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
-                        fluent::lint::remove_into_iter_suggestion,
-                        "",
-                        Applicability::MaybeIncorrect,
+                        call.ident.span,
+                        fluent::lint::use_iter_suggestion,
+                        "iter",
+                        Applicability::MachineApplicable,
                     );
-                } else if receiver_ty.is_array() {
-                    diag.multipart_suggestion(
-                        fluent::lint::use_explicit_into_iter_suggestion,
-                        vec![
-                            (expr.span.shrink_to_lo(), "IntoIterator::into_iter(".into()),
-                            (
-                                receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
-                                ")".into(),
-                            ),
-                        ],
-                        Applicability::MaybeIncorrect,
-                    );
-                }
-                diag.emit();
-            })
+                    if self.for_expr_span == expr.span {
+                        diag.span_suggestion(
+                            receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
+                            fluent::lint::remove_into_iter_suggestion,
+                            "",
+                            Applicability::MaybeIncorrect,
+                        );
+                    } else if receiver_ty.is_array() {
+                        diag.multipart_suggestion(
+                            fluent::lint::use_explicit_into_iter_suggestion,
+                            vec![
+                                (expr.span.shrink_to_lo(), "IntoIterator::into_iter(".into()),
+                                (
+                                    receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
+                                    ")".into(),
+                                ),
+                            ],
+                            Applicability::MaybeIncorrect,
+                        );
+                    }
+                    diag
+                },
+            )
         }
     }
 }
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index 5d69c35ebfc..f28cfbd8b4c 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -33,8 +33,8 @@ use rustc_ast_pretty::pprust::{self, expr_to_string};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_errors::{
-    fluent, Applicability, Diagnostic, DiagnosticMessage, DiagnosticStyledString,
-    LintDiagnosticBuilder, MultiSpan,
+    fluent, Applicability, DelayDm, Diagnostic, DiagnosticBuilder, DiagnosticMessage,
+    DiagnosticStyledString, MultiSpan,
 };
 use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability};
 use rustc_hir as hir;
@@ -103,9 +103,12 @@ impl EarlyLintPass for WhileTrue {
             && !lit.span.from_expansion()
         {
             let condition_span = e.span.with_hi(cond.span.hi());
-            cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| {
-                lint.build(fluent::lint::builtin_while_true)
-                    .span_suggestion_short(
+            cx.struct_span_lint(
+                            WHILE_TRUE,
+                            condition_span,
+                fluent::lint::builtin_while_true,
+                            |lint| {
+                    lint.span_suggestion_short(
                         condition_span,
                         fluent::lint::suggestion,
                         format!(
@@ -117,8 +120,8 @@ impl EarlyLintPass for WhileTrue {
                         ),
                         Applicability::MachineApplicable,
                     )
-                    .emit();
-            })
+                },
+            )
         }
     }
 }
@@ -154,9 +157,12 @@ impl BoxPointers {
         for leaf in ty.walk() {
             if let GenericArgKind::Type(leaf_ty) = leaf.unpack() {
                 if leaf_ty.is_box() {
-                    cx.struct_span_lint(BOX_POINTERS, span, |lint| {
-                        lint.build(fluent::lint::builtin_box_pointers).set_arg("ty", ty).emit();
-                    });
+                    cx.struct_span_lint(
+                        BOX_POINTERS,
+                        span,
+                        fluent::lint::builtin_box_pointers,
+                        |lint| lint.set_arg("ty", ty),
+                    );
                 }
             }
         }
@@ -255,19 +261,21 @@ impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns {
                     if cx.tcx.find_field_index(ident, &variant)
                         == Some(cx.tcx.field_index(fieldpat.hir_id, cx.typeck_results()))
                     {
-                        cx.struct_span_lint(NON_SHORTHAND_FIELD_PATTERNS, fieldpat.span, |lint| {
-                            let suggested_ident =
-                                format!("{}{}", binding_annot.prefix_str(), ident);
-                            lint.build(fluent::lint::builtin_non_shorthand_field_patterns)
-                                .set_arg("ident", ident.clone())
-                                .span_suggestion(
+                        cx.struct_span_lint(
+                            NON_SHORTHAND_FIELD_PATTERNS,
+                            fieldpat.span,
+                            fluent::lint::builtin_non_shorthand_field_patterns,
+                            |lint| {
+                                let suggested_ident =
+                                    format!("{}{}", binding_annot.prefix_str(), ident);
+                                lint.set_arg("ident", ident.clone()).span_suggestion(
                                     fieldpat.span,
                                     fluent::lint::suggestion,
                                     suggested_ident,
                                     Applicability::MachineApplicable,
                                 )
-                                .emit();
-                        });
+                            },
+                        );
                     }
                 }
             }
@@ -307,14 +315,17 @@ impl UnsafeCode {
         &self,
         cx: &EarlyContext<'_>,
         span: Span,
-        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagnosticMessage>,
+        decorate: impl for<'a, 'b> FnOnce(
+            &'b mut DiagnosticBuilder<'a, ()>,
+        ) -> &'b mut DiagnosticBuilder<'a, ()>,
     ) {
         // This comes from a macro that has `#[allow_internal_unsafe]`.
         if span.allows_unsafe() {
             return;
         }
 
-        cx.struct_span_lint(UNSAFE_CODE, span, decorate);
+        cx.struct_span_lint(UNSAFE_CODE, span, msg, decorate);
     }
 
     fn report_overridden_symbol_name(
@@ -323,8 +334,8 @@ impl UnsafeCode {
         span: Span,
         msg: DiagnosticMessage,
     ) {
-        self.report_unsafe(cx, span, |lint| {
-            lint.build(msg).note(fluent::lint::builtin_overridden_symbol_name).emit();
+        self.report_unsafe(cx, span, msg, |lint| {
+            lint.note(fluent::lint::builtin_overridden_symbol_name)
         })
     }
 
@@ -334,8 +345,8 @@ impl UnsafeCode {
         span: Span,
         msg: DiagnosticMessage,
     ) {
-        self.report_unsafe(cx, span, |lint| {
-            lint.build(msg).note(fluent::lint::builtin_overridden_symbol_section).emit();
+        self.report_unsafe(cx, span, msg, |lint| {
+            lint.note(fluent::lint::builtin_overridden_symbol_section)
         })
     }
 }
@@ -343,9 +354,12 @@ impl UnsafeCode {
 impl EarlyLintPass for UnsafeCode {
     fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
         if attr.has_name(sym::allow_internal_unsafe) {
-            self.report_unsafe(cx, attr.span, |lint| {
-                lint.build(fluent::lint::builtin_allow_internal_unsafe).emit();
-            });
+            self.report_unsafe(
+                cx,
+                attr.span,
+                fluent::lint::builtin_allow_internal_unsafe,
+                |lint| lint,
+            );
         }
     }
 
@@ -353,24 +367,20 @@ 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, |lint| {
-                    lint.build(fluent::lint::builtin_unsafe_block).emit();
-                });
+                self.report_unsafe(cx, blk.span, fluent::lint::builtin_unsafe_block, |lint| lint);
             }
         }
     }
 
     fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
         match it.kind {
-            ast::ItemKind::Trait(box ast::Trait { unsafety: ast::Unsafe::Yes(_), .. }) => self
-                .report_unsafe(cx, it.span, |lint| {
-                    lint.build(fluent::lint::builtin_unsafe_trait).emit();
-                }),
+            ast::ItemKind::Trait(box ast::Trait { unsafety: ast::Unsafe::Yes(_), .. }) => {
+                self.report_unsafe(cx, it.span, fluent::lint::builtin_unsafe_trait, |lint| lint)
+            }
 
-            ast::ItemKind::Impl(box ast::Impl { unsafety: ast::Unsafe::Yes(_), .. }) => self
-                .report_unsafe(cx, it.span, |lint| {
-                    lint.build(fluent::lint::builtin_unsafe_impl).emit();
-                }),
+            ast::ItemKind::Impl(box ast::Impl { unsafety: ast::Unsafe::Yes(_), .. }) => {
+                self.report_unsafe(cx, it.span, fluent::lint::builtin_unsafe_impl, |lint| lint)
+            }
 
             ast::ItemKind::Fn(..) => {
                 if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::no_mangle) {
@@ -463,9 +473,7 @@ impl EarlyLintPass for UnsafeCode {
                 FnCtxt::Assoc(_) if body.is_none() => fluent::lint::builtin_decl_unsafe_method,
                 FnCtxt::Assoc(_) => fluent::lint::builtin_impl_unsafe_method,
             };
-            self.report_unsafe(cx, span, |lint| {
-                lint.build(msg).emit();
-            });
+            self.report_unsafe(cx, span, msg, |lint| lint);
         }
     }
 }
@@ -566,12 +574,12 @@ impl MissingDoc {
         let attrs = cx.tcx.hir().attrs(cx.tcx.hir().local_def_id_to_hir_id(def_id));
         let has_doc = attrs.iter().any(has_doc);
         if !has_doc {
-            cx.struct_span_lint(MISSING_DOCS, cx.tcx.def_span(def_id), |lint| {
-                lint.build(fluent::lint::builtin_missing_doc)
-                    .set_arg("article", article)
-                    .set_arg("desc", desc)
-                    .emit();
-            });
+            cx.struct_span_lint(
+                MISSING_DOCS,
+                cx.tcx.def_span(def_id),
+                fluent::lint::builtin_missing_doc,
+                |lint| lint.set_arg("article", article).set_arg("desc", desc),
+            );
         }
     }
 }
@@ -758,9 +766,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations {
         )
         .is_ok()
         {
-            cx.struct_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, |lint| {
-                lint.build(fluent::lint::builtin_missing_copy_impl).emit();
-            })
+            cx.struct_span_lint(
+                MISSING_COPY_IMPLEMENTATIONS,
+                item.span,
+                fluent::lint::builtin_missing_copy_impl,
+                |lint| lint,
+            )
         }
     }
 }
@@ -834,11 +845,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations {
         }
 
         if !self.impling_types.as_ref().unwrap().contains(&item.def_id.def_id) {
-            cx.struct_span_lint(MISSING_DEBUG_IMPLEMENTATIONS, item.span, |lint| {
-                lint.build(fluent::lint::builtin_missing_debug_impl)
-                    .set_arg("debug", cx.tcx.def_path_str(debug))
-                    .emit();
-            });
+            cx.struct_span_lint(
+                MISSING_DEBUG_IMPLEMENTATIONS,
+                item.span,
+                fluent::lint::builtin_missing_debug_impl,
+                |lint| lint.set_arg("debug", cx.tcx.def_path_str(debug)),
+            );
         }
     }
 }
@@ -906,24 +918,26 @@ impl EarlyLintPass for AnonymousParameters {
             for arg in sig.decl.inputs.iter() {
                 if let ast::PatKind::Ident(_, ident, None) = arg.pat.kind {
                     if ident.name == kw::Empty {
-                        cx.struct_span_lint(ANONYMOUS_PARAMETERS, arg.pat.span, |lint| {
-                            let ty_snip = cx.sess().source_map().span_to_snippet(arg.ty.span);
-
-                            let (ty_snip, appl) = if let Ok(ref snip) = ty_snip {
-                                (snip.as_str(), Applicability::MachineApplicable)
-                            } else {
-                                ("<type>", Applicability::HasPlaceholders)
-                            };
+                        let ty_snip = cx.sess().source_map().span_to_snippet(arg.ty.span);
 
-                            lint.build(fluent::lint::builtin_anonymous_params)
-                                .span_suggestion(
+                        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,
+                            fluent::lint::builtin_anonymous_params,
+                            |lint| {
+                                lint.span_suggestion(
                                     arg.pat.span,
                                     fluent::lint::suggestion,
                                     format!("_: {}", ty_snip),
                                     appl,
                                 )
-                                .emit();
-                        })
+                            },
+                        )
                     }
                 }
             }
@@ -958,38 +972,44 @@ impl EarlyLintPass for DeprecatedAttr {
                     _,
                 ) = gate
                 {
-                    cx.struct_span_lint(DEPRECATED, attr.span, |lint| {
-                        // FIXME(davidtwco) translatable deprecated attr
-                        lint.build(fluent::lint::builtin_deprecated_attr_link)
-                            .set_arg("name", name)
-                            .set_arg("reason", reason)
-                            .set_arg("link", link)
-                            .span_suggestion_short(
-                                attr.span,
-                                suggestion.map(|s| s.into()).unwrap_or(
-                                    fluent::lint::builtin_deprecated_attr_default_suggestion,
-                                ),
-                                "",
-                                Applicability::MachineApplicable,
-                            )
-                            .emit();
-                    });
+                    // FIXME(davidtwco) translatable deprecated attr
+                    cx.struct_span_lint(
+                        DEPRECATED,
+                        attr.span,
+                        fluent::lint::builtin_deprecated_attr_link,
+                        |lint| {
+                            lint.set_arg("name", name)
+                                .set_arg("reason", reason)
+                                .set_arg("link", link)
+                                .span_suggestion_short(
+                                    attr.span,
+                                    suggestion.map(|s| s.into()).unwrap_or(
+                                        fluent::lint::builtin_deprecated_attr_default_suggestion,
+                                    ),
+                                    "",
+                                    Applicability::MachineApplicable,
+                                )
+                        },
+                    );
                 }
                 return;
             }
         }
         if attr.has_name(sym::no_start) || attr.has_name(sym::crate_id) {
-            cx.struct_span_lint(DEPRECATED, attr.span, |lint| {
-                lint.build(fluent::lint::builtin_deprecated_attr_used)
-                    .set_arg("name", pprust::path_to_string(&attr.get_normal_item().path))
-                    .span_suggestion_short(
-                        attr.span,
-                        fluent::lint::builtin_deprecated_attr_default_suggestion,
-                        "",
-                        Applicability::MachineApplicable,
-                    )
-                    .emit();
-            });
+            cx.struct_span_lint(
+                DEPRECATED,
+                attr.span,
+                fluent::lint::builtin_deprecated_attr_used,
+                |lint| {
+                    lint.set_arg("name", pprust::path_to_string(&attr.get_normal_item().path))
+                        .span_suggestion_short(
+                            attr.span,
+                            fluent::lint::builtin_deprecated_attr_default_suggestion,
+                            "",
+                            Applicability::MachineApplicable,
+                        )
+                },
+            );
         }
     }
 }
@@ -1016,20 +1036,21 @@ fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &
         let span = sugared_span.take().unwrap_or(attr.span);
 
         if is_doc_comment || attr.has_name(sym::doc) {
-            cx.struct_span_lint(UNUSED_DOC_COMMENTS, span, |lint| {
-                let mut err = lint.build(fluent::lint::builtin_unused_doc_comment);
-                err.set_arg("kind", node_kind);
-                err.span_label(node_span, fluent::lint::label);
-                match attr.kind {
-                    AttrKind::DocComment(CommentKind::Line, _) | AttrKind::Normal(..) => {
-                        err.help(fluent::lint::plain_help);
-                    }
-                    AttrKind::DocComment(CommentKind::Block, _) => {
-                        err.help(fluent::lint::block_help);
-                    }
-                }
-                err.emit();
-            });
+            cx.struct_span_lint(
+                UNUSED_DOC_COMMENTS,
+                span,
+                fluent::lint::builtin_unused_doc_comment,
+                |lint| {
+                    lint.set_arg("kind", node_kind).span_label(node_span, fluent::lint::label).help(
+                        match attr.kind {
+                            AttrKind::DocComment(CommentKind::Line, _) | AttrKind::Normal(..) => {
+                                fluent::lint::plain_help
+                            }
+                            AttrKind::DocComment(CommentKind::Block, _) => fluent::lint::block_help,
+                        },
+                    )
+                },
+            );
         }
     }
 }
@@ -1143,9 +1164,12 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
                 match param.kind {
                     GenericParamKind::Lifetime { .. } => {}
                     GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => {
-                        cx.struct_span_lint(NO_MANGLE_GENERIC_ITEMS, span, |lint| {
-                            lint.build(fluent::lint::builtin_no_mangle_generic)
-                                .span_suggestion_short(
+                        cx.struct_span_lint(
+                            NO_MANGLE_GENERIC_ITEMS,
+                            span,
+                            fluent::lint::builtin_no_mangle_generic,
+                            |lint| {
+                                lint.span_suggestion_short(
                                     no_mangle_attr.span,
                                     fluent::lint::suggestion,
                                     "",
@@ -1153,8 +1177,8 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
                                     // fix may be to monomorphize source by hand
                                     Applicability::MaybeIncorrect,
                                 )
-                                .emit();
-                        });
+                            },
+                        );
                         break;
                     }
                 }
@@ -1170,27 +1194,29 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
                 if cx.sess().contains_name(attrs, sym::no_mangle) {
                     // Const items do not refer to a particular location in memory, and therefore
                     // don't have anything to attach a symbol to
-                    cx.struct_span_lint(NO_MANGLE_CONST_ITEMS, it.span, |lint| {
-                        let mut err = lint.build(fluent::lint::builtin_const_no_mangle);
-
-                        // 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,
-                            fluent::lint::suggestion,
-                            "pub static",
-                            Applicability::MachineApplicable,
-                        );
-                        err.emit();
-                    });
+                    cx.struct_span_lint(
+                        NO_MANGLE_CONST_ITEMS,
+                        it.span,
+                        fluent::lint::builtin_const_no_mangle,
+                        |lint| {
+                            // 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));
+                            lint.span_suggestion(
+                                const_span,
+                                fluent::lint::suggestion,
+                                "pub static",
+                                Applicability::MachineApplicable,
+                            )
+                        },
+                    );
                 }
             }
             hir::ItemKind::Impl(hir::Impl { generics, items, .. }) => {
@@ -1250,9 +1276,12 @@ impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
             get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (ty1.kind(), ty2.kind()))
         {
             if to_mt == hir::Mutability::Mut && from_mt == hir::Mutability::Not {
-                cx.struct_span_lint(MUTABLE_TRANSMUTES, expr.span, |lint| {
-                    lint.build(fluent::lint::builtin_mutable_transmutes).emit();
-                });
+                cx.struct_span_lint(
+                    MUTABLE_TRANSMUTES,
+                    expr.span,
+                    fluent::lint::builtin_mutable_transmutes,
+                    |lint| lint,
+                );
             }
         }
 
@@ -1300,9 +1329,12 @@ impl<'tcx> LateLintPass<'tcx> for UnstableFeatures {
         if attr.has_name(sym::feature) {
             if let Some(items) = attr.meta_item_list() {
                 for item in items {
-                    cx.struct_span_lint(UNSTABLE_FEATURES, item.span(), |lint| {
-                        lint.build(fluent::lint::builtin_unstable_features).emit();
-                    });
+                    cx.struct_span_lint(
+                        UNSTABLE_FEATURES,
+                        item.span(),
+                        fluent::lint::builtin_unstable_features,
+                        |lint| lint,
+                    );
                 }
             }
         }
@@ -1361,21 +1393,25 @@ impl UnreachablePub {
                 applicability = Applicability::MaybeIncorrect;
             }
             let def_span = cx.tcx.def_span(def_id);
-            cx.struct_span_lint(UNREACHABLE_PUB, def_span, |lint| {
-                let mut err = lint.build(fluent::lint::builtin_unreachable_pub);
-                err.set_arg("what", what);
-
-                err.span_suggestion(
-                    vis_span,
-                    fluent::lint::suggestion,
-                    "pub(crate)",
-                    applicability,
-                );
-                if exportable {
-                    err.help(fluent::lint::help);
-                }
-                err.emit();
-            });
+            cx.struct_span_lint(
+                UNREACHABLE_PUB,
+                def_span,
+                fluent::lint::builtin_unreachable_pub,
+                |lint| {
+                    lint.set_arg("what", what);
+
+                    lint.span_suggestion(
+                        vis_span,
+                        fluent::lint::suggestion,
+                        "pub(crate)",
+                        applicability,
+                    );
+                    if exportable {
+                        lint.help(fluent::lint::help);
+                    }
+                    lint
+                },
+            );
         }
     }
 }
@@ -1505,36 +1541,34 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
 
         let mut suggested_changing_assoc_types = false;
         if !where_spans.is_empty() {
-            cx.lint(TYPE_ALIAS_BOUNDS, |lint| {
-                let mut err = lint.build(fluent::lint::builtin_type_alias_where_clause);
-                err.set_span(where_spans);
-                err.span_suggestion(
+            cx.lint(TYPE_ALIAS_BOUNDS, fluent::lint::builtin_type_alias_where_clause, |lint| {
+                lint.set_span(where_spans);
+                lint.span_suggestion(
                     type_alias_generics.where_clause_span,
                     fluent::lint::suggestion,
                     "",
                     Applicability::MachineApplicable,
                 );
                 if !suggested_changing_assoc_types {
-                    TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err);
+                    TypeAliasBounds::suggest_changing_assoc_types(ty, lint);
                     suggested_changing_assoc_types = true;
                 }
-                err.emit();
+                lint
             });
         }
 
         if !inline_spans.is_empty() {
-            cx.lint(TYPE_ALIAS_BOUNDS, |lint| {
-                let mut err = lint.build(fluent::lint::builtin_type_alias_generic_bounds);
-                err.set_span(inline_spans);
-                err.multipart_suggestion(
+            cx.lint(TYPE_ALIAS_BOUNDS, fluent::lint::builtin_type_alias_generic_bounds, |lint| {
+                lint.set_span(inline_spans);
+                lint.multipart_suggestion(
                     fluent::lint::suggestion,
                     inline_sugg,
                     Applicability::MachineApplicable,
                 );
                 if !suggested_changing_assoc_types {
-                    TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err);
+                    TypeAliasBounds::suggest_changing_assoc_types(ty, lint);
                 }
-                err.emit();
+                lint
             });
         }
     }
@@ -1633,12 +1667,15 @@ impl<'tcx> LateLintPass<'tcx> for TrivialConstraints {
                     TypeWellFormedFromEnv(..) => continue,
                 };
                 if predicate.is_global() {
-                    cx.struct_span_lint(TRIVIAL_BOUNDS, span, |lint| {
-                        lint.build(fluent::lint::builtin_trivial_bounds)
-                            .set_arg("predicate_kind_name", predicate_kind_name)
-                            .set_arg("predicate", predicate)
-                            .emit();
-                    });
+                    cx.struct_span_lint(
+                        TRIVIAL_BOUNDS,
+                        span,
+                        fluent::lint::builtin_trivial_bounds,
+                        |lint| {
+                            lint.set_arg("predicate_kind_name", predicate_kind_name)
+                                .set_arg("predicate", predicate)
+                        },
+                    );
                 }
             }
         }
@@ -1754,15 +1791,13 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
                         replace,
                     });
                 } else {
-                    cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, pat.span, |lint| {
-                        lint.build(msg)
-                            .span_suggestion(
-                                pat.span,
-                                suggestion,
-                                replace,
-                                Applicability::MachineApplicable,
-                            )
-                            .emit();
+                    cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, pat.span, msg, |lint| {
+                        lint.span_suggestion(
+                            pat.span,
+                            suggestion,
+                            replace,
+                            Applicability::MachineApplicable,
+                        )
                     });
                 }
             } else {
@@ -1774,15 +1809,13 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
                         replace: replace.to_string(),
                     });
                 } else {
-                    cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, join, |lint| {
-                        lint.build(msg)
-                            .span_suggestion_short(
-                                join,
-                                suggestion,
-                                replace,
-                                Applicability::MachineApplicable,
-                            )
-                            .emit();
+                    cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, join, msg, |lint| {
+                        lint.span_suggestion_short(
+                            join,
+                            suggestion,
+                            replace,
+                            Applicability::MachineApplicable,
+                        )
                     });
                 }
             };
@@ -1863,9 +1896,12 @@ impl<'tcx> LateLintPass<'tcx> for UnnameableTestItems {
 
         let attrs = cx.tcx.hir().attrs(it.hir_id());
         if let Some(attr) = cx.sess().find_by_name(attrs, sym::rustc_test_marker) {
-            cx.struct_span_lint(UNNAMEABLE_TEST_ITEMS, attr.span, |lint| {
-                lint.build(fluent::lint::builtin_unnameable_test_items).emit();
-            });
+            cx.struct_span_lint(
+                UNNAMEABLE_TEST_ITEMS,
+                attr.span,
+                fluent::lint::builtin_unnameable_test_items,
+                |lint| lint,
+            );
         }
     }
 
@@ -1981,18 +2017,19 @@ impl KeywordIdents {
             return;
         }
 
-        cx.struct_span_lint(KEYWORD_IDENTS, ident.span, |lint| {
-            lint.build(fluent::lint::builtin_keyword_idents)
-                .set_arg("kw", ident.clone())
-                .set_arg("next", next_edition)
-                .span_suggestion(
+        cx.struct_span_lint(
+            KEYWORD_IDENTS,
+            ident.span,
+            fluent::lint::builtin_keyword_idents,
+            |lint| {
+                lint.set_arg("kw", ident.clone()).set_arg("next", next_edition).span_suggestion(
                     ident.span,
                     fluent::lint::suggestion,
                     format!("r#{}", ident),
                     Applicability::MachineApplicable,
                 )
-                .emit();
-        });
+            },
+        );
     }
 }
 
@@ -2243,10 +2280,12 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
             }
 
             if !lint_spans.is_empty() {
-                cx.struct_span_lint(EXPLICIT_OUTLIVES_REQUIREMENTS, lint_spans.clone(), |lint| {
-                    lint.build(fluent::lint::builtin_explicit_outlives)
-                        .set_arg("count", bound_count)
-                        .multipart_suggestion(
+                cx.struct_span_lint(
+                    EXPLICIT_OUTLIVES_REQUIREMENTS,
+                    lint_spans.clone(),
+                    fluent::lint::builtin_explicit_outlives,
+                    |lint| {
+                        lint.set_arg("count", bound_count).multipart_suggestion(
                             fluent::lint::suggestion,
                             lint_spans
                                 .into_iter()
@@ -2254,8 +2293,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
                                 .collect::<Vec<_>>(),
                             Applicability::MachineApplicable,
                         )
-                        .emit();
-                });
+                    },
+                );
             }
         }
     }
@@ -2302,18 +2341,24 @@ impl EarlyLintPass for IncompleteFeatures {
             .chain(features.declared_lib_features.iter().map(|(name, span)| (name, span)))
             .filter(|(&name, _)| features.incomplete(name))
             .for_each(|(&name, &span)| {
-                cx.struct_span_lint(INCOMPLETE_FEATURES, span, |lint| {
-                    let mut builder = lint.build(fluent::lint::builtin_incomplete_features);
-                    builder.set_arg("name", name);
-                    if let Some(n) = rustc_feature::find_feature_issue(name, GateIssue::Language) {
-                        builder.set_arg("n", n);
-                        builder.note(fluent::lint::note);
-                    }
-                    if HAS_MIN_FEATURES.contains(&name) {
-                        builder.help(fluent::lint::help);
-                    }
-                    builder.emit();
-                })
+                cx.struct_span_lint(
+                    INCOMPLETE_FEATURES,
+                    span,
+                    fluent::lint::builtin_incomplete_features,
+                    |lint| {
+                        lint.set_arg("name", name);
+                        if let Some(n) =
+                            rustc_feature::find_feature_issue(name, GateIssue::Language)
+                        {
+                            lint.set_arg("n", n);
+                            lint.note(fluent::lint::note);
+                        }
+                        if HAS_MIN_FEATURES.contains(&name) {
+                            lint.help(fluent::lint::help);
+                        }
+                        lint
+                    },
+                )
             });
     }
 }
@@ -2628,28 +2673,37 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
                 with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init))
             {
                 // FIXME(davidtwco): make translatable
-                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, \
+                cx.struct_span_lint(
+                    INVALID_VALUE,
+                    expr.span,
+                    DelayDm(|| {
+                        format!(
+                            "the type `{}` does not permit {}",
+                            conjured_ty,
+                            match init {
+                                InitKind::Zeroed => "zero-initialization",
+                                InitKind::Uninit => "being left uninitialized",
+                            },
+                        )
+                    }),
+                    |lint| {
+                        lint.span_label(
+                            expr.span,
+                            "this code causes undefined behavior when executed",
+                        );
+                        lint.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();
-                });
+                        );
+                        if let Some(span) = span {
+                            lint.span_note(span, &msg);
+                        } else {
+                            lint.note(&msg);
+                        }
+                        lint
+                    },
+                );
             }
         }
     }
@@ -2995,31 +3049,35 @@ impl<'tcx> LateLintPass<'tcx> for ClashingExternDeclarations {
                             SymbolName::Link(_, annot_span) => fi.span.to(annot_span),
                         };
                     // Finally, emit the diagnostic.
+
+                    let msg = if orig.get_name() == this_fi.ident.name {
+                        fluent::lint::builtin_clashing_extern_same_name
+                    } else {
+                        fluent::lint::builtin_clashing_extern_diff_name
+                    };
                     tcx.struct_span_lint_hir(
                         CLASHING_EXTERN_DECLARATIONS,
                         this_fi.hir_id(),
                         get_relevant_span(this_fi),
+                        msg,
                         |lint| {
                             let mut expected_str = DiagnosticStyledString::new();
                             expected_str.push(existing_decl_ty.fn_sig(tcx).to_string(), false);
                             let mut found_str = DiagnosticStyledString::new();
                             found_str.push(this_decl_ty.fn_sig(tcx).to_string(), true);
 
-                            lint.build(if orig.get_name() == this_fi.ident.name {
-                                fluent::lint::builtin_clashing_extern_same_name
-                            } else {
-                                fluent::lint::builtin_clashing_extern_diff_name
-                            })
-                            .set_arg("this_fi", this_fi.ident.name)
-                            .set_arg("orig", orig.get_name())
-                            .span_label(
-                                get_relevant_span(orig_fi),
-                                fluent::lint::previous_decl_label,
-                            )
-                            .span_label(get_relevant_span(this_fi), fluent::lint::mismatch_label)
-                            // FIXME(davidtwco): translatable expected/found
-                            .note_expected_found(&"", expected_str, &"", found_str)
-                            .emit();
+                            lint.set_arg("this_fi", this_fi.ident.name)
+                                .set_arg("orig", orig.get_name())
+                                .span_label(
+                                    get_relevant_span(orig_fi),
+                                    fluent::lint::previous_decl_label,
+                                )
+                                .span_label(
+                                    get_relevant_span(this_fi),
+                                    fluent::lint::mismatch_label,
+                                )
+                                // FIXME(davidtwco): translatable expected/found
+                                .note_expected_found(&"", expected_str, &"", found_str)
                         },
                     );
                 }
@@ -3100,11 +3158,12 @@ impl<'tcx> LateLintPass<'tcx> for DerefNullPtr {
 
         if let rustc_hir::ExprKind::Unary(rustc_hir::UnOp::Deref, expr_deref) = expr.kind {
             if is_null_ptr(cx, expr_deref) {
-                cx.struct_span_lint(DEREF_NULLPTR, expr.span, |lint| {
-                    let mut err = lint.build(fluent::lint::builtin_deref_nullptr);
-                    err.span_label(expr.span, fluent::lint::label);
-                    err.emit();
-                });
+                cx.struct_span_lint(
+                    DEREF_NULLPTR,
+                    expr.span,
+                    fluent::lint::builtin_deref_nullptr,
+                    |lint| lint.span_label(expr.span, fluent::lint::label),
+                );
             }
         }
     }
@@ -3214,9 +3273,8 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels {
                     cx.lookup_with_diagnostics(
                             NAMED_ASM_LABELS,
                             Some(target_spans),
-                            |diag| {
-                                diag.build(fluent::lint::builtin_asm_labels).emit();
-                            },
+                            fluent::lint::builtin_asm_labels,
+                            |lint| lint,
                             BuiltinLintDiagnostics::NamedAsmLabel(
                                 "only local labels of the form `<number>:` should be used in inline asm"
                                     .to_string(),
@@ -3288,16 +3346,14 @@ impl EarlyLintPass for SpecialModuleName {
                 }
 
                 match item.ident.name.as_str() {
-                    "lib" => cx.struct_span_lint(SPECIAL_MODULE_NAME, item.span, |lint| {
-                        lint.build("found module declaration for lib.rs")
+                    "lib" => cx.struct_span_lint(SPECIAL_MODULE_NAME, item.span, "found module declaration for lib.rs", |lint| {
+                        lint
                             .note("lib.rs is the root of this crate's library target")
                             .help("to refer to it from other targets, use the library's name as the path")
-                            .emit()
                     }),
-                    "main" => cx.struct_span_lint(SPECIAL_MODULE_NAME, item.span, |lint| {
-                        lint.build("found module declaration for main.rs")
+                    "main" => cx.struct_span_lint(SPECIAL_MODULE_NAME, item.span, "found module declaration for main.rs", |lint| {
+                        lint
                             .note("a binary crate cannot be used as library")
-                            .emit()
                     }),
                     _ => continue
                 }
@@ -3317,24 +3373,27 @@ impl EarlyLintPass for UnexpectedCfgs {
         for &(name, value) in cfg {
             if let Some(names_valid) = &check_cfg.names_valid {
                 if !names_valid.contains(&name) {
-                    cx.lookup(UNEXPECTED_CFGS, None::<MultiSpan>, |diag| {
-                        diag.build(fluent::lint::builtin_unexpected_cli_config_name)
-                            .help(fluent::lint::help)
-                            .set_arg("name", name)
-                            .emit();
-                    });
+                    cx.lookup(
+                        UNEXPECTED_CFGS,
+                        None::<MultiSpan>,
+                        fluent::lint::builtin_unexpected_cli_config_name,
+                        |diag| diag.help(fluent::lint::help).set_arg("name", name),
+                    );
                 }
             }
             if let Some(value) = value {
                 if let Some(values) = &check_cfg.values_valid.get(&name) {
                     if !values.contains(&value) {
-                        cx.lookup(UNEXPECTED_CFGS, None::<MultiSpan>, |diag| {
-                            diag.build(fluent::lint::builtin_unexpected_cli_config_value)
-                                .help(fluent::lint::help)
-                                .set_arg("name", name)
-                                .set_arg("value", value)
-                                .emit();
-                        });
+                        cx.lookup(
+                            UNEXPECTED_CFGS,
+                            None::<MultiSpan>,
+                            fluent::lint::builtin_unexpected_cli_config_value,
+                            |diag| {
+                                diag.help(fluent::lint::help)
+                                    .set_arg("name", name)
+                                    .set_arg("value", value)
+                            },
+                        );
                     }
                 }
             }
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index cbab56f2066..9df6e0f534e 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -25,10 +25,8 @@ use crate::passes::{EarlyLintPassObject, LateLintPassObject};
 use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync;
-use rustc_errors::add_elided_lifetime_in_path_suggestion;
-use rustc_errors::{
-    Applicability, DecorateLint, LintDiagnosticBuilder, MultiSpan, SuggestionStyle,
-};
+use rustc_errors::{add_elided_lifetime_in_path_suggestion, DiagnosticBuilder, DiagnosticMessage};
+use rustc_errors::{Applicability, DecorateLint, MultiSpan, SuggestionStyle};
 use rustc_hir as hir;
 use rustc_hir::def::Res;
 use rustc_hir::def_id::{CrateNum, DefId};
@@ -580,13 +578,14 @@ pub trait LintContext: Sized {
         &self,
         lint: &'static Lint,
         span: Option<impl Into<MultiSpan>>,
-        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagnosticMessage>,
+        decorate: impl for<'a, 'b> FnOnce(
+            &'b mut DiagnosticBuilder<'a, ()>,
+        ) -> &'b mut DiagnosticBuilder<'a, ()>,
         diagnostic: BuiltinLintDiagnostics,
     ) {
-        self.lookup(lint, span, |lint| {
-            // We first generate a blank diagnostic.
-            let mut db = lint.build("");
-
+        // We first generate a blank diagnostic.
+        self.lookup(lint, span, msg,|db| {
             // Now, set up surrounding context.
             let sess = self.sess();
             match diagnostic {
@@ -660,7 +659,7 @@ pub trait LintContext: Sized {
                 ) => {
                     add_elided_lifetime_in_path_suggestion(
                         sess.source_map(),
-                        &mut db,
+                        db,
                         n,
                         path_span,
                         incl_angl_brckt,
@@ -696,7 +695,7 @@ pub trait LintContext: Sized {
                     }
                 }
                 BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => {
-                    stability::deprecation_suggestion(&mut db, "macro", suggestion, span)
+                    stability::deprecation_suggestion(db, "macro", suggestion, span)
                 }
                 BuiltinLintDiagnostics::UnusedDocComment(span) => {
                     db.span_label(span, "rustdoc does not generate documentation for macro invocations");
@@ -867,7 +866,7 @@ pub trait LintContext: Sized {
                 }
             }
             // Rewrap `db`, and pass control to the user.
-            decorate(LintDiagnosticBuilder::new(db));
+            decorate(db)
         });
     }
 
@@ -877,7 +876,10 @@ pub trait LintContext: Sized {
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagnosticMessage>,
+        decorate: impl for<'a, 'b> FnOnce(
+            &'b mut DiagnosticBuilder<'a, ()>,
+        ) -> &'b mut DiagnosticBuilder<'a, ()>,
     );
 
     /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`,
@@ -888,31 +890,39 @@ pub trait LintContext: Sized {
         span: S,
         decorator: impl for<'a> DecorateLint<'a, ()>,
     ) {
-        self.lookup(lint, Some(span), |diag| decorator.decorate_lint(diag));
+        self.lookup(lint, Some(span), decorator.msg(), |diag| decorator.decorate_lint(diag));
     }
 
     fn struct_span_lint<S: Into<MultiSpan>>(
         &self,
         lint: &'static Lint,
         span: S,
-        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagnosticMessage>,
+        decorate: impl for<'a, 'b> FnOnce(
+            &'b mut DiagnosticBuilder<'a, ()>,
+        ) -> &'b mut DiagnosticBuilder<'a, ()>,
     ) {
-        self.lookup(lint, Some(span), decorate);
+        self.lookup(lint, Some(span), msg, decorate);
     }
 
     /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically
     /// generated by `#[derive(LintDiagnostic)]`).
     fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> DecorateLint<'a, ()>) {
-        self.lookup(lint, None as Option<Span>, |diag| decorator.decorate_lint(diag));
+        self.lookup(lint, None as Option<Span>, decorator.msg(), |diag| {
+            decorator.decorate_lint(diag)
+        });
     }
 
     /// Emit a lint at the appropriate level, with no associated span.
     fn lint(
         &self,
         lint: &'static Lint,
-        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+        msg: DiagnosticMessage,
+        decorate: impl for<'a, 'b> FnOnce(
+            &'b mut DiagnosticBuilder<'a, ()>,
+        ) -> &'b mut DiagnosticBuilder<'a, ()>,
     ) {
-        self.lookup(lint, None as Option<Span>, decorate);
+        self.lookup(lint, None as Option<Span>, msg, decorate);
     }
 
     /// This returns the lint level for the given lint at the current location.
@@ -975,13 +985,16 @@ impl<'tcx> LintContext for LateContext<'tcx> {
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagnosticMessage>,
+        decorate: impl for<'a, 'b> FnOnce(
+            &'b mut DiagnosticBuilder<'a, ()>,
+        ) -> &'b mut DiagnosticBuilder<'a, ()>,
     ) {
         let hir_id = self.last_node_with_lint_attrs;
 
         match span {
-            Some(s) => self.tcx.struct_span_lint_hir(lint, hir_id, s, decorate),
-            None => self.tcx.struct_lint_node(lint, hir_id, decorate),
+            Some(s) => self.tcx.struct_span_lint_hir(lint, hir_id, s, msg, decorate),
+            None => self.tcx.struct_lint_node(lint, hir_id, msg, decorate),
         }
     }
 
@@ -1006,9 +1019,12 @@ impl LintContext for EarlyContext<'_> {
         &self,
         lint: &'static Lint,
         span: Option<S>,
-        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagnosticMessage>,
+        decorate: impl for<'a, 'b> FnOnce(
+            &'b mut DiagnosticBuilder<'a, ()>,
+        ) -> &'b mut DiagnosticBuilder<'a, ()>,
     ) {
-        self.builder.struct_lint(lint, span.map(|s| s.into()), decorate)
+        self.builder.struct_lint(lint, span.map(|s| s.into()), msg, decorate)
     }
 
     fn get_lint_level(&self, lint: &'static Lint) -> Level {
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
index 96ecd79a69c..18d30e1435b 100644
--- a/compiler/rustc_lint/src/early.rs
+++ b/compiler/rustc_lint/src/early.rs
@@ -43,9 +43,8 @@ impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
             self.context.lookup_with_diagnostics(
                 lint_id.lint,
                 Some(span),
-                |lint| {
-                    lint.build(msg).emit();
-                },
+                msg,
+                |lint| lint,
                 diagnostic,
             );
         }
diff --git a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
index f41ee640499..e8d307814b9 100644
--- a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
+++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
@@ -50,26 +50,24 @@ fn enforce_mem_discriminant(
 ) {
     let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0);
     if is_non_enum(ty_param) {
-        cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, expr_span, |builder| {
-            builder
-                .build(fluent::lint::enum_intrinsics_mem_discriminant)
-                .set_arg("ty_param", ty_param)
-                .span_note(args_span, fluent::lint::note)
-                .emit();
-        });
+        cx.struct_span_lint(
+            ENUM_INTRINSICS_NON_ENUMS,
+            expr_span,
+            fluent::lint::enum_intrinsics_mem_discriminant,
+            |lint| lint.set_arg("ty_param", ty_param).span_note(args_span, fluent::lint::note),
+        );
     }
 }
 
 fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) {
     let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0);
     if is_non_enum(ty_param) {
-        cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, |builder| {
-            builder
-                .build(fluent::lint::enum_intrinsics_mem_variant)
-                .set_arg("ty_param", ty_param)
-                .note(fluent::lint::note)
-                .emit();
-        });
+        cx.struct_span_lint(
+            ENUM_INTRINSICS_NON_ENUMS,
+            span,
+            fluent::lint::enum_intrinsics_mem_variant,
+            |lint| lint.set_arg("ty_param", ty_param).note(fluent::lint::note),
+        );
     }
 }
 
diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs
index 699e8154318..af13f453a50 100644
--- a/compiler/rustc_lint/src/expect.rs
+++ b/compiler/rustc_lint/src/expect.rs
@@ -43,17 +43,17 @@ fn emit_unfulfilled_expectation_lint(
         builtin::UNFULFILLED_LINT_EXPECTATIONS,
         hir_id,
         expectation.emission_span,
-        |diag| {
-            let mut diag = diag.build(fluent::lint::expectation);
+        fluent::lint::expectation,
+        |lint| {
             if let Some(rationale) = expectation.reason {
-                diag.note(rationale.as_str());
+                lint.note(rationale.as_str());
             }
 
             if expectation.is_unfulfilled_lint_expectations {
-                diag.note(fluent::lint::note);
+                lint.note(fluent::lint::note);
             }
 
-            diag.emit();
+            lint
         },
     );
 }
diff --git a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
index 8f22221324a..42557068bd3 100644
--- a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
+++ b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
@@ -60,52 +60,56 @@ impl HiddenUnicodeCodepoints {
             })
             .collect();
 
-        cx.struct_span_lint(TEXT_DIRECTION_CODEPOINT_IN_LITERAL, span, |lint| {
-            let mut err = lint.build(fluent::lint::hidden_unicode_codepoints);
-            err.set_arg("label", label);
-            err.set_arg("count", spans.len());
-            err.span_label(span, fluent::lint::label);
-            err.note(fluent::lint::note);
-            if point_at_inner_spans {
-                for (c, span) in &spans {
-                    err.span_label(*span, format!("{:?}", c));
+        cx.struct_span_lint(
+            TEXT_DIRECTION_CODEPOINT_IN_LITERAL,
+            span,
+            fluent::lint::hidden_unicode_codepoints,
+            |lint| {
+                lint.set_arg("label", label);
+                lint.set_arg("count", spans.len());
+                lint.span_label(span, fluent::lint::label);
+                lint.note(fluent::lint::note);
+                if point_at_inner_spans {
+                    for (c, span) in &spans {
+                        lint.span_label(*span, format!("{:?}", c));
+                    }
                 }
-            }
-            if point_at_inner_spans && !spans.is_empty() {
-                err.multipart_suggestion_with_style(
-                    fluent::lint::suggestion_remove,
-                    spans.iter().map(|(_, span)| (*span, "".to_string())).collect(),
-                    Applicability::MachineApplicable,
-                    SuggestionStyle::HideCodeAlways,
-                );
-                err.multipart_suggestion(
-                    fluent::lint::suggestion_escape,
-                    spans
-                        .into_iter()
-                        .map(|(c, span)| {
-                            let c = format!("{:?}", c);
-                            (span, c[1..c.len() - 1].to_string())
-                        })
-                        .collect(),
-                    Applicability::MachineApplicable,
-                );
-            } else {
-                // FIXME: in other suggestions we've reversed the inner spans of doc comments. We
-                // should do the same here to provide the same good suggestions as we do for
-                // literals above.
-                err.set_arg(
-                    "escaped",
-                    spans
-                        .into_iter()
-                        .map(|(c, _)| format!("{:?}", c))
-                        .collect::<Vec<String>>()
-                        .join(", "),
-                );
-                err.note(fluent::lint::suggestion_remove);
-                err.note(fluent::lint::no_suggestion_note_escape);
-            }
-            err.emit();
-        });
+                if point_at_inner_spans && !spans.is_empty() {
+                    lint.multipart_suggestion_with_style(
+                        fluent::lint::suggestion_remove,
+                        spans.iter().map(|(_, span)| (*span, "".to_string())).collect(),
+                        Applicability::MachineApplicable,
+                        SuggestionStyle::HideCodeAlways,
+                    );
+                    lint.multipart_suggestion(
+                        fluent::lint::suggestion_escape,
+                        spans
+                            .into_iter()
+                            .map(|(c, span)| {
+                                let c = format!("{:?}", c);
+                                (span, c[1..c.len() - 1].to_string())
+                            })
+                            .collect(),
+                        Applicability::MachineApplicable,
+                    );
+                } else {
+                    // FIXME: in other suggestions we've reversed the inner spans of doc comments. We
+                    // should do the same here to provide the same good suggestions as we do for
+                    // literals above.
+                    lint.set_arg(
+                        "escaped",
+                        spans
+                            .into_iter()
+                            .map(|(c, _)| format!("{:?}", c))
+                            .collect::<Vec<String>>()
+                            .join(", "),
+                    );
+                    lint.note(fluent::lint::suggestion_remove);
+                    lint.note(fluent::lint::no_suggestion_note_escape);
+                }
+                lint
+            },
+        );
     }
 }
 impl EarlyLintPass for HiddenUnicodeCodepoints {
diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs
index d8a03024d13..ed28620fc36 100644
--- a/compiler/rustc_lint/src/internal.rs
+++ b/compiler/rustc_lint/src/internal.rs
@@ -34,13 +34,16 @@ impl LateLintPass<'_> for DefaultHashTypes {
             Some(sym::HashSet) => "FxHashSet",
             _ => return,
         };
-        cx.struct_span_lint(DEFAULT_HASH_TYPES, path.span, |lint| {
-            lint.build(fluent::lint::default_hash_types)
-                .set_arg("preferred", replace)
-                .set_arg("used", cx.tcx.item_name(def_id))
-                .note(fluent::lint::note)
-                .emit();
-        });
+        cx.struct_span_lint(
+            DEFAULT_HASH_TYPES,
+            path.span,
+            fluent::lint::default_hash_types,
+            |lint| {
+                lint.set_arg("preferred", replace)
+                    .set_arg("used", cx.tcx.item_name(def_id))
+                    .note(fluent::lint::note)
+            },
+        );
     }
 }
 
@@ -80,12 +83,12 @@ impl LateLintPass<'_> for QueryStability {
         if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) {
             let def_id = instance.def_id();
             if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
-                cx.struct_span_lint(POTENTIAL_QUERY_INSTABILITY, span, |lint| {
-                    lint.build(fluent::lint::query_instability)
-                        .set_arg("query", cx.tcx.item_name(def_id))
-                        .note(fluent::lint::note)
-                        .emit();
-                })
+                cx.struct_span_lint(
+                    POTENTIAL_QUERY_INSTABILITY,
+                    span,
+                    fluent::lint::query_instability,
+                    |lint| lint.set_arg("query", cx.tcx.item_name(def_id)).note(fluent::lint::note),
+                )
             }
         }
     }
@@ -123,15 +126,14 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
             let span = path.span.with_hi(
                 segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()
             );
-            cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
-                lint.build(fluent::lint::tykind_kind)
+            cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, fluent::lint::tykind_kind, |lint| {
+                lint
                     .span_suggestion(
                         span,
                         fluent::lint::suggestion,
                         "ty",
                         Applicability::MaybeIncorrect, // ty maybe needs an import
                     )
-                    .emit();
             });
         }
     }
@@ -140,76 +142,77 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
         match &ty.kind {
             TyKind::Path(QPath::Resolved(_, path)) => {
                 if lint_ty_kind_usage(cx, &path.res) {
-                    cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
-                        let hir = cx.tcx.hir();
-                        match hir.find(hir.get_parent_node(ty.hir_id)) {
-                            Some(Node::Pat(Pat {
-                                kind:
-                                    PatKind::Path(qpath)
-                                    | PatKind::TupleStruct(qpath, ..)
-                                    | PatKind::Struct(qpath, ..),
-                                ..
-                            })) => {
-                                if let QPath::TypeRelative(qpath_ty, ..) = qpath
-                                    && qpath_ty.hir_id == ty.hir_id
-                                {
-                                    lint.build(fluent::lint::tykind_kind)
-                                        .span_suggestion(
-                                            path.span,
-                                            fluent::lint::suggestion,
-                                            "ty",
-                                            Applicability::MaybeIncorrect, // ty maybe needs an import
-                                        )
-                                        .emit();
-                                    return;
-                                }
+                    let hir = cx.tcx.hir();
+                    let span = match hir.find(hir.get_parent_node(ty.hir_id)) {
+                        Some(Node::Pat(Pat {
+                            kind:
+                                PatKind::Path(qpath)
+                                | PatKind::TupleStruct(qpath, ..)
+                                | PatKind::Struct(qpath, ..),
+                            ..
+                        })) => {
+                            if let QPath::TypeRelative(qpath_ty, ..) = qpath
+                                && qpath_ty.hir_id == ty.hir_id
+                            {
+                                Some(path.span)
+                            } else {
+                                None
                             }
-                            Some(Node::Expr(Expr {
-                                kind: ExprKind::Path(qpath),
-                                ..
-                            })) => {
-                                if let QPath::TypeRelative(qpath_ty, ..) = qpath
-                                    && qpath_ty.hir_id == ty.hir_id
-                                {
-                                    lint.build(fluent::lint::tykind_kind)
-                                        .span_suggestion(
-                                            path.span,
-                                            fluent::lint::suggestion,
-                                            "ty",
-                                            Applicability::MaybeIncorrect, // ty maybe needs an import
-                                        )
-                                        .emit();
-                                    return;
-                                }
+                        }
+                        Some(Node::Expr(Expr {
+                            kind: ExprKind::Path(qpath),
+                            ..
+                        })) => {
+                            if let QPath::TypeRelative(qpath_ty, ..) = qpath
+                                && qpath_ty.hir_id == ty.hir_id
+                            {
+                                Some(path.span)
+                            } else {
+                                None
                             }
-                            // Can't unify these two branches because qpath below is `&&` and above is `&`
-                            // and `A | B` paths don't play well together with adjustments, apparently.
-                            Some(Node::Expr(Expr {
-                                kind: ExprKind::Struct(qpath, ..),
-                                ..
-                            })) => {
-                                if let QPath::TypeRelative(qpath_ty, ..) = qpath
-                                    && qpath_ty.hir_id == ty.hir_id
-                                {
-                                    lint.build(fluent::lint::tykind_kind)
-                                        .span_suggestion(
-                                            path.span,
-                                            fluent::lint::suggestion,
-                                            "ty",
-                                            Applicability::MaybeIncorrect, // ty maybe needs an import
-                                        )
-                                        .emit();
-                                    return;
-                                }
+                        }
+                        // Can't unify these two branches because qpath below is `&&` and above is `&`
+                        // and `A | B` paths don't play well together with adjustments, apparently.
+                        Some(Node::Expr(Expr {
+                            kind: ExprKind::Struct(qpath, ..),
+                            ..
+                        })) => {
+                            if let QPath::TypeRelative(qpath_ty, ..) = qpath
+                                && qpath_ty.hir_id == ty.hir_id
+                            {
+                                Some(path.span)
+                            } else {
+                                None
                             }
-                            _ => {}
                         }
-                        lint.build(fluent::lint::tykind).help(fluent::lint::help).emit();
-                    })
+                        _ => None
+                    };
+
+                    match span {
+                        Some(span) => {
+                            cx.struct_span_lint(
+                                USAGE_OF_TY_TYKIND,
+                                path.span,
+                                fluent::lint::tykind_kind,
+                                |lint| lint.span_suggestion(
+                                    span,
+                                    fluent::lint::suggestion,
+                                    "ty",
+                                    Applicability::MaybeIncorrect, // ty maybe needs an import
+                                )
+                            )
+                        },
+                        None => cx.struct_span_lint(
+                            USAGE_OF_TY_TYKIND,
+                            path.span,
+                            fluent::lint::tykind,
+                            |lint| lint.help(fluent::lint::help)
+                        )
+                    }
                 } else if !ty.span.from_expansion() && let Some(t) = is_ty_or_ty_ctxt(cx, &path) {
                     if path.segments.len() > 1 {
-                        cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
-                            lint.build(fluent::lint::ty_qualified)
+                        cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, fluent::lint::ty_qualified, |lint| {
+                            lint
                                 .set_arg("ty", t.clone())
                                 .span_suggestion(
                                     path.span,
@@ -218,7 +221,6 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
                                     // The import probably needs to be changed
                                     Applicability::MaybeIncorrect,
                                 )
-                                .emit();
                         })
                     }
                 }
@@ -308,11 +310,8 @@ impl EarlyLintPass for LintPassImpl {
                         cx.struct_span_lint(
                             LINT_PASS_IMPL_WITHOUT_MACRO,
                             lint_pass.path.span,
-                            |lint| {
-                                lint.build(fluent::lint::lintpass_by_hand)
-                                    .help(fluent::lint::help)
-                                    .emit();
-                            },
+                            fluent::lint::lintpass_by_hand,
+                            |lint| lint.help(fluent::lint::help),
                         )
                     }
                 }
@@ -349,12 +348,12 @@ impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
                         if is_doc_keyword(v) {
                             return;
                         }
-                        cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| {
-                            lint.build(fluent::lint::non_existant_doc_keyword)
-                                .set_arg("keyword", v)
-                                .help(fluent::lint::help)
-                                .emit();
-                        });
+                        cx.struct_span_lint(
+                            EXISTING_DOC_KEYWORD,
+                            attr.span,
+                            fluent::lint::non_existant_doc_keyword,
+                            |lint| lint.set_arg("keyword", v).help(fluent::lint::help),
+                        );
                     }
                 }
             }
@@ -412,9 +411,12 @@ impl LateLintPass<'_> for Diagnostics {
         }
         debug!(?found_impl);
         if !found_parent_with_attr && !found_impl {
-            cx.struct_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, |lint| {
-                lint.build(fluent::lint::diag_out_of_impl).emit();
-            })
+            cx.struct_span_lint(
+                DIAGNOSTIC_OUTSIDE_OF_IMPL,
+                span,
+                fluent::lint::diag_out_of_impl,
+                |lint| lint,
+            )
         }
 
         let mut found_diagnostic_message = false;
@@ -430,9 +432,12 @@ impl LateLintPass<'_> for Diagnostics {
         }
         debug!(?found_diagnostic_message);
         if !found_parent_with_attr && !found_diagnostic_message {
-            cx.struct_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, |lint| {
-                lint.build(fluent::lint::untranslatable_diag).emit();
-            })
+            cx.struct_span_lint(
+                UNTRANSLATABLE_DIAGNOSTIC,
+                span,
+                fluent::lint::untranslatable_diag,
+                |lint| lint,
+            )
         }
     }
 }
@@ -464,8 +469,8 @@ impl LateLintPass<'_> for BadOptAccess {
                 let Some(literal) = item.literal()  &&
                 let ast::LitKind::Str(val, _) = literal.kind
             {
-                cx.struct_span_lint(BAD_OPT_ACCESS, expr.span, |lint| {
-                    lint.build(val.as_str()).emit(); }
+                cx.struct_span_lint(BAD_OPT_ACCESS, expr.span, val.as_str(), |lint|
+                    lint
                 );
             }
         }
diff --git a/compiler/rustc_lint/src/let_underscore.rs b/compiler/rustc_lint/src/let_underscore.rs
index 7e885e6c51a..78f355ec3d0 100644
--- a/compiler/rustc_lint/src/let_underscore.rs
+++ b/compiler/rustc_lint/src/let_underscore.rs
@@ -1,5 +1,5 @@
 use crate::{LateContext, LateLintPass, LintContext};
-use rustc_errors::{Applicability, LintDiagnosticBuilder, MultiSpan};
+use rustc_errors::{Applicability, DiagnosticBuilder, MultiSpan};
 use rustc_hir as hir;
 use rustc_middle::ty;
 use rustc_span::Symbol;
@@ -128,48 +128,41 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
                     init.span,
                     "this binding will immediately drop the value assigned to it".to_string(),
                 );
-                cx.struct_span_lint(LET_UNDERSCORE_LOCK, span, |lint| {
-                    build_and_emit_lint(
-                        lint,
-                        local,
-                        init.span,
-                        "non-binding let on a synchronization lock",
-                    )
-                })
+                cx.struct_span_lint(
+                    LET_UNDERSCORE_LOCK,
+                    span,
+                    "non-binding let on a synchronization lock",
+                    |lint| build_lint(lint, local, init.span),
+                )
             } else {
-                cx.struct_span_lint(LET_UNDERSCORE_DROP, local.span, |lint| {
-                    build_and_emit_lint(
-                        lint,
-                        local,
-                        init.span,
-                        "non-binding let on a type that implements `Drop`",
-                    );
-                })
+                cx.struct_span_lint(
+                    LET_UNDERSCORE_DROP,
+                    local.span,
+                    "non-binding let on a type that implements `Drop`",
+                    |lint| build_lint(lint, local, init.span),
+                )
             }
         }
     }
 }
 
-fn build_and_emit_lint(
-    lint: LintDiagnosticBuilder<'_, ()>,
+fn build_lint<'a, 'b>(
+    lint: &'a mut DiagnosticBuilder<'b, ()>,
     local: &hir::Local<'_>,
     init_span: rustc_span::Span,
-    msg: &str,
-) {
-    lint.build(msg)
-        .span_suggestion_verbose(
-            local.pat.span,
-            "consider binding to an unused variable to avoid immediately dropping the value",
-            "_unused",
-            Applicability::MachineApplicable,
-        )
-        .multipart_suggestion(
-            "consider immediately dropping the value",
-            vec![
-                (local.span.until(init_span), "drop(".to_string()),
-                (init_span.shrink_to_hi(), ")".to_string()),
-            ],
-            Applicability::MachineApplicable,
-        )
-        .emit();
+) -> &'a mut DiagnosticBuilder<'b, ()> {
+    lint.span_suggestion_verbose(
+        local.pat.span,
+        "consider binding to an unused variable to avoid immediately dropping the value",
+        "_unused",
+        Applicability::MachineApplicable,
+    )
+    .multipart_suggestion(
+        "consider immediately dropping the value",
+        vec![
+            (local.span.until(init_span), "drop(".to_string()),
+            (init_span.shrink_to_hi(), ")".to_string()),
+        ],
+        Applicability::MachineApplicable,
+    )
 }
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 1e16ac51e9e..82382350823 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -3,7 +3,7 @@ use crate::late::unerased_lint_store;
 use rustc_ast as ast;
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{Applicability, Diagnostic, LintDiagnosticBuilder, MultiSpan};
+use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::{intravisit, HirId};
 use rustc_middle::hir::nested_filter;
@@ -214,14 +214,14 @@ impl<'s> LintLevelsBuilder<'s> {
                     self.struct_lint(
                         FORBIDDEN_LINT_GROUPS,
                         Some(src.span().into()),
-                        |diag_builder| {
-                            let mut diag_builder = diag_builder.build(&format!(
-                                "{}({}) incompatible with previous forbid",
-                                level.as_str(),
-                                src.name(),
-                            ));
-                            decorate_diag(&mut diag_builder);
-                            diag_builder.emit();
+                        format!(
+                            "{}({}) incompatible with previous forbid",
+                            level.as_str(),
+                            src.name(),
+                        ),
+                        |lint| {
+                            decorate_diag(lint);
+                            lint
                         },
                     );
                 }
@@ -466,20 +466,18 @@ impl<'s> LintLevelsBuilder<'s> {
                                     lvl,
                                     src,
                                     Some(sp.into()),
+                                    format!(
+                                        "lint name `{}` is deprecated \
+                                         and may not have an effect in the future.",
+                                        name
+                                    ),
                                     |lint| {
-                                        let msg = format!(
-                                            "lint name `{}` is deprecated \
-                                             and may not have an effect in the future.",
-                                            name
-                                        );
-                                        lint.build(&msg)
-                                            .span_suggestion(
-                                                sp,
-                                                "change it to",
-                                                new_lint_name,
-                                                Applicability::MachineApplicable,
-                                            )
-                                            .emit();
+                                        lint.span_suggestion(
+                                            sp,
+                                            "change it to",
+                                            new_lint_name,
+                                            Applicability::MachineApplicable,
+                                        )
                                     },
                                 );
 
@@ -533,17 +531,17 @@ impl<'s> LintLevelsBuilder<'s> {
                             renamed_lint_level,
                             src,
                             Some(sp.into()),
+                            msg,
                             |lint| {
-                                let mut err = lint.build(msg);
                                 if let Some(new_name) = &renamed {
-                                    err.span_suggestion(
+                                    lint.span_suggestion(
                                         sp,
                                         "use the new name",
                                         new_name,
                                         Applicability::MachineApplicable,
                                     );
                                 }
-                                err.emit();
+                                lint
                             },
                         );
                     }
@@ -555,23 +553,30 @@ impl<'s> LintLevelsBuilder<'s> {
                             Some(self.current_specs()),
                             self.sess,
                         );
-                        struct_lint_level(self.sess, lint, level, src, Some(sp.into()), |lint| {
-                            let name = if let Some(tool_ident) = tool_ident {
-                                format!("{}::{}", tool_ident.name, name)
-                            } else {
-                                name.to_string()
-                            };
-                            let mut db = lint.build(format!("unknown lint: `{}`", name));
-                            if let Some(suggestion) = suggestion {
-                                db.span_suggestion(
-                                    sp,
-                                    "did you mean",
-                                    suggestion,
-                                    Applicability::MachineApplicable,
-                                );
-                            }
-                            db.emit();
-                        });
+                        let name = if let Some(tool_ident) = tool_ident {
+                            format!("{}::{}", tool_ident.name, name)
+                        } else {
+                            name.to_string()
+                        };
+                        struct_lint_level(
+                            self.sess,
+                            lint,
+                            level,
+                            src,
+                            Some(sp.into()),
+                            format!("unknown lint: `{}`", name),
+                            |lint| {
+                                if let Some(suggestion) = suggestion {
+                                    lint.span_suggestion(
+                                        sp,
+                                        "did you mean",
+                                        suggestion,
+                                        Applicability::MachineApplicable,
+                                    );
+                                }
+                                lint
+                            },
+                        );
                     }
                 }
                 // If this lint was renamed, apply the new lint instead of ignoring the attribute.
@@ -621,14 +626,12 @@ impl<'s> LintLevelsBuilder<'s> {
                     lint_level,
                     lint_src,
                     Some(lint_attr_span.into()),
-                    |lint| {
-                        let mut db = lint.build(&format!(
-                            "{}({}) is ignored unless specified at crate level",
-                            level.as_str(),
-                            lint_attr_name
-                        ));
-                        db.emit();
-                    },
+                    format!(
+                        "{}({}) is ignored unless specified at crate level",
+                        level.as_str(),
+                        lint_attr_name
+                    ),
+                    |lint| lint,
                 );
                 // don't set a separate error for every lint in the group
                 break;
@@ -665,13 +668,21 @@ impl<'s> LintLevelsBuilder<'s> {
             if !self.sess.features_untracked().enabled(feature) {
                 let lint = builtin::UNKNOWN_LINTS;
                 let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
-                struct_lint_level(self.sess, lint, level, src, Some(span.into()), |lint_db| {
-                    let mut db =
-                        lint_db.build(&format!("unknown lint: `{}`", lint_id.lint.name_lower()));
-                    db.note(&format!("the `{}` lint is unstable", lint_id.lint.name_lower(),));
-                    add_feature_diagnostics(&mut db, &self.sess.parse_sess, feature);
-                    db.emit();
-                });
+                struct_lint_level(
+                    self.sess,
+                    lint,
+                    level,
+                    src,
+                    Some(span.into()),
+                    format!("unknown lint: `{}`", lint_id.lint.name_lower()),
+                    |lint| {
+                        lint.note(
+                            &format!("the `{}` lint is unstable", lint_id.lint.name_lower(),),
+                        );
+                        add_feature_diagnostics(lint, &self.sess.parse_sess, feature);
+                        lint
+                    },
+                );
                 return false;
             }
         }
@@ -694,10 +705,13 @@ impl<'s> LintLevelsBuilder<'s> {
         &self,
         lint: &'static Lint,
         span: Option<MultiSpan>,
-        decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+        msg: impl Into<DiagnosticMessage>,
+        decorate: impl for<'a, 'b> FnOnce(
+            &'b mut DiagnosticBuilder<'a, ()>,
+        ) -> &'b mut DiagnosticBuilder<'a, ()>,
     ) {
         let (level, src) = self.lint_level(lint);
-        struct_lint_level(self.sess, lint, level, src, span, decorate)
+        struct_lint_level(self.sess, lint, level, src, span, msg, decorate)
     }
 
     /// Registers the ID provided with the current set of lints stored in
diff --git a/compiler/rustc_lint/src/methods.rs b/compiler/rustc_lint/src/methods.rs
index 5f7f03480c0..313119637bc 100644
--- a/compiler/rustc_lint/src/methods.rs
+++ b/compiler/rustc_lint/src/methods.rs
@@ -90,14 +90,17 @@ fn lint_cstring_as_ptr(
         if cx.tcx.is_diagnostic_item(sym::Result, def.did()) {
             if let ty::Adt(adt, _) = substs.type_at(0).kind() {
                 if cx.tcx.is_diagnostic_item(sym::cstring_type, adt.did()) {
-                    cx.struct_span_lint(TEMPORARY_CSTRING_AS_PTR, as_ptr_span, |diag| {
-                        diag.build(fluent::lint::cstring_ptr)
-                            .span_label(as_ptr_span, fluent::lint::as_ptr_label)
-                            .span_label(unwrap.span, fluent::lint::unwrap_label)
-                            .note(fluent::lint::note)
-                            .help(fluent::lint::help)
-                            .emit();
-                    });
+                    cx.struct_span_lint(
+                        TEMPORARY_CSTRING_AS_PTR,
+                        as_ptr_span,
+                        fluent::lint::cstring_ptr,
+                        |diag| {
+                            diag.span_label(as_ptr_span, fluent::lint::as_ptr_label)
+                                .span_label(unwrap.span, fluent::lint::unwrap_label)
+                                .note(fluent::lint::note)
+                                .help(fluent::lint::help)
+                        },
+                    );
                 }
             }
         }
diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs
index 764003e61a6..b2626efb6d7 100644
--- a/compiler/rustc_lint/src/non_ascii_idents.rs
+++ b/compiler/rustc_lint/src/non_ascii_idents.rs
@@ -180,15 +180,21 @@ impl EarlyLintPass for NonAsciiIdents {
                 continue;
             }
             has_non_ascii_idents = true;
-            cx.struct_span_lint(NON_ASCII_IDENTS, sp, |lint| {
-                lint.build(fluent::lint::identifier_non_ascii_char).emit();
-            });
+            cx.struct_span_lint(
+                NON_ASCII_IDENTS,
+                sp,
+                fluent::lint::identifier_non_ascii_char,
+                |lint| lint,
+            );
             if check_uncommon_codepoints
                 && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed)
             {
-                cx.struct_span_lint(UNCOMMON_CODEPOINTS, sp, |lint| {
-                    lint.build(fluent::lint::identifier_uncommon_codepoints).emit();
-                })
+                cx.struct_span_lint(
+                    UNCOMMON_CODEPOINTS,
+                    sp,
+                    fluent::lint::identifier_uncommon_codepoints,
+                    |lint| lint,
+                )
             }
         }
 
@@ -216,13 +222,16 @@ impl EarlyLintPass for NonAsciiIdents {
                     .entry(skeleton_sym)
                     .and_modify(|(existing_symbol, existing_span, existing_is_ascii)| {
                         if !*existing_is_ascii || !is_ascii {
-                            cx.struct_span_lint(CONFUSABLE_IDENTS, sp, |lint| {
-                                lint.build(fluent::lint::confusable_identifier_pair)
-                                    .set_arg("existing_sym", *existing_symbol)
-                                    .set_arg("sym", symbol)
-                                    .span_label(*existing_span, fluent::lint::label)
-                                    .emit();
-                            });
+                            cx.struct_span_lint(
+                                CONFUSABLE_IDENTS,
+                                sp,
+                                fluent::lint::confusable_identifier_pair,
+                                |lint| {
+                                    lint.set_arg("existing_sym", *existing_symbol)
+                                        .set_arg("sym", symbol)
+                                        .span_label(*existing_span, fluent::lint::label)
+                                },
+                            );
                         }
                         if *existing_is_ascii && !is_ascii {
                             *existing_symbol = symbol;
@@ -322,22 +331,25 @@ impl EarlyLintPass for NonAsciiIdents {
                 }
 
                 for ((sp, ch_list), script_set) in lint_reports {
-                    cx.struct_span_lint(MIXED_SCRIPT_CONFUSABLES, sp, |lint| {
-                        let mut includes = String::new();
-                        for (idx, ch) in ch_list.into_iter().enumerate() {
-                            if idx != 0 {
-                                includes += ", ";
+                    cx.struct_span_lint(
+                        MIXED_SCRIPT_CONFUSABLES,
+                        sp,
+                        fluent::lint::mixed_script_confusables,
+                        |lint| {
+                            let mut includes = String::new();
+                            for (idx, ch) in ch_list.into_iter().enumerate() {
+                                if idx != 0 {
+                                    includes += ", ";
+                                }
+                                let char_info = format!("'{}' (U+{:04X})", ch, ch as u32);
+                                includes += &char_info;
                             }
-                            let char_info = format!("'{}' (U+{:04X})", ch, ch as u32);
-                            includes += &char_info;
-                        }
-                        lint.build(fluent::lint::mixed_script_confusables)
-                            .set_arg("set", script_set.to_string())
-                            .set_arg("includes", includes)
-                            .note(fluent::lint::includes_note)
-                            .note(fluent::lint::note)
-                            .emit();
-                    });
+                            lint.set_arg("set", script_set.to_string())
+                                .set_arg("includes", includes)
+                                .note(fluent::lint::includes_note)
+                                .note(fluent::lint::note)
+                        },
+                    );
                 }
             }
         }
diff --git a/compiler/rustc_lint/src/non_fmt_panic.rs b/compiler/rustc_lint/src/non_fmt_panic.rs
index cdad2d2e8f9..9d2a23f2b5f 100644
--- a/compiler/rustc_lint/src/non_fmt_panic.rs
+++ b/compiler/rustc_lint/src/non_fmt_panic.rs
@@ -119,21 +119,19 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
         arg_span = expn.call_site;
     }
 
-    cx.struct_span_lint(NON_FMT_PANICS, arg_span, |lint| {
-        let mut l = lint.build(fluent::lint::non_fmt_panic);
-        l.set_arg("name", symbol);
-        l.note(fluent::lint::note);
-        l.note(fluent::lint::more_info_note);
+    cx.struct_span_lint(NON_FMT_PANICS, arg_span, fluent::lint::non_fmt_panic, |lint| {
+        lint.set_arg("name", symbol);
+        lint.note(fluent::lint::note);
+        lint.note(fluent::lint::more_info_note);
         if !is_arg_inside_call(arg_span, span) {
             // No clue where this argument is coming from.
-            l.emit();
-            return;
+            return lint;
         }
         if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) {
             // A case of `panic!(format!(..))`.
-            l.note(fluent::lint::supports_fmt_note);
+            lint.note(fluent::lint::supports_fmt_note);
             if let Some((open, close, _)) = find_delimiters(cx, arg_span) {
-                l.multipart_suggestion(
+                lint.multipart_suggestion(
                     fluent::lint::supports_fmt_suggestion,
                     vec![
                         (arg_span.until(open.shrink_to_hi()), "".into()),
@@ -180,15 +178,15 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
             };
 
             if suggest_display {
-                l.span_suggestion_verbose(
+                lint.span_suggestion_verbose(
                     arg_span.shrink_to_lo(),
                     fluent::lint::display_suggestion,
                     "\"{}\", ",
                     fmt_applicability,
                 );
             } else if suggest_debug {
-                l.set_arg("ty", ty);
-                l.span_suggestion_verbose(
+                lint.set_arg("ty", ty);
+                lint.span_suggestion_verbose(
                     arg_span.shrink_to_lo(),
                     fluent::lint::debug_suggestion,
                     "\"{:?}\", ",
@@ -198,8 +196,8 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
 
             if suggest_panic_any {
                 if let Some((open, close, del)) = find_delimiters(cx, span) {
-                    l.set_arg("already_suggested", suggest_display || suggest_debug);
-                    l.multipart_suggestion(
+                    lint.set_arg("already_suggested", suggest_display || suggest_debug);
+                    lint.multipart_suggestion(
                         fluent::lint::panic_suggestion,
                         if del == '(' {
                             vec![(span.until(open), "std::panic::panic_any".into())]
@@ -214,7 +212,7 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc
                 }
             }
         }
-        l.emit();
+        lint
     });
 }
 
@@ -258,26 +256,30 @@ fn check_panic_str<'tcx>(
                 .map(|span| fmt_span.from_inner(InnerSpan::new(span.start, span.end)))
                 .collect(),
         };
-        cx.struct_span_lint(NON_FMT_PANICS, arg_spans, |lint| {
-            let mut l = lint.build(fluent::lint::non_fmt_panic_unused);
-            l.set_arg("count", n_arguments);
-            l.note(fluent::lint::note);
-            if is_arg_inside_call(arg.span, span) {
-                l.span_suggestion(
-                    arg.span.shrink_to_hi(),
-                    fluent::lint::add_args_suggestion,
-                    ", ...",
-                    Applicability::HasPlaceholders,
-                );
-                l.span_suggestion(
-                    arg.span.shrink_to_lo(),
-                    fluent::lint::add_fmt_suggestion,
-                    "\"{}\", ",
-                    Applicability::MachineApplicable,
-                );
-            }
-            l.emit();
-        });
+        cx.struct_span_lint(
+            NON_FMT_PANICS,
+            arg_spans,
+            fluent::lint::non_fmt_panic_unused,
+            |lint| {
+                lint.set_arg("count", n_arguments);
+                lint.note(fluent::lint::note);
+                if is_arg_inside_call(arg.span, span) {
+                    lint.span_suggestion(
+                        arg.span.shrink_to_hi(),
+                        fluent::lint::add_args_suggestion,
+                        ", ...",
+                        Applicability::HasPlaceholders,
+                    );
+                    lint.span_suggestion(
+                        arg.span.shrink_to_lo(),
+                        fluent::lint::add_fmt_suggestion,
+                        "\"{}\", ",
+                        Applicability::MachineApplicable,
+                    );
+                }
+                lint
+            },
+        );
     } else {
         let brace_spans: Option<Vec<_>> =
             snippet.filter(|s| s.starts_with('"') || s.starts_with("r#")).map(|s| {
@@ -287,20 +289,24 @@ fn check_panic_str<'tcx>(
                     .collect()
             });
         let count = brace_spans.as_ref().map(|v| v.len()).unwrap_or(/* any number >1 */ 2);
-        cx.struct_span_lint(NON_FMT_PANICS, brace_spans.unwrap_or_else(|| vec![span]), |lint| {
-            let mut l = lint.build(fluent::lint::non_fmt_panic_braces);
-            l.set_arg("count", count);
-            l.note(fluent::lint::note);
-            if is_arg_inside_call(arg.span, span) {
-                l.span_suggestion(
-                    arg.span.shrink_to_lo(),
-                    fluent::lint::suggestion,
-                    "\"{}\", ",
-                    Applicability::MachineApplicable,
-                );
-            }
-            l.emit();
-        });
+        cx.struct_span_lint(
+            NON_FMT_PANICS,
+            brace_spans.unwrap_or_else(|| vec![span]),
+            fluent::lint::non_fmt_panic_braces,
+            |lint| {
+                lint.set_arg("count", count);
+                lint.note(fluent::lint::note);
+                if is_arg_inside_call(arg.span, span) {
+                    lint.span_suggestion(
+                        arg.span.shrink_to_lo(),
+                        fluent::lint::suggestion,
+                        "\"{}\", ",
+                        Applicability::MachineApplicable,
+                    );
+                }
+                lint
+            },
+        );
     }
 }
 
diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs
index 768ad84838b..9f800e9c8c9 100644
--- a/compiler/rustc_lint/src/nonstandard_style.rs
+++ b/compiler/rustc_lint/src/nonstandard_style.rs
@@ -136,26 +136,30 @@ impl NonCamelCaseTypes {
         let name = ident.name.as_str();
 
         if !is_camel_case(name) {
-            cx.struct_span_lint(NON_CAMEL_CASE_TYPES, ident.span, |lint| {
-                let mut err = lint.build(fluent::lint::non_camel_case_type);
-                let cc = to_camel_case(name);
-                // We cannot provide meaningful suggestions
-                // if the characters are in the category of "Lowercase Letter".
-                if *name != cc {
-                    err.span_suggestion(
-                        ident.span,
-                        fluent::lint::suggestion,
-                        to_camel_case(name),
-                        Applicability::MaybeIncorrect,
-                    );
-                } else {
-                    err.span_label(ident.span, fluent::lint::label);
-                }
+            cx.struct_span_lint(
+                NON_CAMEL_CASE_TYPES,
+                ident.span,
+                fluent::lint::non_camel_case_type,
+                |lint| {
+                    let cc = to_camel_case(name);
+                    // We cannot provide meaningful suggestions
+                    // if the characters are in the category of "Lowercase Letter".
+                    if *name != cc {
+                        lint.span_suggestion(
+                            ident.span,
+                            fluent::lint::suggestion,
+                            to_camel_case(name),
+                            Applicability::MaybeIncorrect,
+                        );
+                    } else {
+                        lint.span_label(ident.span, fluent::lint::label);
+                    }
 
-                err.set_arg("sort", sort);
-                err.set_arg("name", name);
-                err.emit();
-            })
+                    lint.set_arg("sort", sort);
+                    lint.set_arg("name", name);
+                    lint
+                },
+            )
         }
     }
 }
@@ -280,9 +284,8 @@ impl NonSnakeCase {
         let name = ident.name.as_str();
 
         if !is_snake_case(name) {
-            cx.struct_span_lint(NON_SNAKE_CASE, ident.span, |lint| {
+            cx.struct_span_lint(NON_SNAKE_CASE, ident.span, fluent::lint::non_snake_case, |lint| {
                 let sc = NonSnakeCase::to_snake_case(name);
-                let mut err = lint.build(fluent::lint::non_snake_case);
                 // We cannot provide meaningful suggestions
                 // if the characters are in the category of "Uppercase Letter".
                 if name != sc {
@@ -297,30 +300,30 @@ impl NonSnakeCase {
                             if sc_ident.name.can_be_raw() {
                                 (fluent::lint::rename_or_convert_suggestion, sc_ident.to_string())
                             } else {
-                                err.note(fluent::lint::cannot_convert_note);
+                                lint.note(fluent::lint::cannot_convert_note);
                                 (fluent::lint::rename_suggestion, String::new())
                             }
                         } else {
                             (fluent::lint::convert_suggestion, sc.clone())
                         };
 
-                        err.span_suggestion(
+                        lint.span_suggestion(
                             ident.span,
                             message,
                             suggestion,
                             Applicability::MaybeIncorrect,
                         );
                     } else {
-                        err.help(fluent::lint::help);
+                        lint.help(fluent::lint::help);
                     }
                 } else {
-                    err.span_label(ident.span, fluent::lint::label);
+                    lint.span_label(ident.span, fluent::lint::label);
                 }
 
-                err.set_arg("sort", sort);
-                err.set_arg("name", name);
-                err.set_arg("sc", sc);
-                err.emit();
+                lint.set_arg("sort", sort);
+                lint.set_arg("name", name);
+                lint.set_arg("sc", sc);
+                lint
             });
         }
     }
@@ -478,26 +481,30 @@ impl NonUpperCaseGlobals {
     fn check_upper_case(cx: &LateContext<'_>, sort: &str, ident: &Ident) {
         let name = ident.name.as_str();
         if name.chars().any(|c| c.is_lowercase()) {
-            cx.struct_span_lint(NON_UPPER_CASE_GLOBALS, ident.span, |lint| {
-                let uc = NonSnakeCase::to_snake_case(&name).to_uppercase();
-                let mut err = lint.build(fluent::lint::non_upper_case_global);
-                // We cannot provide meaningful suggestions
-                // if the characters are in the category of "Lowercase Letter".
-                if *name != uc {
-                    err.span_suggestion(
-                        ident.span,
-                        fluent::lint::suggestion,
-                        uc,
-                        Applicability::MaybeIncorrect,
-                    );
-                } else {
-                    err.span_label(ident.span, fluent::lint::label);
-                }
+            cx.struct_span_lint(
+                NON_UPPER_CASE_GLOBALS,
+                ident.span,
+                fluent::lint::non_upper_case_global,
+                |lint| {
+                    let uc = NonSnakeCase::to_snake_case(&name).to_uppercase();
+                    // We cannot provide meaningful suggestions
+                    // if the characters are in the category of "Lowercase Letter".
+                    if *name != uc {
+                        lint.span_suggestion(
+                            ident.span,
+                            fluent::lint::suggestion,
+                            uc,
+                            Applicability::MaybeIncorrect,
+                        );
+                    } else {
+                        lint.span_label(ident.span, fluent::lint::label);
+                    }
 
-                err.set_arg("sort", sort);
-                err.set_arg("name", name);
-                err.emit();
-            })
+                    lint.set_arg("sort", sort);
+                    lint.set_arg("name", name);
+                    lint
+                },
+            )
         }
     }
 }
diff --git a/compiler/rustc_lint/src/noop_method_call.rs b/compiler/rustc_lint/src/noop_method_call.rs
index d1449496d33..19188d5c376 100644
--- a/compiler/rustc_lint/src/noop_method_call.rs
+++ b/compiler/rustc_lint/src/noop_method_call.rs
@@ -90,13 +90,11 @@ impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
         }
         let expr_span = expr.span;
         let span = expr_span.with_lo(receiver.span.hi());
-        cx.struct_span_lint(NOOP_METHOD_CALL, span, |lint| {
-            lint.build(fluent::lint::noop_method_call)
-                .set_arg("method", call.ident.name)
+        cx.struct_span_lint(NOOP_METHOD_CALL, span, fluent::lint::noop_method_call, |lint| {
+            lint.set_arg("method", call.ident.name)
                 .set_arg("receiver_ty", receiver_ty)
                 .span_label(span, fluent::lint::label)
                 .note(fluent::lint::note)
-                .emit();
         });
     }
 }
diff --git a/compiler/rustc_lint/src/pass_by_value.rs b/compiler/rustc_lint/src/pass_by_value.rs
index af5e5faf1f5..7e5a20a3bc1 100644
--- a/compiler/rustc_lint/src/pass_by_value.rs
+++ b/compiler/rustc_lint/src/pass_by_value.rs
@@ -29,18 +29,20 @@ impl<'tcx> LateLintPass<'tcx> for PassByValue {
                     }
                 }
                 if let Some(t) = path_for_pass_by_value(cx, &inner_ty) {
-                    cx.struct_span_lint(PASS_BY_VALUE, ty.span, |lint| {
-                        lint.build(fluent::lint::pass_by_value)
-                            .set_arg("ty", t.clone())
-                            .span_suggestion(
+                    cx.struct_span_lint(
+                        PASS_BY_VALUE,
+                        ty.span,
+                        fluent::lint::pass_by_value,
+                        |lint| {
+                            lint.set_arg("ty", t.clone()).span_suggestion(
                                 ty.span,
                                 fluent::lint::suggestion,
                                 t,
                                 // Changing type of function argument
                                 Applicability::MaybeIncorrect,
                             )
-                            .emit();
-                    })
+                        },
+                    )
                 }
             }
             _ => {}
diff --git a/compiler/rustc_lint/src/redundant_semicolon.rs b/compiler/rustc_lint/src/redundant_semicolon.rs
index 26f41345383..46c84550e9f 100644
--- a/compiler/rustc_lint/src/redundant_semicolon.rs
+++ b/compiler/rustc_lint/src/redundant_semicolon.rs
@@ -48,11 +48,18 @@ fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, boo
             return;
         }
 
-        cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| {
-            lint.build(fluent::lint::redundant_semicolons)
-                .set_arg("multiple", multiple)
-                .span_suggestion(span, fluent::lint::suggestion, "", Applicability::MaybeIncorrect)
-                .emit();
-        });
+        cx.struct_span_lint(
+            REDUNDANT_SEMICOLONS,
+            span,
+            fluent::lint::redundant_semicolons,
+            |lint| {
+                lint.set_arg("multiple", multiple).span_suggestion(
+                    span,
+                    fluent::lint::suggestion,
+                    "",
+                    Applicability::MaybeIncorrect,
+                )
+            },
+        );
     }
 }
diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs
index df1587c5948..078465bdce6 100644
--- a/compiler/rustc_lint/src/traits.rs
+++ b/compiler/rustc_lint/src/traits.rs
@@ -100,15 +100,18 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints {
                 if trait_predicate.trait_ref.self_ty().is_impl_trait() {
                     continue;
                 }
-                cx.struct_span_lint(DROP_BOUNDS, span, |lint| {
-                    let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else {
-                        return
-                    };
-                    lint.build(fluent::lint::drop_trait_constraints)
-                        .set_arg("predicate", predicate)
-                        .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop))
-                        .emit();
-                });
+                let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else {
+                    continue;
+                };
+                cx.struct_span_lint(
+                    DROP_BOUNDS,
+                    span,
+                    fluent::lint::drop_trait_constraints,
+                    |lint| {
+                        lint.set_arg("predicate", predicate)
+                            .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop))
+                    },
+                );
             }
         }
     }
@@ -119,14 +122,11 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints {
         };
         for bound in &bounds[..] {
             let def_id = bound.trait_ref.trait_def_id();
-            if cx.tcx.lang_items().drop_trait() == def_id {
-                cx.struct_span_lint(DYN_DROP, bound.span, |lint| {
-                    let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else {
-                        return
-                    };
-                    lint.build(fluent::lint::drop_glue)
-                        .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop))
-                        .emit();
+            if cx.tcx.lang_items().drop_trait() == def_id
+                && let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop)
+            {
+                cx.struct_span_lint(DYN_DROP, bound.span, fluent::lint::drop_glue, |lint| {
+                    lint.set_arg("needs_drop", cx.tcx.def_path_str(needs_drop))
                 });
             }
         }
diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
index 4fb6d65a6e9..b6009bd800a 100644
--- a/compiler/rustc_lint/src/types.rs
+++ b/compiler/rustc_lint/src/types.rs
@@ -144,12 +144,18 @@ fn lint_overflowing_range_endpoint<'tcx>(
     // We can suggest using an inclusive range
     // (`..=`) instead only if it is the `end` that is
     // overflowing and only by 1.
-    if eps[1].expr.hir_id == expr.hir_id && lit_val - 1 == max {
-        cx.struct_span_lint(OVERFLOWING_LITERALS, struct_expr.span, |lint| {
-            let mut err = lint.build(fluent::lint::range_endpoint_out_of_range);
-            err.set_arg("ty", ty);
-            if let Ok(start) = cx.sess().source_map().span_to_snippet(eps[0].span) {
+    if eps[1].expr.hir_id == expr.hir_id && lit_val - 1 == max
+        && let Ok(start) = cx.sess().source_map().span_to_snippet(eps[0].span)
+    {
+        cx.struct_span_lint(
+            OVERFLOWING_LITERALS,
+            struct_expr.span,
+            fluent::lint::range_endpoint_out_of_range,
+            |lint| {
                 use ast::{LitIntType, LitKind};
+
+                lint.set_arg("ty", ty);
+
                 // We need to preserve the literal's suffix,
                 // as it may determine typing information.
                 let suffix = match lit.node {
@@ -159,16 +165,17 @@ fn lint_overflowing_range_endpoint<'tcx>(
                     _ => bug!(),
                 };
                 let suggestion = format!("{}..={}{}", start, lit_val - 1, suffix);
-                err.span_suggestion(
+                lint.span_suggestion(
                     struct_expr.span,
                     fluent::lint::suggestion,
                     suggestion,
                     Applicability::MachineApplicable,
                 );
-                err.emit();
                 overwritten = true;
-            }
-        });
+
+                lint
+            },
+        );
     }
     overwritten
 }
@@ -221,52 +228,58 @@ fn report_bin_hex_error(
     negative: bool,
 ) {
     let size = Integer::from_attr(&cx.tcx, ty).size();
-    cx.struct_span_lint(OVERFLOWING_LITERALS, expr.span, |lint| {
-        let (t, actually) = match ty {
-            attr::IntType::SignedInt(t) => {
-                let actually = if negative {
-                    -(size.sign_extend(val) as i128)
-                } else {
-                    size.sign_extend(val) as i128
-                };
-                (t.name_str(), actually.to_string())
-            }
-            attr::IntType::UnsignedInt(t) => {
-                let actually = size.truncate(val);
-                (t.name_str(), actually.to_string())
-            }
-        };
-        let mut err = lint.build(fluent::lint::overflowing_bin_hex);
-        if negative {
-            // If the value is negative,
-            // emits a note about the value itself, apart from the literal.
-            err.note(fluent::lint::negative_note);
-            err.note(fluent::lint::negative_becomes_note);
-        } else {
-            err.note(fluent::lint::positive_note);
-        }
-        if let Some(sugg_ty) =
-            get_type_suggestion(cx.typeck_results().node_type(expr.hir_id), val, negative)
-        {
-            err.set_arg("suggestion_ty", sugg_ty);
-            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,
-                    fluent::lint::suggestion,
-                    format!("{}{}", sans_suffix, sugg_ty),
-                    Applicability::MachineApplicable,
-                );
+    cx.struct_span_lint(
+        OVERFLOWING_LITERALS,
+        expr.span,
+        fluent::lint::overflowing_bin_hex,
+        |lint| {
+            let (t, actually) = match ty {
+                attr::IntType::SignedInt(t) => {
+                    let actually = if negative {
+                        -(size.sign_extend(val) as i128)
+                    } else {
+                        size.sign_extend(val) as i128
+                    };
+                    (t.name_str(), actually.to_string())
+                }
+                attr::IntType::UnsignedInt(t) => {
+                    let actually = size.truncate(val);
+                    (t.name_str(), actually.to_string())
+                }
+            };
+
+            if negative {
+                // If the value is negative,
+                // emits a note about the value itself, apart from the literal.
+                lint.note(fluent::lint::negative_note);
+                lint.note(fluent::lint::negative_becomes_note);
             } else {
-                err.help(fluent::lint::help);
+                lint.note(fluent::lint::positive_note);
             }
-        }
-        err.set_arg("ty", t);
-        err.set_arg("lit", repr_str);
-        err.set_arg("dec", val);
-        err.set_arg("actually", actually);
-        err.emit();
-    });
+            if let Some(sugg_ty) =
+                get_type_suggestion(cx.typeck_results().node_type(expr.hir_id), val, negative)
+            {
+                lint.set_arg("suggestion_ty", sugg_ty);
+                if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
+                    let (sans_suffix, _) = repr_str.split_at(pos);
+                    lint.span_suggestion(
+                        expr.span,
+                        fluent::lint::suggestion,
+                        format!("{}{}", sans_suffix, sugg_ty),
+                        Applicability::MachineApplicable,
+                    );
+                } else {
+                    lint.help(fluent::lint::help);
+                }
+            }
+            lint.set_arg("ty", t)
+                .set_arg("lit", repr_str)
+                .set_arg("dec", val)
+                .set_arg("actually", actually);
+
+            lint
+        },
+    );
 }
 
 // This function finds the next fitting type and generates a suggestion string.
@@ -349,26 +362,27 @@ fn lint_int_literal<'tcx>(
             return;
         }
 
-        cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
-            let mut err = lint.build(fluent::lint::overflowing_int);
-            err.set_arg("ty", t.name_str());
-            err.set_arg(
-                "lit",
-                cx.sess()
-                    .source_map()
-                    .span_to_snippet(lit.span)
-                    .expect("must get snippet from literal"),
-            );
-            err.set_arg("min", min);
-            err.set_arg("max", max);
-            err.note(fluent::lint::note);
+        cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, fluent::lint::overflowing_int, |lint| {
+            lint.set_arg("ty", t.name_str())
+                .set_arg(
+                    "lit",
+                    cx.sess()
+                        .source_map()
+                        .span_to_snippet(lit.span)
+                        .expect("must get snippet from literal"),
+                )
+                .set_arg("min", min)
+                .set_arg("max", max)
+                .note(fluent::lint::note);
+
             if let Some(sugg_ty) =
                 get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative)
             {
-                err.set_arg("suggestion_ty", sugg_ty);
-                err.help(fluent::lint::help);
+                lint.set_arg("suggestion_ty", sugg_ty);
+                lint.help(fluent::lint::help);
             }
-            err.emit();
+
+            lint
         });
     }
 }
@@ -393,16 +407,19 @@ fn lint_uint_literal<'tcx>(
             match par_e.kind {
                 hir::ExprKind::Cast(..) => {
                     if let ty::Char = cx.typeck_results().expr_ty(par_e).kind() {
-                        cx.struct_span_lint(OVERFLOWING_LITERALS, par_e.span, |lint| {
-                            lint.build(fluent::lint::only_cast_u8_to_char)
-                                .span_suggestion(
+                        cx.struct_span_lint(
+                            OVERFLOWING_LITERALS,
+                            par_e.span,
+                            fluent::lint::only_cast_u8_to_char,
+                            |lint| {
+                                lint.span_suggestion(
                                     par_e.span,
                                     fluent::lint::suggestion,
                                     format!("'\\u{{{:X}}}'", lit_val),
                                     Applicability::MachineApplicable,
                                 )
-                                .emit();
-                        });
+                            },
+                        );
                         return;
                     }
                 }
@@ -424,9 +441,8 @@ fn lint_uint_literal<'tcx>(
             );
             return;
         }
-        cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
-            lint.build(fluent::lint::overflowing_uint)
-                .set_arg("ty", t.name_str())
+        cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, fluent::lint::overflowing_uint, |lint| {
+            lint.set_arg("ty", t.name_str())
                 .set_arg(
                     "lit",
                     cx.sess()
@@ -437,7 +453,6 @@ fn lint_uint_literal<'tcx>(
                 .set_arg("min", min)
                 .set_arg("max", max)
                 .note(fluent::lint::note)
-                .emit();
         });
     }
 }
@@ -467,19 +482,22 @@ fn lint_literal<'tcx>(
                 _ => bug!(),
             };
             if is_infinite == Ok(true) {
-                cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
-                    lint.build(fluent::lint::overflowing_literal)
-                        .set_arg("ty", t.name_str())
-                        .set_arg(
-                            "lit",
-                            cx.sess()
-                                .source_map()
-                                .span_to_snippet(lit.span)
-                                .expect("must get snippet from literal"),
-                        )
-                        .note(fluent::lint::note)
-                        .emit();
-                });
+                cx.struct_span_lint(
+                    OVERFLOWING_LITERALS,
+                    e.span,
+                    fluent::lint::overflowing_literal,
+                    |lint| {
+                        lint.set_arg("ty", t.name_str())
+                            .set_arg(
+                                "lit",
+                                cx.sess()
+                                    .source_map()
+                                    .span_to_snippet(lit.span)
+                                    .expect("must get snippet from literal"),
+                            )
+                            .note(fluent::lint::note)
+                    },
+                );
             }
         }
         _ => {}
@@ -497,9 +515,12 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
             }
             hir::ExprKind::Binary(binop, ref l, ref r) => {
                 if is_comparison(binop) && !check_limits(cx, binop, &l, &r) {
-                    cx.struct_span_lint(UNUSED_COMPARISONS, e.span, |lint| {
-                        lint.build(fluent::lint::unused_comparisons).emit();
-                    });
+                    cx.struct_span_lint(
+                        UNUSED_COMPARISONS,
+                        e.span,
+                        fluent::lint::unused_comparisons,
+                        |lint| lint,
+                    );
                 }
             }
             hir::ExprKind::Lit(ref lit) => lint_literal(cx, self, e, lit),
@@ -1150,25 +1171,24 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
             CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS,
         };
 
-        self.cx.struct_span_lint(lint, sp, |lint| {
+        self.cx.struct_span_lint(lint, sp, fluent::lint::improper_ctypes, |lint| {
             let item_description = match self.mode {
                 CItemKind::Declaration => "block",
                 CItemKind::Definition => "fn",
             };
-            let mut diag = lint.build(fluent::lint::improper_ctypes);
-            diag.set_arg("ty", ty);
-            diag.set_arg("desc", item_description);
-            diag.span_label(sp, fluent::lint::label);
+            lint.set_arg("ty", ty);
+            lint.set_arg("desc", item_description);
+            lint.span_label(sp, fluent::lint::label);
             if let Some(help) = help {
-                diag.help(help);
+                lint.help(help);
             }
-            diag.note(note);
+            lint.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, fluent::lint::note);
+                    lint.span_note(sp, fluent::lint::note);
                 }
             }
-            diag.emit();
+            lint
         });
     }
 
@@ -1381,11 +1401,8 @@ impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences {
                 cx.struct_span_lint(
                     VARIANT_SIZE_DIFFERENCES,
                     enum_definition.variants[largest_index].span,
-                    |lint| {
-                        lint.build(fluent::lint::variant_size_differences)
-                            .set_arg("largest", largest)
-                            .emit();
-                    },
+                    fluent::lint::variant_size_differences,
+                    |lint| lint.set_arg("largest", largest),
                 );
             }
         }
@@ -1493,25 +1510,16 @@ impl InvalidAtomicOrdering {
 
     fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
         if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::load, sym::store])
-            && let Some((ordering_arg, invalid_ordering)) = match method {
-                sym::load => Some((&args[0], sym::Release)),
-                sym::store => Some((&args[1], sym::Acquire)),
+            && let Some((ordering_arg, invalid_ordering, msg)) = match method {
+                sym::load => Some((&args[0], sym::Release, fluent::lint::atomic_ordering_load)),
+                sym::store => Some((&args[1], sym::Acquire, fluent::lint::atomic_ordering_store)),
                 _ => None,
             }
             && let Some(ordering) = Self::match_ordering(cx, ordering_arg)
             && (ordering == invalid_ordering || ordering == sym::AcqRel)
         {
-            cx.struct_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, |diag| {
-                if method == sym::load {
-                    diag.build(fluent::lint::atomic_ordering_load)
-                        .help(fluent::lint::help)
-                        .emit()
-                } else {
-                    debug_assert_eq!(method, sym::store);
-                    diag.build(fluent::lint::atomic_ordering_store)
-                        .help(fluent::lint::help)
-                        .emit();
-                }
+            cx.struct_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, msg, |lint| {
+                lint.help(fluent::lint::help)
             });
         }
     }
@@ -1523,10 +1531,9 @@ impl InvalidAtomicOrdering {
             && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::fence | sym::compiler_fence))
             && Self::match_ordering(cx, &args[0]) == Some(sym::Relaxed)
         {
-            cx.struct_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, |diag| {
-                diag.build(fluent::lint::atomic_ordering_fence)
+            cx.struct_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, fluent::lint::atomic_ordering_fence, |lint| {
+                lint
                     .help(fluent::lint::help)
-                    .emit();
             });
         }
     }
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
index 3d426ecbfcb..787c9518b50 100644
--- a/compiler/rustc_lint/src/unused.rs
+++ b/compiler/rustc_lint/src/unused.rs
@@ -154,9 +154,8 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
         };
 
         if let Some(must_use_op) = must_use_op {
-            cx.struct_span_lint(UNUSED_MUST_USE, expr.span, |lint| {
-                lint.build(fluent::lint::unused_op)
-                    .set_arg("op", must_use_op)
+            cx.struct_span_lint(UNUSED_MUST_USE, expr.span, fluent::lint::unused_op, |lint| {
+                lint.set_arg("op", must_use_op)
                     .span_label(expr.span, fluent::lint::label)
                     .span_suggestion_verbose(
                         expr.span.shrink_to_lo(),
@@ -164,14 +163,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
                         "let _ = ",
                         Applicability::MachineApplicable,
                     )
-                    .emit();
             });
             op_warned = true;
         }
 
         if !(type_permits_lack_of_use || fn_warned || op_warned) {
-            cx.struct_span_lint(UNUSED_RESULTS, s.span, |lint| {
-                lint.build(fluent::lint::unused_result).set_arg("ty", ty).emit();
+            cx.struct_span_lint(UNUSED_RESULTS, s.span, fluent::lint::unused_result, |lint| {
+                lint.set_arg("ty", ty)
             });
         }
 
@@ -267,29 +265,35 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
                     }
                 },
                 ty::Closure(..) => {
-                    cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
-                        // FIXME(davidtwco): this isn't properly translatable because of the
-                        // pre/post strings
-                        lint.build(fluent::lint::unused_closure)
-                            .set_arg("count", plural_len)
-                            .set_arg("pre", descr_pre)
-                            .set_arg("post", descr_post)
-                            .note(fluent::lint::note)
-                            .emit();
-                    });
+                    cx.struct_span_lint(
+                        UNUSED_MUST_USE,
+                        span,
+                        fluent::lint::unused_closure,
+                        |lint| {
+                            // FIXME(davidtwco): this isn't properly translatable because of the
+                            // pre/post strings
+                            lint.set_arg("count", plural_len)
+                                .set_arg("pre", descr_pre)
+                                .set_arg("post", descr_post)
+                                .note(fluent::lint::note)
+                        },
+                    );
                     true
                 }
                 ty::Generator(..) => {
-                    cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
-                        // FIXME(davidtwco): this isn't properly translatable because of the
-                        // pre/post strings
-                        lint.build(fluent::lint::unused_generator)
-                            .set_arg("count", plural_len)
-                            .set_arg("pre", descr_pre)
-                            .set_arg("post", descr_post)
-                            .note(fluent::lint::note)
-                            .emit();
-                    });
+                    cx.struct_span_lint(
+                        UNUSED_MUST_USE,
+                        span,
+                        fluent::lint::unused_generator,
+                        |lint| {
+                            // FIXME(davidtwco): this isn't properly translatable because of the
+                            // pre/post strings
+                            lint.set_arg("count", plural_len)
+                                .set_arg("pre", descr_pre)
+                                .set_arg("post", descr_post)
+                                .note(fluent::lint::note)
+                        },
+                    );
                     true
                 }
                 _ => false,
@@ -309,18 +313,17 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
             descr_post_path: &str,
         ) -> bool {
             if let Some(attr) = cx.tcx.get_attr(def_id, sym::must_use) {
-                cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
+                cx.struct_span_lint(UNUSED_MUST_USE, span, fluent::lint::unused_def, |lint| {
                     // FIXME(davidtwco): this isn't properly translatable because of the pre/post
                     // strings
-                    let mut err = lint.build(fluent::lint::unused_def);
-                    err.set_arg("pre", descr_pre_path);
-                    err.set_arg("post", descr_post_path);
-                    err.set_arg("def", cx.tcx.def_path_str(def_id));
+                    lint.set_arg("pre", descr_pre_path);
+                    lint.set_arg("post", descr_post_path);
+                    lint.set_arg("def", cx.tcx.def_path_str(def_id));
                     // check for #[must_use = "..."]
                     if let Some(note) = attr.value_str() {
-                        err.note(note.as_str());
+                        lint.note(note.as_str());
                     }
-                    err.emit();
+                    lint
                 });
                 true
             } else {
@@ -357,25 +360,34 @@ impl<'tcx> LateLintPass<'tcx> for PathStatements {
     fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
         if let hir::StmtKind::Semi(expr) = s.kind {
             if let hir::ExprKind::Path(_) = expr.kind {
-                cx.struct_span_lint(PATH_STATEMENTS, s.span, |lint| {
-                    let ty = cx.typeck_results().expr_ty(expr);
-                    if ty.needs_drop(cx.tcx, cx.param_env) {
-                        let mut lint = lint.build(fluent::lint::path_statement_drop);
-                        if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span) {
-                            lint.span_suggestion(
-                                s.span,
-                                fluent::lint::suggestion,
-                                format!("drop({});", snippet),
-                                Applicability::MachineApplicable,
-                            );
-                        } else {
-                            lint.span_help(s.span, fluent::lint::suggestion);
-                        }
-                        lint.emit();
-                    } else {
-                        lint.build(fluent::lint::path_statement_no_effect).emit();
-                    }
-                });
+                let ty = cx.typeck_results().expr_ty(expr);
+                if ty.needs_drop(cx.tcx, cx.param_env) {
+                    cx.struct_span_lint(
+                        PATH_STATEMENTS,
+                        s.span,
+                        fluent::lint::path_statement_drop,
+                        |lint| {
+                            if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span) {
+                                lint.span_suggestion(
+                                    s.span,
+                                    fluent::lint::suggestion,
+                                    format!("drop({});", snippet),
+                                    Applicability::MachineApplicable,
+                                );
+                            } else {
+                                lint.span_help(s.span, fluent::lint::suggestion);
+                            }
+                            lint
+                        },
+                    );
+                } else {
+                    cx.struct_span_lint(
+                        PATH_STATEMENTS,
+                        s.span,
+                        fluent::lint::path_statement_no_effect,
+                        |lint| lint,
+                    );
+                }
             }
         }
     }
@@ -545,22 +557,21 @@ trait UnusedDelimLint {
         } else {
             MultiSpan::from(value_span)
         };
-        cx.struct_span_lint(self.lint(), primary_span, |lint| {
-            let mut db = lint.build(fluent::lint::unused_delim);
-            db.set_arg("delim", Self::DELIM_STR);
-            db.set_arg("item", msg);
+        cx.struct_span_lint(self.lint(), primary_span, fluent::lint::unused_delim, |lint| {
+            lint.set_arg("delim", Self::DELIM_STR);
+            lint.set_arg("item", msg);
             if let Some((lo, hi)) = spans {
                 let replacement = vec![
                     (lo, if keep_space.0 { " ".into() } else { "".into() }),
                     (hi, if keep_space.1 { " ".into() } else { "".into() }),
                 ];
-                db.multipart_suggestion(
+                lint.multipart_suggestion(
                     fluent::lint::suggestion,
                     replacement,
                     Applicability::MachineApplicable,
                 );
             }
-            db.emit();
+            lint
         });
     }
 
@@ -1128,9 +1139,12 @@ impl UnusedImportBraces {
                 ast::UseTreeKind::Nested(_) => return,
             };
 
-            cx.struct_span_lint(UNUSED_IMPORT_BRACES, item.span, |lint| {
-                lint.build(fluent::lint::unused_import_braces).set_arg("node", node_name).emit();
-            });
+            cx.struct_span_lint(
+                UNUSED_IMPORT_BRACES,
+                item.span,
+                fluent::lint::unused_import_braces,
+                |lint| lint.set_arg("node", node_name),
+            );
         }
     }
 }
@@ -1179,15 +1193,17 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAllocation {
 
         for adj in cx.typeck_results().expr_adjustments(e) {
             if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind {
-                cx.struct_span_lint(UNUSED_ALLOCATION, e.span, |lint| {
-                    lint.build(match m {
+                cx.struct_span_lint(
+                    UNUSED_ALLOCATION,
+                    e.span,
+                    match m {
                         adjustment::AutoBorrowMutability::Not => fluent::lint::unused_allocation,
                         adjustment::AutoBorrowMutability::Mut { .. } => {
                             fluent::lint::unused_allocation_mut
                         }
-                    })
-                    .emit();
-                });
+                    },
+                    |lint| lint,
+                );
             }
         }
     }