about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock142
-rw-r--r--compiler/rustc_borrowck/src/borrowck_errors.rs4
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs4
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/region_errors.rs19
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/region_name.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs11
-rw-r--r--compiler/rustc_codegen_ssa/src/back/write.rs20
-rw-r--r--compiler/rustc_driver/src/lib.rs6
-rw-r--r--compiler/rustc_error_messages/Cargo.toml18
-rw-r--r--compiler/rustc_error_messages/locales/en-US/diagnostics.ftl87
-rw-r--r--compiler/rustc_error_messages/src/lib.rs390
-rw-r--r--compiler/rustc_errors/Cargo.toml1
-rw-r--r--compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs44
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs208
-rw-r--r--compiler/rustc_errors/src/diagnostic_builder.rs68
-rw-r--r--compiler/rustc_errors/src/emitter.rs177
-rw-r--r--compiler/rustc_errors/src/json.rs93
-rw-r--r--compiler/rustc_errors/src/json/tests.rs4
-rw-r--r--compiler/rustc_errors/src/lib.rs168
-rw-r--r--compiler/rustc_expand/src/base.rs4
-rw-r--r--compiler/rustc_expand/src/mbe/macro_check.rs11
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs9
-rw-r--r--compiler/rustc_expand/src/mbe/metavar_expr.rs8
-rw-r--r--compiler/rustc_expand/src/proc_macro_server.rs4
-rw-r--r--compiler/rustc_expand/src/tests.rs8
-rw-r--r--compiler/rustc_hir/Cargo.toml1
-rw-r--r--compiler/rustc_hir/src/hir.rs3
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/mod.rs6
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs6
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs4
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs5
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/note.rs2
-rw-r--r--compiler/rustc_infer/src/traits/error_reporting/mod.rs4
-rw-r--r--compiler/rustc_interface/src/passes.rs4
-rw-r--r--compiler/rustc_lint/src/builtin.rs6
-rw-r--r--compiler/rustc_lint/src/context.rs4
-rw-r--r--compiler/rustc_lint/src/expect.rs2
-rw-r--r--compiler/rustc_lint/src/levels.rs4
-rw-r--r--compiler/rustc_lint/src/types.rs4
-rw-r--r--compiler/rustc_lint/src/unused.rs4
-rw-r--r--compiler/rustc_lint_defs/Cargo.toml1
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs3
-rw-r--r--compiler/rustc_macros/src/lib.rs9
-rw-r--r--compiler/rustc_macros/src/session_diagnostic.rs871
-rw-r--r--compiler/rustc_middle/src/lint.rs4
-rw-r--r--compiler/rustc_middle/src/ty/context.rs4
-rw-r--r--compiler/rustc_middle/src/ty/diagnostics.rs8
-rw-r--r--compiler/rustc_middle/src/ty/error.rs4
-rw-r--r--compiler/rustc_middle/src/util/bug.rs3
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/check_match.rs4
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs36
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs4
-rw-r--r--compiler/rustc_parse/src/parser/item.rs3
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs4
-rw-r--r--compiler/rustc_passes/src/check_attr.rs4
-rw-r--r--compiler/rustc_query_system/src/query/plumbing.rs4
-rw-r--r--compiler/rustc_resolve/src/check_unused.rs4
-rw-r--r--compiler/rustc_resolve/src/diagnostics.rs6
-rw-r--r--compiler/rustc_resolve/src/imports.rs6
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs7
-rw-r--r--compiler/rustc_serialize/src/serialize.rs14
-rw-r--r--compiler/rustc_session/src/config.rs2
-rw-r--r--compiler/rustc_session/src/options.rs22
-rw-r--r--compiler/rustc_session/src/parse.rs23
-rw-r--r--compiler/rustc_session/src/session.rs160
-rw-r--r--compiler/rustc_span/src/lib.rs137
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs4
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs13
-rw-r--r--compiler/rustc_trait_selection/src/traits/object_safety.rs9
-rw-r--r--compiler/rustc_typeck/src/astconv/generics.rs4
-rw-r--r--compiler/rustc_typeck/src/check/_match.rs4
-rw-r--r--compiler/rustc_typeck/src/check/check.rs4
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs4
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/checks.rs4
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs4
-rw-r--r--compiler/rustc_typeck/src/check/intrinsic.rs3
-rw-r--r--compiler/rustc_typeck/src/check/method/suggest.rs3
-rw-r--r--compiler/rustc_typeck/src/check/mod.rs4
-rw-r--r--compiler/rustc_typeck/src/check/pat.rs3
-rw-r--r--compiler/rustc_typeck/src/check/upvar.rs4
-rw-r--r--compiler/rustc_typeck/src/errors.rs125
-rw-r--r--compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs3
-rw-r--r--src/bootstrap/bin/rustdoc.rs20
-rw-r--r--src/bootstrap/bootstrap.py2
-rw-r--r--src/librustdoc/core.rs6
-rw-r--r--src/librustdoc/doctest.rs31
-rw-r--r--src/librustdoc/lib.rs8
-rw-r--r--src/librustdoc/passes/check_code_block_syntax.rs16
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links/early.rs5
-rw-r--r--src/test/run-make/translation/Makefile33
-rw-r--r--src/test/run-make/translation/basic-translation.ftl2
-rw-r--r--src/test/run-make/translation/basic-translation.rs18
-rw-r--r--src/test/rustdoc/issue-95633.rs7
-rw-r--r--src/test/ui-fulldeps/session-derive-errors.rs358
-rw-r--r--src/test/ui-fulldeps/session-derive-errors.stderr299
-rw-r--r--src/test/ui/associated-types/cache/project-fn-ret-invariant.krisskross.nll.stderr8
-rw-r--r--src/test/ui/associated-types/cache/project-fn-ret-invariant.oneuse.nll.stderr8
-rw-r--r--src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.nll.stderr4
-rw-r--r--src/test/ui/c-variadic/variadic-ffi-4.stderr32
-rw-r--r--src/test/ui/error-codes/E0184.stderr2
-rw-r--r--src/test/ui/exclusive-drop-and-copy.stderr4
-rw-r--r--src/test/ui/hr-subtype/hr-subtype.free_inv_x_vs_free_inv_y.nll.stderr8
-rw-r--r--src/test/ui/match/match-ref-mut-invariance.nll.stderr2
-rw-r--r--src/test/ui/match/match-ref-mut-let-invariance.nll.stderr2
-rw-r--r--src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr4
-rw-r--r--src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr4
-rw-r--r--src/test/ui/nll/issue-95272.rs17
-rw-r--r--src/test/ui/nll/issue-95272.stderr17
-rw-r--r--src/test/ui/nll/type-check-pointer-coercions.stderr4
-rw-r--r--src/test/ui/nll/type-check-pointer-comparisons.stderr12
-rw-r--r--src/test/ui/nll/where_clauses_in_structs.stderr4
-rw-r--r--src/test/ui/regions/region-invariant-static-error-reporting.nll.stderr4
-rw-r--r--src/test/ui/regions/region-lifetime-bounds-on-fns-where-clause.nll.stderr2
-rw-r--r--src/test/ui/regions/region-multiple-lifetime-bounds-on-fns-where-clause.nll.stderr2
-rw-r--r--src/test/ui/regions/regions-bounded-method-type-parameters-cross-crate.nll.stderr4
-rw-r--r--src/test/ui/regions/regions-bounded-method-type-parameters-trait-bound.nll.stderr4
-rw-r--r--src/test/ui/regions/regions-infer-invariance-due-to-decl.nll.stderr4
-rw-r--r--src/test/ui/regions/regions-infer-invariance-due-to-mutability-3.nll.stderr4
-rw-r--r--src/test/ui/regions/regions-infer-invariance-due-to-mutability-4.nll.stderr4
-rw-r--r--src/test/ui/regions/regions-infer-not-param.nll.stderr8
-rw-r--r--src/test/ui/regions/regions-lifetime-bounds-on-fns.nll.stderr2
-rw-r--r--src/test/ui/regions/regions-trait-object-subtyping.nll.stderr4
-rw-r--r--src/test/ui/regions/regions-variance-invariant-use-contravariant.nll.stderr4
-rw-r--r--src/test/ui/regions/regions-variance-invariant-use-covariant.nll.stderr4
-rw-r--r--src/test/ui/variance/variance-btree-invariant-types.nll.stderr64
-rw-r--r--src/test/ui/variance/variance-cell-is-invariant.nll.stderr4
-rw-r--r--src/test/ui/variance/variance-use-invariant-struct-1.nll.stderr8
-rw-r--r--src/tools/clippy/clippy_lints/src/collapsible_match.rs7
-rw-r--r--src/tools/clippy/clippy_lints/src/doc.rs18
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/needless_collect.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs10
-rw-r--r--src/tools/clippy/clippy_lints/src/ptr.rs4
-rw-r--r--src/tools/clippy/clippy_utils/src/diagnostics.rs21
-rw-r--r--src/tools/clippy/src/driver.rs6
-rw-r--r--src/tools/rustfmt/src/parse/session.rs27
-rw-r--r--src/tools/tidy/src/deps.rs20
137 files changed, 3104 insertions, 1191 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 80f0a0b8b5b..d8cb1133c73 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1239,6 +1239,40 @@ dependencies = [
 ]
 
 [[package]]
+name = "fluent-bundle"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd"
+dependencies = [
+ "fluent-langneg",
+ "fluent-syntax",
+ "intl-memoizer",
+ "intl_pluralrules",
+ "rustc-hash",
+ "self_cell",
+ "smallvec",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-langneg"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94"
+dependencies = [
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-syntax"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
 name = "fnv"
 version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1783,6 +1817,26 @@ dependencies = [
 ]
 
 [[package]]
+name = "intl-memoizer"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f"
+dependencies = [
+ "type-map",
+ "unic-langid",
+]
+
+[[package]]
+name = "intl_pluralrules"
+version = "7.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf"
+dependencies = [
+ "tinystr",
+ "unic-langid",
+]
+
+[[package]]
 name = "itertools"
 version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2813,6 +2867,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
 name = "proc-macro2"
 version = "1.0.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3646,12 +3706,28 @@ name = "rustc_error_codes"
 version = "0.0.0"
 
 [[package]]
+name = "rustc_error_messages"
+version = "0.0.0"
+dependencies = [
+ "fluent-bundle",
+ "fluent-syntax",
+ "intl-memoizer",
+ "rustc_data_structures",
+ "rustc_macros",
+ "rustc_serialize",
+ "rustc_span",
+ "tracing",
+ "unic-langid",
+]
+
+[[package]]
 name = "rustc_errors"
 version = "0.0.0"
 dependencies = [
  "annotate-snippets",
  "atty",
  "rustc_data_structures",
+ "rustc_error_messages",
  "rustc_lint_defs",
  "rustc_macros",
  "rustc_serialize",
@@ -3708,6 +3784,7 @@ dependencies = [
  "odht",
  "rustc_ast",
  "rustc_data_structures",
+ "rustc_error_messages",
  "rustc_feature",
  "rustc_index",
  "rustc_macros",
@@ -3864,6 +3941,7 @@ version = "0.0.0"
 dependencies = [
  "rustc_ast",
  "rustc_data_structures",
+ "rustc_error_messages",
  "rustc_hir",
  "rustc_macros",
  "rustc_serialize",
@@ -4574,6 +4652,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "self_cell"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af"
+
+[[package]]
 name = "semver"
 version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5105,6 +5189,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "tinystr"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1"
+
+[[package]]
 name = "tinyvec"
 version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5263,6 +5353,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "type-map"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46"
+dependencies = [
+ "rustc-hash",
+]
+
+[[package]]
 name = "typenum"
 version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5317,6 +5416,49 @@ dependencies = [
 ]
 
 [[package]]
+name = "unic-langid"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5"
+dependencies = [
+ "unic-langid-impl",
+ "unic-langid-macros",
+]
+
+[[package]]
+name = "unic-langid-impl"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d"
+dependencies = [
+ "tinystr",
+]
+
+[[package]]
+name = "unic-langid-macros"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18f980d6d87e8805f2836d64b4138cc95aa7986fa63b1f51f67d5fbff64dd6e5"
+dependencies = [
+ "proc-macro-hack",
+ "tinystr",
+ "unic-langid-impl",
+ "unic-langid-macros-impl",
+]
+
+[[package]]
+name = "unic-langid-macros-impl"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29396ffd97e27574c3e01368b1a64267d3064969e4848e2e130ff668be9daa9f"
+dependencies = [
+ "proc-macro-hack",
+ "quote",
+ "syn",
+ "unic-langid-impl",
+]
+
+[[package]]
 name = "unic-ucd-version"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/compiler/rustc_borrowck/src/borrowck_errors.rs b/compiler/rustc_borrowck/src/borrowck_errors.rs
index 21b582ef334..70f7f1e493e 100644
--- a/compiler/rustc_borrowck/src/borrowck_errors.rs
+++ b/compiler/rustc_borrowck/src/borrowck_errors.rs
@@ -1,6 +1,6 @@
-use rustc_errors::{struct_span_err, DiagnosticBuilder, DiagnosticId, ErrorGuaranteed};
+use rustc_errors::{struct_span_err, DiagnosticBuilder, DiagnosticId, ErrorGuaranteed, MultiSpan};
 use rustc_middle::ty::{self, Ty, TyCtxt};
-use rustc_span::{MultiSpan, Span};
+use rustc_span::Span;
 
 impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> {
     crate fn cannot_move_when_borrowed(
diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
index 883f711b201..aa2ddada350 100644
--- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
@@ -1,7 +1,7 @@
 use either::Either;
 use rustc_const_eval::util::CallKind;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed};
+use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_hir::{AsyncGeneratorKind, GeneratorKind};
@@ -15,7 +15,7 @@ use rustc_middle::mir::{
 use rustc_middle::ty::{self, subst::Subst, suggest_constraining_type_params, PredicateKind, Ty};
 use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex};
 use rustc_span::symbol::sym;
-use rustc_span::{BytePos, MultiSpan, Span};
+use rustc_span::{BytePos, Span};
 use rustc_trait_selection::infer::InferCtxtExt;
 use rustc_trait_selection::traits::TraitEngineExt as _;
 
diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
index e63450a1f58..1798c525f6d 100644
--- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
@@ -330,14 +330,14 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
                     ty::RawPtr(ty_mut) => {
                         assert_eq!(ty_mut.mutbl, rustc_hir::Mutability::Mut);
                         (
-                            format!("a mutable pointer to {}", ty_mut.ty),
+                            format!("a mutable pointer to `{}`", ty_mut.ty),
                             "mutable pointers are invariant over their type parameter".to_string(),
                         )
                     }
                     ty::Ref(_, inner_ty, mutbl) => {
                         assert_eq!(*mutbl, rustc_hir::Mutability::Mut);
                         (
-                            format!("a mutable reference to {}", inner_ty),
+                            format!("a mutable reference to `{}`", inner_ty),
                             "mutable references are invariant over their type parameter"
                                 .to_string(),
                         )
@@ -351,10 +351,21 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
                         let adt_desc = adt.descr();
 
                         let desc = format!(
-                            "the type {ty}, which makes the generic argument {generic_arg} invariant"
+                            "the type `{ty}`, which makes the generic argument `{generic_arg}` invariant"
                         );
                         let note = format!(
-                            "the {adt_desc} {base_ty} is invariant over the parameter {base_generic_arg}"
+                            "the {adt_desc} `{base_ty}` is invariant over the parameter `{base_generic_arg}`"
+                        );
+                        (desc, note)
+                    }
+                    ty::FnDef(def_id, _) => {
+                        let name = self.infcx.tcx.item_name(*def_id);
+                        let identity_substs =
+                            InternalSubsts::identity_for_item(self.infcx.tcx, *def_id);
+                        let desc = format!("a function pointer to `{name}`");
+                        let note = format!(
+                            "the function `{name}` is invariant over the parameter `{}`",
+                            identity_substs[param_index as usize]
                         );
                         (desc, note)
                     }
diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs
index c9395492c9e..723adb8da1b 100644
--- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs
@@ -109,7 +109,7 @@ impl RegionName {
                     *span,
                     format!("lifetime `{}` represents this closure's body", self),
                 );
-                diag.note(&note);
+                diag.note(note);
             }
             RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::CannotMatchHirTy(
                 span,
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index 31213412d45..138e1fa0176 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -7,11 +7,11 @@ use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::visit::{self, Visitor};
 use rustc_ast::{token, BlockCheckMode, UnsafeSource};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
-use rustc_errors::{pluralize, Applicability, PResult};
+use rustc_errors::{pluralize, Applicability, MultiSpan, PResult};
 use rustc_expand::base::{self, *};
 use rustc_parse_format as parse;
 use rustc_span::symbol::{sym, Ident, Symbol};
-use rustc_span::{InnerSpan, MultiSpan, Span};
+use rustc_span::{InnerSpan, Span};
 use smallvec::SmallVec;
 
 use std::borrow::Cow;
@@ -446,7 +446,9 @@ impl<'a, 'b> Context<'a, 'b> {
                                 .iter()
                                 .filter(|fmt| fmt.precision_span.is_some())
                                 .count();
-                        e.span_label(span, &format!(
+                        e.span_label(
+                            span,
+                            &format!(
                             "this precision flag adds an extra required argument at position {}, \
                              which is why there {} expected",
                             pos,
@@ -455,7 +457,8 @@ impl<'a, 'b> Context<'a, 'b> {
                             } else {
                                 format!("are {} arguments", count)
                             },
-                        ));
+                        ),
+                        );
                         if let Some(arg) = self.args.get(pos) {
                             e.span_label(
                                 arg.span,
diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs
index 9417874ffb4..92c4ab7eb86 100644
--- a/compiler/rustc_codegen_ssa/src/back/write.rs
+++ b/compiler/rustc_codegen_ssa/src/back/write.rs
@@ -1707,23 +1707,33 @@ impl SharedEmitter {
 
 impl Emitter for SharedEmitter {
     fn emit_diagnostic(&mut self, diag: &rustc_errors::Diagnostic) {
+        let fluent_args = self.to_fluent_args(diag.args());
         drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic {
-            msg: diag.message(),
+            msg: self.translate_messages(&diag.message, &fluent_args).to_string(),
             code: diag.code.clone(),
             lvl: diag.level(),
         })));
         for child in &diag.children {
             drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic {
-                msg: child.message(),
+                msg: self.translate_messages(&child.message, &fluent_args).to_string(),
                 code: None,
                 lvl: child.level,
             })));
         }
         drop(self.sender.send(SharedEmitterMessage::AbortIfErrors));
     }
+
     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
         None
     }
+
+    fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
+        None
+    }
+
+    fn fallback_fluent_bundle(&self) -> &Lrc<rustc_errors::FluentBundle> {
+        panic!("shared emitter attempted to translate a diagnostic");
+    }
 }
 
 impl SharedEmitterMain {
@@ -1754,9 +1764,9 @@ impl SharedEmitterMain {
                     let msg = msg.strip_prefix("error: ").unwrap_or(&msg);
 
                     let mut err = match level {
-                        Level::Error { lint: false } => sess.struct_err(&msg).forget_guarantee(),
-                        Level::Warning => sess.struct_warn(&msg),
-                        Level::Note => sess.struct_note_without_error(&msg),
+                        Level::Error { lint: false } => sess.struct_err(msg).forget_guarantee(),
+                        Level::Warning => sess.struct_warn(msg),
+                        Level::Note => sess.struct_note_without_error(msg),
                         _ => bug!("Invalid inline asm diagnostic level"),
                     };
 
diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs
index 69f96d07f90..febdd0ed746 100644
--- a/compiler/rustc_driver/src/lib.rs
+++ b/compiler/rustc_driver/src/lib.rs
@@ -1172,9 +1172,13 @@ static DEFAULT_HOOK: SyncLazy<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send +
 /// When `install_ice_hook` is called, this function will be called as the panic
 /// hook.
 pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
+    let fallback_bundle =
+        rustc_errors::fallback_fluent_bundle(false).expect("failed to load fallback fluent bundle");
     let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
         rustc_errors::ColorConfig::Auto,
         None,
+        None,
+        fallback_bundle,
         false,
         false,
         None,
@@ -1209,7 +1213,7 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
     }
 
     for note in &xs {
-        handler.note_without_error(note);
+        handler.note_without_error(note.as_ref());
     }
 
     // If backtraces are enabled, also print the query stack
diff --git a/compiler/rustc_error_messages/Cargo.toml b/compiler/rustc_error_messages/Cargo.toml
new file mode 100644
index 00000000000..fc84c7c8665
--- /dev/null
+++ b/compiler/rustc_error_messages/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "rustc_error_messages"
+version = "0.0.0"
+edition = "2021"
+
+[lib]
+doctest = false
+
+[dependencies]
+fluent-bundle = "0.15.2"
+fluent-syntax = "0.11"
+intl-memoizer = "0.5.1"
+rustc_data_structures = { path = "../rustc_data_structures" }
+rustc_serialize = { path = "../rustc_serialize" }
+rustc_span = { path = "../rustc_span" }
+rustc_macros = { path = "../rustc_macros" }
+tracing = "0.1"
+unic-langid = { version = "0.9.0", features = ["macros"] }
diff --git a/compiler/rustc_error_messages/locales/en-US/diagnostics.ftl b/compiler/rustc_error_messages/locales/en-US/diagnostics.ftl
new file mode 100644
index 00000000000..336e7a66857
--- /dev/null
+++ b/compiler/rustc_error_messages/locales/en-US/diagnostics.ftl
@@ -0,0 +1,87 @@
+parser-struct-literal-body-without-path =
+    struct literal body without path
+    .suggestion = you might have forgotten to add the struct literal inside the block
+
+typeck-field-multiply-specified-in-initializer =
+    field `{$ident}` specified more than once
+    .label = used more than once
+    .previous-use-label = first use of `{$ident}`
+
+typeck-unrecognized-atomic-operation =
+    unrecognized atomic operation function: `{$op}`
+    .label = unrecognized atomic operation
+
+typeck-wrong-number-of-generic-arguments-to-intrinsic =
+    intrinsic has wrong number of {$descr} parameters: found {$found}, expected {$expected}
+    .label = expected {$expected} {$descr} {$expected ->
+        [one] parameter
+        *[other] parameters
+    }
+
+typeck-unrecognized-intrinsic-function =
+    unrecognized intrinsic function: `{$name}`
+    .label = unrecognized intrinsic
+
+typeck-lifetimes-or-bounds-mismatch-on-trait =
+    lifetime parameters or bounds on {$item_kind} `{$ident}` do not match the trait declaration
+    .label = lifetimes do not match {$item_kind} in trait
+    .generics-label = lifetimes in impl do not match this {$item_kind} in trait
+
+typeck-drop-impl-on-wrong-item =
+    the `Drop` trait may only be implemented for structs, enums, and unions
+    .label = must be a struct, enum, or union
+
+typeck-field-already-declared =
+    field `{$field_name}` is already declared
+    .label = field already declared
+    .previous-decl-label = `{$field_name}` first declared here
+
+typeck-copy-impl-on-type-with-dtor =
+    the trait `Copy` may not be implemented for this type; the type has a destructor
+    .label = `Copy` not allowed on types with destructors
+
+typeck-multiple-relaxed-default-bounds =
+    type parameter has more than one relaxed default bound, only one is supported
+
+typeck-copy-impl-on-non-adt =
+    the trait `Copy` may not be implemented for this type
+    .label = type is not a structure or enumeration
+
+typeck-trait-object-declared-with-no-traits =
+    at least one trait is required for an object type
+
+typeck-ambiguous-lifetime-bound =
+    ambiguous lifetime bound, explicit lifetime bound required
+
+typeck-assoc-type-binding-not-allowed =
+    associated type bindings are not allowed here
+    .label = associated type not allowed here
+
+typeck-functional-record-update-on-non-struct =
+    functional record update syntax requires a struct
+
+typeck-typeof-reserved-keyword-used =
+    `typeof` is a reserved keyword but unimplemented
+    .label = reserved keyword
+
+typeck-return-stmt-outside-of-fn-body =
+    return statement outside of function body
+    .encl-body-label = the return is part of this body...
+    .encl-fn-label = ...not the enclosing function body
+
+typeck-yield-expr-outside-of-generator =
+    yield expression outside of generator literal
+
+typeck-struct-expr-non-exhaustive =
+    cannot create non-exhaustive {$what} using struct expression
+
+typeck-method-call-on-unknown-type =
+    the type of this value must be known to call a method on a raw pointer on it
+
+typeck-value-of-associated-struct-already-specified =
+    the value of the associated type `{$item_name}` (from trait `{$def_path}`) is already specified
+    .label = re-bound here
+    .previous-bound-label = `{$item_name}` bound here first
+
+typeck-address-of-temporary-taken = cannot take address of a temporary
+    .label = temporary value
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
new file mode 100644
index 00000000000..88f3b547605
--- /dev/null
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -0,0 +1,390 @@
+#![feature(let_chains)]
+#![feature(path_try_exists)]
+
+use fluent_bundle::FluentResource;
+use fluent_syntax::parser::ParserError;
+use rustc_data_structures::sync::Lrc;
+use rustc_macros::{Decodable, Encodable};
+use rustc_span::Span;
+use std::borrow::Cow;
+use std::error::Error;
+use std::fmt;
+use std::fs;
+use std::io;
+use std::path::Path;
+use tracing::{instrument, trace};
+
+#[cfg(parallel_compiler)]
+use intl_memoizer::concurrent::IntlLangMemoizer;
+#[cfg(not(parallel_compiler))]
+use intl_memoizer::IntlLangMemoizer;
+
+pub use fluent_bundle::{FluentArgs, FluentError, FluentValue};
+pub use unic_langid::{langid, LanguageIdentifier};
+
+static FALLBACK_FLUENT_RESOURCE: &'static str = include_str!("../locales/en-US/diagnostics.ftl");
+
+pub type FluentBundle = fluent_bundle::bundle::FluentBundle<FluentResource, IntlLangMemoizer>;
+
+#[cfg(parallel_compiler)]
+fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle {
+    FluentBundle::new_concurrent(locales)
+}
+
+#[cfg(not(parallel_compiler))]
+fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle {
+    FluentBundle::new(locales)
+}
+
+#[derive(Debug)]
+pub enum TranslationBundleError {
+    /// Failed to read from `.ftl` file.
+    ReadFtl(io::Error),
+    /// Failed to parse contents of `.ftl` file.
+    ParseFtl(ParserError),
+    /// Failed to add `FluentResource` to `FluentBundle`.
+    AddResource(FluentError),
+    /// `$sysroot/share/locale/$locale` does not exist.
+    MissingLocale(io::Error),
+    /// Cannot read directory entries of `$sysroot/share/locale/$locale`.
+    ReadLocalesDir(io::Error),
+    /// Cannot read directory entry of `$sysroot/share/locale/$locale`.
+    ReadLocalesDirEntry(io::Error),
+    /// `$sysroot/share/locale/$locale` is not a directory.
+    LocaleIsNotDir,
+}
+
+impl fmt::Display for TranslationBundleError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            TranslationBundleError::ReadFtl(e) => write!(f, "could not read ftl file: {}", e),
+            TranslationBundleError::ParseFtl(e) => {
+                write!(f, "could not parse ftl file: {}", e)
+            }
+            TranslationBundleError::AddResource(e) => write!(f, "failed to add resource: {}", e),
+            TranslationBundleError::MissingLocale(e) => {
+                write!(f, "missing locale directory: {}", e)
+            }
+            TranslationBundleError::ReadLocalesDir(e) => {
+                write!(f, "could not read locales dir: {}", e)
+            }
+            TranslationBundleError::ReadLocalesDirEntry(e) => {
+                write!(f, "could not read locales dir entry: {}", e)
+            }
+            TranslationBundleError::LocaleIsNotDir => {
+                write!(f, "`$sysroot/share/locales/$locale` is not a directory")
+            }
+        }
+    }
+}
+
+impl Error for TranslationBundleError {
+    fn source(&self) -> Option<&(dyn Error + 'static)> {
+        match self {
+            TranslationBundleError::ReadFtl(e) => Some(e),
+            TranslationBundleError::ParseFtl(e) => Some(e),
+            TranslationBundleError::AddResource(e) => Some(e),
+            TranslationBundleError::MissingLocale(e) => Some(e),
+            TranslationBundleError::ReadLocalesDir(e) => Some(e),
+            TranslationBundleError::ReadLocalesDirEntry(e) => Some(e),
+            TranslationBundleError::LocaleIsNotDir => None,
+        }
+    }
+}
+
+impl From<(FluentResource, Vec<ParserError>)> for TranslationBundleError {
+    fn from((_, mut errs): (FluentResource, Vec<ParserError>)) -> Self {
+        TranslationBundleError::ParseFtl(errs.pop().expect("failed ftl parse with no errors"))
+    }
+}
+
+impl From<Vec<FluentError>> for TranslationBundleError {
+    fn from(mut errs: Vec<FluentError>) -> Self {
+        TranslationBundleError::AddResource(
+            errs.pop().expect("failed adding resource to bundle with no errors"),
+        )
+    }
+}
+
+/// Returns Fluent bundle with the user's locale resources from
+/// `$sysroot/share/locale/$requested_locale/*.ftl`.
+///
+/// If `-Z additional-ftl-path` was provided, load that resource and add it  to the bundle
+/// (overriding any conflicting messages).
+#[instrument(level = "trace")]
+pub fn fluent_bundle(
+    sysroot: &Path,
+    requested_locale: Option<LanguageIdentifier>,
+    additional_ftl_path: Option<&Path>,
+    with_directionality_markers: bool,
+) -> Result<Option<Lrc<FluentBundle>>, TranslationBundleError> {
+    if requested_locale.is_none() && additional_ftl_path.is_none() {
+        return Ok(None);
+    }
+
+    let fallback_locale = langid!("en-US");
+    let requested_fallback_locale = requested_locale.as_ref() == Some(&fallback_locale);
+
+    // If there is only `-Z additional-ftl-path`, assume locale is "en-US", otherwise use user
+    // provided locale.
+    let locale = requested_locale.clone().unwrap_or(fallback_locale);
+    trace!(?locale);
+    let mut bundle = new_bundle(vec![locale]);
+
+    // Fluent diagnostics can insert directionality isolation markers around interpolated variables
+    // indicating that there may be a shift from right-to-left to left-to-right text (or
+    // vice-versa). These are disabled because they are sometimes visible in the error output, but
+    // may be worth investigating in future (for example: if type names are left-to-right and the
+    // surrounding diagnostic messages are right-to-left, then these might be helpful).
+    bundle.set_use_isolating(with_directionality_markers);
+
+    // If the user requests the default locale then don't try to load anything.
+    if !requested_fallback_locale && let Some(requested_locale) = requested_locale {
+        let mut sysroot = sysroot.to_path_buf();
+        sysroot.push("share");
+        sysroot.push("locale");
+        sysroot.push(requested_locale.to_string());
+        trace!(?sysroot);
+
+        let _ = sysroot.try_exists().map_err(TranslationBundleError::MissingLocale)?;
+
+        if !sysroot.is_dir() {
+            return Err(TranslationBundleError::LocaleIsNotDir);
+        }
+
+        for entry in sysroot.read_dir().map_err(TranslationBundleError::ReadLocalesDir)? {
+            let entry = entry.map_err(TranslationBundleError::ReadLocalesDirEntry)?;
+            let path = entry.path();
+            trace!(?path);
+            if path.extension().and_then(|s| s.to_str()) != Some("ftl") {
+                trace!("skipping");
+                continue;
+            }
+
+            let resource_str =
+                fs::read_to_string(path).map_err(TranslationBundleError::ReadFtl)?;
+            let resource =
+                FluentResource::try_new(resource_str).map_err(TranslationBundleError::from)?;
+            trace!(?resource);
+            bundle.add_resource(resource).map_err(TranslationBundleError::from)?;
+        }
+    }
+
+    if let Some(additional_ftl_path) = additional_ftl_path {
+        let resource_str =
+            fs::read_to_string(additional_ftl_path).map_err(TranslationBundleError::ReadFtl)?;
+        let resource =
+            FluentResource::try_new(resource_str).map_err(TranslationBundleError::from)?;
+        trace!(?resource);
+        bundle.add_resource_overriding(resource);
+    }
+
+    let bundle = Lrc::new(bundle);
+    Ok(Some(bundle))
+}
+
+/// Return the default `FluentBundle` with standard "en-US" diagnostic messages.
+#[instrument(level = "trace")]
+pub fn fallback_fluent_bundle(
+    with_directionality_markers: bool,
+) -> Result<Lrc<FluentBundle>, TranslationBundleError> {
+    let fallback_resource = FluentResource::try_new(FALLBACK_FLUENT_RESOURCE.to_string())
+        .map_err(TranslationBundleError::from)?;
+    trace!(?fallback_resource);
+    let mut fallback_bundle = new_bundle(vec![langid!("en-US")]);
+    // See comment in `fluent_bundle`.
+    fallback_bundle.set_use_isolating(with_directionality_markers);
+    fallback_bundle.add_resource(fallback_resource).map_err(TranslationBundleError::from)?;
+    let fallback_bundle = Lrc::new(fallback_bundle);
+    Ok(fallback_bundle)
+}
+
+/// Identifier for the Fluent message/attribute corresponding to a diagnostic message.
+type FluentId = Cow<'static, str>;
+
+/// Abstraction over a message in a diagnostic to support both translatable and non-translatable
+/// diagnostic messages.
+///
+/// Intended to be removed once diagnostics are entirely translatable.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
+pub enum DiagnosticMessage {
+    /// Non-translatable diagnostic message.
+    // FIXME(davidtwco): can a `Cow<'static, str>` be used here?
+    Str(String),
+    /// Identifier for a Fluent message (with optional attribute) corresponding to the diagnostic
+    /// message.
+    ///
+    /// <https://projectfluent.org/fluent/guide/hello.html>
+    /// <https://projectfluent.org/fluent/guide/attributes.html>
+    FluentIdentifier(FluentId, Option<FluentId>),
+}
+
+impl DiagnosticMessage {
+    /// Returns the `String` contained within the `DiagnosticMessage::Str` variant, assuming that
+    /// this diagnostic message is of the legacy, non-translatable variety. Panics if this
+    /// assumption does not hold.
+    ///
+    /// Don't use this - it exists to support some places that do comparison with diagnostic
+    /// strings.
+    pub fn expect_str(&self) -> &str {
+        match self {
+            DiagnosticMessage::Str(s) => s,
+            _ => panic!("expected non-translatable diagnostic message"),
+        }
+    }
+
+    /// Create a `DiagnosticMessage` for the provided Fluent identifier.
+    pub fn fluent(id: impl Into<FluentId>) -> Self {
+        DiagnosticMessage::FluentIdentifier(id.into(), None)
+    }
+
+    /// Create a `DiagnosticMessage` for the provided Fluent identifier and attribute.
+    pub fn fluent_attr(id: impl Into<FluentId>, attr: impl Into<FluentId>) -> Self {
+        DiagnosticMessage::FluentIdentifier(id.into(), Some(attr.into()))
+    }
+}
+
+/// `From` impl that enables existing diagnostic calls to functions which now take
+/// `impl Into<DiagnosticMessage>` to continue to work as before.
+impl<S: Into<String>> From<S> for DiagnosticMessage {
+    fn from(s: S) -> Self {
+        DiagnosticMessage::Str(s.into())
+    }
+}
+
+/// A span together with some additional data.
+#[derive(Clone, Debug)]
+pub struct SpanLabel {
+    /// The span we are going to include in the final snippet.
+    pub span: Span,
+
+    /// Is this a primary span? This is the "locus" of the message,
+    /// and is indicated with a `^^^^` underline, versus `----`.
+    pub is_primary: bool,
+
+    /// What label should we attach to this span (if any)?
+    pub label: Option<DiagnosticMessage>,
+}
+
+/// A collection of `Span`s.
+///
+/// Spans have two orthogonal attributes:
+///
+/// - They can be *primary spans*. In this case they are the locus of
+///   the error, and would be rendered with `^^^`.
+/// - They can have a *label*. In this case, the label is written next
+///   to the mark in the snippet when we render.
+#[derive(Clone, Debug, Hash, PartialEq, Eq, Encodable, Decodable)]
+pub struct MultiSpan {
+    primary_spans: Vec<Span>,
+    span_labels: Vec<(Span, DiagnosticMessage)>,
+}
+
+impl MultiSpan {
+    #[inline]
+    pub fn new() -> MultiSpan {
+        MultiSpan { primary_spans: vec![], span_labels: vec![] }
+    }
+
+    pub fn from_span(primary_span: Span) -> MultiSpan {
+        MultiSpan { primary_spans: vec![primary_span], span_labels: vec![] }
+    }
+
+    pub fn from_spans(mut vec: Vec<Span>) -> MultiSpan {
+        vec.sort();
+        MultiSpan { primary_spans: vec, span_labels: vec![] }
+    }
+
+    pub fn push_span_label(&mut self, span: Span, label: impl Into<DiagnosticMessage>) {
+        self.span_labels.push((span, label.into()));
+    }
+
+    /// Selects the first primary span (if any).
+    pub fn primary_span(&self) -> Option<Span> {
+        self.primary_spans.first().cloned()
+    }
+
+    /// Returns all primary spans.
+    pub fn primary_spans(&self) -> &[Span] {
+        &self.primary_spans
+    }
+
+    /// Returns `true` if any of the primary spans are displayable.
+    pub fn has_primary_spans(&self) -> bool {
+        self.primary_spans.iter().any(|sp| !sp.is_dummy())
+    }
+
+    /// Returns `true` if this contains only a dummy primary span with any hygienic context.
+    pub fn is_dummy(&self) -> bool {
+        let mut is_dummy = true;
+        for span in &self.primary_spans {
+            if !span.is_dummy() {
+                is_dummy = false;
+            }
+        }
+        is_dummy
+    }
+
+    /// Replaces all occurrences of one Span with another. Used to move `Span`s in areas that don't
+    /// display well (like std macros). Returns whether replacements occurred.
+    pub fn replace(&mut self, before: Span, after: Span) -> bool {
+        let mut replacements_occurred = false;
+        for primary_span in &mut self.primary_spans {
+            if *primary_span == before {
+                *primary_span = after;
+                replacements_occurred = true;
+            }
+        }
+        for span_label in &mut self.span_labels {
+            if span_label.0 == before {
+                span_label.0 = after;
+                replacements_occurred = true;
+            }
+        }
+        replacements_occurred
+    }
+
+    /// Returns the strings to highlight. We always ensure that there
+    /// is an entry for each of the primary spans -- for each primary
+    /// span `P`, if there is at least one label with span `P`, we return
+    /// those labels (marked as primary). But otherwise we return
+    /// `SpanLabel` instances with empty labels.
+    pub fn span_labels(&self) -> Vec<SpanLabel> {
+        let is_primary = |span| self.primary_spans.contains(&span);
+
+        let mut span_labels = self
+            .span_labels
+            .iter()
+            .map(|&(span, ref label)| SpanLabel {
+                span,
+                is_primary: is_primary(span),
+                label: Some(label.clone()),
+            })
+            .collect::<Vec<_>>();
+
+        for &span in &self.primary_spans {
+            if !span_labels.iter().any(|sl| sl.span == span) {
+                span_labels.push(SpanLabel { span, is_primary: true, label: None });
+            }
+        }
+
+        span_labels
+    }
+
+    /// Returns `true` if any of the span labels is displayable.
+    pub fn has_span_labels(&self) -> bool {
+        self.span_labels.iter().any(|(sp, _)| !sp.is_dummy())
+    }
+}
+
+impl From<Span> for MultiSpan {
+    fn from(span: Span) -> MultiSpan {
+        MultiSpan::from_span(span)
+    }
+}
+
+impl From<Vec<Span>> for MultiSpan {
+    fn from(spans: Vec<Span>) -> MultiSpan {
+        MultiSpan::from_spans(spans)
+    }
+}
diff --git a/compiler/rustc_errors/Cargo.toml b/compiler/rustc_errors/Cargo.toml
index 4846dc43605..5f919982890 100644
--- a/compiler/rustc_errors/Cargo.toml
+++ b/compiler/rustc_errors/Cargo.toml
@@ -8,6 +8,7 @@ doctest = false
 
 [dependencies]
 tracing = "0.1"
+rustc_error_messages = { path = "../rustc_error_messages" }
 rustc_serialize = { path = "../rustc_serialize" }
 rustc_span = { path = "../rustc_span" }
 rustc_macros = { path = "../rustc_macros" }
diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
index 5f59eba23f8..003fd1eea3a 100644
--- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
+++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
@@ -7,16 +7,23 @@
 
 use crate::emitter::FileWithAnnotatedLines;
 use crate::snippet::Line;
-use crate::{CodeSuggestion, Diagnostic, DiagnosticId, Emitter, Level, SubDiagnostic};
+use crate::{
+    CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, Emitter, FluentBundle, Level,
+    MultiSpan, Style, SubDiagnostic,
+};
 use annotate_snippets::display_list::{DisplayList, FormatOptions};
 use annotate_snippets::snippet::*;
 use rustc_data_structures::sync::Lrc;
+use rustc_error_messages::FluentArgs;
 use rustc_span::source_map::SourceMap;
-use rustc_span::{MultiSpan, SourceFile};
+use rustc_span::SourceFile;
 
 /// Generates diagnostics using annotate-snippet
 pub struct AnnotateSnippetEmitterWriter {
     source_map: Option<Lrc<SourceMap>>,
+    fluent_bundle: Option<Lrc<FluentBundle>>,
+    fallback_bundle: Lrc<FluentBundle>,
+
     /// If true, hides the longer explanation text
     short_message: bool,
     /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
@@ -28,8 +35,10 @@ pub struct AnnotateSnippetEmitterWriter {
 impl Emitter for AnnotateSnippetEmitterWriter {
     /// The entry point for the diagnostics generation
     fn emit_diagnostic(&mut self, diag: &Diagnostic) {
+        let fluent_args = self.to_fluent_args(diag.args());
+
         let mut children = diag.children.clone();
-        let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
+        let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args);
 
         self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
             &self.source_map,
@@ -41,7 +50,8 @@ impl Emitter for AnnotateSnippetEmitterWriter {
 
         self.emit_messages_default(
             &diag.level,
-            diag.message(),
+            &diag.message,
+            &fluent_args,
             &diag.code,
             &primary_span,
             &children,
@@ -53,6 +63,14 @@ impl Emitter for AnnotateSnippetEmitterWriter {
         self.source_map.as_ref()
     }
 
+    fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
+        self.fluent_bundle.as_ref()
+    }
+
+    fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle> {
+        &self.fallback_bundle
+    }
+
     fn should_show_explain(&self) -> bool {
         !self.short_message
     }
@@ -82,10 +100,19 @@ fn annotation_type_for_level(level: Level) -> AnnotationType {
 impl AnnotateSnippetEmitterWriter {
     pub fn new(
         source_map: Option<Lrc<SourceMap>>,
+        fluent_bundle: Option<Lrc<FluentBundle>>,
+        fallback_bundle: Lrc<FluentBundle>,
         short_message: bool,
         macro_backtrace: bool,
     ) -> Self {
-        Self { source_map, short_message, ui_testing: false, macro_backtrace }
+        Self {
+            source_map,
+            fluent_bundle,
+            fallback_bundle,
+            short_message,
+            ui_testing: false,
+            macro_backtrace,
+        }
     }
 
     /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
@@ -99,12 +126,14 @@ impl AnnotateSnippetEmitterWriter {
     fn emit_messages_default(
         &mut self,
         level: &Level,
-        message: String,
+        messages: &[(DiagnosticMessage, Style)],
+        args: &FluentArgs<'_>,
         code: &Option<DiagnosticId>,
         msp: &MultiSpan,
         _children: &[SubDiagnostic],
         _suggestions: &[CodeSuggestion],
     ) {
+        let message = self.translate_messages(messages, args);
         if let Some(source_map) = &self.source_map {
             // Make sure our primary file comes first
             let primary_lo = if let Some(ref primary_span) = msp.primary_span().as_ref() {
@@ -120,8 +149,7 @@ impl AnnotateSnippetEmitterWriter {
                 // should be done if it happens
                 return;
             };
-            let mut annotated_files =
-                FileWithAnnotatedLines::collect_annotations(msp, &self.source_map);
+            let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
             if let Ok(pos) =
                 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
             {
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 32c52a6a8a6..ecb3cdd627c 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -1,15 +1,16 @@
 use crate::snippet::Style;
-use crate::CodeSuggestion;
-use crate::Level;
-use crate::Substitution;
-use crate::SubstitutionPart;
-use crate::SuggestionStyle;
-use crate::ToolMetadata;
+use crate::{
+    CodeSuggestion, DiagnosticMessage, Level, MultiSpan, Substitution, SubstitutionPart,
+    SuggestionStyle, ToolMetadata,
+};
 use rustc_data_structures::stable_map::FxHashMap;
+use rustc_error_messages::FluentValue;
 use rustc_lint_defs::{Applicability, LintExpectationId};
 use rustc_serialize::json::Json;
 use rustc_span::edition::LATEST_STABLE_EDITION;
-use rustc_span::{MultiSpan, Span, DUMMY_SP};
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use std::borrow::Cow;
 use std::fmt;
 use std::hash::{Hash, Hasher};
 
@@ -18,6 +19,66 @@ use std::hash::{Hash, Hasher};
 #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
 pub struct SuggestionsDisabled;
 
+/// Simplified version of `FluentArg` that can implement `Encodable` and `Decodable`. Collection of
+/// `DiagnosticArg` are converted to `FluentArgs` (consuming the collection) at the start of
+/// diagnostic emission.
+pub type DiagnosticArg<'source> = (Cow<'source, str>, DiagnosticArgValue<'source>);
+
+/// Simplified version of `FluentValue` that can implement `Encodable` and `Decodable`. Converted
+/// to a `FluentValue` by the emitter to be used in diagnostic translation.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
+pub enum DiagnosticArgValue<'source> {
+    Str(Cow<'source, str>),
+    Number(usize),
+}
+
+/// Converts a value of a type into a `DiagnosticArg` (typically a field of a `SessionDiagnostic`
+/// struct). Implemented as a custom trait rather than `From` so that it is implemented on the type
+/// being converted rather than on `DiagnosticArgValue`, which enables types from other `rustc_*`
+/// crates to implement this.
+pub trait IntoDiagnosticArg {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static>;
+}
+
+impl IntoDiagnosticArg for String {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self))
+    }
+}
+
+impl IntoDiagnosticArg for Symbol {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        self.to_ident_string().into_diagnostic_arg()
+    }
+}
+
+impl IntoDiagnosticArg for Ident {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        self.to_string().into_diagnostic_arg()
+    }
+}
+
+impl<'a> IntoDiagnosticArg for &'a str {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        self.to_string().into_diagnostic_arg()
+    }
+}
+
+impl IntoDiagnosticArg for usize {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Number(self)
+    }
+}
+
+impl<'source> Into<FluentValue<'source>> for DiagnosticArgValue<'source> {
+    fn into(self) -> FluentValue<'source> {
+        match self {
+            DiagnosticArgValue::Str(s) => From::from(s),
+            DiagnosticArgValue::Number(n) => From::from(n),
+        }
+    }
+}
+
 #[must_use]
 #[derive(Clone, Debug, Encodable, Decodable)]
 pub struct Diagnostic {
@@ -25,11 +86,12 @@ pub struct Diagnostic {
     // outside of what methods in this crate themselves allow.
     crate level: Level,
 
-    pub message: Vec<(String, Style)>,
+    pub message: Vec<(DiagnosticMessage, Style)>,
     pub code: Option<DiagnosticId>,
     pub span: MultiSpan,
     pub children: Vec<SubDiagnostic>,
     pub suggestions: Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
+    args: Vec<DiagnosticArg<'static>>,
 
     /// This is not used for highlighting or rendering any error message.  Rather, it can be used
     /// as a sort key to sort a buffer of diagnostics.  By default, it is the primary span of
@@ -52,7 +114,7 @@ pub enum DiagnosticId {
 #[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
 pub struct SubDiagnostic {
     pub level: Level,
-    pub message: Vec<(String, Style)>,
+    pub message: Vec<(DiagnosticMessage, Style)>,
     pub span: MultiSpan,
     pub render_span: Option<MultiSpan>,
 }
@@ -105,18 +167,23 @@ impl StringPart {
 }
 
 impl Diagnostic {
-    pub fn new(level: Level, message: &str) -> Self {
+    pub fn new<M: Into<DiagnosticMessage>>(level: Level, message: M) -> Self {
         Diagnostic::new_with_code(level, None, message)
     }
 
-    pub fn new_with_code(level: Level, code: Option<DiagnosticId>, message: &str) -> Self {
+    pub fn new_with_code<M: Into<DiagnosticMessage>>(
+        level: Level,
+        code: Option<DiagnosticId>,
+        message: M,
+    ) -> Self {
         Diagnostic {
             level,
-            message: vec![(message.to_owned(), Style::NoStyle)],
+            message: vec![(message.into(), Style::NoStyle)],
             code,
             span: MultiSpan::new(),
             children: vec![],
             suggestions: Ok(vec![]),
+            args: vec![],
             sort_span: DUMMY_SP,
             is_lint: false,
         }
@@ -210,7 +277,7 @@ impl Diagnostic {
     ///
     /// This span is *not* considered a ["primary span"][`MultiSpan`]; only
     /// the `Span` supplied when creating the diagnostic is primary.
-    pub fn span_label<T: Into<String>>(&mut self, span: Span, label: T) -> &mut Self {
+    pub fn span_label(&mut self, span: Span, label: impl Into<DiagnosticMessage>) -> &mut Self {
         self.span.push_span_label(span, label.into());
         self
     }
@@ -234,7 +301,7 @@ impl Diagnostic {
         self.set_span(after);
         for span_label in before.span_labels() {
             if let Some(label) = span_label.label {
-                self.span_label(after, label);
+                self.span.push_span_label(after, label);
             }
         }
         self
@@ -328,52 +395,67 @@ impl Diagnostic {
     }
 
     /// Add a note attached to this diagnostic.
-    pub fn note(&mut self, msg: &str) -> &mut Self {
+    pub fn note(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
         self.sub(Level::Note, msg, MultiSpan::new(), None);
         self
     }
 
-    pub fn highlighted_note(&mut self, msg: Vec<(String, Style)>) -> &mut Self {
+    pub fn highlighted_note<M: Into<DiagnosticMessage>>(
+        &mut self,
+        msg: Vec<(M, Style)>,
+    ) -> &mut Self {
         self.sub_with_highlights(Level::Note, msg, MultiSpan::new(), None);
         self
     }
 
     /// Prints the span with a note above it.
     /// This is like [`Diagnostic::note()`], but it gets its own span.
-    pub fn note_once(&mut self, msg: &str) -> &mut Self {
+    pub fn note_once(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
         self.sub(Level::OnceNote, msg, MultiSpan::new(), None);
         self
     }
 
     /// Prints the span with a note above it.
     /// This is like [`Diagnostic::note()`], but it gets its own span.
-    pub fn span_note<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
+    pub fn span_note<S: Into<MultiSpan>>(
+        &mut self,
+        sp: S,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> &mut Self {
         self.sub(Level::Note, msg, sp.into(), None);
         self
     }
 
     /// Prints the span with a note above it.
     /// This is like [`Diagnostic::note()`], but it gets its own span.
-    pub fn span_note_once<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
+    pub fn span_note_once<S: Into<MultiSpan>>(
+        &mut self,
+        sp: S,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> &mut Self {
         self.sub(Level::OnceNote, msg, sp.into(), None);
         self
     }
 
     /// Add a warning attached to this diagnostic.
-    pub fn warn(&mut self, msg: &str) -> &mut Self {
+    pub fn warn(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
         self.sub(Level::Warning, msg, MultiSpan::new(), None);
         self
     }
 
     /// Prints the span with a warning above it.
     /// This is like [`Diagnostic::warn()`], but it gets its own span.
-    pub fn span_warn<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
+    pub fn span_warn<S: Into<MultiSpan>>(
+        &mut self,
+        sp: S,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> &mut Self {
         self.sub(Level::Warning, msg, sp.into(), None);
         self
     }
 
     /// Add a help message attached to this diagnostic.
-    pub fn help(&mut self, msg: &str) -> &mut Self {
+    pub fn help(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
         self.sub(Level::Help, msg, MultiSpan::new(), None);
         self
     }
@@ -386,7 +468,11 @@ impl Diagnostic {
 
     /// Prints the span with some help above it.
     /// This is like [`Diagnostic::help()`], but it gets its own span.
-    pub fn span_help<S: Into<MultiSpan>>(&mut self, sp: S, msg: &str) -> &mut Self {
+    pub fn span_help<S: Into<MultiSpan>>(
+        &mut self,
+        sp: S,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> &mut Self {
         self.sub(Level::Help, msg, sp.into(), None);
         self
     }
@@ -422,7 +508,7 @@ impl Diagnostic {
     /// In other words, multiple changes need to be applied as part of this suggestion.
     pub fn multipart_suggestion(
         &mut self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self {
@@ -438,7 +524,7 @@ impl Diagnostic {
     /// In other words, multiple changes need to be applied as part of this suggestion.
     pub fn multipart_suggestion_verbose(
         &mut self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self {
@@ -452,7 +538,7 @@ impl Diagnostic {
     /// [`Diagnostic::multipart_suggestion()`] but you can set the [`SuggestionStyle`].
     pub fn multipart_suggestion_with_style(
         &mut self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
         style: SuggestionStyle,
@@ -465,7 +551,7 @@ impl Diagnostic {
                     .map(|(span, snippet)| SubstitutionPart { snippet, span })
                     .collect(),
             }],
-            msg: msg.to_owned(),
+            msg: msg.into(),
             style,
             applicability,
             tool_metadata: Default::default(),
@@ -481,7 +567,7 @@ impl Diagnostic {
     /// improve understandability.
     pub fn tool_only_multipart_suggestion(
         &mut self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self {
@@ -493,7 +579,7 @@ impl Diagnostic {
                     .map(|(span, snippet)| SubstitutionPart { snippet, span })
                     .collect(),
             }],
-            msg: msg.to_owned(),
+            msg: msg.into(),
             style: SuggestionStyle::CompletelyHidden,
             applicability,
             tool_metadata: Default::default(),
@@ -521,7 +607,7 @@ impl Diagnostic {
     pub fn span_suggestion(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
     ) -> &mut Self {
@@ -539,7 +625,7 @@ impl Diagnostic {
     pub fn span_suggestion_with_style(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
         style: SuggestionStyle,
@@ -548,7 +634,7 @@ impl Diagnostic {
             substitutions: vec![Substitution {
                 parts: vec![SubstitutionPart { snippet: suggestion, span: sp }],
             }],
-            msg: msg.to_owned(),
+            msg: msg.into(),
             style,
             applicability,
             tool_metadata: Default::default(),
@@ -560,7 +646,7 @@ impl Diagnostic {
     pub fn span_suggestion_verbose(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
     ) -> &mut Self {
@@ -579,7 +665,7 @@ impl Diagnostic {
     pub fn span_suggestions(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestions: impl Iterator<Item = String>,
         applicability: Applicability,
     ) -> &mut Self {
@@ -591,7 +677,7 @@ impl Diagnostic {
             .collect();
         self.push_suggestion(CodeSuggestion {
             substitutions,
-            msg: msg.to_owned(),
+            msg: msg.into(),
             style: SuggestionStyle::ShowCode,
             applicability,
             tool_metadata: Default::default(),
@@ -603,7 +689,7 @@ impl Diagnostic {
     /// See also [`Diagnostic::span_suggestion()`].
     pub fn multipart_suggestions(
         &mut self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestions: impl Iterator<Item = Vec<(Span, String)>>,
         applicability: Applicability,
     ) -> &mut Self {
@@ -616,7 +702,7 @@ impl Diagnostic {
                         .collect(),
                 })
                 .collect(),
-            msg: msg.to_owned(),
+            msg: msg.into(),
             style: SuggestionStyle::ShowCode,
             applicability,
             tool_metadata: Default::default(),
@@ -630,7 +716,7 @@ impl Diagnostic {
     pub fn span_suggestion_short(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
     ) -> &mut Self {
@@ -653,7 +739,7 @@ impl Diagnostic {
     pub fn span_suggestion_hidden(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
     ) -> &mut Self {
@@ -674,7 +760,7 @@ impl Diagnostic {
     pub fn tool_only_span_suggestion(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
     ) -> &mut Self {
@@ -692,13 +778,13 @@ impl Diagnostic {
     /// the suggestion in a tool-specific way, as it may not even directly involve Rust code.
     pub fn tool_only_suggestion_with_metadata(
         &mut self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         applicability: Applicability,
         tool_metadata: Json,
     ) {
         self.push_suggestion(CodeSuggestion {
             substitutions: vec![],
-            msg: msg.to_owned(),
+            msg: msg.into(),
             style: SuggestionStyle::CompletelyHidden,
             applicability,
             tool_metadata: ToolMetadata::new(tool_metadata),
@@ -732,16 +818,25 @@ impl Diagnostic {
         self.code.clone()
     }
 
-    pub fn set_primary_message<M: Into<String>>(&mut self, msg: M) -> &mut Self {
+    pub fn set_primary_message(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
         self.message[0] = (msg.into(), Style::NoStyle);
         self
     }
 
-    pub fn message(&self) -> String {
-        self.message.iter().map(|i| i.0.as_str()).collect::<String>()
+    pub fn args(&self) -> &[DiagnosticArg<'static>] {
+        &self.args
+    }
+
+    pub fn set_arg(
+        &mut self,
+        name: impl Into<Cow<'static, str>>,
+        arg: DiagnosticArgValue<'static>,
+    ) -> &mut Self {
+        self.args.push((name.into(), arg.into()));
+        self
     }
 
-    pub fn styled_message(&self) -> &Vec<(String, Style)> {
+    pub fn styled_message(&self) -> &Vec<(DiagnosticMessage, Style)> {
         &self.message
     }
 
@@ -752,13 +847,13 @@ impl Diagnostic {
     pub fn sub(
         &mut self,
         level: Level,
-        message: &str,
+        message: impl Into<DiagnosticMessage>,
         span: MultiSpan,
         render_span: Option<MultiSpan>,
     ) {
         let sub = SubDiagnostic {
             level,
-            message: vec![(message.to_owned(), Style::NoStyle)],
+            message: vec![(message.into(), Style::NoStyle)],
             span,
             render_span,
         };
@@ -767,13 +862,14 @@ impl Diagnostic {
 
     /// Convenience function for internal use, clients should use one of the
     /// public methods above.
-    fn sub_with_highlights(
+    fn sub_with_highlights<M: Into<DiagnosticMessage>>(
         &mut self,
         level: Level,
-        message: Vec<(String, Style)>,
+        mut message: Vec<(M, Style)>,
         span: MultiSpan,
         render_span: Option<MultiSpan>,
     ) {
+        let message = message.drain(..).map(|m| (m.0.into(), m.1)).collect();
         let sub = SubDiagnostic { level, message, span, render_span };
         self.children.push(sub);
     }
@@ -783,7 +879,7 @@ impl Diagnostic {
         &self,
     ) -> (
         &Level,
-        &Vec<(String, Style)>,
+        &Vec<(DiagnosticMessage, Style)>,
         &Option<DiagnosticId>,
         &MultiSpan,
         &Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
@@ -814,13 +910,3 @@ impl PartialEq for Diagnostic {
         self.keys() == other.keys()
     }
 }
-
-impl SubDiagnostic {
-    pub fn message(&self) -> String {
-        self.message.iter().map(|i| i.0.as_str()).collect::<String>()
-    }
-
-    pub fn styled_message(&self) -> &Vec<(String, Style)> {
-        &self.message
-    }
-}
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index 853243ef3f0..74e0f742946 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -1,8 +1,10 @@
-use crate::{Diagnostic, DiagnosticId, DiagnosticStyledString, ErrorGuaranteed};
-use crate::{Handler, Level, StashKey};
+use crate::diagnostic::DiagnosticArgValue;
+use crate::{Diagnostic, DiagnosticId, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed};
+use crate::{Handler, Level, MultiSpan, StashKey};
 use rustc_lint_defs::Applicability;
 
-use rustc_span::{MultiSpan, Span};
+use rustc_span::Span;
+use std::borrow::Cow;
 use std::fmt::{self, Debug};
 use std::marker::PhantomData;
 use std::ops::{Deref, DerefMut};
@@ -99,7 +101,10 @@ mod sealed_level_is_error {
 impl<'a> DiagnosticBuilder<'a, ErrorGuaranteed> {
     /// Convenience function for internal use, clients should use one of the
     /// `struct_*` methods on [`Handler`].
-    crate fn new_guaranteeing_error<const L: Level>(handler: &'a Handler, message: &str) -> Self
+    crate fn new_guaranteeing_error<M: Into<DiagnosticMessage>, const L: Level>(
+        handler: &'a Handler,
+        message: M,
+    ) -> Self
     where
         (): sealed_level_is_error::IsError<L>,
     {
@@ -163,7 +168,11 @@ impl EmissionGuarantee for ErrorGuaranteed {
 impl<'a> DiagnosticBuilder<'a, ()> {
     /// Convenience function for internal use, clients should use one of the
     /// `struct_*` methods on [`Handler`].
-    crate fn new(handler: &'a Handler, level: Level, message: &str) -> Self {
+    crate fn new<M: Into<DiagnosticMessage>>(
+        handler: &'a Handler,
+        level: Level,
+        message: M,
+    ) -> Self {
         let diagnostic = Diagnostic::new_with_code(level, None, message);
         Self::new_diagnostic(handler, diagnostic)
     }
@@ -201,7 +210,7 @@ impl EmissionGuarantee for () {
 impl<'a> DiagnosticBuilder<'a, !> {
     /// Convenience function for internal use, clients should use one of the
     /// `struct_*` methods on [`Handler`].
-    crate fn new_fatal(handler: &'a Handler, message: &str) -> Self {
+    crate fn new_fatal(handler: &'a Handler, message: impl Into<DiagnosticMessage>) -> Self {
         let diagnostic = Diagnostic::new_with_code(Level::Fatal, None, message);
         Self::new_diagnostic_fatal(handler, diagnostic)
     }
@@ -346,7 +355,7 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
         }
 
         // Take the `Diagnostic` by replacing it with a dummy.
-        let dummy = Diagnostic::new(Level::Allow, "");
+        let dummy = Diagnostic::new(Level::Allow, DiagnosticMessage::Str("".to_string()));
         let diagnostic = std::mem::replace(&mut *self.inner.diagnostic, dummy);
 
         // Disable the ICE on `Drop`.
@@ -399,7 +408,7 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
     /// the diagnostic was constructed. However, the label span is *not* considered a
     /// ["primary span"][`MultiSpan`]; only the `Span` supplied when creating the diagnostic is
     /// primary.
-    pub fn span_label(&mut self, span: Span, label: impl Into<String>) -> &mut Self);
+    pub fn span_label(&mut self, span: Span, label: impl Into<DiagnosticMessage>) -> &mut Self);
 
     forward!(
     /// Labels all the given spans with the provided label.
@@ -434,25 +443,25 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
         found: DiagnosticStyledString,
     ) -> &mut Self);
 
-    forward!(pub fn note(&mut self, msg: &str) -> &mut Self);
-    forward!(pub fn note_once(&mut self, msg: &str) -> &mut Self);
+    forward!(pub fn note(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self);
+    forward!(pub fn note_once(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self);
     forward!(pub fn span_note(
         &mut self,
         sp: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> &mut Self);
     forward!(pub fn span_note_once(
         &mut self,
         sp: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> &mut Self);
-    forward!(pub fn warn(&mut self, msg: &str) -> &mut Self);
+    forward!(pub fn warn(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self);
     forward!(pub fn span_warn(&mut self, sp: impl Into<MultiSpan>, msg: &str) -> &mut Self);
-    forward!(pub fn help(&mut self, msg: &str) -> &mut Self);
+    forward!(pub fn help(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self);
     forward!(pub fn span_help(
         &mut self,
         sp: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> &mut Self);
     forward!(pub fn help_use_latest_edition(&mut self,) -> &mut Self);
     forward!(pub fn set_is_lint(&mut self,) -> &mut Self);
@@ -461,67 +470,67 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
 
     forward!(pub fn multipart_suggestion(
         &mut self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn multipart_suggestion_verbose(
         &mut self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn tool_only_multipart_suggestion(
         &mut self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn span_suggestion(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn span_suggestions(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestions: impl Iterator<Item = String>,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn multipart_suggestions(
         &mut self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestions: impl Iterator<Item = Vec<(Span, String)>>,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn span_suggestion_short(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn span_suggestion_verbose(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn span_suggestion_hidden(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn tool_only_span_suggestion(
         &mut self,
         sp: Span,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         suggestion: String,
         applicability: Applicability,
     ) -> &mut Self);
@@ -529,6 +538,11 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
     forward!(pub fn set_primary_message(&mut self, msg: impl Into<String>) -> &mut Self);
     forward!(pub fn set_span(&mut self, sp: impl Into<MultiSpan>) -> &mut Self);
     forward!(pub fn code(&mut self, s: DiagnosticId) -> &mut Self);
+    forward!(pub fn set_arg(
+        &mut self,
+        name: impl Into<Cow<'static, str>>,
+        arg: DiagnosticArgValue<'static>,
+    ) -> &mut Self);
 }
 
 impl<G: EmissionGuarantee> Debug for DiagnosticBuilder<'_, G> {
@@ -547,7 +561,9 @@ impl Drop for DiagnosticBuilderInner<'_> {
                 if !panicking() {
                     handler.emit_diagnostic(&mut Diagnostic::new(
                         Level::Bug,
-                        "the following error was constructed but not emitted",
+                        DiagnosticMessage::Str(
+                            "the following error was constructed but not emitted".to_string(),
+                        ),
                     ));
                     handler.emit_diagnostic(&mut self.diagnostic);
                     panic!();
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 93b7201023a..6a763d4d140 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -10,19 +10,20 @@
 use Destination::*;
 
 use rustc_span::source_map::SourceMap;
-use rustc_span::{MultiSpan, SourceFile, Span};
+use rustc_span::{SourceFile, Span};
 
 use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString};
 use crate::styled_buffer::StyledBuffer;
 use crate::{
-    CodeSuggestion, Diagnostic, DiagnosticId, Handler, Level, SubDiagnostic, SubstitutionHighlight,
-    SuggestionStyle,
+    CodeSuggestion, Diagnostic, DiagnosticArg, DiagnosticId, DiagnosticMessage, FluentBundle,
+    Handler, Level, MultiSpan, SubDiagnostic, SubstitutionHighlight, SuggestionStyle,
 };
 
 use rustc_lint_defs::pluralize;
 
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync::Lrc;
+use rustc_error_messages::FluentArgs;
 use rustc_span::hygiene::{ExpnKind, MacroKind};
 use std::borrow::Cow;
 use std::cmp::{max, min, Reverse};
@@ -58,13 +59,25 @@ impl HumanReadableErrorType {
         self,
         dst: Box<dyn Write + Send>,
         source_map: Option<Lrc<SourceMap>>,
+        bundle: Option<Lrc<FluentBundle>>,
+        fallback_bundle: Lrc<FluentBundle>,
         teach: bool,
         terminal_width: Option<usize>,
         macro_backtrace: bool,
     ) -> EmitterWriter {
         let (short, color_config) = self.unzip();
         let color = color_config.suggests_using_colors();
-        EmitterWriter::new(dst, source_map, short, teach, color, terminal_width, macro_backtrace)
+        EmitterWriter::new(
+            dst,
+            source_map,
+            bundle,
+            fallback_bundle,
+            short,
+            teach,
+            color,
+            terminal_width,
+            macro_backtrace,
+        )
     }
 }
 
@@ -212,6 +225,74 @@ pub trait Emitter {
 
     fn source_map(&self) -> Option<&Lrc<SourceMap>>;
 
+    /// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
+    /// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
+    /// should be used.
+    fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>>;
+
+    /// Return `FluentBundle` with localized diagnostics for the default locale of the compiler.
+    /// Used when the user has not requested a specific language or when a localized diagnostic is
+    /// unavailable for the requested locale.
+    fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle>;
+
+    /// Convert diagnostic arguments (a rustc internal type that exists to implement
+    /// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
+    ///
+    /// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
+    /// passed around as a reference thereafter.
+    fn to_fluent_args<'arg>(&self, args: &[DiagnosticArg<'arg>]) -> FluentArgs<'arg> {
+        FromIterator::from_iter(args.to_vec().drain(..))
+    }
+
+    /// Convert `DiagnosticMessage`s to a string, performing translation if necessary.
+    fn translate_messages(
+        &self,
+        messages: &[(DiagnosticMessage, Style)],
+        args: &FluentArgs<'_>,
+    ) -> Cow<'_, str> {
+        Cow::Owned(
+            messages.iter().map(|(m, _)| self.translate_message(m, args)).collect::<String>(),
+        )
+    }
+
+    /// Convert a `DiagnosticMessage` to a string, performing translation if necessary.
+    fn translate_message<'a>(
+        &'a self,
+        message: &'a DiagnosticMessage,
+        args: &'a FluentArgs<'_>,
+    ) -> Cow<'_, str> {
+        trace!(?message, ?args);
+        let (identifier, attr) = match message {
+            DiagnosticMessage::Str(msg) => return Cow::Borrowed(&msg),
+            DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
+        };
+
+        let bundle = match self.fluent_bundle() {
+            Some(bundle) if bundle.has_message(&identifier) => bundle,
+            _ => self.fallback_fluent_bundle(),
+        };
+
+        let message = bundle.get_message(&identifier).expect("missing diagnostic in fluent bundle");
+        let value = match attr {
+            Some(attr) => {
+                message.get_attribute(attr).expect("missing attribute in fluent message").value()
+            }
+            None => message.value().expect("missing value in fluent message"),
+        };
+
+        let mut err = vec![];
+        let translated = bundle.format_pattern(value, Some(&args), &mut err);
+        trace!(?translated, ?err);
+        debug_assert!(
+            err.is_empty(),
+            "identifier: {:?}, args: {:?}, errors: {:?}",
+            identifier,
+            args,
+            err
+        );
+        translated
+    }
+
     /// Formats the substitutions of the primary_span
     ///
     /// There are a lot of conditions to this method, but in short:
@@ -225,10 +306,12 @@ pub trait Emitter {
     fn primary_span_formatted<'a>(
         &mut self,
         diag: &'a Diagnostic,
+        fluent_args: &FluentArgs<'_>,
     ) -> (MultiSpan, &'a [CodeSuggestion]) {
         let mut primary_span = diag.span.clone();
         let suggestions = diag.suggestions.as_ref().map_or(&[][..], |suggestions| &suggestions[..]);
         if let Some((sugg, rest)) = suggestions.split_first() {
+            let msg = self.translate_message(&sugg.msg, fluent_args);
             if rest.is_empty() &&
                // ^ if there is only one suggestion
                // don't display multi-suggestions as labels
@@ -236,7 +319,7 @@ pub trait Emitter {
                // don't display multipart suggestions as labels
                sugg.substitutions[0].parts.len() == 1 &&
                // don't display long messages as labels
-               sugg.msg.split_whitespace().count() < 10 &&
+               msg.split_whitespace().count() < 10 &&
                // don't display multiline suggestions as labels
                !sugg.substitutions[0].parts[0].snippet.contains('\n') &&
                ![
@@ -252,12 +335,12 @@ pub trait Emitter {
                 let msg = if substitution.is_empty() || sugg.style.hide_inline() {
                     // This substitution is only removal OR we explicitly don't want to show the
                     // code inline (`hide_inline`). Therefore, we don't show the substitution.
-                    format!("help: {}", sugg.msg)
+                    format!("help: {}", &msg)
                 } else {
                     // Show the default suggestion text with the substitution
                     format!(
                         "help: {}{}: `{}`",
-                        sugg.msg,
+                        &msg,
                         if self
                             .source_map()
                             .map(|sm| is_case_difference(
@@ -333,7 +416,7 @@ pub trait Emitter {
 
                 children.push(SubDiagnostic {
                     level: Level::Note,
-                    message: vec![(msg, Style::NoStyle)],
+                    message: vec![(DiagnosticMessage::Str(msg), Style::NoStyle)],
                     span: MultiSpan::new(),
                     render_span: None,
                 });
@@ -492,9 +575,19 @@ impl Emitter for EmitterWriter {
         self.sm.as_ref()
     }
 
+    fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
+        self.fluent_bundle.as_ref()
+    }
+
+    fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle> {
+        &self.fallback_bundle
+    }
+
     fn emit_diagnostic(&mut self, diag: &Diagnostic) {
+        let fluent_args = self.to_fluent_args(diag.args());
+
         let mut children = diag.children.clone();
-        let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
+        let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args);
         debug!("emit_diagnostic: suggestions={:?}", suggestions);
 
         self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
@@ -507,7 +600,8 @@ impl Emitter for EmitterWriter {
 
         self.emit_messages_default(
             &diag.level,
-            &diag.styled_message(),
+            &diag.message,
+            &fluent_args,
             &diag.code,
             &primary_span,
             &children,
@@ -536,6 +630,15 @@ impl Emitter for SilentEmitter {
     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
         None
     }
+
+    fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
+        None
+    }
+
+    fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle> {
+        panic!("silent emitter attempted to translate message")
+    }
+
     fn emit_diagnostic(&mut self, d: &Diagnostic) {
         if d.level == Level::Fatal {
             let mut d = d.clone();
@@ -591,6 +694,8 @@ impl ColorConfig {
 pub struct EmitterWriter {
     dst: Destination,
     sm: Option<Lrc<SourceMap>>,
+    fluent_bundle: Option<Lrc<FluentBundle>>,
+    fallback_bundle: Lrc<FluentBundle>,
     short_message: bool,
     teach: bool,
     ui_testing: bool,
@@ -610,6 +715,8 @@ impl EmitterWriter {
     pub fn stderr(
         color_config: ColorConfig,
         source_map: Option<Lrc<SourceMap>>,
+        fluent_bundle: Option<Lrc<FluentBundle>>,
+        fallback_bundle: Lrc<FluentBundle>,
         short_message: bool,
         teach: bool,
         terminal_width: Option<usize>,
@@ -619,6 +726,8 @@ impl EmitterWriter {
         EmitterWriter {
             dst,
             sm: source_map,
+            fluent_bundle,
+            fallback_bundle,
             short_message,
             teach,
             ui_testing: false,
@@ -630,6 +739,8 @@ impl EmitterWriter {
     pub fn new(
         dst: Box<dyn Write + Send>,
         source_map: Option<Lrc<SourceMap>>,
+        fluent_bundle: Option<Lrc<FluentBundle>>,
+        fallback_bundle: Lrc<FluentBundle>,
         short_message: bool,
         teach: bool,
         colored: bool,
@@ -639,6 +750,8 @@ impl EmitterWriter {
         EmitterWriter {
             dst: Raw(dst, colored),
             sm: source_map,
+            fluent_bundle,
+            fallback_bundle,
             short_message,
             teach,
             ui_testing: false,
@@ -1176,7 +1289,8 @@ impl EmitterWriter {
     fn msg_to_buffer(
         &self,
         buffer: &mut StyledBuffer,
-        msg: &[(String, Style)],
+        msg: &[(DiagnosticMessage, Style)],
+        args: &FluentArgs<'_>,
         padding: usize,
         label: &str,
         override_style: Option<Style>,
@@ -1229,6 +1343,7 @@ impl EmitterWriter {
         //                very *weird* formats
         //                see?
         for &(ref text, ref style) in msg.iter() {
+            let text = self.translate_message(text, args);
             let lines = text.split('\n').collect::<Vec<_>>();
             if lines.len() > 1 {
                 for (i, line) in lines.iter().enumerate() {
@@ -1239,7 +1354,7 @@ impl EmitterWriter {
                     buffer.append(line_number, line, style_or_override(*style, override_style));
                 }
             } else {
-                buffer.append(line_number, text, style_or_override(*style, override_style));
+                buffer.append(line_number, &text, style_or_override(*style, override_style));
             }
         }
     }
@@ -1247,7 +1362,8 @@ impl EmitterWriter {
     fn emit_message_default(
         &mut self,
         msp: &MultiSpan,
-        msg: &[(String, Style)],
+        msg: &[(DiagnosticMessage, Style)],
+        args: &FluentArgs<'_>,
         code: &Option<DiagnosticId>,
         level: &Level,
         max_line_num_len: usize,
@@ -1266,7 +1382,7 @@ impl EmitterWriter {
                 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
                 buffer.append(0, ": ", Style::NoStyle);
             }
-            self.msg_to_buffer(&mut buffer, msg, max_line_num_len, "note", None);
+            self.msg_to_buffer(&mut buffer, msg, args, max_line_num_len, "note", None);
         } else {
             let mut label_width = 0;
             // The failure note level itself does not provide any useful diagnostic information
@@ -1287,8 +1403,9 @@ impl EmitterWriter {
                 label_width += 2;
             }
             for &(ref text, _) in msg.iter() {
+                let text = self.translate_message(text, args);
                 // Account for newlines to align output to its label.
-                for (line, text) in normalize_whitespace(text).lines().enumerate() {
+                for (line, text) in normalize_whitespace(&text).lines().enumerate() {
                     buffer.append(
                         0 + line,
                         &format!(
@@ -1302,7 +1419,7 @@ impl EmitterWriter {
             }
         }
 
-        let mut annotated_files = FileWithAnnotatedLines::collect_annotations(msp, &self.sm);
+        let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
 
         // Make sure our primary file comes first
         let (primary_lo, sm) = if let (Some(sm), Some(ref primary_span)) =
@@ -1586,6 +1703,7 @@ impl EmitterWriter {
     fn emit_suggestion_default(
         &mut self,
         suggestion: &CodeSuggestion,
+        args: &FluentArgs<'_>,
         level: &Level,
         max_line_num_len: usize,
     ) -> io::Result<()> {
@@ -1612,6 +1730,7 @@ impl EmitterWriter {
         self.msg_to_buffer(
             &mut buffer,
             &[(suggestion.msg.to_owned(), Style::NoStyle)],
+            args,
             max_line_num_len,
             "suggestion",
             Some(Style::HeaderMsg),
@@ -1852,7 +1971,8 @@ impl EmitterWriter {
     fn emit_messages_default(
         &mut self,
         level: &Level,
-        message: &[(String, Style)],
+        message: &[(DiagnosticMessage, Style)],
+        args: &FluentArgs<'_>,
         code: &Option<DiagnosticId>,
         span: &MultiSpan,
         children: &[SubDiagnostic],
@@ -1865,7 +1985,7 @@ impl EmitterWriter {
             num_decimal_digits(n)
         };
 
-        match self.emit_message_default(span, message, code, level, max_line_num_len, false) {
+        match self.emit_message_default(span, message, args, code, level, max_line_num_len, false) {
             Ok(()) => {
                 if !children.is_empty()
                     || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
@@ -1888,7 +2008,8 @@ impl EmitterWriter {
                         let span = child.render_span.as_ref().unwrap_or(&child.span);
                         if let Err(err) = self.emit_message_default(
                             &span,
-                            &child.styled_message(),
+                            &child.message,
+                            args,
                             &None,
                             &child.level,
                             max_line_num_len,
@@ -1904,6 +2025,7 @@ impl EmitterWriter {
                             if let Err(e) = self.emit_message_default(
                                 &MultiSpan::new(),
                                 &[(sugg.msg.to_owned(), Style::HeaderMsg)],
+                                args,
                                 &None,
                                 &Level::Help,
                                 max_line_num_len,
@@ -1912,7 +2034,7 @@ impl EmitterWriter {
                                 panic!("failed to emit error: {}", e);
                             }
                         } else if let Err(e) =
-                            self.emit_suggestion_default(sugg, &Level::Help, max_line_num_len)
+                            self.emit_suggestion_default(sugg, args, &Level::Help, max_line_num_len)
                         {
                             panic!("failed to emit error: {}", e);
                         };
@@ -1938,8 +2060,9 @@ impl FileWithAnnotatedLines {
     /// Preprocess all the annotations so that they are grouped by file and by line number
     /// This helps us quickly iterate over the whole message (including secondary file spans)
     pub fn collect_annotations(
+        emitter: &dyn Emitter,
+        args: &FluentArgs<'_>,
         msp: &MultiSpan,
-        source_map: &Option<Lrc<SourceMap>>,
     ) -> Vec<FileWithAnnotatedLines> {
         fn add_annotation_to_file(
             file_vec: &mut Vec<FileWithAnnotatedLines>,
@@ -1974,7 +2097,7 @@ impl FileWithAnnotatedLines {
         let mut output = vec![];
         let mut multiline_annotations = vec![];
 
-        if let Some(ref sm) = source_map {
+        if let Some(ref sm) = emitter.source_map() {
             for span_label in msp.span_labels() {
                 if span_label.span.is_dummy() {
                     continue;
@@ -2001,7 +2124,10 @@ impl FileWithAnnotatedLines {
                         start_col: lo.col_display,
                         end_col: hi.col_display,
                         is_primary: span_label.is_primary,
-                        label: span_label.label,
+                        label: span_label
+                            .label
+                            .as_ref()
+                            .map(|m| emitter.translate_message(m, args).to_string()),
                         overlaps_exactly: false,
                     };
                     multiline_annotations.push((lo.file, ml));
@@ -2010,7 +2136,10 @@ impl FileWithAnnotatedLines {
                         start_col: lo.col_display,
                         end_col: hi.col_display,
                         is_primary: span_label.is_primary,
-                        label: span_label.label,
+                        label: span_label
+                            .label
+                            .as_ref()
+                            .map(|m| emitter.translate_message(m, args).to_string()),
                         annotation_type: AnnotationType::Singleline,
                     };
                     add_annotation_to_file(&mut output, lo.file, lo.line, ann);
diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
index dc28d1bb452..f78490da245 100644
--- a/compiler/rustc_errors/src/json.rs
+++ b/compiler/rustc_errors/src/json.rs
@@ -15,12 +15,13 @@ use crate::emitter::{Emitter, HumanReadableErrorType};
 use crate::registry::Registry;
 use crate::DiagnosticId;
 use crate::ToolMetadata;
-use crate::{CodeSuggestion, SubDiagnostic};
+use crate::{CodeSuggestion, FluentBundle, MultiSpan, SpanLabel, SubDiagnostic};
 use rustc_lint_defs::Applicability;
 
 use rustc_data_structures::sync::Lrc;
+use rustc_error_messages::FluentArgs;
 use rustc_span::hygiene::ExpnData;
-use rustc_span::{MultiSpan, Span, SpanLabel};
+use rustc_span::Span;
 use std::io::{self, Write};
 use std::path::Path;
 use std::sync::{Arc, Mutex};
@@ -36,6 +37,8 @@ pub struct JsonEmitter {
     dst: Box<dyn Write + Send>,
     registry: Option<Registry>,
     sm: Lrc<SourceMap>,
+    fluent_bundle: Option<Lrc<FluentBundle>>,
+    fallback_bundle: Lrc<FluentBundle>,
     pretty: bool,
     ui_testing: bool,
     json_rendered: HumanReadableErrorType,
@@ -47,6 +50,8 @@ impl JsonEmitter {
     pub fn stderr(
         registry: Option<Registry>,
         source_map: Lrc<SourceMap>,
+        fluent_bundle: Option<Lrc<FluentBundle>>,
+        fallback_bundle: Lrc<FluentBundle>,
         pretty: bool,
         json_rendered: HumanReadableErrorType,
         terminal_width: Option<usize>,
@@ -56,6 +61,8 @@ impl JsonEmitter {
             dst: Box::new(io::BufWriter::new(io::stderr())),
             registry,
             sm: source_map,
+            fluent_bundle,
+            fallback_bundle,
             pretty,
             ui_testing: false,
             json_rendered,
@@ -67,6 +74,8 @@ impl JsonEmitter {
     pub fn basic(
         pretty: bool,
         json_rendered: HumanReadableErrorType,
+        fluent_bundle: Option<Lrc<FluentBundle>>,
+        fallback_bundle: Lrc<FluentBundle>,
         terminal_width: Option<usize>,
         macro_backtrace: bool,
     ) -> JsonEmitter {
@@ -74,6 +83,8 @@ impl JsonEmitter {
         JsonEmitter::stderr(
             None,
             Lrc::new(SourceMap::new(file_path_mapping)),
+            fluent_bundle,
+            fallback_bundle,
             pretty,
             json_rendered,
             terminal_width,
@@ -85,6 +96,8 @@ impl JsonEmitter {
         dst: Box<dyn Write + Send>,
         registry: Option<Registry>,
         source_map: Lrc<SourceMap>,
+        fluent_bundle: Option<Lrc<FluentBundle>>,
+        fallback_bundle: Lrc<FluentBundle>,
         pretty: bool,
         json_rendered: HumanReadableErrorType,
         terminal_width: Option<usize>,
@@ -94,6 +107,8 @@ impl JsonEmitter {
             dst,
             registry,
             sm: source_map,
+            fluent_bundle,
+            fallback_bundle,
             pretty,
             ui_testing: false,
             json_rendered,
@@ -173,6 +188,14 @@ impl Emitter for JsonEmitter {
         Some(&self.sm)
     }
 
+    fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
+        self.fluent_bundle.as_ref()
+    }
+
+    fn fallback_fluent_bundle(&self) -> &Lrc<FluentBundle> {
+        &self.fallback_bundle
+    }
+
     fn should_show_explain(&self) -> bool {
         !matches!(self.json_rendered, HumanReadableErrorType::Short(_))
     }
@@ -345,14 +368,18 @@ struct UnusedExterns<'a, 'b, 'c> {
 
 impl Diagnostic {
     fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
-        let sugg = diag.suggestions.iter().flatten().map(|sugg| Diagnostic {
-            message: sugg.msg.clone(),
-            code: None,
-            level: "help",
-            spans: DiagnosticSpan::from_suggestion(sugg, je),
-            children: vec![],
-            rendered: None,
-            tool_metadata: sugg.tool_metadata.clone(),
+        let args = je.to_fluent_args(diag.args());
+        let sugg = diag.suggestions.iter().flatten().map(|sugg| {
+            let translated_message = je.translate_message(&sugg.msg, &args);
+            Diagnostic {
+                message: translated_message.to_string(),
+                code: None,
+                level: "help",
+                spans: DiagnosticSpan::from_suggestion(sugg, &args, je),
+                children: vec![],
+                rendered: None,
+                tool_metadata: sugg.tool_metadata.clone(),
+            }
         });
 
         // generate regular command line output and store it in the json
@@ -375,6 +402,8 @@ impl Diagnostic {
             .new_emitter(
                 Box::new(buf),
                 Some(je.sm.clone()),
+                je.fluent_bundle.clone(),
+                je.fallback_bundle.clone(),
                 false,
                 je.terminal_width,
                 je.macro_backtrace,
@@ -384,15 +413,16 @@ impl Diagnostic {
         let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
         let output = String::from_utf8(output).unwrap();
 
+        let translated_message = je.translate_messages(&diag.message, &args);
         Diagnostic {
-            message: diag.message(),
+            message: translated_message.to_string(),
             code: DiagnosticCode::map_opt_string(diag.code.clone(), je),
             level: diag.level.to_str(),
-            spans: DiagnosticSpan::from_multispan(&diag.span, je),
+            spans: DiagnosticSpan::from_multispan(&diag.span, &args, je),
             children: diag
                 .children
                 .iter()
-                .map(|c| Diagnostic::from_sub_diagnostic(c, je))
+                .map(|c| Diagnostic::from_sub_diagnostic(c, &args, je))
                 .chain(sugg)
                 .collect(),
             rendered: Some(output),
@@ -400,16 +430,21 @@ impl Diagnostic {
         }
     }
 
-    fn from_sub_diagnostic(diag: &SubDiagnostic, je: &JsonEmitter) -> Diagnostic {
+    fn from_sub_diagnostic(
+        diag: &SubDiagnostic,
+        args: &FluentArgs<'_>,
+        je: &JsonEmitter,
+    ) -> Diagnostic {
+        let translated_message = je.translate_messages(&diag.message, args);
         Diagnostic {
-            message: diag.message(),
+            message: translated_message.to_string(),
             code: None,
             level: diag.level.to_str(),
             spans: diag
                 .render_span
                 .as_ref()
-                .map(|sp| DiagnosticSpan::from_multispan(sp, je))
-                .unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, je)),
+                .map(|sp| DiagnosticSpan::from_multispan(sp, args, je))
+                .unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, args, je)),
             children: vec![],
             rendered: None,
             tool_metadata: ToolMetadata::default(),
@@ -421,9 +456,16 @@ impl DiagnosticSpan {
     fn from_span_label(
         span: SpanLabel,
         suggestion: Option<(&String, Applicability)>,
+        args: &FluentArgs<'_>,
         je: &JsonEmitter,
     ) -> DiagnosticSpan {
-        Self::from_span_etc(span.span, span.is_primary, span.label, suggestion, je)
+        Self::from_span_etc(
+            span.span,
+            span.is_primary,
+            span.label.as_ref().map(|m| je.translate_message(m, args)).map(|m| m.to_string()),
+            suggestion,
+            je,
+        )
     }
 
     fn from_span_etc(
@@ -486,14 +528,22 @@ impl DiagnosticSpan {
         }
     }
 
-    fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
+    fn from_multispan(
+        msp: &MultiSpan,
+        args: &FluentArgs<'_>,
+        je: &JsonEmitter,
+    ) -> Vec<DiagnosticSpan> {
         msp.span_labels()
             .into_iter()
-            .map(|span_str| Self::from_span_label(span_str, None, je))
+            .map(|span_str| Self::from_span_label(span_str, None, args, je))
             .collect()
     }
 
-    fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
+    fn from_suggestion(
+        suggestion: &CodeSuggestion,
+        args: &FluentArgs<'_>,
+        je: &JsonEmitter,
+    ) -> Vec<DiagnosticSpan> {
         suggestion
             .substitutions
             .iter()
@@ -504,6 +554,7 @@ impl DiagnosticSpan {
                     DiagnosticSpan::from_span_label(
                         span_label,
                         Some((&suggestion_inner.snippet, suggestion.applicability)),
+                        args,
                         je,
                     )
                 })
diff --git a/compiler/rustc_errors/src/json/tests.rs b/compiler/rustc_errors/src/json/tests.rs
index ed01afe6e30..0f175c732c1 100644
--- a/compiler/rustc_errors/src/json/tests.rs
+++ b/compiler/rustc_errors/src/json/tests.rs
@@ -39,12 +39,16 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
     rustc_span::create_default_session_globals_then(|| {
         let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
         sm.new_source_file(Path::new("test.rs").to_owned().into(), code.to_owned());
+        let fallback_bundle =
+            crate::fallback_fluent_bundle(false).expect("failed to load fallback fluent bundle");
 
         let output = Arc::new(Mutex::new(Vec::new()));
         let je = JsonEmitter::new(
             Box::new(Shared { data: output.clone() }),
             None,
             sm,
+            None,
+            fallback_bundle,
             true,
             HumanReadableErrorType::Short(ColorConfig::Never),
             None,
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index ec00910ec8b..c927fcb2109 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -31,11 +31,15 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
 use rustc_data_structures::stable_hasher::StableHasher;
 use rustc_data_structures::sync::{self, Lock, Lrc};
 use rustc_data_structures::AtomicRef;
+pub use rustc_error_messages::{
+    fallback_fluent_bundle, fluent_bundle, DiagnosticMessage, FluentBundle, LanguageIdentifier,
+    MultiSpan, SpanLabel,
+};
 pub use rustc_lint_defs::{pluralize, Applicability};
 use rustc_serialize::json::Json;
 use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
 use rustc_span::source_map::SourceMap;
-use rustc_span::{Loc, MultiSpan, Span};
+use rustc_span::{Loc, Span};
 
 use std::borrow::Cow;
 use std::hash::{Hash, Hasher};
@@ -55,6 +59,7 @@ mod lock;
 pub mod registry;
 mod snippet;
 mod styled_buffer;
+
 pub use snippet::Style;
 
 pub type PResult<'a, T> = Result<T, DiagnosticBuilder<'a, ErrorGuaranteed>>;
@@ -145,7 +150,7 @@ pub struct CodeSuggestion {
     /// ]
     /// ```
     pub substitutions: Vec<Substitution>,
-    pub msg: String,
+    pub msg: DiagnosticMessage,
     /// Visual representation of this suggestion.
     pub style: SuggestionStyle,
     /// Whether or not the suggestion is approximate
@@ -400,7 +405,10 @@ impl fmt::Display for ExplicitBug {
 
 impl error::Error for ExplicitBug {}
 
-pub use diagnostic::{Diagnostic, DiagnosticId, DiagnosticStyledString, SubDiagnostic};
+pub use diagnostic::{
+    Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId, DiagnosticStyledString,
+    IntoDiagnosticArg, SubDiagnostic,
+};
 pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee};
 use std::backtrace::Backtrace;
 
@@ -538,10 +546,14 @@ impl Handler {
         can_emit_warnings: bool,
         treat_err_as_bug: Option<NonZeroUsize>,
         sm: Option<Lrc<SourceMap>>,
+        fluent_bundle: Option<Lrc<FluentBundle>>,
+        fallback_bundle: Lrc<FluentBundle>,
     ) -> Self {
         Self::with_tty_emitter_and_flags(
             color_config,
             sm,
+            fluent_bundle,
+            fallback_bundle,
             HandlerFlags { can_emit_warnings, treat_err_as_bug, ..Default::default() },
         )
     }
@@ -549,11 +561,15 @@ impl Handler {
     pub fn with_tty_emitter_and_flags(
         color_config: ColorConfig,
         sm: Option<Lrc<SourceMap>>,
+        fluent_bundle: Option<Lrc<FluentBundle>>,
+        fallback_bundle: Lrc<FluentBundle>,
         flags: HandlerFlags,
     ) -> Self {
         let emitter = Box::new(EmitterWriter::stderr(
             color_config,
             sm,
+            fluent_bundle,
+            fallback_bundle,
             false,
             false,
             None,
@@ -658,7 +674,7 @@ impl Handler {
     pub fn struct_span_warn(
         &self,
         span: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, ()> {
         let mut result = self.struct_warn(msg);
         result.set_span(span);
@@ -669,7 +685,7 @@ impl Handler {
     pub fn struct_span_allow(
         &self,
         span: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, ()> {
         let mut result = self.struct_allow(msg);
         result.set_span(span);
@@ -681,7 +697,7 @@ impl Handler {
     pub fn struct_span_warn_with_code(
         &self,
         span: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> DiagnosticBuilder<'_, ()> {
         let mut result = self.struct_span_warn(span, msg);
@@ -694,17 +710,21 @@ impl Handler {
     /// Attempting to `.emit()` the builder will only emit if either:
     /// * `can_emit_warnings` is `true`
     /// * `is_force_warn` was set in `DiagnosticId::Lint`
-    pub fn struct_warn(&self, msg: &str) -> DiagnosticBuilder<'_, ()> {
+    pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Warning, msg)
     }
 
     /// Construct a builder at the `Allow` level with the `msg`.
-    pub fn struct_allow(&self, msg: &str) -> DiagnosticBuilder<'_, ()> {
+    pub fn struct_allow(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Allow, msg)
     }
 
     /// Construct a builder at the `Expect` level with the `msg`.
-    pub fn struct_expect(&self, msg: &str, id: LintExpectationId) -> DiagnosticBuilder<'_, ()> {
+    pub fn struct_expect(
+        &self,
+        msg: impl Into<DiagnosticMessage>,
+        id: LintExpectationId,
+    ) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Expect(id), msg)
     }
 
@@ -712,7 +732,7 @@ impl Handler {
     pub fn struct_span_err(
         &self,
         span: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         let mut result = self.struct_err(msg);
         result.set_span(span);
@@ -723,7 +743,7 @@ impl Handler {
     pub fn struct_span_err_with_code(
         &self,
         span: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         let mut result = self.struct_span_err(span, msg);
@@ -733,20 +753,23 @@ impl Handler {
 
     /// Construct a builder at the `Error` level with the `msg`.
     // FIXME: This method should be removed (every error should have an associated error code).
-    pub fn struct_err(&self, msg: &str) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
-        DiagnosticBuilder::new_guaranteeing_error::<{ Level::Error { lint: false } }>(self, msg)
+    pub fn struct_err(
+        &self,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
+        DiagnosticBuilder::new_guaranteeing_error::<_, { Level::Error { lint: false } }>(self, msg)
     }
 
     /// This should only be used by `rustc_middle::lint::struct_lint_level`. Do not use it for hard errors.
     #[doc(hidden)]
-    pub fn struct_err_lint(&self, msg: &str) -> DiagnosticBuilder<'_, ()> {
+    pub fn struct_err_lint(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Error { lint: true }, msg)
     }
 
     /// Construct a builder at the `Error` level with the `msg` and the `code`.
     pub fn struct_err_with_code(
         &self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         let mut result = self.struct_err(msg);
@@ -754,11 +777,22 @@ impl Handler {
         result
     }
 
+    /// Construct a builder at the `Warn` level with the `msg` and the `code`.
+    pub fn struct_warn_with_code(
+        &self,
+        msg: impl Into<DiagnosticMessage>,
+        code: DiagnosticId,
+    ) -> DiagnosticBuilder<'_, ()> {
+        let mut result = self.struct_warn(msg);
+        result.code(code);
+        result
+    }
+
     /// Construct a builder at the `Fatal` level at the given `span` and with the `msg`.
     pub fn struct_span_fatal(
         &self,
         span: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, !> {
         let mut result = self.struct_fatal(msg);
         result.set_span(span);
@@ -769,7 +803,7 @@ impl Handler {
     pub fn struct_span_fatal_with_code(
         &self,
         span: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> DiagnosticBuilder<'_, !> {
         let mut result = self.struct_span_fatal(span, msg);
@@ -778,21 +812,24 @@ impl Handler {
     }
 
     /// Construct a builder at the `Error` level with the `msg`.
-    pub fn struct_fatal(&self, msg: &str) -> DiagnosticBuilder<'_, !> {
+    pub fn struct_fatal(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, !> {
         DiagnosticBuilder::new_fatal(self, msg)
     }
 
     /// Construct a builder at the `Help` level with the `msg`.
-    pub fn struct_help(&self, msg: &str) -> DiagnosticBuilder<'_, ()> {
+    pub fn struct_help(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Help, msg)
     }
 
     /// Construct a builder at the `Note` level with the `msg`.
-    pub fn struct_note_without_error(&self, msg: &str) -> DiagnosticBuilder<'_, ()> {
+    pub fn struct_note_without_error(
+        &self,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, ()> {
         DiagnosticBuilder::new(self, Level::Note, msg)
     }
 
-    pub fn span_fatal(&self, span: impl Into<MultiSpan>, msg: &str) -> ! {
+    pub fn span_fatal(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
         self.emit_diag_at_span(Diagnostic::new(Fatal, msg), span);
         FatalError.raise()
     }
@@ -800,80 +837,106 @@ impl Handler {
     pub fn span_fatal_with_code(
         &self,
         span: impl Into<MultiSpan>,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> ! {
         self.emit_diag_at_span(Diagnostic::new_with_code(Fatal, Some(code), msg), span);
         FatalError.raise()
     }
 
-    pub fn span_err(&self, span: impl Into<MultiSpan>, msg: &str) -> ErrorGuaranteed {
+    pub fn span_err(
+        &self,
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> ErrorGuaranteed {
         self.emit_diag_at_span(Diagnostic::new(Error { lint: false }, msg), span).unwrap()
     }
 
-    pub fn span_err_with_code(&self, span: impl Into<MultiSpan>, msg: &str, code: DiagnosticId) {
+    pub fn span_err_with_code(
+        &self,
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+        code: DiagnosticId,
+    ) {
         self.emit_diag_at_span(
             Diagnostic::new_with_code(Error { lint: false }, Some(code), msg),
             span,
         );
     }
 
-    pub fn span_warn(&self, span: impl Into<MultiSpan>, msg: &str) {
+    pub fn span_warn(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) {
         self.emit_diag_at_span(Diagnostic::new(Warning, msg), span);
     }
 
-    pub fn span_warn_with_code(&self, span: impl Into<MultiSpan>, msg: &str, code: DiagnosticId) {
+    pub fn span_warn_with_code(
+        &self,
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+        code: DiagnosticId,
+    ) {
         self.emit_diag_at_span(Diagnostic::new_with_code(Warning, Some(code), msg), span);
     }
 
-    pub fn span_bug(&self, span: impl Into<MultiSpan>, msg: &str) -> ! {
+    pub fn span_bug(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
         self.inner.borrow_mut().span_bug(span, msg)
     }
 
     #[track_caller]
-    pub fn delay_span_bug(&self, span: impl Into<MultiSpan>, msg: &str) -> ErrorGuaranteed {
+    pub fn delay_span_bug(
+        &self,
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> ErrorGuaranteed {
         self.inner.borrow_mut().delay_span_bug(span, msg)
     }
 
     // FIXME(eddyb) note the comment inside `impl Drop for HandlerInner`, that's
     // where the explanation of what "good path" is (also, it should be renamed).
-    pub fn delay_good_path_bug(&self, msg: &str) {
+    pub fn delay_good_path_bug(&self, msg: impl Into<DiagnosticMessage>) {
         self.inner.borrow_mut().delay_good_path_bug(msg)
     }
 
-    pub fn span_bug_no_panic(&self, span: impl Into<MultiSpan>, msg: &str) {
+    pub fn span_bug_no_panic(&self, span: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) {
         self.emit_diag_at_span(Diagnostic::new(Bug, msg), span);
     }
 
-    pub fn span_note_without_error(&self, span: impl Into<MultiSpan>, msg: &str) {
+    pub fn span_note_without_error(
+        &self,
+        span: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) {
         self.emit_diag_at_span(Diagnostic::new(Note, msg), span);
     }
 
-    pub fn span_note_diag(&self, span: Span, msg: &str) -> DiagnosticBuilder<'_, ()> {
+    pub fn span_note_diag(
+        &self,
+        span: Span,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, ()> {
         let mut db = DiagnosticBuilder::new(self, Note, msg);
         db.set_span(span);
         db
     }
 
     // NOTE: intentionally doesn't raise an error so rustc_codegen_ssa only reports fatal errors in the main thread
-    pub fn fatal(&self, msg: &str) -> FatalError {
+    pub fn fatal(&self, msg: impl Into<DiagnosticMessage>) -> FatalError {
         self.inner.borrow_mut().fatal(msg)
     }
 
-    pub fn err(&self, msg: &str) -> ErrorGuaranteed {
+    pub fn err(&self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
         self.inner.borrow_mut().err(msg)
     }
 
-    pub fn warn(&self, msg: &str) {
+    pub fn warn(&self, msg: impl Into<DiagnosticMessage>) {
         let mut db = DiagnosticBuilder::new(self, Warning, msg);
         db.emit();
     }
 
-    pub fn note_without_error(&self, msg: &str) {
+    pub fn note_without_error(&self, msg: impl Into<DiagnosticMessage>) {
         DiagnosticBuilder::new(self, Note, msg).emit();
     }
 
-    pub fn bug(&self, msg: &str) -> ! {
+    pub fn bug(&self, msg: impl Into<DiagnosticMessage>) -> ! {
         self.inner.borrow_mut().bug(msg)
     }
 
@@ -1143,7 +1206,10 @@ impl HandlerInner {
 
         match (errors.len(), warnings.len()) {
             (0, 0) => return,
-            (0, _) => self.emitter.emit_diagnostic(&Diagnostic::new(Level::Warning, &warnings)),
+            (0, _) => self.emitter.emit_diagnostic(&Diagnostic::new(
+                Level::Warning,
+                DiagnosticMessage::Str(warnings.to_owned()),
+            )),
             (_, 0) => {
                 let _ = self.fatal(&errors);
             }
@@ -1218,7 +1284,7 @@ impl HandlerInner {
         }
     }
 
-    fn span_bug(&mut self, sp: impl Into<MultiSpan>, msg: &str) -> ! {
+    fn span_bug(&mut self, sp: impl Into<MultiSpan>, msg: impl Into<DiagnosticMessage>) -> ! {
         self.emit_diag_at_span(Diagnostic::new(Bug, msg), sp);
         panic::panic_any(ExplicitBug);
     }
@@ -1228,7 +1294,11 @@ impl HandlerInner {
     }
 
     #[track_caller]
-    fn delay_span_bug(&mut self, sp: impl Into<MultiSpan>, msg: &str) -> ErrorGuaranteed {
+    fn delay_span_bug(
+        &mut self,
+        sp: impl Into<MultiSpan>,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> ErrorGuaranteed {
         // This is technically `self.treat_err_as_bug()` but `delay_span_bug` is called before
         // incrementing `err_count` by one, so we need to +1 the comparing.
         // FIXME: Would be nice to increment err_count in a more coherent way.
@@ -1244,7 +1314,7 @@ impl HandlerInner {
 
     // FIXME(eddyb) note the comment inside `impl Drop for HandlerInner`, that's
     // where the explanation of what "good path" is (also, it should be renamed).
-    fn delay_good_path_bug(&mut self, msg: &str) {
+    fn delay_good_path_bug(&mut self, msg: impl Into<DiagnosticMessage>) {
         let mut diagnostic = Diagnostic::new(Level::DelayedBug, msg);
         if self.flags.report_delayed_bugs {
             self.emit_diagnostic(&mut diagnostic);
@@ -1253,33 +1323,37 @@ impl HandlerInner {
         self.delayed_good_path_bugs.push(DelayedDiagnostic::with_backtrace(diagnostic, backtrace));
     }
 
-    fn failure(&mut self, msg: &str) {
+    fn failure(&mut self, msg: impl Into<DiagnosticMessage>) {
         self.emit_diagnostic(&mut Diagnostic::new(FailureNote, msg));
     }
 
-    fn fatal(&mut self, msg: &str) -> FatalError {
+    fn fatal(&mut self, msg: impl Into<DiagnosticMessage>) -> FatalError {
         self.emit(Fatal, msg);
         FatalError
     }
 
-    fn err(&mut self, msg: &str) -> ErrorGuaranteed {
+    fn err(&mut self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
         self.emit(Error { lint: false }, msg)
     }
 
     /// Emit an error; level should be `Error` or `Fatal`.
-    fn emit(&mut self, level: Level, msg: &str) -> ErrorGuaranteed {
+    fn emit(&mut self, level: Level, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
         if self.treat_err_as_bug() {
             self.bug(msg);
         }
         self.emit_diagnostic(&mut Diagnostic::new(level, msg)).unwrap()
     }
 
-    fn bug(&mut self, msg: &str) -> ! {
+    fn bug(&mut self, msg: impl Into<DiagnosticMessage>) -> ! {
         self.emit_diagnostic(&mut Diagnostic::new(Bug, msg));
         panic::panic_any(ExplicitBug);
     }
 
-    fn flush_delayed(&mut self, bugs: impl IntoIterator<Item = Diagnostic>, explanation: &str) {
+    fn flush_delayed(
+        &mut self,
+        bugs: impl IntoIterator<Item = Diagnostic>,
+        explanation: impl Into<DiagnosticMessage> + Copy,
+    ) {
         let mut no_bugs = true;
         for mut bug in bugs {
             if no_bugs {
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 7f569af4abd..556b2c6fbf3 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -10,7 +10,7 @@ use rustc_ast::{self as ast, AstLike, Attribute, Item, NodeId, PatKind};
 use rustc_attr::{self as attr, Deprecation, Stability};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::sync::{self, Lrc};
-use rustc_errors::{Applicability, DiagnosticBuilder, ErrorGuaranteed};
+use rustc_errors::{Applicability, DiagnosticBuilder, ErrorGuaranteed, MultiSpan};
 use rustc_lint_defs::builtin::PROC_MACRO_BACK_COMPAT;
 use rustc_lint_defs::BuiltinLintDiagnostics;
 use rustc_parse::{self, nt_to_tokenstream, parser, MACRO_ARGUMENTS};
@@ -20,7 +20,7 @@ use rustc_span::edition::Edition;
 use rustc_span::hygiene::{AstPass, ExpnData, ExpnKind, LocalExpnId};
 use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
-use rustc_span::{MultiSpan, Span, DUMMY_SP};
+use rustc_span::{Span, DUMMY_SP};
 use smallvec::{smallvec, SmallVec};
 
 use std::default::Default;
diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs
index b55a40c5b2c..0e20e0970f4 100644
--- a/compiler/rustc_expand/src/mbe/macro_check.rs
+++ b/compiler/rustc_expand/src/mbe/macro_check.rs
@@ -109,10 +109,11 @@ use crate::mbe::{KleeneToken, TokenTree};
 use rustc_ast::token::{DelimToken, Token, TokenKind};
 use rustc_ast::{NodeId, DUMMY_NODE_ID};
 use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::MultiSpan;
 use rustc_session::lint::builtin::META_VARIABLE_MISUSE;
 use rustc_session::parse::ParseSess;
 use rustc_span::symbol::kw;
-use rustc_span::{symbol::MacroRulesNormalizedIdent, MultiSpan, Span};
+use rustc_span::{symbol::MacroRulesNormalizedIdent, Span};
 
 use smallvec::SmallVec;
 
@@ -249,7 +250,7 @@ fn check_binders(
             if let Some(prev_info) = binders.get(&name) {
                 // 1. The meta-variable is already bound in the current LHS: This is an error.
                 let mut span = MultiSpan::from_span(span);
-                span.push_span_label(prev_info.span, "previous declaration".into());
+                span.push_span_label(prev_info.span, "previous declaration");
                 buffer_lint(sess, span, node_id, "duplicate matcher binding");
             } else if get_binder_info(macros, binders, name).is_none() {
                 // 2. The meta-variable is free: This is a binder.
@@ -621,7 +622,7 @@ fn ops_is_prefix(
     for (i, binder) in binder_ops.iter().enumerate() {
         if i >= occurrence_ops.len() {
             let mut span = MultiSpan::from_span(span);
-            span.push_span_label(binder.span, "expected repetition".into());
+            span.push_span_label(binder.span, "expected repetition");
             let message = &format!("variable '{}' is still repeating at this depth", name);
             buffer_lint(sess, span, node_id, message);
             return;
@@ -629,8 +630,8 @@ fn ops_is_prefix(
         let occurrence = &occurrence_ops[i];
         if occurrence.op != binder.op {
             let mut span = MultiSpan::from_span(span);
-            span.push_span_label(binder.span, "expected repetition".into());
-            span.push_span_label(occurrence.span, "conflicting repetition".into());
+            span.push_span_label(binder.span, "expected repetition");
+            span.push_span_label(occurrence.span, "conflicting repetition");
             let message = "meta-variable repeats with different Kleene operator";
             buffer_lint(sess, span, node_id, message);
             return;
diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs
index 10b2b9f07e2..7fef87ed977 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -68,17 +68,18 @@ fn emit_frag_parse_err(
     arm_span: Span,
     kind: AstFragmentKind,
 ) {
-    if parser.token == token::Eof && e.message().ends_with(", found `<eof>`") {
+    // FIXME(davidtwco): avoid depending on the error message text
+    if parser.token == token::Eof && e.message[0].0.expect_str().ends_with(", found `<eof>`") {
         if !e.span.is_dummy() {
             // early end of macro arm (#52866)
             e.replace_span_with(parser.sess.source_map().next_point(parser.token.span));
         }
         let msg = &e.message[0];
         e.message[0] = (
-            format!(
+            rustc_errors::DiagnosticMessage::Str(format!(
                 "macro expansion ends with an incomplete expression: {}",
-                msg.0.replace(", found `<eof>`", ""),
-            ),
+                msg.0.expect_str().replace(", found `<eof>`", ""),
+            )),
             msg.1,
         );
     }
diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs
index 2949ca716b2..52a656e1d1c 100644
--- a/compiler/rustc_expand/src/mbe/metavar_expr.rs
+++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs
@@ -128,13 +128,15 @@ fn parse_ident<'sess>(
     sess: &'sess ParseSess,
     span: Span,
 ) -> PResult<'sess, Ident> {
-    let err_fn = |msg| sess.span_diagnostic.struct_span_err(span, msg);
     if let Some(tt) = iter.next() && let TokenTree::Token(token) = tt {
         if let Some((elem, false)) = token.ident() {
             return Ok(elem);
         }
         let token_str = pprust::token_to_string(&token);
-        let mut err = err_fn(&format!("expected identifier, found `{}`", &token_str));
+        let mut err = sess.span_diagnostic.struct_span_err(
+            span,
+            &format!("expected identifier, found `{}`", &token_str)
+        );
         err.span_suggestion(
             token.span,
             &format!("try removing `{}`", &token_str),
@@ -143,7 +145,7 @@ fn parse_ident<'sess>(
         );
         return Err(err);
     }
-    Err(err_fn("expected identifier"))
+    Err(sess.span_diagnostic.struct_span_err(span, "expected identifier"))
 }
 
 /// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs
index 20351070f71..bd6f0b77ebf 100644
--- a/compiler/rustc_expand/src/proc_macro_server.rs
+++ b/compiler/rustc_expand/src/proc_macro_server.rs
@@ -7,13 +7,13 @@ use rustc_ast::tokenstream::{DelimSpan, Spacing::*, TokenStream, TreeAndSpacing}
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync::Lrc;
-use rustc_errors::{Diagnostic, PResult};
+use rustc_errors::{Diagnostic, MultiSpan, PResult};
 use rustc_parse::lexer::nfc_normalize;
 use rustc_parse::{nt_to_tokenstream, parse_stream_from_source_str};
 use rustc_session::parse::ParseSess;
 use rustc_span::def_id::CrateNum;
 use rustc_span::symbol::{self, kw, sym, Symbol};
-use rustc_span::{BytePos, FileName, MultiSpan, Pos, SourceFile, Span};
+use rustc_span::{BytePos, FileName, Pos, SourceFile, Span};
 
 use pm::bridge::{server, TokenTree};
 use pm::{Delimiter, Level, LineColumn, Spacing};
diff --git a/compiler/rustc_expand/src/tests.rs b/compiler/rustc_expand/src/tests.rs
index ed3aa1eaca8..88f3cf9d722 100644
--- a/compiler/rustc_expand/src/tests.rs
+++ b/compiler/rustc_expand/src/tests.rs
@@ -4,11 +4,11 @@ use rustc_parse::{new_parser_from_source_str, parser::Parser, source_file_to_str
 use rustc_session::parse::ParseSess;
 use rustc_span::create_default_session_if_not_set_then;
 use rustc_span::source_map::{FilePathMapping, SourceMap};
-use rustc_span::{BytePos, MultiSpan, Span};
+use rustc_span::{BytePos, Span};
 
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::emitter::EmitterWriter;
-use rustc_errors::{Handler, PResult};
+use rustc_errors::{Handler, MultiSpan, PResult};
 
 use std::io;
 use std::io::prelude::*;
@@ -127,6 +127,8 @@ fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &
     create_default_session_if_not_set_then(|_| {
         let output = Arc::new(Mutex::new(Vec::new()));
 
+        let fallback_bundle = rustc_errors::fallback_fluent_bundle(false)
+            .expect("failed to load fallback fluent bundle");
         let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
         source_map.new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned());
 
@@ -142,6 +144,8 @@ fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &
         let emitter = EmitterWriter::new(
             Box::new(Shared { data: output.clone() }),
             Some(source_map.clone()),
+            None,
+            fallback_bundle,
             false,
             false,
             false,
diff --git a/compiler/rustc_hir/Cargo.toml b/compiler/rustc_hir/Cargo.toml
index 41c63440ba3..34d366f4013 100644
--- a/compiler/rustc_hir/Cargo.toml
+++ b/compiler/rustc_hir/Cargo.toml
@@ -11,6 +11,7 @@ rustc_target = { path = "../rustc_target" }
 rustc_feature = { path = "../rustc_feature" }
 rustc_macros = { path = "../rustc_macros" }
 rustc_data_structures = { path = "../rustc_data_structures" }
+rustc_error_messages = { path = "../rustc_error_messages" }
 rustc_index = { path = "../rustc_index" }
 rustc_span = { path = "../rustc_span" }
 rustc_serialize = { path = "../rustc_serialize" }
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index 10871df3ab9..15118073b33 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -13,12 +13,13 @@ use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
 use rustc_data_structures::fingerprint::Fingerprint;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sorted_map::SortedMap;
+use rustc_error_messages::MultiSpan;
 use rustc_index::vec::IndexVec;
 use rustc_macros::HashStable_Generic;
 use rustc_span::hygiene::MacroKind;
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
-use rustc_span::{def_id::LocalDefId, BytePos, MultiSpan, Span, DUMMY_SP};
+use rustc_span::{def_id::LocalDefId, BytePos, Span, DUMMY_SP};
 use rustc_target::asm::InlineAsmRegOrRegClass;
 use rustc_target::spec::abi::Abi;
 
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index d915f9a5ae8..f25215fe813 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -59,7 +59,7 @@ use crate::traits::{
 
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed};
-use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString};
+use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_hir::lang_items::LangItem;
@@ -72,7 +72,7 @@ use rustc_middle::ty::{
     subst::{GenericArgKind, Subst, SubstsRef},
     Binder, List, Region, Ty, TyCtxt, TypeFoldable,
 };
-use rustc_span::{sym, BytePos, DesugaringKind, MultiSpan, Pos, Span};
+use rustc_span::{sym, BytePos, DesugaringKind, Pos, Span};
 use rustc_target::spec::abi;
 use std::ops::ControlFlow;
 use std::{cmp, fmt, iter};
@@ -2075,7 +2075,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
                         {
                             diag.span_suggestion(
                                 span,
-                                msg,
+                                *msg,
                                 format!("{}.as_ref()", snippet),
                                 Applicability::MachineApplicable,
                             );
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs
index 61e3334862b..80500f3fe65 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs
@@ -7,11 +7,10 @@ use crate::infer::lexical_region_resolve::RegionResolutionError;
 use crate::infer::{SubregionOrigin, TypeTrace};
 use crate::traits::ObligationCauseCode;
 use rustc_data_structures::stable_set::FxHashSet;
-use rustc_errors::{Applicability, ErrorGuaranteed};
+use rustc_errors::{Applicability, ErrorGuaranteed, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::intravisit::Visitor;
 use rustc_middle::ty::TypeVisitor;
-use rustc_span::MultiSpan;
 
 impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
     pub(super) fn try_report_mismatched_static_lifetime(&self) -> Option<ErrorGuaranteed> {
@@ -42,8 +41,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
         let mut err = self.tcx().sess.struct_span_err(cause.span, "incompatible lifetime on type");
         // FIXME: we should point at the lifetime
         let mut multi_span: MultiSpan = vec![binding_span].into();
-        multi_span
-            .push_span_label(binding_span, "introduces a `'static` lifetime requirement".into());
+        multi_span.push_span_label(binding_span, "introduces a `'static` lifetime requirement");
         err.span_note(multi_span, "because this has an unmet lifetime requirement");
         note_and_explain_region(self.tcx(), &mut err, "", sup, "...", Some(binding_span));
         if let Some(impl_node) = self.tcx().hir().get_if_local(*impl_def_id) {
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
index c33c0c996bd..a4c46d5cf0b 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
@@ -5,7 +5,7 @@ use crate::infer::lexical_region_resolve::RegionResolutionError;
 use crate::infer::{SubregionOrigin, TypeTrace};
 use crate::traits::{ObligationCauseCode, UnifyReceiverContext};
 use rustc_data_structures::stable_set::FxHashSet;
-use rustc_errors::{struct_span_err, Applicability, Diagnostic, ErrorGuaranteed};
+use rustc_errors::{struct_span_err, Applicability, Diagnostic, ErrorGuaranteed, MultiSpan};
 use rustc_hir::def_id::DefId;
 use rustc_hir::intravisit::{walk_ty, Visitor};
 use rustc_hir::{self as hir, GenericBound, Item, ItemKind, Lifetime, LifetimeName, Node, TyKind};
@@ -13,7 +13,7 @@ use rustc_middle::ty::{
     self, AssocItemContainer, StaticLifetimeVisitor, Ty, TyCtxt, TypeFoldable, TypeVisitor,
 };
 use rustc_span::symbol::Ident;
-use rustc_span::{MultiSpan, Span};
+use rustc_span::Span;
 
 use std::ops::ControlFlow;
 
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs
index 6d23dc4f471..1788eb8628a 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs
@@ -4,7 +4,7 @@ use crate::infer::error_reporting::nice_region_error::NiceRegionError;
 use crate::infer::lexical_region_resolve::RegionResolutionError;
 use crate::infer::{SubregionOrigin, Subtype};
 use crate::traits::ObligationCauseCode::CompareImplMethodObligation;
-use rustc_errors::ErrorGuaranteed;
+use rustc_errors::{ErrorGuaranteed, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::Res;
 use rustc_hir::def_id::{DefId, LocalDefId};
@@ -12,8 +12,7 @@ use rustc_hir::intravisit::Visitor;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::print::RegionHighlightMode;
 use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeVisitor};
-
-use rustc_span::{MultiSpan, Span, Symbol};
+use rustc_span::{Span, Symbol};
 
 use std::ops::ControlFlow;
 
diff --git a/compiler/rustc_infer/src/infer/error_reporting/note.rs b/compiler/rustc_infer/src/infer/error_reporting/note.rs
index 5dcac7f56cc..baea3e8285a 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/note.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/note.rs
@@ -7,7 +7,7 @@ use rustc_middle::ty::{self, Region};
 
 impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
     pub(super) fn note_region_origin(&self, err: &mut Diagnostic, origin: &SubregionOrigin<'tcx>) {
-        let mut label_or_note = |span, msg| {
+        let mut label_or_note = |span, msg: &str| {
             let sub_count = err.children.iter().filter(|d| d.span.is_dummy()).count();
             let expanded_sub_count = err.children.iter().filter(|d| !d.span.is_dummy()).count();
             let span_is_primary = err.span.primary_spans().iter().all(|&sp| sp == span);
diff --git a/compiler/rustc_infer/src/traits/error_reporting/mod.rs b/compiler/rustc_infer/src/traits/error_reporting/mod.rs
index 25b11e31d57..d297640c140 100644
--- a/compiler/rustc_infer/src/traits/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/traits/error_reporting/mod.rs
@@ -2,11 +2,11 @@ use super::ObjectSafetyViolation;
 
 use crate::infer::InferCtxt;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{struct_span_err, DiagnosticBuilder, ErrorGuaranteed};
+use rustc_errors::{struct_span_err, DiagnosticBuilder, ErrorGuaranteed, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_middle::ty::TyCtxt;
-use rustc_span::{MultiSpan, Span};
+use rustc_span::Span;
 use std::fmt;
 use std::iter;
 
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index f2164bccc3e..eac6a33cf22 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -10,7 +10,7 @@ use rustc_codegen_ssa::traits::CodegenBackend;
 use rustc_data_structures::parallel;
 use rustc_data_structures::sync::{Lrc, OnceCell, WorkerLocal};
 use rustc_data_structures::temp_dir::MaybeTempDir;
-use rustc_errors::{Applicability, ErrorGuaranteed, PResult};
+use rustc_errors::{Applicability, ErrorGuaranteed, MultiSpan, PResult};
 use rustc_expand::base::{ExtCtxt, LintStoreExpand, ResolverExpand};
 use rustc_hir::def_id::{StableCrateId, LOCAL_CRATE};
 use rustc_hir::Crate;
@@ -35,7 +35,7 @@ use rustc_session::output::{filename_for_input, filename_for_metadata};
 use rustc_session::search_paths::PathKind;
 use rustc_session::{Limit, Session};
 use rustc_span::symbol::{sym, Symbol};
-use rustc_span::{FileName, MultiSpan};
+use rustc_span::FileName;
 use rustc_trait_selection::traits;
 use rustc_typeck as typeck;
 use tempfile::Builder as TempFileBuilder;
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index d43c661dda6..e6f9246d732 100644
--- a/compiler/rustc_lint/src/builtin.rs
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -31,7 +31,7 @@ use rustc_ast::{self as ast, *};
 use rustc_ast_pretty::pprust::{self, expr_to_string};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::stack::ensure_sufficient_stack;
-use rustc_errors::{Applicability, Diagnostic, DiagnosticStyledString};
+use rustc_errors::{Applicability, Diagnostic, DiagnosticStyledString, MultiSpan};
 use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
@@ -49,7 +49,7 @@ use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason};
 use rustc_span::edition::Edition;
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
-use rustc_span::{BytePos, InnerSpan, MultiSpan, Span};
+use rustc_span::{BytePos, InnerSpan, Span};
 use rustc_target::abi::VariantIdx;
 use rustc_trait_selection::traits::{self, misc::can_type_implement_copy};
 
@@ -1571,7 +1571,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
                         lint.build("bounds on generic parameters are not enforced in type aliases");
                     let msg = "the bound will not be checked when the type alias is used, \
                                    and should be removed";
-                    err.multipart_suggestion(&msg, suggestion, Applicability::MachineApplicable);
+                    err.multipart_suggestion(msg, suggestion, Applicability::MachineApplicable);
                     if !suggested_changing_assoc_types {
                         TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err);
                         suggested_changing_assoc_types = true;
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 882fa4496ca..3600b6ad212 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -21,7 +21,7 @@ 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::{struct_span_err, Applicability, SuggestionStyle};
+use rustc_errors::{struct_span_err, Applicability, MultiSpan, SuggestionStyle};
 use rustc_hir as hir;
 use rustc_hir::def::Res;
 use rustc_hir::def_id::{CrateNum, DefId};
@@ -38,7 +38,7 @@ use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintI
 use rustc_session::Session;
 use rustc_span::lev_distance::find_best_match_for_name;
 use rustc_span::symbol::{sym, Ident, Symbol};
-use rustc_span::{BytePos, MultiSpan, Span, DUMMY_SP};
+use rustc_span::{BytePos, Span, DUMMY_SP};
 use rustc_target::abi;
 use tracing::debug;
 
diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs
index 74fef0be9e9..67f5aa0540f 100644
--- a/compiler/rustc_lint/src/expect.rs
+++ b/compiler/rustc_lint/src/expect.rs
@@ -37,7 +37,7 @@ fn emit_unfulfilled_expectation_lint(
         |diag| {
             let mut diag = diag.build("this lint expectation is unfulfilled");
             if let Some(rationale) = expectation.reason {
-                diag.note(&rationale.as_str());
+                diag.note(rationale.as_str());
             }
 
             if expectation.is_unfulfilled_lint_expectations {
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 99a5720832e..01f1d1e79ac 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::{struct_span_err, Applicability, Diagnostic};
+use rustc_errors::{struct_span_err, Applicability, Diagnostic, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::{intravisit, HirId};
 use rustc_middle::hir::nested_filter;
@@ -20,7 +20,7 @@ use rustc_session::lint::{
 use rustc_session::parse::{add_feature_diagnostics, feature_err};
 use rustc_session::Session;
 use rustc_span::symbol::{sym, Symbol};
-use rustc_span::{source_map::MultiSpan, Span, DUMMY_SP};
+use rustc_span::{Span, DUMMY_SP};
 use tracing::debug;
 
 fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap {
diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
index c95905b9b18..14ac30987b3 100644
--- a/compiler/rustc_lint/src/types.rs
+++ b/compiler/rustc_lint/src/types.rs
@@ -154,7 +154,7 @@ fn lint_overflowing_range_endpoint<'tcx>(
                     let suggestion = format!("{}..={}{}", start, lit_val - 1, suffix);
                     err.span_suggestion(
                         parent_expr.span,
-                        &"use an inclusive range instead",
+                        "use an inclusive range instead",
                         suggestion,
                         Applicability::MachineApplicable,
                     );
@@ -399,7 +399,7 @@ fn lint_uint_literal<'tcx>(
                             lint.build("only `u8` can be cast into `char`")
                                 .span_suggestion(
                                     par_e.span,
-                                    &"use a `char` literal instead",
+                                    "use a `char` literal instead",
                                     format!("'\\u{{{:X}}}'", lit_val),
                                     Applicability::MachineApplicable,
                                 )
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
index 91b72f1d2b1..494bdaa1e2b 100644
--- a/compiler/rustc_lint/src/unused.rs
+++ b/compiler/rustc_lint/src/unused.rs
@@ -3,7 +3,7 @@ use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}
 use rustc_ast as ast;
 use rustc_ast::util::{classify, parser};
 use rustc_ast::{ExprKind, StmtKind};
-use rustc_errors::{pluralize, Applicability};
+use rustc_errors::{pluralize, Applicability, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::DefId;
@@ -11,7 +11,7 @@ use rustc_middle::ty::adjustment;
 use rustc_middle::ty::{self, Ty};
 use rustc_span::symbol::Symbol;
 use rustc_span::symbol::{kw, sym};
-use rustc_span::{BytePos, MultiSpan, Span, DUMMY_SP};
+use rustc_span::{BytePos, Span, DUMMY_SP};
 
 declare_lint! {
     /// The `unused_must_use` lint detects unused result of a type flagged as
diff --git a/compiler/rustc_lint_defs/Cargo.toml b/compiler/rustc_lint_defs/Cargo.toml
index 8acf7943de9..fcd8c37d677 100644
--- a/compiler/rustc_lint_defs/Cargo.toml
+++ b/compiler/rustc_lint_defs/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2021"
 [dependencies]
 rustc_ast = { path = "../rustc_ast" }
 rustc_data_structures = { path = "../rustc_data_structures" }
+rustc_error_messages = { path = "../rustc_error_messages" }
 rustc_span = { path = "../rustc_span" }
 rustc_serialize = { path = "../rustc_serialize" }
 rustc_macros = { path = "../rustc_macros" }
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index f0eb27c9002..031b01af5dd 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -7,10 +7,11 @@ pub use self::Level::*;
 use rustc_ast::node_id::{NodeId, NodeMap};
 use rustc_ast::{AttrId, Attribute};
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey};
+use rustc_error_messages::MultiSpan;
 use rustc_hir::HirId;
 use rustc_serialize::json::Json;
 use rustc_span::edition::Edition;
-use rustc_span::{sym, symbol::Ident, MultiSpan, Span, Symbol};
+use rustc_span::{sym, symbol::Ident, Span, Symbol};
 use rustc_target::spec::abi::Abi;
 
 pub mod builtin;
diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs
index 03e139755ba..3589860eb0e 100644
--- a/compiler/rustc_macros/src/lib.rs
+++ b/compiler/rustc_macros/src/lib.rs
@@ -63,9 +63,14 @@ decl_derive!([TypeFoldable, attributes(type_foldable)] => type_foldable::type_fo
 decl_derive!([Lift, attributes(lift)] => lift::lift_derive);
 decl_derive!(
     [SessionDiagnostic, attributes(
-        message,
-        lint,
+        // struct attributes
+        warning,
         error,
+        note,
+        help,
+        // field attributes
+        skip_arg,
+        primary_span,
         label,
         suggestion,
         suggestion_short,
diff --git a/compiler/rustc_macros/src/session_diagnostic.rs b/compiler/rustc_macros/src/session_diagnostic.rs
index c9e404903f5..46f698f6f9b 100644
--- a/compiler/rustc_macros/src/session_diagnostic.rs
+++ b/compiler/rustc_macros/src/session_diagnostic.rs
@@ -5,8 +5,9 @@ use syn::spanned::Spanned;
 
 use std::collections::{BTreeSet, HashMap};
 
-/// Implements #[derive(SessionDiagnostic)], which allows for errors to be specified as a struct, independent
-/// from the actual diagnostics emitting code.
+/// Implements `#[derive(SessionDiagnostic)]`, which allows for errors to be specified as a struct,
+/// independent from the actual diagnostics emitting code.
+///
 /// ```ignore (pseudo-rust)
 /// # extern crate rustc_errors;
 /// # use rustc_errors::Applicability;
@@ -15,11 +16,11 @@ use std::collections::{BTreeSet, HashMap};
 /// # extern crate rust_middle;
 /// # use rustc_middle::ty::Ty;
 /// #[derive(SessionDiagnostic)]
-/// #[code = "E0505"]
-/// #[error = "cannot move out of {name} because it is borrowed"]
+/// #[error(code = "E0505", slug = "move-out-of-borrow-error")]
 /// pub struct MoveOutOfBorrowError<'tcx> {
 ///     pub name: Ident,
 ///     pub ty: Ty<'tcx>,
+///     #[primary_span]
 ///     #[label = "cannot move out of borrow"]
 ///     pub span: Span,
 ///     #[label = "`{ty}` first borrowed here"]
@@ -28,6 +29,7 @@ use std::collections::{BTreeSet, HashMap};
 ///     pub opt_sugg: Option<(Span, Applicability)>
 /// }
 /// ```
+///
 /// Then, later, to emit the error:
 ///
 /// ```ignore (pseudo-rust)
@@ -47,10 +49,10 @@ pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2:
     SessionDiagnosticDerive::new(diag, sess, s).into_tokens()
 }
 
-// Checks whether the type name of `ty` matches `name`.
-//
-// Given some struct at a::b::c::Foo, this will return true for c::Foo, b::c::Foo, or
-// a::b::c::Foo. This reasonably allows qualified names to be used in the macro.
+/// Checks whether the type name of `ty` matches `name`.
+///
+/// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
+/// `a::b::c::Foo`. This reasonably allows qualified names to be used in the macro.
 fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool {
     if let syn::Type::Path(ty) = ty {
         ty.path
@@ -65,7 +67,7 @@ fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool {
     }
 }
 
-/// The central struct for constructing the as_error method from an annotated struct.
+/// The central struct for constructing the `as_error` method from an annotated struct.
 struct SessionDiagnosticDerive<'a> {
     structure: synstructure::Structure<'a>,
     builder: SessionDiagnosticDeriveBuilder<'a>,
@@ -77,13 +79,6 @@ impl std::convert::From<syn::Error> for SessionDiagnosticDeriveError {
     }
 }
 
-/// Equivalent to rustc:errors::diagnostic::DiagnosticId, except stores the quoted expression to
-/// initialise the code with.
-enum DiagnosticId {
-    Error(proc_macro2::TokenStream),
-    Lint(proc_macro2::TokenStream),
-}
-
 #[derive(Debug)]
 enum SessionDiagnosticDeriveError {
     SynError(syn::Error),
@@ -98,7 +93,7 @@ impl SessionDiagnosticDeriveError {
                 // Return ! to avoid having to create a blank DiagnosticBuilder to return when an
                 // error has already been emitted to the compiler.
                 quote! {
-                    unreachable!()
+                    { unreachable!(); }
                 }
             }
         }
@@ -109,9 +104,10 @@ fn span_err(span: impl proc_macro::MultiSpan, msg: &str) -> proc_macro::Diagnost
     Diagnostic::spanned(span, proc_macro::Level::Error, msg)
 }
 
-/// For methods that return a Result<_, SessionDiagnosticDeriveError>: emit a diagnostic on
-/// span $span with msg $msg (and, optionally, perform additional decoration using the FnOnce
-/// passed in `diag`). Then, return Err(ErrorHandled).
+/// For methods that return a `Result<_, SessionDiagnosticDeriveError>`:
+///
+/// Emit a diagnostic on span `$span` with msg `$msg` (optionally performing additional decoration
+/// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
 macro_rules! throw_span_err {
     ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }};
     ($span:expr, $msg:expr, $f:expr) => {{
@@ -119,8 +115,8 @@ macro_rules! throw_span_err {
     }};
 }
 
-/// When possible, prefer using throw_span_err! over using this function directly. This only exists
-/// as a function to constrain `f` to an impl FnOnce.
+/// When possible, prefer using `throw_span_err!` over using this function directly. This only
+/// exists as a function to constrain `f` to an `impl FnOnce`.
 fn _throw_span_err(
     span: impl proc_macro::MultiSpan,
     msg: &str,
@@ -149,17 +145,25 @@ impl<'a> SessionDiagnosticDerive<'a> {
         }
 
         Self {
-            builder: SessionDiagnosticDeriveBuilder { diag, sess, fields: fields_map, kind: None },
+            builder: SessionDiagnosticDeriveBuilder {
+                diag,
+                sess,
+                fields: fields_map,
+                kind: None,
+                code: None,
+                slug: None,
+            },
             structure,
         }
     }
+
     fn into_tokens(self) -> proc_macro2::TokenStream {
-        let SessionDiagnosticDerive { structure, mut builder } = self;
+        let SessionDiagnosticDerive { mut structure, mut builder } = self;
 
         let ast = structure.ast();
         let attrs = &ast.attrs;
 
-        let implementation = {
+        let (implementation, param_ty) = {
             if let syn::Data::Struct(..) = ast.data {
                 let preamble = {
                     let preamble = attrs.iter().map(|attr| {
@@ -167,16 +171,23 @@ impl<'a> SessionDiagnosticDerive<'a> {
                             .generate_structure_code(attr)
                             .unwrap_or_else(|v| v.to_compile_error())
                     });
+
                     quote! {
                         #(#preamble)*;
                     }
                 };
 
-                let body = structure.each(|field_binding| {
+                // Generates calls to `span_label` and similar functions based on the attributes
+                // on fields. Code for suggestions uses formatting machinery and the value of
+                // other fields - because any given field can be referenced multiple times, it
+                // should be accessed through a borrow. When passing fields to `set_arg` (which
+                // happens below) for Fluent, we want to move the data, so that has to happen
+                // in a separate pass over the fields.
+                let attrs = structure.each(|field_binding| {
                     let field = field_binding.ast();
                     let result = field.attrs.iter().map(|attr| {
                         builder
-                            .generate_field_code(
+                            .generate_field_attr_code(
                                 attr,
                                 FieldInfo {
                                     vis: &field.vis,
@@ -187,52 +198,109 @@ impl<'a> SessionDiagnosticDerive<'a> {
                             )
                             .unwrap_or_else(|v| v.to_compile_error())
                     });
-                    return quote! {
-                        #(#result);*
-                    };
+
+                    quote! { #(#result);* }
+                });
+
+                // When generating `set_arg` calls, move data rather than borrow it to avoid
+                // requiring clones - this must therefore be the last use of each field (for
+                // example, any formatting machinery that might refer to a field should be
+                // generated already).
+                structure.bind_with(|_| synstructure::BindStyle::Move);
+                let args = structure.each(|field_binding| {
+                    let field = field_binding.ast();
+                    // When a field has attributes like `#[label]` or `#[note]` then it doesn't
+                    // need to be passed as an argument to the diagnostic. But when a field has no
+                    // attributes then it must be passed as an argument to the diagnostic so that
+                    // it can be referred to by Fluent messages.
+                    if field.attrs.is_empty() {
+                        let diag = &builder.diag;
+                        let ident = field_binding.ast().ident.as_ref().unwrap();
+                        quote! {
+                            #diag.set_arg(
+                                stringify!(#ident),
+                                #field_binding.into_diagnostic_arg()
+                            );
+                        }
+                    } else {
+                        quote! {}
+                    }
                 });
-                // Finally, putting it altogether.
-                match builder.kind {
-                    None => {
-                        span_err(ast.span().unwrap(), "`code` not specified")
-                        .help("use the [code = \"...\"] attribute to set this diagnostic's error code ")
-                        .emit();
-                        SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
+
+                let span = ast.span().unwrap();
+                let (diag, sess) = (&builder.diag, &builder.sess);
+                let init = match (builder.kind, builder.slug) {
+                    (None, _) => {
+                        span_err(span, "diagnostic kind not specified")
+                            .help("use the `#[error(...)]` attribute to create an error")
+                            .emit();
+                        return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
                     }
-                    Some((kind, _)) => match kind {
-                        DiagnosticId::Lint(_lint) => todo!(),
-                        DiagnosticId::Error(code) => {
-                            let (diag, sess) = (&builder.diag, &builder.sess);
-                            quote! {
-                                let mut #diag = #sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error(#code));
-                                #preamble
-                                match self {
-                                    #body
-                                }
-                                #diag
-                            }
+                    (Some((kind, _)), None) => {
+                        span_err(span, "`slug` not specified")
+                            .help(&format!("use the `#[{}(slug = \"...\")]` attribute to set this diagnostic's slug", kind.descr()))
+                            .emit();
+                        return SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
+                    }
+                    (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => {
+                        quote! {
+                            let mut #diag = #sess.struct_err(
+                                rustc_errors::DiagnosticMessage::fluent(#slug),
+                            );
                         }
-                    },
-                }
+                    }
+                    (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => {
+                        quote! {
+                            let mut #diag = #sess.struct_warn(
+                                rustc_errors::DiagnosticMessage::fluent(#slug),
+                            );
+                        }
+                    }
+                };
+
+                let implementation = quote! {
+                    #init
+                    #preamble
+                    match self {
+                        #attrs
+                    }
+                    match self {
+                        #args
+                    }
+                    #diag
+                };
+                let param_ty = match builder.kind {
+                    Some((SessionDiagnosticKind::Error, _)) => {
+                        quote! { rustc_errors::ErrorGuaranteed }
+                    }
+                    Some((SessionDiagnosticKind::Warn, _)) => quote! { () },
+                    _ => unreachable!(),
+                };
+
+                (implementation, param_ty)
             } else {
                 span_err(
                     ast.span().unwrap(),
                     "`#[derive(SessionDiagnostic)]` can only be used on structs",
                 )
                 .emit();
-                SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
+
+                let implementation = SessionDiagnosticDeriveError::ErrorHandled.to_compile_error();
+                let param_ty = quote! { rustc_errors::ErrorGuaranteed };
+                (implementation, param_ty)
             }
         };
 
         let sess = &builder.sess;
         structure.gen_impl(quote! {
-            gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess>
+            gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty>
                     for @Self
             {
                 fn into_diagnostic(
                     self,
                     #sess: &'__session_diagnostic_sess rustc_session::Session
-                ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, rustc_errors::ErrorGuaranteed> {
+                ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> {
+                    use rustc_errors::IntoDiagnosticArg;
                     #implementation
                 }
             }
@@ -240,8 +308,8 @@ impl<'a> SessionDiagnosticDerive<'a> {
     }
 }
 
-/// Field information passed to the builder. Deliberately omits attrs to discourage the generate_*
-/// methods from walking the attributes themselves.
+/// Field information passed to the builder. Deliberately omits attrs to discourage the
+/// `generate_*` methods from walking the attributes themselves.
 struct FieldInfo<'a> {
     vis: &'a syn::Visibility,
     binding: &'a synstructure::BindingInfo<'a>,
@@ -249,108 +317,256 @@ struct FieldInfo<'a> {
     span: &'a proc_macro2::Span,
 }
 
+/// What kind of session diagnostic is being derived - an error or a warning?
+#[derive(Copy, Clone)]
+enum SessionDiagnosticKind {
+    /// `#[error(..)]`
+    Error,
+    /// `#[warn(..)]`
+    Warn,
+}
+
+impl SessionDiagnosticKind {
+    /// Returns human-readable string corresponding to the kind.
+    fn descr(&self) -> &'static str {
+        match self {
+            SessionDiagnosticKind::Error => "error",
+            SessionDiagnosticKind::Warn => "warning",
+        }
+    }
+}
+
 /// Tracks persistent information required for building up the individual calls to diagnostic
-/// methods for the final generated method. This is a separate struct to SessionDerive only to be
-/// able to destructure and split self.builder and the self.structure up to avoid a double mut
-/// borrow later on.
+/// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive`
+/// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
+/// double mut borrow later on.
 struct SessionDiagnosticDeriveBuilder<'a> {
-    /// Name of the session parameter that's passed in to the as_error method.
+    /// Name of the session parameter that's passed in to the `as_error` method.
     sess: syn::Ident,
+    /// The identifier to use for the generated `DiagnosticBuilder` instance.
+    diag: syn::Ident,
 
     /// Store a map of field name to its corresponding field. This is built on construction of the
     /// derive builder.
     fields: HashMap<String, &'a syn::Field>,
 
-    /// The identifier to use for the generated DiagnosticBuilder instance.
-    diag: syn::Ident,
-
-    /// Whether this is a lint or an error. This dictates how the diag will be initialised. Span
-    /// stores at what Span the kind was first set at (for error reporting purposes, if the kind
-    /// was multiply specified).
-    kind: Option<(DiagnosticId, proc_macro2::Span)>,
+    /// Kind of diagnostic requested via the struct attribute.
+    kind: Option<(SessionDiagnosticKind, proc_macro::Span)>,
+    /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
+    /// has the actual diagnostic message.
+    slug: Option<(String, proc_macro::Span)>,
+    /// Error codes are a optional part of the struct attribute - this is only set to detect
+    /// multiple specifications.
+    code: Option<proc_macro::Span>,
 }
 
 impl<'a> SessionDiagnosticDeriveBuilder<'a> {
+    /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
+    /// attributes like `#[error(..)#`, such as the diagnostic kind and slug. Generates
+    /// diagnostic builder calls for setting error code and creating note/help messages.
     fn generate_structure_code(
         &mut self,
         attr: &syn::Attribute,
     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
-        Ok(match attr.parse_meta()? {
-            syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
-                let formatted_str = self.build_format(&s.value(), attr.span());
-                let name = attr.path.segments.last().unwrap().ident.to_string();
-                let name = name.as_str();
-                match name {
-                    "message" => {
-                        let diag = &self.diag;
-                        quote! {
-                            #diag.set_primary_message(#formatted_str);
+        let span = attr.span().unwrap();
+
+        let name = attr.path.segments.last().unwrap().ident.to_string();
+        let name = name.as_str();
+        let meta = attr.parse_meta()?;
+
+        if matches!(name, "help" | "note")
+            && matches!(meta, syn::Meta::Path(_) | syn::Meta::NameValue(_))
+        {
+            let diag = &self.diag;
+            let slug = match &self.slug {
+                Some((slug, _)) => slug.as_str(),
+                None => throw_span_err!(
+                    span,
+                    &format!(
+                        "`#[{}{}]` must come after `#[error(..)]` or `#[warn(..)]`",
+                        name,
+                        match meta {
+                            syn::Meta::Path(_) => "",
+                            syn::Meta::NameValue(_) => " = ...",
+                            _ => unreachable!(),
+                        }
+                    )
+                ),
+            };
+            let id = match meta {
+                syn::Meta::Path(..) => quote! { #name },
+                syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
+                    quote! { #s }
+                }
+                _ => unreachable!(),
+            };
+            let fn_name = proc_macro2::Ident::new(name, attr.span());
+
+            return Ok(quote! {
+                #diag.#fn_name(rustc_errors::DiagnosticMessage::fluent_attr(#slug, #id));
+            });
+        }
+
+        let nested = match meta {
+            syn::Meta::List(syn::MetaList { nested, .. }) => nested,
+            syn::Meta::Path(..) => throw_span_err!(
+                span,
+                &format!("`#[{}]` is not a valid `SessionDiagnostic` struct attribute", name)
+            ),
+            syn::Meta::NameValue(..) => throw_span_err!(
+                span,
+                &format!("`#[{} = ...]` is not a valid `SessionDiagnostic` struct attribute", name)
+            ),
+        };
+
+        let kind = match name {
+            "error" => SessionDiagnosticKind::Error,
+            "warning" => SessionDiagnosticKind::Warn,
+            other => throw_span_err!(
+                span,
+                &format!("`#[{}(...)]` is not a valid `SessionDiagnostic` struct attribute", other)
+            ),
+        };
+        self.set_kind_once(kind, span)?;
+
+        let mut tokens = Vec::new();
+        for attr in nested {
+            let span = attr.span().unwrap();
+            let meta = match attr {
+                syn::NestedMeta::Meta(meta) => meta,
+                syn::NestedMeta::Lit(_) => throw_span_err!(
+                    span,
+                    &format!(
+                        "`#[{}(\"...\")]` is not a valid `SessionDiagnostic` struct attribute",
+                        name
+                    )
+                ),
+            };
+
+            let path = meta.path();
+            let nested_name = path.segments.last().unwrap().ident.to_string();
+            match &meta {
+                // Struct attributes are only allowed to be applied once, and the diagnostic
+                // changes will be set in the initialisation code.
+                syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
+                    match nested_name.as_str() {
+                        "slug" => {
+                            self.set_slug_once(s.value(), s.span().unwrap());
+                        }
+                        "code" => {
+                            tokens.push(self.set_code_once(s.value(), s.span().unwrap()));
+                        }
+                        other => {
+                            let diag = span_err(
+                                span,
+                                &format!(
+                                    "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute",
+                                    name, other
+                                ),
+                            );
+                            diag.emit();
                         }
                     }
-                    attr @ "error" | attr @ "lint" => {
-                        self.set_kind_once(
-                            if attr == "error" {
-                                DiagnosticId::Error(formatted_str)
-                            } else if attr == "lint" {
-                                DiagnosticId::Lint(formatted_str)
-                            } else {
-                                unreachable!()
-                            },
-                            s.span(),
-                        )?;
-                        // This attribute is only allowed to be applied once, and the attribute
-                        // will be set in the initialisation code.
-                        quote! {}
-                    }
-                    other => throw_span_err!(
-                        attr.span().unwrap(),
+                }
+                syn::Meta::NameValue(..) => {
+                    span_err(
+                        span,
                         &format!(
-                            "`#[{} = ...]` is not a valid SessionDiagnostic struct attribute",
-                            other
-                        )
-                    ),
+                            "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute",
+                            name, nested_name
+                        ),
+                    )
+                    .help("value must be a string")
+                    .emit();
+                }
+                syn::Meta::Path(..) => {
+                    span_err(
+                        span,
+                        &format!(
+                            "`#[{}({})]` is not a valid `SessionDiagnostic` struct attribute",
+                            name, nested_name
+                        ),
+                    )
+                    .emit();
+                }
+                syn::Meta::List(..) => {
+                    span_err(
+                        span,
+                        &format!(
+                            "`#[{}({}(...))]` is not a valid `SessionDiagnostic` struct attribute",
+                            name, nested_name
+                        ),
+                    )
+                    .emit();
                 }
             }
-            _ => todo!("unhandled meta kind"),
-        })
+        }
+
+        Ok(tokens.drain(..).collect())
     }
 
     #[must_use]
     fn set_kind_once(
         &mut self,
-        kind: DiagnosticId,
-        span: proc_macro2::Span,
+        kind: SessionDiagnosticKind,
+        span: proc_macro::Span,
     ) -> Result<(), SessionDiagnosticDeriveError> {
-        if self.kind.is_none() {
-            self.kind = Some((kind, span));
-            Ok(())
-        } else {
-            let kind_str = |kind: &DiagnosticId| match kind {
-                DiagnosticId::Lint(..) => "lint",
-                DiagnosticId::Error(..) => "error",
-            };
+        match self.kind {
+            None => {
+                self.kind = Some((kind, span));
+                Ok(())
+            }
+            Some((prev_kind, prev_span)) => {
+                let existing = prev_kind.descr();
+                let current = kind.descr();
+
+                let msg = if current == existing {
+                    format!("`{}` specified multiple times", existing)
+                } else {
+                    format!("`{}` specified when `{}` was already specified", current, existing)
+                };
+                throw_span_err!(span, &msg, |diag| diag
+                    .span_note(prev_span, "previously specified here"));
+            }
+        }
+    }
+
+    fn set_code_once(&mut self, code: String, span: proc_macro::Span) -> proc_macro2::TokenStream {
+        match self.code {
+            None => {
+                self.code = Some(span);
+            }
+            Some(prev_span) => {
+                span_err(span, "`code` specified multiple times")
+                    .span_note(prev_span, "previously specified here")
+                    .emit();
+            }
+        }
 
-            let existing_kind = kind_str(&self.kind.as_ref().unwrap().0);
-            let this_kind = kind_str(&kind);
+        let diag = &self.diag;
+        quote! { #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); }
+    }
 
-            let msg = if this_kind == existing_kind {
-                format!("`{}` specified multiple times", existing_kind)
-            } else {
-                format!("`{}` specified when `{}` was already specified", this_kind, existing_kind)
-            };
-            throw_span_err!(span.unwrap(), &msg);
+    fn set_slug_once(&mut self, slug: String, span: proc_macro::Span) {
+        match self.slug {
+            None => {
+                self.slug = Some((slug, span));
+            }
+            Some((_, prev_span)) => {
+                span_err(span, "`slug` specified multiple times")
+                    .span_note(prev_span, "previously specified here")
+                    .emit();
+            }
         }
     }
 
-    fn generate_field_code(
+    fn generate_field_attr_code(
         &mut self,
         attr: &syn::Attribute,
         info: FieldInfo<'_>,
     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
         let field_binding = &info.binding.binding;
-
         let option_ty = option_inner_ty(&info.ty);
-
         let generated_code = self.generate_non_option_field_code(
             attr,
             FieldInfo {
@@ -360,15 +576,16 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
                 span: info.span,
             },
         )?;
-        Ok(if option_ty.is_none() {
-            quote! { #generated_code }
+
+        if option_ty.is_none() {
+            Ok(quote! { #generated_code })
         } else {
-            quote! {
+            Ok(quote! {
                 if let Some(#field_binding) = #field_binding {
                     #generated_code
                 }
-            }
-        })
+            })
+        }
     }
 
     fn generate_non_option_field_code(
@@ -377,190 +594,261 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
         info: FieldInfo<'_>,
     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
         let diag = &self.diag;
+        let span = attr.span().unwrap();
         let field_binding = &info.binding.binding;
+
         let name = attr.path.segments.last().unwrap().ident.to_string();
         let name = name.as_str();
-        // At this point, we need to dispatch based on the attribute key + the
-        // type.
+
         let meta = attr.parse_meta()?;
-        Ok(match meta {
-            syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
-                let formatted_str = self.build_format(&s.value(), attr.span());
+        match meta {
+            syn::Meta::Path(_) => match name {
+                "skip_arg" => {
+                    // Don't need to do anything - by virtue of the attribute existing, the
+                    // `set_arg` call will not be generated.
+                    Ok(quote! {})
+                }
+                "primary_span" => {
+                    self.report_error_if_not_applied_to_span(attr, info)?;
+                    Ok(quote! {
+                        #diag.set_span(*#field_binding);
+                    })
+                }
+                "label" | "note" | "help" => {
+                    self.report_error_if_not_applied_to_span(attr, info)?;
+                    Ok(self.add_subdiagnostic(field_binding, name, name))
+                }
+                other => throw_span_err!(
+                    span,
+                    &format!("`#[{}]` is not a valid `SessionDiagnostic` field attribute", other)
+                ),
+            },
+            syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => match name {
+                "label" | "note" | "help" => {
+                    self.report_error_if_not_applied_to_span(attr, info)?;
+                    Ok(self.add_subdiagnostic(field_binding, name, &s.value()))
+                }
+                other => throw_span_err!(
+                    span,
+                    &format!(
+                        "`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute",
+                        other
+                    )
+                ),
+            },
+            syn::Meta::NameValue(_) => throw_span_err!(
+                span,
+                &format!("`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", name),
+                |diag| diag.help("value must be a string")
+            ),
+            syn::Meta::List(syn::MetaList { path, nested, .. }) => {
+                let name = path.segments.last().unwrap().ident.to_string();
+                let name = name.as_ref();
+
                 match name {
-                    "message" => {
-                        if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
-                            quote! {
-                                #diag.set_span(*#field_binding);
-                                #diag.set_primary_message(#formatted_str);
-                            }
-                        } else {
-                            throw_span_err!(
-                                attr.span().unwrap(),
-                                "the `#[message = \"...\"]` attribute can only be applied to fields of type Span"
-                            );
-                        }
-                    }
-                    "label" => {
-                        if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
-                            quote! {
-                                #diag.span_label(*#field_binding, #formatted_str);
-                            }
-                        } else {
-                            throw_span_err!(
-                                attr.span().unwrap(),
-                                "The `#[label = ...]` attribute can only be applied to fields of type Span"
-                            );
-                        }
-                    }
+                    "suggestion" | "suggestion_short" | "suggestion_hidden"
+                    | "suggestion_verbose" => (),
                     other => throw_span_err!(
-                        attr.span().unwrap(),
+                        span,
                         &format!(
-                            "`#[{} = ...]` is not a valid SessionDiagnostic field attribute",
+                            "`#[{}(...)]` is not a valid `SessionDiagnostic` field attribute",
                             other
                         )
                     ),
-                }
-            }
-            syn::Meta::List(list) => {
-                match list.path.segments.iter().last().unwrap().ident.to_string().as_str() {
-                    suggestion_kind @ "suggestion"
-                    | suggestion_kind @ "suggestion_short"
-                    | suggestion_kind @ "suggestion_hidden"
-                    | suggestion_kind @ "suggestion_verbose" => {
-                        // For suggest, we need to ensure we are running on a (Span,
-                        // Applicability) pair.
-                        let (span, applicability) = (|| match &info.ty {
-                            ty @ syn::Type::Path(..)
-                                if type_matches_path(ty, &["rustc_span", "Span"]) =>
-                            {
-                                let binding = &info.binding.binding;
-                                Ok((
-                                    quote!(*#binding),
-                                    quote!(rustc_errors::Applicability::Unspecified),
-                                ))
+                };
+
+                let (span_, applicability) = self.span_and_applicability_of_ty(info)?;
+
+                let mut msg = None;
+                let mut code = None;
+
+                for attr in nested {
+                    let meta = match attr {
+                        syn::NestedMeta::Meta(meta) => meta,
+                        syn::NestedMeta::Lit(_) => throw_span_err!(
+                            span,
+                            &format!(
+                                "`#[{}(\"...\")]` is not a valid `SessionDiagnostic` field attribute",
+                                name
+                            )
+                        ),
+                    };
+
+                    let span = meta.span().unwrap();
+                    let nested_name = meta.path().segments.last().unwrap().ident.to_string();
+                    let nested_name = nested_name.as_str();
+
+                    match meta {
+                        syn::Meta::NameValue(syn::MetaNameValue {
+                            lit: syn::Lit::Str(s), ..
+                        }) => match nested_name {
+                            "message" => {
+                                msg = Some(s.value());
                             }
-                            syn::Type::Tuple(tup) => {
-                                let mut span_idx = None;
-                                let mut applicability_idx = None;
-                                for (idx, elem) in tup.elems.iter().enumerate() {
-                                    if type_matches_path(elem, &["rustc_span", "Span"]) {
-                                        if span_idx.is_none() {
-                                            span_idx = Some(syn::Index::from(idx));
-                                        } else {
-                                            throw_span_err!(
-                                                info.span.unwrap(),
-                                                "type of field annotated with `#[suggestion(...)]` contains more than one Span"
-                                            );
-                                        }
-                                    } else if type_matches_path(
-                                        elem,
-                                        &["rustc_errors", "Applicability"],
-                                    ) {
-                                        if applicability_idx.is_none() {
-                                            applicability_idx = Some(syn::Index::from(idx));
-                                        } else {
-                                            throw_span_err!(
-                                                info.span.unwrap(),
-                                                "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
-                                            );
-                                        }
-                                    }
-                                }
-                                if let Some(span_idx) = span_idx {
-                                    let binding = &info.binding.binding;
-                                    let span = quote!(#binding.#span_idx);
-                                    let applicability = applicability_idx
-                                        .map(
-                                            |applicability_idx| quote!(#binding.#applicability_idx),
-                                        )
-                                        .unwrap_or_else(|| {
-                                            quote!(rustc_errors::Applicability::Unspecified)
-                                        });
-                                    return Ok((span, applicability));
-                                }
-                                throw_span_err!(
-                                    info.span.unwrap(),
-                                    "wrong types for suggestion",
-                                    |diag| {
-                                        diag.help("#[suggestion(...)] on a tuple field must be applied to fields of type (Span, Applicability)")
-                                    }
-                                );
+                            "code" => {
+                                let formatted_str = self.build_format(&s.value(), s.span());
+                                code = Some(formatted_str);
                             }
-                            _ => throw_span_err!(
-                                info.span.unwrap(),
-                                "wrong field type for suggestion",
-                                |diag| {
-                                    diag.help("#[suggestion(...)] should be applied to fields of type Span or (Span, Applicability)")
-                                }
+                            other => throw_span_err!(
+                                span,
+                                &format!(
+                                    "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` field attribute",
+                                    name, other
+                                )
                             ),
-                        })()?;
-                        // Now read the key-value pairs.
-                        let mut msg = None;
-                        let mut code = None;
-
-                        for arg in list.nested.iter() {
-                            if let syn::NestedMeta::Meta(syn::Meta::NameValue(arg_name_value)) = arg
-                            {
-                                if let syn::MetaNameValue { lit: syn::Lit::Str(s), .. } =
-                                    arg_name_value
-                                {
-                                    let name = arg_name_value
-                                        .path
-                                        .segments
-                                        .last()
-                                        .unwrap()
-                                        .ident
-                                        .to_string();
-                                    let name = name.as_str();
-                                    let formatted_str = self.build_format(&s.value(), arg.span());
-                                    match name {
-                                        "message" => {
-                                            msg = Some(formatted_str);
-                                        }
-                                        "code" => {
-                                            code = Some(formatted_str);
-                                        }
-                                        other => throw_span_err!(
-                                            arg.span().unwrap(),
-                                            &format!(
-                                                "`{}` is not a valid key for `#[suggestion(...)]`",
-                                                other
-                                            )
-                                        ),
-                                    }
-                                }
-                            }
+                        },
+                        syn::Meta::NameValue(..) => throw_span_err!(
+                            span,
+                            &format!(
+                                "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute",
+                                name, nested_name
+                            ),
+                            |diag| diag.help("value must be a string")
+                        ),
+                        syn::Meta::Path(..) => throw_span_err!(
+                            span,
+                            &format!(
+                                "`#[{}({})]` is not a valid `SessionDiagnostic` struct attribute",
+                                name, nested_name
+                            )
+                        ),
+                        syn::Meta::List(..) => throw_span_err!(
+                            span,
+                            &format!(
+                                "`#[{}({}(...))]` is not a valid `SessionDiagnostic` struct attribute",
+                                name, nested_name
+                            )
+                        ),
+                    }
+                }
+
+                let method = format_ident!("span_{}", name);
+
+                let slug = self
+                    .slug
+                    .as_ref()
+                    .map(|(slug, _)| slug.as_str())
+                    .unwrap_or_else(|| "missing-slug");
+                let msg = msg.as_deref().unwrap_or("suggestion");
+                let msg = quote! { rustc_errors::DiagnosticMessage::fluent_attr(#slug, #msg) };
+                let code = code.unwrap_or_else(|| quote! { String::new() });
+
+                Ok(quote! { #diag.#method(#span_, #msg, #code, #applicability); })
+            }
+        }
+    }
+
+    /// Reports an error if the field's type is not `Span`.
+    fn report_error_if_not_applied_to_span(
+        &self,
+        attr: &syn::Attribute,
+        info: FieldInfo<'_>,
+    ) -> Result<(), SessionDiagnosticDeriveError> {
+        if !type_matches_path(&info.ty, &["rustc_span", "Span"]) {
+            let name = attr.path.segments.last().unwrap().ident.to_string();
+            let name = name.as_str();
+            let meta = attr.parse_meta()?;
+
+            throw_span_err!(
+                attr.span().unwrap(),
+                &format!(
+                    "the `#[{}{}]` attribute can only be applied to fields of type `Span`",
+                    name,
+                    match meta {
+                        syn::Meta::Path(_) => "",
+                        syn::Meta::NameValue(_) => " = ...",
+                        syn::Meta::List(_) => "(...)",
+                    }
+                )
+            );
+        }
+
+        Ok(())
+    }
+
+    /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug and
+    /// `fluent_attr_identifier`.
+    fn add_subdiagnostic(
+        &self,
+        field_binding: &proc_macro2::Ident,
+        kind: &str,
+        fluent_attr_identifier: &str,
+    ) -> proc_macro2::TokenStream {
+        let diag = &self.diag;
+
+        let slug =
+            self.slug.as_ref().map(|(slug, _)| slug.as_str()).unwrap_or_else(|| "missing-slug");
+        let fn_name = format_ident!("span_{}", kind);
+        quote! {
+            #diag.#fn_name(
+                *#field_binding,
+                rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier)
+            );
+        }
+    }
+
+    fn span_and_applicability_of_ty(
+        &self,
+        info: FieldInfo<'_>,
+    ) -> Result<(proc_macro2::TokenStream, proc_macro2::TokenStream), SessionDiagnosticDeriveError>
+    {
+        match &info.ty {
+            // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
+            ty @ syn::Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
+                let binding = &info.binding.binding;
+                Ok((quote!(*#binding), quote!(rustc_errors::Applicability::Unspecified)))
+            }
+            // If `ty` is `(Span, Applicability)` then return tokens accessing those.
+            syn::Type::Tuple(tup) => {
+                let mut span_idx = None;
+                let mut applicability_idx = None;
+
+                for (idx, elem) in tup.elems.iter().enumerate() {
+                    if type_matches_path(elem, &["rustc_span", "Span"]) {
+                        if span_idx.is_none() {
+                            span_idx = Some(syn::Index::from(idx));
+                        } else {
+                            throw_span_err!(
+                                info.span.unwrap(),
+                                "type of field annotated with `#[suggestion(...)]` contains more than one `Span`"
+                            );
                         }
-                        let msg = if let Some(msg) = msg {
-                            quote!(#msg.as_str())
+                    } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
+                        if applicability_idx.is_none() {
+                            applicability_idx = Some(syn::Index::from(idx));
                         } else {
                             throw_span_err!(
-                                list.span().unwrap(),
-                                "missing suggestion message",
-                                |diag| {
-                                    diag.help("provide a suggestion message using #[suggestion(message = \"...\")]")
-                                }
+                                info.span.unwrap(),
+                                "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
                             );
-                        };
-                        let code = code.unwrap_or_else(|| quote! { String::new() });
-                        // Now build it out:
-                        let suggestion_method = format_ident!("span_{}", suggestion_kind);
-                        quote! {
-                            #diag.#suggestion_method(#span, #msg, #code, #applicability);
                         }
                     }
-                    other => throw_span_err!(
-                        list.span().unwrap(),
-                        &format!("invalid annotation list `#[{}(...)]`", other)
-                    ),
                 }
+
+                if let Some(span_idx) = span_idx {
+                    let binding = &info.binding.binding;
+                    let span = quote!(#binding.#span_idx);
+                    let applicability = applicability_idx
+                        .map(|applicability_idx| quote!(#binding.#applicability_idx))
+                        .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
+
+                    return Ok((span, applicability));
+                }
+
+                throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
+                    diag.help("`#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`")
+                });
             }
-            _ => panic!("unhandled meta kind"),
-        })
+            // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
+            _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
+                diag.help("`#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)`")
+            }),
+        }
     }
 
     /// In the strings in the attributes supplied to this macro, we want callers to be able to
-    /// reference fields in the format string. Take this, for example:
+    /// reference fields in the format string. For example:
+    ///
     /// ```ignore (not-usage-example)
     /// struct Point {
     ///     #[error = "Expected a point greater than ({x}, {y})"]
@@ -568,12 +856,15 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
     ///     y: i32,
     /// }
     /// ```
-    /// We want to automatically pick up that {x} refers `self.x` and {y} refers to `self.y`, then
-    /// generate this call to format!:
+    ///
+    /// We want to automatically pick up that `{x}` refers `self.x` and `{y}` refers to `self.y`,
+    /// then generate this call to `format!`:
+    ///
     /// ```ignore (not-usage-example)
     /// format!("Expected a point greater than ({x}, {y})", x = self.x, y = self.y)
     /// ```
-    /// This function builds the entire call to format!.
+    ///
+    /// This function builds the entire call to `format!`.
     fn build_format(&self, input: &str, span: proc_macro2::Span) -> proc_macro2::TokenStream {
         // This set is used later to generate the final format string. To keep builds reproducible,
         // the iteration order needs to be deterministic, hence why we use a BTreeSet here instead
@@ -646,7 +937,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
     }
 }
 
-/// If `ty` is an Option, returns Some(inner type). Else, returns None.
+/// If `ty` is an Option, returns `Some(inner type)`, otherwise returns `None`.
 fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> {
     if type_matches_path(ty, &["std", "option", "Option"]) {
         if let syn::Type::Path(ty_path) = ty {
diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs
index 002d25ab770..e55b0454eef 100644
--- a/compiler/rustc_middle/src/lint.rs
+++ b/compiler/rustc_middle/src/lint.rs
@@ -3,7 +3,7 @@ use std::cmp;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_errors::{
-    Diagnostic, DiagnosticBuilder, DiagnosticId, EmissionGuarantee, ErrorGuaranteed,
+    Diagnostic, DiagnosticBuilder, DiagnosticId, EmissionGuarantee, ErrorGuaranteed, MultiSpan,
 };
 use rustc_hir::HirId;
 use rustc_index::vec::IndexVec;
@@ -14,7 +14,7 @@ use rustc_session::lint::{
 };
 use rustc_session::Session;
 use rustc_span::hygiene::MacroKind;
-use rustc_span::source_map::{DesugaringKind, ExpnKind, MultiSpan};
+use rustc_span::source_map::{DesugaringKind, ExpnKind};
 use rustc_span::{symbol, Span, Symbol, DUMMY_SP};
 
 /// How a lint level was set.
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index ac9f04ee055..6ca8f8b1309 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -34,7 +34,7 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_data_structures::steal::Steal;
 use rustc_data_structures::sync::{self, Lock, Lrc, WorkerLocal};
 use rustc_data_structures::vec_map::VecMap;
-use rustc_errors::ErrorGuaranteed;
+use rustc_errors::{ErrorGuaranteed, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LOCAL_CRATE};
@@ -54,7 +54,7 @@ use rustc_session::lint::{Level, Lint};
 use rustc_session::Limit;
 use rustc_session::Session;
 use rustc_span::def_id::{DefPathHash, StableCrateId};
-use rustc_span::source_map::{MultiSpan, SourceMap};
+use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::{Span, DUMMY_SP};
 use rustc_target::abi::{Layout, LayoutS, TargetDataLayout, VariantIdx};
diff --git a/compiler/rustc_middle/src/ty/diagnostics.rs b/compiler/rustc_middle/src/ty/diagnostics.rs
index ee4ba494100..49d0ce52052 100644
--- a/compiler/rustc_middle/src/ty/diagnostics.rs
+++ b/compiler/rustc_middle/src/ty/diagnostics.rs
@@ -8,12 +8,18 @@ use crate::ty::{
 };
 
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{Applicability, Diagnostic};
+use rustc_errors::{Applicability, Diagnostic, DiagnosticArgValue, IntoDiagnosticArg};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_hir::{QPath, TyKind, WhereBoundPredicate, WherePredicate};
 use rustc_span::Span;
 
+impl<'tcx> IntoDiagnosticArg for Ty<'tcx> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        format!("{}", self).into_diagnostic_arg()
+    }
+}
+
 impl<'tcx> Ty<'tcx> {
     /// Similar to `Ty::is_primitive`, but also considers inferred numeric values to be primitive.
     pub fn is_primitive_ty(self) -> bool {
diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs
index 3aad189b222..d93c9a79c65 100644
--- a/compiler/rustc_middle/src/ty/error.rs
+++ b/compiler/rustc_middle/src/ty/error.rs
@@ -3,11 +3,11 @@ use crate::ty::diagnostics::suggest_constraining_type_param;
 use crate::ty::print::{FmtPrinter, Printer};
 use crate::ty::{self, BoundRegionKind, Region, Ty, TyCtxt};
 use rustc_errors::Applicability::{MachineApplicable, MaybeIncorrect};
-use rustc_errors::{pluralize, Diagnostic};
+use rustc_errors::{pluralize, Diagnostic, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_span::symbol::{sym, Symbol};
-use rustc_span::{BytePos, MultiSpan, Span};
+use rustc_span::{BytePos, Span};
 use rustc_target::spec::abi;
 
 use std::borrow::Cow;
diff --git a/compiler/rustc_middle/src/util/bug.rs b/compiler/rustc_middle/src/util/bug.rs
index 791d5060fe5..fd7045d6a03 100644
--- a/compiler/rustc_middle/src/util/bug.rs
+++ b/compiler/rustc_middle/src/util/bug.rs
@@ -1,7 +1,8 @@
 // These functions are used by macro expansion for bug! and span_bug!
 
 use crate::ty::{tls, TyCtxt};
-use rustc_span::{MultiSpan, Span};
+use rustc_errors::MultiSpan;
+use rustc_span::Span;
 use std::fmt;
 use std::panic::{panic_any, Location};
 
diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
index 8a3a46c1190..44caa2ac076 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
@@ -8,7 +8,7 @@ use rustc_arena::TypedArena;
 use rustc_ast::Mutability;
 use rustc_errors::{
     error_code, pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder,
-    ErrorGuaranteed,
+    ErrorGuaranteed, MultiSpan,
 };
 use rustc_hir as hir;
 use rustc_hir::def::*;
@@ -21,7 +21,7 @@ use rustc_session::lint::builtin::{
 };
 use rustc_session::Session;
 use rustc_span::source_map::Spanned;
-use rustc_span::{BytePos, DesugaringKind, ExpnKind, MultiSpan, Span};
+use rustc_span::{BytePos, DesugaringKind, ExpnKind, Span};
 
 crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) {
     let body_id = match def_id.as_local() {
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index 3ac226114cd..ed264045170 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -18,10 +18,12 @@ use rustc_ast::{
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::{pluralize, struct_span_err, Diagnostic, EmissionGuarantee, ErrorGuaranteed};
-use rustc_errors::{Applicability, DiagnosticBuilder, Handler, PResult};
+use rustc_errors::{
+    Applicability, DiagnosticBuilder, DiagnosticMessage, Handler, MultiSpan, PResult,
+};
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{kw, Ident};
-use rustc_span::{MultiSpan, Span, SpanSnippetError, DUMMY_SP};
+use rustc_span::{Span, SpanSnippetError, DUMMY_SP};
 use std::ops::{Deref, DerefMut};
 
 use std::mem::take;
@@ -273,12 +275,12 @@ impl<'a> Parser<'a> {
     pub fn struct_span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
-        m: &str,
+        m: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
         self.sess.span_diagnostic.struct_span_err(sp, m)
     }
 
-    pub fn span_bug<S: Into<MultiSpan>>(&self, sp: S, m: &str) -> ! {
+    pub fn span_bug<S: Into<MultiSpan>>(&self, sp: S, m: impl Into<DiagnosticMessage>) -> ! {
         self.sess.span_diagnostic.span_bug(sp, m)
     }
 
@@ -584,16 +586,22 @@ impl<'a> Parser<'a> {
                     //     field: value,
                     // } }
                     err.delay_as_bug();
-                    self.struct_span_err(expr.span, "struct literal body without path")
-                        .multipart_suggestion(
-                            "you might have forgotten to add the struct literal inside the block",
-                            vec![
-                                (expr.span.shrink_to_lo(), "{ SomeStruct ".to_string()),
-                                (expr.span.shrink_to_hi(), " }".to_string()),
-                            ],
-                            Applicability::MaybeIncorrect,
-                        )
-                        .emit();
+                    self.struct_span_err(
+                        expr.span,
+                        DiagnosticMessage::fluent("parser-struct-literal-body-without-path"),
+                    )
+                    .multipart_suggestion(
+                        DiagnosticMessage::fluent_attr(
+                            "parser-struct-literal-body-without-path",
+                            "suggestion",
+                        ),
+                        vec![
+                            (expr.span.shrink_to_lo(), "{ SomeStruct ".to_string()),
+                            (expr.span.shrink_to_hi(), " }".to_string()),
+                        ],
+                        Applicability::MaybeIncorrect,
+                    )
+                    .emit();
                     self.restore_snapshot(snapshot);
                     let mut tail = self.mk_block(
                         vec![self.mk_stmt_err(expr.span)],
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 7c407cbe465..7978a1a7f5f 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -1703,7 +1703,7 @@ impl<'a> Parser<'a> {
                     if matches!(expr.kind, ExprKind::Err) {
                         let mut err = self
                             .diagnostic()
-                            .struct_span_err(self.token.span, &"invalid interpolated expression");
+                            .struct_span_err(self.token.span, "invalid interpolated expression");
                         err.downgrade_to_delayed_bug();
                         return err;
                     }
@@ -1820,7 +1820,7 @@ impl<'a> Parser<'a> {
                 } else if let Some(fixed) = fix_base_capitalisation(suf) {
                     let msg = "invalid base prefix for number literal";
 
-                    self.struct_span_err(span, &msg)
+                    self.struct_span_err(span, msg)
                         .note("base prefixes (`0xff`, `0b1010`, `0o755`) are lowercase")
                         .span_suggestion(
                             span,
diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs
index e55bdb0e553..5bf6f22b5d0 100644
--- a/compiler/rustc_parse/src/parser/item.rs
+++ b/compiler/rustc_parse/src/parser/item.rs
@@ -1010,7 +1010,8 @@ impl<'a> Parser<'a> {
                 let current_qual_sp = self.prev_token.span;
                 let current_qual_sp = current_qual_sp.to(sp_start);
                 if let Ok(current_qual) = self.span_to_snippet(current_qual_sp) {
-                    if err.message() == "expected `{`, found keyword `unsafe`" {
+                    // FIXME(davidtwco): avoid depending on the error message text
+                    if err.message[0].0.expect_str() == "expected `{`, found keyword `unsafe`" {
                         let invalid_qual_sp = self.token.uninterpolated_span();
                         let invalid_qual = self.span_to_snippet(invalid_qual_sp).unwrap();
 
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index b242c1e050d..792f9d9ccce 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -33,10 +33,10 @@ use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::PResult;
 use rustc_errors::{
-    struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, FatalError,
+    struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, FatalError, MultiSpan,
 };
 use rustc_session::parse::ParseSess;
-use rustc_span::source_map::{MultiSpan, Span, DUMMY_SP};
+use rustc_span::source_map::{Span, DUMMY_SP};
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use tracing::debug;
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 7ad87481356..1f12f99efb3 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -6,7 +6,7 @@
 
 use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{pluralize, struct_span_err, Applicability};
+use rustc_errors::{pluralize, struct_span_err, Applicability, MultiSpan};
 use rustc_feature::{AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
 use rustc_hir as hir;
 use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID};
@@ -21,7 +21,7 @@ use rustc_session::lint::builtin::{
 };
 use rustc_session::parse::feature_err;
 use rustc_span::symbol::{sym, Symbol};
-use rustc_span::{MultiSpan, Span, DUMMY_SP};
+use rustc_span::{Span, DUMMY_SP};
 use std::collections::hash_map::Entry;
 
 pub(crate) fn target_from_impl_item<'tcx>(
diff --git a/compiler/rustc_query_system/src/query/plumbing.rs b/compiler/rustc_query_system/src/query/plumbing.rs
index bd4e1ddd25c..700290d67a4 100644
--- a/compiler/rustc_query_system/src/query/plumbing.rs
+++ b/compiler/rustc_query_system/src/query/plumbing.rs
@@ -620,8 +620,8 @@ fn incremental_verify_ich_cold(sess: &Session, dep_node: DebugArg<'_>, result: D
     } else {
         sess.struct_err(&format!("internal compiler error: encountered incremental compilation error with {:?}", dep_node))
                 .help(&format!("This is a known issue with the compiler. Run {} to allow your project to compile", run_cmd))
-                .note(&"Please follow the instructions below to create a bug report with the provided information")
-                .note(&"See <https://github.com/rust-lang/rust/issues/84970> for more information")
+                .note("Please follow the instructions below to create a bug report with the provided information")
+                .note("See <https://github.com/rust-lang/rust/issues/84970> for more information")
                 .emit();
         panic!("Found unstable fingerprints for {:?}: {:?}", dep_node, result);
     }
diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs
index 601f2d96ff5..6503b97a1d3 100644
--- a/compiler/rustc_resolve/src/check_unused.rs
+++ b/compiler/rustc_resolve/src/check_unused.rs
@@ -32,10 +32,10 @@ use rustc_ast::node_id::NodeMap;
 use rustc_ast::visit::{self, Visitor};
 use rustc_ast_lowering::ResolverAstLowering;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::pluralize;
+use rustc_errors::{pluralize, MultiSpan};
 use rustc_session::lint::builtin::{MACRO_USE_EXTERN_CRATE, UNUSED_IMPORTS};
 use rustc_session::lint::BuiltinLintDiagnostics;
-use rustc_span::{MultiSpan, Span, DUMMY_SP};
+use rustc_span::{Span, DUMMY_SP};
 
 struct UnusedImport<'a> {
     use_tree: &'a ast::UseTree,
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index 6c7d4afea67..609dbd1fe1b 100644
--- a/compiler/rustc_resolve/src/diagnostics.rs
+++ b/compiler/rustc_resolve/src/diagnostics.rs
@@ -4,7 +4,7 @@ use rustc_ast::{self as ast, Path};
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::{
-    struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
+    struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan,
 };
 use rustc_feature::BUILTIN_ATTRIBUTES;
 use rustc_hir::def::Namespace::{self, *};
@@ -18,7 +18,7 @@ use rustc_span::hygiene::MacroKind;
 use rustc_span::lev_distance::find_best_match_for_name;
 use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
-use rustc_span::{BytePos, MultiSpan, Span};
+use rustc_span::{BytePos, Span};
 use tracing::debug;
 
 use crate::imports::{Import, ImportKind, ImportResolver};
@@ -1341,7 +1341,7 @@ impl<'a> Resolver<'a> {
             let def_span = self.session.source_map().guess_head_span(binding.span);
             let mut note_span = MultiSpan::from_span(def_span);
             if !first && binding.vis.is_public() {
-                note_span.push_span_label(def_span, "consider importing it directly".into());
+                note_span.push_span_label(def_span, "consider importing it directly");
             }
             err.span_note(note_span, &msg);
         }
diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs
index 253b604bd23..02abdbaa983 100644
--- a/compiler/rustc_resolve/src/imports.rs
+++ b/compiler/rustc_resolve/src/imports.rs
@@ -12,7 +12,7 @@ use crate::{NameBinding, NameBindingKind, PathResult, PrivacyError, ToNameBindin
 use rustc_ast::NodeId;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::intern::Interned;
-use rustc_errors::{pluralize, struct_span_err, Applicability};
+use rustc_errors::{pluralize, struct_span_err, Applicability, MultiSpan};
 use rustc_hir::def::{self, PartialRes};
 use rustc_hir::def_id::DefId;
 use rustc_middle::metadata::ModChild;
@@ -23,7 +23,7 @@ use rustc_session::lint::BuiltinLintDiagnostics;
 use rustc_span::hygiene::LocalExpnId;
 use rustc_span::lev_distance::find_best_match_for_name;
 use rustc_span::symbol::{kw, Ident, Symbol};
-use rustc_span::{MultiSpan, Span};
+use rustc_span::Span;
 
 use tracing::*;
 
@@ -739,7 +739,7 @@ impl<'a, 'b> ImportResolver<'a, 'b> {
 
         if let Some((_, UnresolvedImportError { note, .. })) = errors.iter().last() {
             for message in note {
-                diag.note(&message);
+                diag.note(message);
             }
         }
 
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index b05ec654997..1e943f0e44a 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -14,6 +14,7 @@ use rustc_ast_pretty::pprust::path_segment_to_string;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::{
     pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
+    MultiSpan,
 };
 use rustc_hir as hir;
 use rustc_hir::def::Namespace::{self, *};
@@ -25,7 +26,7 @@ use rustc_span::edition::Edition;
 use rustc_span::hygiene::MacroKind;
 use rustc_span::lev_distance::find_best_match_for_name;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
-use rustc_span::{BytePos, MultiSpan, Span, DUMMY_SP};
+use rustc_span::{BytePos, Span, DUMMY_SP};
 
 use std::iter;
 use std::ops::Deref;
@@ -1106,7 +1107,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
                         .collect();
 
                     if non_visible_spans.len() > 0 {
-                        let mut m: rustc_span::MultiSpan = non_visible_spans.clone().into();
+                        let mut m: MultiSpan = non_visible_spans.clone().into();
                         non_visible_spans
                             .into_iter()
                             .for_each(|s| m.push_span_label(s, "private field".to_string()));
@@ -1139,7 +1140,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
                         }
                         err.span_suggestion(
                             span,
-                            &"use this syntax instead",
+                            "use this syntax instead",
                             path_str.to_string(),
                             Applicability::MaybeIncorrect,
                         );
diff --git a/compiler/rustc_serialize/src/serialize.rs b/compiler/rustc_serialize/src/serialize.rs
index 42bf6ff2a98..d5053034ed8 100644
--- a/compiler/rustc_serialize/src/serialize.rs
+++ b/compiler/rustc_serialize/src/serialize.rs
@@ -431,6 +431,20 @@ where
     }
 }
 
+impl<'a, S: Encoder> Encodable<S> for Cow<'a, str> {
+    fn encode(&self, s: &mut S) -> Result<(), S::Error> {
+        let val: &str = self;
+        val.encode(s)
+    }
+}
+
+impl<'a, D: Decoder> Decodable<D> for Cow<'a, str> {
+    fn decode(d: &mut D) -> Cow<'static, str> {
+        let v: String = Decodable::decode(d);
+        Cow::Owned(v)
+    }
+}
+
 impl<S: Encoder, T: Encodable<S>> Encodable<S> for Option<T> {
     fn encode(&self, s: &mut S) -> Result<(), S::Error> {
         s.emit_option(|s| match *self {
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 4182a5d0711..5a447aa6237 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -2856,6 +2856,7 @@ crate mod dep_tracking {
     use crate::lint;
     use crate::options::WasiExecModel;
     use crate::utils::{NativeLib, NativeLibKind};
+    use rustc_errors::LanguageIdentifier;
     use rustc_feature::UnstableFeatures;
     use rustc_span::edition::Edition;
     use rustc_span::RealFileName;
@@ -2948,6 +2949,7 @@ crate mod dep_tracking {
         LocationDetail,
         BranchProtection,
         OomStrategy,
+        LanguageIdentifier,
     );
 
     impl<T1, T2> DepTrackingHash for (T1, T2)
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 463c5c9d8b5..4994f8eaeb2 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -4,6 +4,7 @@ use crate::early_error;
 use crate::lint;
 use crate::search_paths::SearchPath;
 use crate::utils::NativeLib;
+use rustc_errors::LanguageIdentifier;
 use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy, SanitizerSet};
 use rustc_target::spec::{
     RelocModel, RelroLevel, SplitDebuginfo, StackProtector, TargetTriple, TlsModel,
@@ -365,6 +366,7 @@ mod desc {
     pub const parse_string: &str = "a string";
     pub const parse_opt_string: &str = parse_string;
     pub const parse_string_push: &str = parse_string;
+    pub const parse_opt_langid: &str = "a language identifier";
     pub const parse_opt_pathbuf: &str = "a path";
     pub const parse_list: &str = "a space-separated list of strings";
     pub const parse_opt_comma_list: &str = "a comma-separated list of strings";
@@ -487,6 +489,17 @@ mod parse {
         }
     }
 
+    /// Parse an optional language identifier, e.g. `en-US` or `zh-CN`.
+    crate fn parse_opt_langid(slot: &mut Option<LanguageIdentifier>, v: Option<&str>) -> bool {
+        match v {
+            Some(s) => {
+                *slot = rustc_errors::LanguageIdentifier::from_str(s).ok();
+                true
+            }
+            None => false,
+        }
+    }
+
     crate fn parse_opt_pathbuf(slot: &mut Option<PathBuf>, v: Option<&str>) -> bool {
         match v {
             Some(s) => {
@@ -1462,6 +1475,15 @@ options! {
         "the directory the intermediate files are written to"),
     terminal_width: Option<usize> = (None, parse_opt_number, [UNTRACKED],
         "set the current terminal width"),
+    // Diagnostics are considered side-effects of a query (see `QuerySideEffects`) and are saved
+    // alongside query results and changes to translation options can affect diagnostics - so
+    // translation options should be tracked.
+    translate_lang: Option<LanguageIdentifier> = (None, parse_opt_langid, [TRACKED],
+        "language identifier for diagnostic output"),
+    translate_additional_ftl: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
+        "additional fluent translation to preferentially use (for testing translation)"),
+    translate_directionality_markers: bool = (false, parse_bool, [TRACKED],
+        "emit directionality isolation markers in translated diagnostics"),
     tune_cpu: Option<String> = (None, parse_opt_string, [TRACKED],
         "select processor to schedule for (`rustc --print target-cpus` for details)"),
     thinlto: Option<bool> = (None, parse_opt_bool, [TRACKED],
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
index 6f0c4761841..0b9c27c2cd6 100644
--- a/compiler/rustc_session/src/parse.rs
+++ b/compiler/rustc_session/src/parse.rs
@@ -7,12 +7,15 @@ use rustc_ast::node_id::NodeId;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::sync::{Lock, Lrc};
 use rustc_errors::{emitter::SilentEmitter, ColorConfig, Handler};
-use rustc_errors::{error_code, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed};
+use rustc_errors::{
+    error_code, fallback_fluent_bundle, Applicability, Diagnostic, DiagnosticBuilder,
+    ErrorGuaranteed, MultiSpan,
+};
 use rustc_feature::{find_feature_issue, GateIssue, UnstableFeatures};
 use rustc_span::edition::Edition;
 use rustc_span::hygiene::ExpnId;
 use rustc_span::source_map::{FilePathMapping, SourceMap};
-use rustc_span::{MultiSpan, Span, Symbol};
+use rustc_span::{Span, Symbol};
 
 use std::str;
 
@@ -171,8 +174,17 @@ pub struct ParseSess {
 impl ParseSess {
     /// Used for testing.
     pub fn new(file_path_mapping: FilePathMapping) -> Self {
+        let fallback_bundle =
+            fallback_fluent_bundle(false).expect("failed to load fallback fluent bundle");
         let sm = Lrc::new(SourceMap::new(file_path_mapping));
-        let handler = Handler::with_tty_emitter(ColorConfig::Auto, true, None, Some(sm.clone()));
+        let handler = Handler::with_tty_emitter(
+            ColorConfig::Auto,
+            true,
+            None,
+            Some(sm.clone()),
+            None,
+            fallback_bundle,
+        );
         ParseSess::with_span_handler(handler, sm)
     }
 
@@ -201,8 +213,11 @@ impl ParseSess {
     }
 
     pub fn with_silent_emitter(fatal_note: Option<String>) -> Self {
+        let fallback_bundle =
+            fallback_fluent_bundle(false).expect("failed to load fallback fluent bundle");
         let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
-        let fatal_handler = Handler::with_tty_emitter(ColorConfig::Auto, false, None, None);
+        let fatal_handler =
+            Handler::with_tty_emitter(ColorConfig::Auto, false, None, None, None, fallback_bundle);
         let handler = Handler::with_emitter(
             false,
             None,
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index eed0f1e09ff..9881046ddfa 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -19,11 +19,14 @@ use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitterWriter;
 use rustc_errors::emitter::{Emitter, EmitterWriter, HumanReadableErrorType};
 use rustc_errors::json::JsonEmitter;
 use rustc_errors::registry::Registry;
-use rustc_errors::{DiagnosticBuilder, DiagnosticId, ErrorGuaranteed};
+use rustc_errors::{
+    fallback_fluent_bundle, fluent_bundle, DiagnosticBuilder, DiagnosticId, DiagnosticMessage,
+    EmissionGuarantee, ErrorGuaranteed, FluentBundle, MultiSpan,
+};
 use rustc_macros::HashStable_Generic;
 pub use rustc_span::def_id::StableCrateId;
 use rustc_span::edition::Edition;
-use rustc_span::source_map::{FileLoader, MultiSpan, RealFileLoader, SourceMap, Span};
+use rustc_span::source_map::{FileLoader, RealFileLoader, SourceMap, Span};
 use rustc_span::{sym, SourceFileHashAlgorithm, Symbol};
 use rustc_target::asm::InlineAsmArch;
 use rustc_target::spec::{CodeModel, PanicStrategy, RelocModel, RelroLevel};
@@ -206,10 +209,10 @@ pub struct PerfStats {
 
 /// Trait implemented by error types. This should not be implemented manually. Instead, use
 /// `#[derive(SessionDiagnostic)]` -- see [rustc_macros::SessionDiagnostic].
-pub trait SessionDiagnostic<'a> {
+pub trait SessionDiagnostic<'a, T: EmissionGuarantee = ErrorGuaranteed> {
     /// Write out as a diagnostic out of `sess`.
     #[must_use]
-    fn into_diagnostic(self, sess: &'a Session) -> DiagnosticBuilder<'a, ErrorGuaranteed>;
+    fn into_diagnostic(self, sess: &'a Session) -> DiagnosticBuilder<'a, T>;
 }
 
 impl Session {
@@ -279,34 +282,34 @@ impl Session {
     pub fn struct_span_warn<S: Into<MultiSpan>>(
         &self,
         sp: S,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_warn(sp, msg)
     }
     pub fn struct_span_warn_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_warn_with_code(sp, msg, code)
     }
-    pub fn struct_warn(&self, msg: &str) -> DiagnosticBuilder<'_, ()> {
+    pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_warn(msg)
     }
     pub fn struct_span_allow<S: Into<MultiSpan>>(
         &self,
         sp: S,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_span_allow(sp, msg)
     }
-    pub fn struct_allow(&self, msg: &str) -> DiagnosticBuilder<'_, ()> {
+    pub fn struct_allow(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_allow(msg)
     }
     pub fn struct_expect(
         &self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         id: lint::LintExpectationId,
     ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_expect(msg, id)
@@ -314,81 +317,108 @@ impl Session {
     pub fn struct_span_err<S: Into<MultiSpan>>(
         &self,
         sp: S,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         self.diagnostic().struct_span_err(sp, msg)
     }
     pub fn struct_span_err_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         self.diagnostic().struct_span_err_with_code(sp, msg, code)
     }
     // FIXME: This method should be removed (every error should have an associated error code).
-    pub fn struct_err(&self, msg: &str) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
+    pub fn struct_err(
+        &self,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         self.diagnostic().struct_err(msg)
     }
     pub fn struct_err_with_code(
         &self,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
         self.diagnostic().struct_err_with_code(msg, code)
     }
+    pub fn struct_warn_with_code(
+        &self,
+        msg: impl Into<DiagnosticMessage>,
+        code: DiagnosticId,
+    ) -> DiagnosticBuilder<'_, ()> {
+        self.diagnostic().struct_warn_with_code(msg, code)
+    }
     pub fn struct_span_fatal<S: Into<MultiSpan>>(
         &self,
         sp: S,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
     ) -> DiagnosticBuilder<'_, !> {
         self.diagnostic().struct_span_fatal(sp, msg)
     }
     pub fn struct_span_fatal_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> DiagnosticBuilder<'_, !> {
         self.diagnostic().struct_span_fatal_with_code(sp, msg, code)
     }
-    pub fn struct_fatal(&self, msg: &str) -> DiagnosticBuilder<'_, !> {
+    pub fn struct_fatal(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, !> {
         self.diagnostic().struct_fatal(msg)
     }
 
-    pub fn span_fatal<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! {
+    pub fn span_fatal<S: Into<MultiSpan>>(&self, sp: S, msg: impl Into<DiagnosticMessage>) -> ! {
         self.diagnostic().span_fatal(sp, msg)
     }
     pub fn span_fatal_with_code<S: Into<MultiSpan>>(
         &self,
         sp: S,
-        msg: &str,
+        msg: impl Into<DiagnosticMessage>,
         code: DiagnosticId,
     ) -> ! {
         self.diagnostic().span_fatal_with_code(sp, msg, code)
     }
-    pub fn fatal(&self, msg: &str) -> ! {
+    pub fn fatal(&self, msg: impl Into<DiagnosticMessage>) -> ! {
         self.diagnostic().fatal(msg).raise()
     }
-    pub fn span_err_or_warn<S: Into<MultiSpan>>(&self, is_warning: bool, sp: S, msg: &str) {
+    pub fn span_err_or_warn<S: Into<MultiSpan>>(
+        &self,
+        is_warning: bool,
+        sp: S,
+        msg: impl Into<DiagnosticMessage>,
+    ) {
         if is_warning {
             self.span_warn(sp, msg);
         } else {
             self.span_err(sp, msg);
         }
     }
-    pub fn span_err<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ErrorGuaranteed {
+    pub fn span_err<S: Into<MultiSpan>>(
+        &self,
+        sp: S,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> ErrorGuaranteed {
         self.diagnostic().span_err(sp, msg)
     }
-    pub fn span_err_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: DiagnosticId) {
-        self.diagnostic().span_err_with_code(sp, &msg, code)
+    pub fn span_err_with_code<S: Into<MultiSpan>>(
+        &self,
+        sp: S,
+        msg: impl Into<DiagnosticMessage>,
+        code: DiagnosticId,
+    ) {
+        self.diagnostic().span_err_with_code(sp, msg, code)
     }
-    pub fn err(&self, msg: &str) -> ErrorGuaranteed {
+    pub fn err(&self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
         self.diagnostic().err(msg)
     }
     pub fn emit_err<'a>(&'a self, err: impl SessionDiagnostic<'a>) -> ErrorGuaranteed {
         err.into_diagnostic(self).emit()
     }
+    pub fn emit_warning<'a>(&'a self, warning: impl SessionDiagnostic<'a, ()>) {
+        warning.into_diagnostic(self).emit()
+    }
     #[inline]
     pub fn err_count(&self) -> usize {
         self.diagnostic().err_count()
@@ -423,25 +453,34 @@ impl Session {
             Err(ErrorGuaranteed::unchecked_claim_error_was_emitted())
         }
     }
-    pub fn span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
+    pub fn span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: impl Into<DiagnosticMessage>) {
         self.diagnostic().span_warn(sp, msg)
     }
-    pub fn span_warn_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: DiagnosticId) {
+    pub fn span_warn_with_code<S: Into<MultiSpan>>(
+        &self,
+        sp: S,
+        msg: impl Into<DiagnosticMessage>,
+        code: DiagnosticId,
+    ) {
         self.diagnostic().span_warn_with_code(sp, msg, code)
     }
-    pub fn warn(&self, msg: &str) {
+    pub fn warn(&self, msg: impl Into<DiagnosticMessage>) {
         self.diagnostic().warn(msg)
     }
     /// Delay a span_bug() call until abort_if_errors()
     #[track_caller]
-    pub fn delay_span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ErrorGuaranteed {
+    pub fn delay_span_bug<S: Into<MultiSpan>>(
+        &self,
+        sp: S,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> ErrorGuaranteed {
         self.diagnostic().delay_span_bug(sp, msg)
     }
 
     /// Used for code paths of expensive computations that should only take place when
     /// warnings or errors are emitted. If no messages are emitted ("good path"), then
     /// it's likely a bug.
-    pub fn delay_good_path_bug(&self, msg: &str) {
+    pub fn delay_good_path_bug(&self, msg: impl Into<DiagnosticMessage>) {
         if self.opts.debugging_opts.print_type_sizes
             || self.opts.debugging_opts.query_dep_graph
             || self.opts.debugging_opts.dump_mir.is_some()
@@ -455,13 +494,20 @@ impl Session {
         self.diagnostic().delay_good_path_bug(msg)
     }
 
-    pub fn note_without_error(&self, msg: &str) {
+    pub fn note_without_error(&self, msg: impl Into<DiagnosticMessage>) {
         self.diagnostic().note_without_error(msg)
     }
-    pub fn span_note_without_error<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
+    pub fn span_note_without_error<S: Into<MultiSpan>>(
+        &self,
+        sp: S,
+        msg: impl Into<DiagnosticMessage>,
+    ) {
         self.diagnostic().span_note_without_error(sp, msg)
     }
-    pub fn struct_note_without_error(&self, msg: &str) -> DiagnosticBuilder<'_, ()> {
+    pub fn struct_note_without_error(
+        &self,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, ()> {
         self.diagnostic().struct_note_without_error(msg)
     }
 
@@ -1033,6 +1079,8 @@ fn default_emitter(
     sopts: &config::Options,
     registry: rustc_errors::registry::Registry,
     source_map: Lrc<SourceMap>,
+    bundle: Option<Lrc<FluentBundle>>,
+    fallback_bundle: Lrc<FluentBundle>,
     emitter_dest: Option<Box<dyn Write + Send>>,
 ) -> Box<dyn Emitter + sync::Send> {
     let macro_backtrace = sopts.debugging_opts.macro_backtrace;
@@ -1041,14 +1089,21 @@ fn default_emitter(
             let (short, color_config) = kind.unzip();
 
             if let HumanReadableErrorType::AnnotateSnippet(_) = kind {
-                let emitter =
-                    AnnotateSnippetEmitterWriter::new(Some(source_map), short, macro_backtrace);
+                let emitter = AnnotateSnippetEmitterWriter::new(
+                    Some(source_map),
+                    bundle,
+                    fallback_bundle,
+                    short,
+                    macro_backtrace,
+                );
                 Box::new(emitter.ui_testing(sopts.debugging_opts.ui_testing))
             } else {
                 let emitter = match dst {
                     None => EmitterWriter::stderr(
                         color_config,
                         Some(source_map),
+                        bundle,
+                        fallback_bundle,
                         short,
                         sopts.debugging_opts.teach,
                         sopts.debugging_opts.terminal_width,
@@ -1057,6 +1112,8 @@ fn default_emitter(
                     Some(dst) => EmitterWriter::new(
                         dst,
                         Some(source_map),
+                        bundle,
+                        fallback_bundle,
                         short,
                         false, // no teach messages when writing to a buffer
                         false, // no colors when writing to a buffer
@@ -1071,6 +1128,8 @@ fn default_emitter(
             JsonEmitter::stderr(
                 Some(registry),
                 source_map,
+                bundle,
+                fallback_bundle,
                 pretty,
                 json_rendered,
                 sopts.debugging_opts.terminal_width,
@@ -1083,6 +1142,8 @@ fn default_emitter(
                 dst,
                 Some(registry),
                 source_map,
+                bundle,
+                fallback_bundle,
                 pretty,
                 json_rendered,
                 sopts.debugging_opts.terminal_width,
@@ -1152,7 +1213,19 @@ pub fn build_session(
         sopts.file_path_mapping(),
         hash_kind,
     ));
-    let emitter = default_emitter(&sopts, registry, source_map.clone(), write_dest);
+
+    let bundle = fluent_bundle(
+        &sysroot,
+        sopts.debugging_opts.translate_lang.clone(),
+        sopts.debugging_opts.translate_additional_ftl.as_deref(),
+        sopts.debugging_opts.translate_directionality_markers,
+    )
+    .expect("failed to load fluent bundle");
+    let fallback_bundle =
+        fallback_fluent_bundle(sopts.debugging_opts.translate_directionality_markers)
+            .expect("failed to load fallback fluent bundle");
+    let emitter =
+        default_emitter(&sopts, registry, source_map.clone(), bundle, fallback_bundle, write_dest);
 
     let span_diagnostic = rustc_errors::Handler::with_emitter_and_flags(
         emitter,
@@ -1385,13 +1458,24 @@ pub enum IncrCompSession {
 }
 
 fn early_error_handler(output: config::ErrorOutputType) -> rustc_errors::Handler {
+    let fallback_bundle =
+        fallback_fluent_bundle(false).expect("failed to load fallback fluent bundle");
     let emitter: Box<dyn Emitter + sync::Send> = match output {
         config::ErrorOutputType::HumanReadable(kind) => {
             let (short, color_config) = kind.unzip();
-            Box::new(EmitterWriter::stderr(color_config, None, short, false, None, false))
+            Box::new(EmitterWriter::stderr(
+                color_config,
+                None,
+                None,
+                fallback_bundle,
+                short,
+                false,
+                None,
+                false,
+            ))
         }
         config::ErrorOutputType::Json { pretty, json_rendered } => {
-            Box::new(JsonEmitter::basic(pretty, json_rendered, None, false))
+            Box::new(JsonEmitter::basic(pretty, json_rendered, None, fallback_bundle, None, false))
         }
     };
     rustc_errors::Handler::with_emitter(true, None, emitter)
diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs
index 6d7377927c2..5232c8d7006 100644
--- a/compiler/rustc_span/src/lib.rs
+++ b/compiler/rustc_span/src/lib.rs
@@ -520,20 +520,6 @@ impl Ord for Span {
     }
 }
 
-/// A collection of `Span`s.
-///
-/// Spans have two orthogonal attributes:
-///
-/// - They can be *primary spans*. In this case they are the locus of
-///   the error, and would be rendered with `^^^`.
-/// - They can have a *label*. In this case, the label is written next
-///   to the mark in the snippet when we render.
-#[derive(Clone, Debug, Hash, PartialEq, Eq, Encodable, Decodable)]
-pub struct MultiSpan {
-    primary_spans: Vec<Span>,
-    span_labels: Vec<(Span, String)>,
-}
-
 impl Span {
     #[inline]
     pub fn lo(self) -> BytePos {
@@ -949,20 +935,6 @@ impl Span {
     }
 }
 
-/// A span together with some additional data.
-#[derive(Clone, Debug)]
-pub struct SpanLabel {
-    /// The span we are going to include in the final snippet.
-    pub span: Span,
-
-    /// Is this a primary span? This is the "locus" of the message,
-    /// and is indicated with a `^^^^` underline, versus `----`.
-    pub is_primary: bool,
-
-    /// What label should we attach to this span (if any)?
-    pub label: Option<String>,
-}
-
 impl Default for Span {
     fn default() -> Self {
         DUMMY_SP
@@ -1035,115 +1007,6 @@ impl fmt::Debug for SpanData {
     }
 }
 
-impl MultiSpan {
-    #[inline]
-    pub fn new() -> MultiSpan {
-        MultiSpan { primary_spans: vec![], span_labels: vec![] }
-    }
-
-    pub fn from_span(primary_span: Span) -> MultiSpan {
-        MultiSpan { primary_spans: vec![primary_span], span_labels: vec![] }
-    }
-
-    pub fn from_spans(mut vec: Vec<Span>) -> MultiSpan {
-        vec.sort();
-        MultiSpan { primary_spans: vec, span_labels: vec![] }
-    }
-
-    pub fn push_span_label(&mut self, span: Span, label: String) {
-        self.span_labels.push((span, label));
-    }
-
-    /// Selects the first primary span (if any).
-    pub fn primary_span(&self) -> Option<Span> {
-        self.primary_spans.first().cloned()
-    }
-
-    /// Returns all primary spans.
-    pub fn primary_spans(&self) -> &[Span] {
-        &self.primary_spans
-    }
-
-    /// Returns `true` if any of the primary spans are displayable.
-    pub fn has_primary_spans(&self) -> bool {
-        self.primary_spans.iter().any(|sp| !sp.is_dummy())
-    }
-
-    /// Returns `true` if this contains only a dummy primary span with any hygienic context.
-    pub fn is_dummy(&self) -> bool {
-        let mut is_dummy = true;
-        for span in &self.primary_spans {
-            if !span.is_dummy() {
-                is_dummy = false;
-            }
-        }
-        is_dummy
-    }
-
-    /// Replaces all occurrences of one Span with another. Used to move `Span`s in areas that don't
-    /// display well (like std macros). Returns whether replacements occurred.
-    pub fn replace(&mut self, before: Span, after: Span) -> bool {
-        let mut replacements_occurred = false;
-        for primary_span in &mut self.primary_spans {
-            if *primary_span == before {
-                *primary_span = after;
-                replacements_occurred = true;
-            }
-        }
-        for span_label in &mut self.span_labels {
-            if span_label.0 == before {
-                span_label.0 = after;
-                replacements_occurred = true;
-            }
-        }
-        replacements_occurred
-    }
-
-    /// Returns the strings to highlight. We always ensure that there
-    /// is an entry for each of the primary spans -- for each primary
-    /// span `P`, if there is at least one label with span `P`, we return
-    /// those labels (marked as primary). But otherwise we return
-    /// `SpanLabel` instances with empty labels.
-    pub fn span_labels(&self) -> Vec<SpanLabel> {
-        let is_primary = |span| self.primary_spans.contains(&span);
-
-        let mut span_labels = self
-            .span_labels
-            .iter()
-            .map(|&(span, ref label)| SpanLabel {
-                span,
-                is_primary: is_primary(span),
-                label: Some(label.clone()),
-            })
-            .collect::<Vec<_>>();
-
-        for &span in &self.primary_spans {
-            if !span_labels.iter().any(|sl| sl.span == span) {
-                span_labels.push(SpanLabel { span, is_primary: true, label: None });
-            }
-        }
-
-        span_labels
-    }
-
-    /// Returns `true` if any of the span labels is displayable.
-    pub fn has_span_labels(&self) -> bool {
-        self.span_labels.iter().any(|(sp, _)| !sp.is_dummy())
-    }
-}
-
-impl From<Span> for MultiSpan {
-    fn from(span: Span) -> MultiSpan {
-        MultiSpan::from_span(span)
-    }
-}
-
-impl From<Vec<Span>> for MultiSpan {
-    fn from(spans: Vec<Span>) -> MultiSpan {
-        MultiSpan::from_spans(spans)
-    }
-}
-
 /// Identifies an offset of a multi-byte character in a `SourceFile`.
 #[derive(Copy, Clone, Encodable, Decodable, Eq, PartialEq, Debug)]
 pub struct MultiByteChar {
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
index 27748eef8f2..ff3488b7b76 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
@@ -15,7 +15,7 @@ use crate::infer::{self, InferCtxt, TyCtxtInferExt};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{
     pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
-    Style,
+    MultiSpan, Style,
 };
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
@@ -33,7 +33,7 @@ use rustc_middle::ty::{
     self, SubtypePredicate, ToPolyTraitRef, ToPredicate, TraitRef, Ty, TyCtxt, TypeFoldable,
 };
 use rustc_span::symbol::{kw, sym};
-use rustc_span::{ExpnKind, MultiSpan, Span, DUMMY_SP};
+use rustc_span::{ExpnKind, Span, DUMMY_SP};
 use std::fmt;
 use std::iter;
 
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index c5324bf85a7..8dac56120e6 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -11,7 +11,7 @@ use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_errors::{
     error_code, pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder,
-    ErrorGuaranteed, Style,
+    ErrorGuaranteed, MultiSpan, Style,
 };
 use rustc_hir as hir;
 use rustc_hir::def::DefKind;
@@ -27,7 +27,7 @@ use rustc_middle::ty::{TypeAndMut, TypeckResults};
 use rustc_session::Limit;
 use rustc_span::def_id::LOCAL_CRATE;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
-use rustc_span::{BytePos, DesugaringKind, ExpnKind, MultiSpan, Span, DUMMY_SP};
+use rustc_span::{BytePos, DesugaringKind, ExpnKind, Span, DUMMY_SP};
 use rustc_target::spec::abi;
 use std::fmt;
 
@@ -597,7 +597,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
                     Some(format!("{}", name))
                 }
                 _ => {
-                    err.note(&msg);
+                    err.note(msg);
                     None
                 }
             }
@@ -780,7 +780,8 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
                     if has_custom_message {
                         err.note(&msg);
                     } else {
-                        err.message = vec![(msg, Style::NoStyle)];
+                        err.message =
+                            vec![(rustc_errors::DiagnosticMessage::Str(msg), Style::NoStyle)];
                     }
                     if snippet.starts_with('&') {
                         // This is already a literal borrow and the obligation is failing
@@ -2480,7 +2481,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
                     .opt_associated_item(trait_item_def_id)
                     .and_then(|i| self.tcx.opt_item_name(i.container.id()))
                 {
-                    assoc_span.push_span_label(ident.span, "in this trait".into());
+                    assoc_span.push_span_label(ident.span, "in this trait");
                 }
                 err.span_note(assoc_span, &msg);
             }
@@ -2505,7 +2506,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
                     .opt_associated_item(trait_item_def_id)
                     .and_then(|i| self.tcx.opt_item_name(i.container.id()))
                 {
-                    assoc_span.push_span_label(ident.span, "in this trait".into());
+                    assoc_span.push_span_label(ident.span, "in this trait");
                 }
                 err.span_note(assoc_span, &msg);
             }
diff --git a/compiler/rustc_trait_selection/src/traits/object_safety.rs b/compiler/rustc_trait_selection/src/traits/object_safety.rs
index 54f7c68060f..84958136cac 100644
--- a/compiler/rustc_trait_selection/src/traits/object_safety.rs
+++ b/compiler/rustc_trait_selection/src/traits/object_safety.rs
@@ -14,7 +14,7 @@ use crate::infer::TyCtxtInferExt;
 use crate::traits::const_evaluatable::{self, AbstractConst};
 use crate::traits::query::evaluate_obligation::InferCtxtExt;
 use crate::traits::{self, Obligation, ObligationCause};
-use rustc_errors::FatalError;
+use rustc_errors::{FatalError, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_middle::ty::subst::{GenericArg, InternalSubsts, Subst};
@@ -22,7 +22,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeVisitor};
 use rustc_middle::ty::{Predicate, ToPredicate};
 use rustc_session::lint::builtin::WHERE_CLAUSES_OBJECT_SAFETY;
 use rustc_span::symbol::Symbol;
-use rustc_span::{MultiSpan, Span};
+use rustc_span::Span;
 use smallvec::SmallVec;
 
 use std::iter;
@@ -169,10 +169,7 @@ fn lint_object_unsafe_trait(
         let node = tcx.hir().get_if_local(trait_def_id);
         let mut spans = MultiSpan::from_span(span);
         if let Some(hir::Node::Item(item)) = node {
-            spans.push_span_label(
-                item.ident.span,
-                "this trait cannot be made into an object...".into(),
-            );
+            spans.push_span_label(item.ident.span, "this trait cannot be made into an object...");
             spans.push_span_label(span, format!("...because {}", violation.error_msg()));
         } else {
             spans.push_span_label(
diff --git a/compiler/rustc_typeck/src/astconv/generics.rs b/compiler/rustc_typeck/src/astconv/generics.rs
index a07700aa9f5..a50301dbc87 100644
--- a/compiler/rustc_typeck/src/astconv/generics.rs
+++ b/compiler/rustc_typeck/src/astconv/generics.rs
@@ -6,7 +6,7 @@ use crate::astconv::{
 use crate::errors::AssocTypeBindingNotAllowed;
 use crate::structured_errors::{GenericArgsInfo, StructuredDiagnostic, WrongNumberOfGenericArgs};
 use rustc_ast::ast::ParamKindOrd;
-use rustc_errors::{struct_span_err, Applicability, Diagnostic};
+use rustc_errors::{struct_span_err, Applicability, Diagnostic, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::DefId;
@@ -16,7 +16,7 @@ use rustc_middle::ty::{
     self, subst, subst::SubstsRef, GenericParamDef, GenericParamDefKind, Ty, TyCtxt,
 };
 use rustc_session::lint::builtin::LATE_BOUND_LIFETIME_ARGUMENTS;
-use rustc_span::{symbol::kw, MultiSpan, Span};
+use rustc_span::{symbol::kw, Span};
 use smallvec::SmallVec;
 
 impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
diff --git a/compiler/rustc_typeck/src/check/_match.rs b/compiler/rustc_typeck/src/check/_match.rs
index 79be8bde55a..26f7c267ed1 100644
--- a/compiler/rustc_typeck/src/check/_match.rs
+++ b/compiler/rustc_typeck/src/check/_match.rs
@@ -1,11 +1,11 @@
 use crate::check::coercion::{AsCoercionSite, CoerceMany};
 use crate::check::{Diverges, Expectation, FnCtxt, Needs};
-use rustc_errors::{Applicability, Diagnostic};
+use rustc_errors::{Applicability, Diagnostic, MultiSpan};
 use rustc_hir::{self as hir, ExprKind};
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_infer::traits::Obligation;
 use rustc_middle::ty::{self, ToPredicate, Ty, TypeFoldable};
-use rustc_span::{MultiSpan, Span};
+use rustc_span::Span;
 use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
 use rustc_trait_selection::traits::{
     IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs
index 7cb478d7888..9ebafc26f61 100644
--- a/compiler/rustc_typeck/src/check/check.rs
+++ b/compiler/rustc_typeck/src/check/check.rs
@@ -4,7 +4,7 @@ use super::compare_method::{compare_const_impl, compare_impl_method, compare_ty_
 use super::*;
 
 use rustc_attr as attr;
-use rustc_errors::{Applicability, ErrorGuaranteed};
+use rustc_errors::{Applicability, ErrorGuaranteed, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir::intravisit::Visitor;
@@ -20,7 +20,7 @@ use rustc_middle::ty::util::{Discr, IntTypeExt};
 use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt};
 use rustc_session::lint::builtin::{UNINHABITED_STATIC, UNSUPPORTED_CALLING_CONVENTIONS};
 use rustc_span::symbol::sym;
-use rustc_span::{self, MultiSpan, Span};
+use rustc_span::{self, Span};
 use rustc_target::spec::abi::Abi;
 use rustc_trait_selection::traits;
 use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
index bb3bfbb7dd1..d403d6e3f33 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
@@ -8,7 +8,7 @@ use crate::check::{BreakableCtxt, Diverges, Expectation, FnCtxt, LocalTy};
 
 use rustc_data_structures::captures::Captures;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{Applicability, Diagnostic, ErrorGuaranteed};
+use rustc_errors::{Applicability, Diagnostic, ErrorGuaranteed, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::{CtorOf, DefKind, Res};
 use rustc_hir::def_id::DefId;
@@ -30,7 +30,7 @@ use rustc_session::lint;
 use rustc_span::hygiene::DesugaringKind;
 use rustc_span::source_map::{original_sp, DUMMY_SP};
 use rustc_span::symbol::{kw, sym, Ident};
-use rustc_span::{self, BytePos, MultiSpan, Span};
+use rustc_span::{self, BytePos, Span};
 use rustc_trait_selection::infer::InferCtxtExt as _;
 use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
 use rustc_trait_selection::traits::{
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
index 2a1b7a5ab47..1cc1460750a 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs
@@ -11,7 +11,7 @@ use crate::check::{
 
 use rustc_ast as ast;
 use rustc_data_structures::sync::Lrc;
-use rustc_errors::{Applicability, Diagnostic, DiagnosticId};
+use rustc_errors::{Applicability, Diagnostic, DiagnosticId, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::{CtorOf, DefKind, Res};
 use rustc_hir::def_id::DefId;
@@ -21,7 +21,7 @@ use rustc_middle::ty::fold::TypeFoldable;
 use rustc_middle::ty::{self, Ty};
 use rustc_session::Session;
 use rustc_span::symbol::Ident;
-use rustc_span::{self, MultiSpan, Span};
+use rustc_span::{self, Span};
 use rustc_trait_selection::traits::{self, ObligationCauseCode, StatementAsExpression};
 
 use crate::structured_errors::StructuredDiagnostic;
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
index 68d555b3a65..1ccdbb0aa50 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
@@ -2,9 +2,9 @@ use super::FnCtxt;
 use crate::astconv::AstConv;
 
 use rustc_ast::util::parser::ExprPrecedence;
-use rustc_span::{self, MultiSpan, Span};
+use rustc_span::{self, Span};
 
-use rustc_errors::{Applicability, Diagnostic};
+use rustc_errors::{Applicability, Diagnostic, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::{CtorOf, DefKind};
 use rustc_hir::lang_items::LangItem;
diff --git a/compiler/rustc_typeck/src/check/intrinsic.rs b/compiler/rustc_typeck/src/check/intrinsic.rs
index 74f6f50d412..cd6b1115ed8 100644
--- a/compiler/rustc_typeck/src/check/intrinsic.rs
+++ b/compiler/rustc_typeck/src/check/intrinsic.rs
@@ -7,7 +7,7 @@ use crate::errors::{
 };
 use crate::require_same_types;
 
-use rustc_errors::{pluralize, struct_span_err};
+use rustc_errors::struct_span_err;
 use rustc_hir as hir;
 use rustc_middle::traits::{ObligationCause, ObligationCauseCode};
 use rustc_middle::ty::subst::Subst;
@@ -43,7 +43,6 @@ fn equate_intrinsic_type<'tcx>(
                 span,
                 found,
                 expected,
-                expected_pluralize: pluralize!(expected),
                 descr,
             });
             false
diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs
index c2cb233f5b8..ecc29965937 100644
--- a/compiler/rustc_typeck/src/check/method/suggest.rs
+++ b/compiler/rustc_typeck/src/check/method/suggest.rs
@@ -5,6 +5,7 @@ use crate::check::FnCtxt;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_errors::{
     pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
+    MultiSpan,
 };
 use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, LocalDefId};
@@ -17,7 +18,7 @@ use rustc_middle::ty::print::with_crate_prefix;
 use rustc_middle::ty::ToPolyTraitRef;
 use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeFoldable};
 use rustc_span::symbol::{kw, sym, Ident};
-use rustc_span::{lev_distance, source_map, ExpnKind, FileName, MacroKind, MultiSpan, Span};
+use rustc_span::{lev_distance, source_map, ExpnKind, FileName, MacroKind, Span};
 use rustc_trait_selection::traits::error_reporting::on_unimplemented::InferCtxtExt as _;
 use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
 use rustc_trait_selection::traits::{
diff --git a/compiler/rustc_typeck/src/check/mod.rs b/compiler/rustc_typeck/src/check/mod.rs
index bf0bf2ed59b..19d52f430fc 100644
--- a/compiler/rustc_typeck/src/check/mod.rs
+++ b/compiler/rustc_typeck/src/check/mod.rs
@@ -103,7 +103,7 @@ pub use inherited::{Inherited, InheritedBuilder};
 use crate::astconv::AstConv;
 use crate::check::gather_locals::GatherLocalsVisitor;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
-use rustc_errors::{pluralize, struct_span_err, Applicability};
+use rustc_errors::{pluralize, struct_span_err, Applicability, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::Res;
 use rustc_hir::def_id::{DefId, LocalDefId};
@@ -121,7 +121,7 @@ use rustc_session::parse::feature_err;
 use rustc_session::Session;
 use rustc_span::source_map::DUMMY_SP;
 use rustc_span::symbol::{kw, Ident};
-use rustc_span::{self, BytePos, MultiSpan, Span};
+use rustc_span::{self, BytePos, Span};
 use rustc_target::abi::VariantIdx;
 use rustc_target::spec::abi::Abi;
 use rustc_trait_selection::traits;
diff --git a/compiler/rustc_typeck/src/check/pat.rs b/compiler/rustc_typeck/src/check/pat.rs
index 464a2cd9524..0baca9048b4 100644
--- a/compiler/rustc_typeck/src/check/pat.rs
+++ b/compiler/rustc_typeck/src/check/pat.rs
@@ -4,6 +4,7 @@ use rustc_ast as ast;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{
     pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
+    MultiSpan,
 };
 use rustc_hir as hir;
 use rustc_hir::def::{CtorKind, DefKind, Res};
@@ -18,7 +19,7 @@ use rustc_span::hygiene::DesugaringKind;
 use rustc_span::lev_distance::find_best_match_for_name;
 use rustc_span::source_map::{Span, Spanned};
 use rustc_span::symbol::{kw, sym, Ident};
-use rustc_span::{BytePos, MultiSpan, DUMMY_SP};
+use rustc_span::{BytePos, DUMMY_SP};
 use rustc_trait_selection::autoderef::Autoderef;
 use rustc_trait_selection::traits::{ObligationCause, Pattern};
 use ty::VariantDef;
diff --git a/compiler/rustc_typeck/src/check/upvar.rs b/compiler/rustc_typeck/src/check/upvar.rs
index 0b343a5f905..1118e967707 100644
--- a/compiler/rustc_typeck/src/check/upvar.rs
+++ b/compiler/rustc_typeck/src/check/upvar.rs
@@ -33,7 +33,7 @@
 use super::FnCtxt;
 
 use crate::expr_use_visitor as euv;
-use rustc_errors::Applicability;
+use rustc_errors::{Applicability, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_hir::def_id::LocalDefId;
@@ -46,7 +46,7 @@ use rustc_middle::ty::{
 };
 use rustc_session::lint;
 use rustc_span::sym;
-use rustc_span::{BytePos, MultiSpan, Pos, Span, Symbol};
+use rustc_span::{BytePos, Pos, Span, Symbol};
 use rustc_trait_selection::infer::InferCtxtExt;
 
 use rustc_data_structures::stable_map::FxHashMap;
diff --git a/compiler/rustc_typeck/src/errors.rs b/compiler/rustc_typeck/src/errors.rs
index 47077779616..0b78aea9f05 100644
--- a/compiler/rustc_typeck/src/errors.rs
+++ b/compiler/rustc_typeck/src/errors.rs
@@ -3,188 +3,185 @@ use rustc_macros::SessionDiagnostic;
 use rustc_span::{symbol::Ident, Span, Symbol};
 
 #[derive(SessionDiagnostic)]
-#[error = "E0062"]
+#[error(code = "E0062", slug = "typeck-field-multiply-specified-in-initializer")]
 pub struct FieldMultiplySpecifiedInInitializer {
-    #[message = "field `{ident}` specified more than once"]
-    #[label = "used more than once"]
+    #[primary_span]
+    #[label]
     pub span: Span,
-    #[label = "first use of `{ident}`"]
+    #[label = "previous-use-label"]
     pub prev_span: Span,
     pub ident: Ident,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0092"]
+#[error(code = "E0092", slug = "typeck-unrecognized-atomic-operation")]
 pub struct UnrecognizedAtomicOperation<'a> {
-    #[message = "unrecognized atomic operation function: `{op}`"]
-    #[label = "unrecognized atomic operation"]
+    #[primary_span]
+    #[label]
     pub span: Span,
     pub op: &'a str,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0094"]
+#[error(code = "E0094", slug = "typeck-wrong-number-of-generic-arguments-to-intrinsic")]
 pub struct WrongNumberOfGenericArgumentsToIntrinsic<'a> {
-    #[message = "intrinsic has wrong number of {descr} \
-                         parameters: found {found}, expected {expected}"]
-    #[label = "expected {expected} {descr} parameter{expected_pluralize}"]
+    #[primary_span]
+    #[label]
     pub span: Span,
     pub found: usize,
     pub expected: usize,
-    pub expected_pluralize: &'a str,
     pub descr: &'a str,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0093"]
+#[error(code = "E0093", slug = "typeck-unrecognized-intrinsic-function")]
 pub struct UnrecognizedIntrinsicFunction {
-    #[message = "unrecognized intrinsic function: `{name}`"]
-    #[label = "unrecognized intrinsic"]
+    #[primary_span]
+    #[label]
     pub span: Span,
     pub name: Symbol,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0195"]
+#[error(code = "E0195", slug = "typeck-lifetimes-or-bounds-mismatch-on-trait")]
 pub struct LifetimesOrBoundsMismatchOnTrait {
-    #[message = "lifetime parameters or bounds on {item_kind} `{ident}` do not match the trait declaration"]
-    #[label = "lifetimes do not match {item_kind} in trait"]
+    #[primary_span]
+    #[label]
     pub span: Span,
-    #[label = "lifetimes in impl do not match this {item_kind} in trait"]
+    #[label = "generics-label"]
     pub generics_span: Option<Span>,
     pub item_kind: &'static str,
     pub ident: Ident,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0120"]
+#[error(code = "E0120", slug = "typeck-drop-impl-on-wrong-item")]
 pub struct DropImplOnWrongItem {
-    #[message = "the `Drop` trait may only be implemented for structs, enums, and unions"]
-    #[label = "must be a struct, enum, or union"]
+    #[primary_span]
+    #[label]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0124"]
+#[error(code = "E0124", slug = "typeck-field-already-declared")]
 pub struct FieldAlreadyDeclared {
     pub field_name: Ident,
-    #[message = "field `{field_name}` is already declared"]
-    #[label = "field already declared"]
+    #[primary_span]
+    #[label]
     pub span: Span,
-    #[label = "`{field_name}` first declared here"]
+    #[label = "previous-decl-label"]
     pub prev_span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0184"]
+#[error(code = "E0184", slug = "typeck-copy-impl-on-type-with-dtor")]
 pub struct CopyImplOnTypeWithDtor {
-    #[message = "the trait `Copy` may not be implemented for this type; the \
-                              type has a destructor"]
-    #[label = "Copy not allowed on types with destructors"]
+    #[primary_span]
+    #[label]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0203"]
+#[error(code = "E0203", slug = "typeck-multiple-relaxed-default-bounds")]
 pub struct MultipleRelaxedDefaultBounds {
-    #[message = "type parameter has more than one relaxed default bound, only one is supported"]
+    #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0206"]
+#[error(code = "E0206", slug = "typeck-copy-impl-on-non-adt")]
 pub struct CopyImplOnNonAdt {
-    #[message = "the trait `Copy` may not be implemented for this type"]
-    #[label = "type is not a structure or enumeration"]
+    #[primary_span]
+    #[label]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0224"]
+#[error(code = "E0224", slug = "typeck-trait-object-declared-with-no-traits")]
 pub struct TraitObjectDeclaredWithNoTraits {
-    #[message = "at least one trait is required for an object type"]
+    #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0227"]
+#[error(code = "E0227", slug = "typeck-ambiguous-lifetime-bound")]
 pub struct AmbiguousLifetimeBound {
-    #[message = "ambiguous lifetime bound, explicit lifetime bound required"]
+    #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0229"]
+#[error(code = "E0229", slug = "typeck-assoc-type-binding-not-allowed")]
 pub struct AssocTypeBindingNotAllowed {
-    #[message = "associated type bindings are not allowed here"]
-    #[label = "associated type not allowed here"]
+    #[primary_span]
+    #[label]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0436"]
+#[error(code = "E0436", slug = "typeck-functional-record-update-on-non-struct")]
 pub struct FunctionalRecordUpdateOnNonStruct {
-    #[message = "functional record update syntax requires a struct"]
+    #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0516"]
+#[error(code = "E0516", slug = "typeck-typeof-reserved-keyword-used")]
 pub struct TypeofReservedKeywordUsed {
-    #[message = "`typeof` is a reserved keyword but unimplemented"]
-    #[label = "reserved keyword"]
+    #[primary_span]
+    #[label]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0572"]
+#[error(code = "E0572", slug = "typeck-return-stmt-outside-of-fn-body")]
 pub struct ReturnStmtOutsideOfFnBody {
-    #[message = "return statement outside of function body"]
+    #[primary_span]
     pub span: Span,
-    #[label = "the return is part of this body..."]
+    #[label = "encl-body-label"]
     pub encl_body_span: Option<Span>,
-    #[label = "...not the enclosing function body"]
+    #[label = "encl-fn-label"]
     pub encl_fn_span: Option<Span>,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0627"]
+#[error(code = "E0627", slug = "typeck-yield-expr-outside-of-generator")]
 pub struct YieldExprOutsideOfGenerator {
-    #[message = "yield expression outside of generator literal"]
+    #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0639"]
+#[error(code = "E0639", slug = "typeck-struct-expr-non-exhaustive")]
 pub struct StructExprNonExhaustive {
-    #[message = "cannot create non-exhaustive {what} using struct expression"]
+    #[primary_span]
     pub span: Span,
     pub what: &'static str,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0699"]
+#[error(code = "E0699", slug = "typeck-method-call-on-unknown-type")]
 pub struct MethodCallOnUnknownType {
-    #[message = "the type of this value must be known to call a method on a raw pointer on it"]
+    #[primary_span]
     pub span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0719"]
+#[error(code = "E0719", slug = "typeck-value-of-associated-struct-already-specified")]
 pub struct ValueOfAssociatedStructAlreadySpecified {
-    #[message = "the value of the associated type `{item_name}` (from trait `{def_path}`) is already specified"]
-    #[label = "re-bound here"]
+    #[primary_span]
+    #[label]
     pub span: Span,
-    #[label = "`{item_name}` bound here first"]
+    #[label = "previous-bound-label"]
     pub prev_span: Span,
     pub item_name: Ident,
     pub def_path: String,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0745"]
+#[error(code = "E0745", slug = "typeck-address-of-temporary-taken")]
 pub struct AddressOfTemporaryTaken {
-    #[message = "cannot take address of a temporary"]
-    #[label = "temporary value"]
+    #[primary_span]
+    #[label]
     pub span: Span,
 }
diff --git a/compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs b/compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs
index a117ed68ff7..24b6639d7f2 100644
--- a/compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs
+++ b/compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs
@@ -1,13 +1,14 @@
 use crate::structured_errors::StructuredDiagnostic;
 use rustc_errors::{
     pluralize, Applicability, Diagnostic, DiagnosticBuilder, DiagnosticId, ErrorGuaranteed,
+    MultiSpan,
 };
 use rustc_hir as hir;
 use rustc_middle::hir::map::fn_sig;
 use rustc_middle::middle::resolve_lifetime::LifetimeScopeForPath;
 use rustc_middle::ty::{self as ty, TyCtxt};
 use rustc_session::Session;
-use rustc_span::{def_id::DefId, MultiSpan};
+use rustc_span::def_id::DefId;
 
 use GenericArgsInfo::*;
 
diff --git a/src/bootstrap/bin/rustdoc.rs b/src/bootstrap/bin/rustdoc.rs
index ad3800834b0..4bba2eb4507 100644
--- a/src/bootstrap/bin/rustdoc.rs
+++ b/src/bootstrap/bin/rustdoc.rs
@@ -15,6 +15,10 @@ fn main() {
     let libdir = env::var_os("RUSTDOC_LIBDIR").expect("RUSTDOC_LIBDIR was not set");
     let sysroot = env::var_os("RUSTC_SYSROOT").expect("RUSTC_SYSROOT was not set");
 
+    // Detect whether or not we're a build script depending on whether --target
+    // is passed (a bit janky...)
+    let target = args.windows(2).find(|w| &*w[0] == "--target").and_then(|w| w[1].to_str());
+
     use std::str::FromStr;
 
     let verbose = match env::var("RUSTC_VERBOSE") {
@@ -26,10 +30,18 @@ fn main() {
     dylib_path.insert(0, PathBuf::from(libdir.clone()));
 
     let mut cmd = Command::new(rustdoc);
-    cmd.args(&args)
-        .arg("--sysroot")
-        .arg(&sysroot)
-        .env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
+
+    if target.is_some() {
+        // The stage0 compiler has a special sysroot distinct from what we
+        // actually downloaded, so we just always pass the `--sysroot` option,
+        // unless one is already set.
+        if !args.iter().any(|arg| arg == "--sysroot") {
+            cmd.arg("--sysroot").arg(&sysroot);
+        }
+    }
+
+    cmd.args(&args);
+    cmd.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
 
     // Force all crates compiled by this compiler to (a) be unstable and (b)
     // allow the `rustc_private` feature to link to other unstable crates
diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py
index 71b8f3c4553..0b6bdf47419 100644
--- a/src/bootstrap/bootstrap.py
+++ b/src/bootstrap/bootstrap.py
@@ -678,7 +678,7 @@ class RustBuild(object):
             # The latter one does not exist on NixOS when using tmpfs as root.
             try:
                 with open("/etc/os-release", "r") as f:
-                    if not any(line.strip() == "ID=nixos" for line in f):
+                    if not any(l.strip() in ["ID=nixos", "ID='nixos'", 'ID="nixos"'] for l in f):
                         return
             except FileNotFoundError:
                 return
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index bd64e2b03ce..a32b9caa30f 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -143,6 +143,8 @@ crate fn new_handler(
     source_map: Option<Lrc<source_map::SourceMap>>,
     debugging_opts: &DebuggingOptions,
 ) -> rustc_errors::Handler {
+    let fallback_bundle =
+        rustc_errors::fallback_fluent_bundle(false).expect("failed to load fallback fluent bundle");
     let emitter: Box<dyn Emitter + sync::Send> = match error_format {
         ErrorOutputType::HumanReadable(kind) => {
             let (short, color_config) = kind.unzip();
@@ -150,6 +152,8 @@ crate fn new_handler(
                 EmitterWriter::stderr(
                     color_config,
                     source_map.map(|sm| sm as _),
+                    None,
+                    fallback_bundle,
                     short,
                     debugging_opts.teach,
                     debugging_opts.terminal_width,
@@ -166,6 +170,8 @@ crate fn new_handler(
                 JsonEmitter::stderr(
                     None,
                     source_map,
+                    None,
+                    fallback_bundle,
                     pretty,
                     json_rendered,
                     debugging_opts.terminal_width,
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index c4201e22212..63b744133a2 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -537,12 +537,31 @@ crate fn make_test(
             // Any errors in parsing should also appear when the doctest is compiled for real, so just
             // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
             let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
-            supports_color =
-                EmitterWriter::stderr(ColorConfig::Auto, None, false, false, Some(80), false)
-                    .supports_color();
-
-            let emitter =
-                EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
+            let fallback_bundle = rustc_errors::fallback_fluent_bundle(false)
+                .expect("failed to load fallback fluent bundle");
+            supports_color = EmitterWriter::stderr(
+                ColorConfig::Auto,
+                None,
+                None,
+                fallback_bundle.clone(),
+                false,
+                false,
+                Some(80),
+                false,
+            )
+            .supports_color();
+
+            let emitter = EmitterWriter::new(
+                box io::sink(),
+                None,
+                None,
+                fallback_bundle,
+                false,
+                false,
+                false,
+                None,
+                false,
+            );
 
             // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
             let handler = Handler::with_emitter(false, None, box emitter);
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index f59222b780d..1d7a790bdb7 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -771,6 +771,7 @@ fn main_options(options: config::Options) -> MainResult {
     let externs = options.externs.clone();
     let render_options = options.render_options.clone();
     let scrape_examples_options = options.scrape_examples_options.clone();
+    let document_private = options.render_options.document_private;
     let config = core::create_config(options);
 
     interface::create_compiler_and_run(config, |compiler| {
@@ -791,7 +792,12 @@ fn main_options(options: config::Options) -> MainResult {
             let (resolver, resolver_caches) = {
                 let (krate, resolver, _) = &*abort_on_err(queries.expansion(), sess).peek();
                 let resolver_caches = resolver.borrow_mut().access(|resolver| {
-                    collect_intra_doc_links::early_resolve_intra_doc_links(resolver, krate, externs)
+                    collect_intra_doc_links::early_resolve_intra_doc_links(
+                        resolver,
+                        krate,
+                        externs,
+                        document_private,
+                    )
                 });
                 (resolver.clone(), resolver_caches)
             };
diff --git a/src/librustdoc/passes/check_code_block_syntax.rs b/src/librustdoc/passes/check_code_block_syntax.rs
index 8d9b3377a69..465dd523ff4 100644
--- a/src/librustdoc/passes/check_code_block_syntax.rs
+++ b/src/librustdoc/passes/check_code_block_syntax.rs
@@ -32,7 +32,9 @@ struct SyntaxChecker<'a, 'tcx> {
 impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
     fn check_rust_syntax(&self, item: &clean::Item, dox: &str, code_block: RustCodeBlock) {
         let buffer = Lrc::new(Lock::new(Buffer::default()));
-        let emitter = BufferEmitter { buffer: Lrc::clone(&buffer) };
+        let fallback_bundle = rustc_errors::fallback_fluent_bundle(false)
+            .expect("failed to load fallback fluent bundle");
+        let emitter = BufferEmitter { buffer: Lrc::clone(&buffer), fallback_bundle };
 
         let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
         let handler = Handler::with_emitter(false, None, Box::new(emitter));
@@ -171,12 +173,14 @@ struct Buffer {
 
 struct BufferEmitter {
     buffer: Lrc<Lock<Buffer>>,
+    fallback_bundle: Lrc<rustc_errors::FluentBundle>,
 }
 
 impl Emitter for BufferEmitter {
     fn emit_diagnostic(&mut self, diag: &Diagnostic) {
         let mut buffer = self.buffer.borrow_mut();
-        buffer.messages.push(format!("error from rustc: {}", diag.message[0].0));
+        // FIXME(davidtwco): need to support translation here eventually
+        buffer.messages.push(format!("error from rustc: {}", diag.message[0].0.expect_str()));
         if diag.is_error() {
             buffer.has_errors = true;
         }
@@ -185,4 +189,12 @@ impl Emitter for BufferEmitter {
     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
         None
     }
+
+    fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
+        None
+    }
+
+    fn fallback_fluent_bundle(&self) -> &Lrc<rustc_errors::FluentBundle> {
+        &self.fallback_bundle
+    }
 }
diff --git a/src/librustdoc/passes/collect_intra_doc_links/early.rs b/src/librustdoc/passes/collect_intra_doc_links/early.rs
index 39900270ccb..44bf86b082a 100644
--- a/src/librustdoc/passes/collect_intra_doc_links/early.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links/early.rs
@@ -22,6 +22,7 @@ crate fn early_resolve_intra_doc_links(
     resolver: &mut Resolver<'_>,
     krate: &ast::Crate,
     externs: Externs,
+    document_private_items: bool,
 ) -> ResolverCaches {
     let mut loader = IntraLinkCrateLoader {
         resolver,
@@ -30,6 +31,7 @@ crate fn early_resolve_intra_doc_links(
         traits_in_scope: Default::default(),
         all_traits: Default::default(),
         all_trait_impls: Default::default(),
+        document_private_items,
     };
 
     // Overridden `visit_item` below doesn't apply to the crate root,
@@ -61,6 +63,7 @@ struct IntraLinkCrateLoader<'r, 'ra> {
     traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
     all_traits: Vec<DefId>,
     all_trait_impls: Vec<DefId>,
+    document_private_items: bool,
 }
 
 impl IntraLinkCrateLoader<'_, '_> {
@@ -167,7 +170,7 @@ impl IntraLinkCrateLoader<'_, '_> {
         }
 
         for child in self.resolver.module_children_or_reexports(module_id) {
-            if child.vis == Visibility::Public {
+            if child.vis == Visibility::Public || self.document_private_items {
                 if let Some(def_id) = child.res.opt_def_id() {
                     self.add_traits_in_parent_scope(def_id);
                 }
diff --git a/src/test/run-make/translation/Makefile b/src/test/run-make/translation/Makefile
new file mode 100644
index 00000000000..22a3bf57ecf
--- /dev/null
+++ b/src/test/run-make/translation/Makefile
@@ -0,0 +1,33 @@
+include ../../run-make-fulldeps/tools.mk
+
+# This test uses `ln -s` rather than copying to save testing time, but its
+# usage doesn't work on Windows.
+# ignore-windows
+
+SYSROOT:=$(shell $(RUSTC) --print sysroot)
+FAKEROOT=$(TMPDIR)/fakeroot
+
+all: normal custom sysroot
+
+normal: basic-translation.rs
+	$(RUSTC) $< 2>&1 | grep "struct literal body without path"
+
+custom: basic-translation.rs basic-translation.ftl
+	$(RUSTC) $< -Ztranslate-additional-ftl=$(CURDIR)/basic-translation.ftl 2>&1 | grep "this is a test message"
+
+# Make a local copy of the sysroot and add the custom locale to it.
+sysroot: basic-translation.rs basic-translation.ftl
+	mkdir $(FAKEROOT)
+	ln -s $(SYSROOT)/* $(FAKEROOT)
+	rm -f $(FAKEROOT)/lib
+	mkdir $(FAKEROOT)/lib
+	ln -s $(SYSROOT)/lib/* $(FAKEROOT)/lib
+	rm -f $(FAKEROOT)/lib/rustlib
+	mkdir $(FAKEROOT)/lib/rustlib
+	ln -s $(SYSROOT)/lib/rustlib/* $(FAKEROOT)/lib/rustlib
+	rm -f $(FAKEROOT)/lib/rustlib/src
+	mkdir $(FAKEROOT)/lib/rustlib/src
+	ln -s $(SYSROOT)/lib/rustlib/src/* $(FAKEROOT)/lib/rustlib/src
+	mkdir -p $(FAKEROOT)/share/locale/zh-CN/
+	ln -s $(CURDIR)/basic-translation.ftl $(FAKEROOT)/share/locale/zh-CN/basic-translation.ftl
+	$(RUSTC) $< --sysroot $(FAKEROOT) -Ztranslate-lang=zh-CN 2>&1 | grep "this is a test message"
diff --git a/src/test/run-make/translation/basic-translation.ftl b/src/test/run-make/translation/basic-translation.ftl
new file mode 100644
index 00000000000..4681b879cda
--- /dev/null
+++ b/src/test/run-make/translation/basic-translation.ftl
@@ -0,0 +1,2 @@
+parser-struct-literal-body-without-path = this is a test message
+    .suggestion = this is a test suggestion
diff --git a/src/test/run-make/translation/basic-translation.rs b/src/test/run-make/translation/basic-translation.rs
new file mode 100644
index 00000000000..b8f5bff3153
--- /dev/null
+++ b/src/test/run-make/translation/basic-translation.rs
@@ -0,0 +1,18 @@
+// Exact error being tested isn't relevant, it just needs to be known that it uses Fluent-backed
+// diagnostics.
+
+struct Foo {
+    val: (),
+}
+
+fn foo() -> Foo {
+    val: (),
+}
+
+fn main() {
+    let x = foo();
+    x.val == 42;
+    let x = {
+        val: (),
+    };
+}
diff --git a/src/test/rustdoc/issue-95633.rs b/src/test/rustdoc/issue-95633.rs
new file mode 100644
index 00000000000..a71d0a03731
--- /dev/null
+++ b/src/test/rustdoc/issue-95633.rs
@@ -0,0 +1,7 @@
+// compile-flags: --document-private-items
+
+// This ensures that no ICE is triggered when rustdoc is run on this code.
+
+mod stdlib {
+    pub (crate) use std::i8;
+}
diff --git a/src/test/ui-fulldeps/session-derive-errors.rs b/src/test/ui-fulldeps/session-derive-errors.rs
index 140aaad3b38..adec548b390 100644
--- a/src/test/ui-fulldeps/session-derive-errors.rs
+++ b/src/test/ui-fulldeps/session-derive-errors.rs
@@ -11,8 +11,8 @@
 #![crate_type = "lib"]
 
 extern crate rustc_span;
-use rustc_span::Span;
 use rustc_span::symbol::Ident;
+use rustc_span::Span;
 
 extern crate rustc_macros;
 use rustc_macros::SessionDiagnostic;
@@ -26,12 +26,15 @@ use rustc_errors::Applicability;
 extern crate rustc_session;
 
 #[derive(SessionDiagnostic)]
-#[message = "Hello, world!"]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "hello-world")]
 struct Hello {}
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[warning(code = "E0123", slug = "hello-world")]
+struct HelloWarn {}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
 //~^ ERROR `#[derive(SessionDiagnostic)]` can only be used on structs
 enum SessionDiagnosticOnEnum {
     Foo,
@@ -39,228 +42,387 @@ enum SessionDiagnosticOnEnum {
 }
 
 #[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
 #[error = "E0123"]
-#[label = "This is in the wrong place"]
-//~^ ERROR `#[label = ...]` is not a valid SessionDiagnostic struct attribute
-struct WrongPlace {}
+//~^ ERROR `#[error = ...]` is not a valid `SessionDiagnostic` struct attribute
+struct WrongStructAttrStyle {}
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[nonsense(code = "E0123", slug = "foo")]
+//~^ ERROR `#[nonsense(...)]` is not a valid `SessionDiagnostic` struct attribute
+//~^^ ERROR diagnostic kind not specified
+//~^^^ ERROR cannot find attribute `nonsense` in this scope
+struct InvalidStructAttr {}
+
+#[derive(SessionDiagnostic)]
+#[error("E0123")]
+//~^ ERROR `#[error("...")]` is not a valid `SessionDiagnostic` struct attribute
+//~^^ ERROR `slug` not specified
+struct InvalidLitNestedAttr {}
+
+#[derive(SessionDiagnostic)]
+#[error(nonsense, code = "E0123", slug = "foo")]
+//~^ ERROR `#[error(nonsense)]` is not a valid `SessionDiagnostic` struct attribute
+struct InvalidNestedStructAttr {}
+
+#[derive(SessionDiagnostic)]
+#[error(nonsense("foo"), code = "E0123", slug = "foo")]
+//~^ ERROR `#[error(nonsense(...))]` is not a valid `SessionDiagnostic` struct attribute
+struct InvalidNestedStructAttr1 {}
+
+#[derive(SessionDiagnostic)]
+#[error(nonsense = "...", code = "E0123", slug = "foo")]
+//~^ ERROR `#[error(nonsense = ...)]` is not a valid `SessionDiagnostic` struct attribute
+struct InvalidNestedStructAttr2 {}
+
+#[derive(SessionDiagnostic)]
+#[error(nonsense = 4, code = "E0123", slug = "foo")]
+//~^ ERROR `#[error(nonsense = ...)]` is not a valid `SessionDiagnostic` struct attribute
+struct InvalidNestedStructAttr3 {}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
 struct WrongPlaceField {
-    #[suggestion = "this is the wrong kind of attribute"]
-//~^ ERROR `#[suggestion = ...]` is not a valid SessionDiagnostic field attribute
+    #[suggestion = "bar"]
+    //~^ ERROR `#[suggestion = ...]` is not a valid `SessionDiagnostic` field attribute
     sp: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[message = "Hello, world!"]
-#[error = "E0123"]
-#[error = "E0456"] //~ ERROR `error` specified multiple times
+#[error(code = "E0123", slug = "foo")]
+#[error(code = "E0456", slug = "bar")] //~ ERROR `error` specified multiple times
 struct ErrorSpecifiedTwice {}
 
 #[derive(SessionDiagnostic)]
-#[message = "Hello, world!"]
-#[error = "E0123"]
-#[lint = "some_useful_lint"] //~ ERROR `lint` specified when `error` was already specified
-struct LintSpecifiedAfterError {}
+#[error(code = "E0123", slug = "foo")]
+#[warning(code = "E0293", slug = "bar")]
+//~^ ERROR `warning` specified when `error` was already specified
+struct WarnSpecifiedAfterError {}
 
 #[derive(SessionDiagnostic)]
-#[message = "Some lint message"]
-#[error = "E0123"]
-struct LintButHasErrorCode {}
+#[error(code = "E0456", code = "E0457", slug = "bar")] //~ ERROR `code` specified multiple times
+struct CodeSpecifiedTwice {}
 
 #[derive(SessionDiagnostic)]
-struct ErrorCodeNotProvided {} //~ ERROR `code` not specified
+#[error(code = "E0456", slug = "foo", slug = "bar")] //~ ERROR `slug` specified multiple times
+struct SlugSpecifiedTwice {}
 
-// FIXME: Uncomment when emitting lints is supported.
-/*
 #[derive(SessionDiagnostic)]
-#[message = "Hello, world!"]
-#[lint = "clashing_extern_declarations"]
-#[lint = "improper_ctypes"] // FIXME: ERROR `lint` specified multiple times
-struct LintSpecifiedTwice {}
+struct KindNotProvided {} //~ ERROR diagnostic kind not specified
 
 #[derive(SessionDiagnostic)]
-#[lint = "Some lint message"]
-#[message = "Some error message"]
-#[error = "E0123"] // ERROR `error` specified when `lint` was already specified
-struct ErrorSpecifiedAfterLint {}
-*/
+#[error(code = "E0456")] //~ ERROR `slug` not specified
+struct SlugNotProvided {}
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(slug = "foo")]
+struct CodeNotProvided {}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+struct MessageWrongType {
+    #[primary_span]
+    //~^ ERROR `#[primary_span]` attribute can only be applied to fields of type `Span`
+    foo: String,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+struct InvalidPathFieldAttr {
+    #[nonsense]
+    //~^ ERROR `#[nonsense]` is not a valid `SessionDiagnostic` field attribute
+    //~^^ ERROR cannot find attribute `nonsense` in this scope
+    foo: String,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
 struct ErrorWithField {
     name: String,
-    #[message = "This error has a field, and references {name}"]
-    span: Span
+    #[label = "bar"]
+    span: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct ErrorWithMessageAppliedToField {
-    #[message = "this message is applied to a String field"]
-    //~^ ERROR the `#[message = "..."]` attribute can only be applied to fields of type Span
+    #[label = "bar"]
+    //~^ ERROR the `#[label = ...]` attribute can only be applied to fields of type `Span`
     name: String,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
-#[message = "This error has a field, and references {name}"]
-//~^ ERROR `name` doesn't refer to a field on this type
+#[error(code = "E0123", slug = "foo")]
 struct ErrorWithNonexistentField {
-    span: Span
+    #[suggestion(message = "bar", code = "{name}")]
+    //~^ ERROR `name` doesn't refer to a field on this type
+    suggestion: (Span, Applicability),
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
-#[message = "This is missing a closing brace: {name"]
 //~^ ERROR invalid format string: expected `'}'`
+#[error(code = "E0123", slug = "foo")]
 struct ErrorMissingClosingBrace {
+    #[suggestion(message = "bar", code = "{name")]
+    suggestion: (Span, Applicability),
     name: String,
-    span: Span
+    val: usize,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
-#[message = "This is missing an opening brace: name}"]
 //~^ ERROR invalid format string: unmatched `}`
+#[error(code = "E0123", slug = "foo")]
 struct ErrorMissingOpeningBrace {
+    #[suggestion(message = "bar", code = "name}")]
+    suggestion: (Span, Applicability),
     name: String,
-    span: Span
+    val: usize,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
-#[message = "Something something"]
+#[error(code = "E0123", slug = "foo")]
 struct LabelOnSpan {
-    #[label = "See here"]
-    sp: Span
+    #[label = "bar"]
+    sp: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
-#[message = "Something something"]
+#[error(code = "E0123", slug = "foo")]
 struct LabelOnNonSpan {
-    #[label = "See here"]
-    //~^ ERROR The `#[label = ...]` attribute can only be applied to fields of type Span
+    #[label = "bar"]
+    //~^ ERROR the `#[label = ...]` attribute can only be applied to fields of type `Span`
     id: u32,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct Suggest {
-    #[suggestion(message = "This is a suggestion", code = "This is the suggested code")]
-    #[suggestion_short(message = "This is a suggestion", code = "This is the suggested code")]
-    #[suggestion_hidden(message = "This is a suggestion", code = "This is the suggested code")]
-    #[suggestion_verbose(message = "This is a suggestion", code = "This is the suggested code")]
+    #[suggestion(message = "bar", code = "This is the suggested code")]
+    #[suggestion_short(message = "qux", code = "This is the suggested code")]
+    #[suggestion_hidden(message = "foobar", code = "This is the suggested code")]
+    #[suggestion_verbose(message = "fooqux", code = "This is the suggested code")]
     suggestion: (Span, Applicability),
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct SuggestWithoutCode {
-    #[suggestion(message = "This is a suggestion")]
+    #[suggestion(message = "bar")]
     suggestion: (Span, Applicability),
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct SuggestWithBadKey {
-    #[suggestion(nonsense = "This is nonsense")]
-    //~^ ERROR `nonsense` is not a valid key for `#[suggestion(...)]`
+    #[suggestion(nonsense = "bar")]
+    //~^ ERROR `#[suggestion(nonsense = ...)]` is not a valid `SessionDiagnostic` field attribute
     suggestion: (Span, Applicability),
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct SuggestWithShorthandMsg {
-    #[suggestion(msg = "This is a suggestion")]
-    //~^ ERROR `msg` is not a valid key for `#[suggestion(...)]`
+    #[suggestion(msg = "bar")]
+    //~^ ERROR `#[suggestion(msg = ...)]` is not a valid `SessionDiagnostic` field attribute
     suggestion: (Span, Applicability),
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct SuggestWithoutMsg {
-    #[suggestion(code = "This is suggested code")]
-    //~^ ERROR missing suggestion message
+    #[suggestion(code = "bar")]
     suggestion: (Span, Applicability),
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct SuggestWithTypesSwapped {
-    #[suggestion(message = "This is a message", code = "This is suggested code")]
+    #[suggestion(message = "bar", code = "This is suggested code")]
     suggestion: (Applicability, Span),
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct SuggestWithWrongTypeApplicabilityOnly {
-    #[suggestion(message = "This is a message", code = "This is suggested code")]
+    #[suggestion(message = "bar", code = "This is suggested code")]
     //~^ ERROR wrong field type for suggestion
     suggestion: Applicability,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
-struct SuggestWithSpanOnly{
-    #[suggestion(message = "This is a message", code = "This is suggested code")]
+#[error(code = "E0123", slug = "foo")]
+struct SuggestWithSpanOnly {
+    #[suggestion(message = "bar", code = "This is suggested code")]
     suggestion: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct SuggestWithDuplicateSpanAndApplicability {
-    #[suggestion(message = "This is a message", code = "This is suggested code")]
-    //~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one Span
+    #[suggestion(message = "bar", code = "This is suggested code")]
+    //~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one `Span`
     suggestion: (Span, Span, Applicability),
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct SuggestWithDuplicateApplicabilityAndSpan {
-    #[suggestion(message = "This is a message", code = "This is suggested code")]
+    #[suggestion(message = "bar", code = "This is suggested code")]
     //~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one
     suggestion: (Applicability, Applicability, Span),
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct WrongKindOfAnnotation {
-    #[label("wrong kind of annotation for label")]
-    //~^ ERROR invalid annotation list `#[label(...)]`
+    #[label("bar")]
+    //~^ ERROR `#[label(...)]` is not a valid `SessionDiagnostic` field attribute
     z: Span,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
-#[message = "Something something else"]
+#[error(code = "E0123", slug = "foo")]
 struct OptionsInErrors {
-    #[label = "Label message"]
+    #[label = "bar"]
     label: Option<Span>,
-    #[suggestion(message = "suggestion message")]
+    #[suggestion(message = "bar")]
     opt_sugg: Option<(Span, Applicability)>,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0456"]
+#[error(code = "E0456", slug = "foo")]
 struct MoveOutOfBorrowError<'tcx> {
     name: Ident,
     ty: Ty<'tcx>,
-    #[message = "cannot move {ty} out of borrow"]
-    #[label = "cannot move out of borrow"]
+    #[primary_span]
+    #[label = "bar"]
     span: Span,
-    #[label = "`{ty}` first borrowed here"]
+    #[label = "qux"]
     other_span: Span,
-    #[suggestion(message = "consider cloning here", code = "{name}.clone()")]
+    #[suggestion(message = "bar", code = "{name}.clone()")]
     opt_sugg: Option<(Span, Applicability)>,
 }
 
 #[derive(SessionDiagnostic)]
-#[error = "E0123"]
+#[error(code = "E0123", slug = "foo")]
 struct ErrorWithLifetime<'a> {
-    #[message = "Some message that references {name}"]
+    #[label = "bar"]
     span: Span,
     name: &'a str,
 }
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+struct ErrorWithDefaultLabelAttr<'a> {
+    #[label]
+    span: Span,
+    name: &'a str,
+}
+
+#[derive(SessionDiagnostic)]
+//~^ ERROR no method named `into_diagnostic_arg` found for struct `Hello` in the current scope
+#[error(code = "E0123", slug = "foo")]
+struct ArgFieldWithoutSkip {
+    #[primary_span]
+    span: Span,
+    other: Hello,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+struct ArgFieldWithSkip {
+    #[primary_span]
+    span: Span,
+    // `Hello` does not implement `IntoDiagnosticArg` so this would result in an error if
+    // not for `#[skip_arg]`.
+    #[skip_arg]
+    other: Hello,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+struct ErrorWithSpannedNote {
+    #[note]
+    span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+struct ErrorWithSpannedNoteCustom {
+    #[note = "bar"]
+    span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+#[note]
+struct ErrorWithNote {
+    val: String,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+#[note = "bar"]
+struct ErrorWithNoteCustom {
+    val: String,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+struct ErrorWithSpannedHelp {
+    #[help]
+    span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+struct ErrorWithSpannedHelpCustom {
+    #[help = "bar"]
+    span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+#[help]
+struct ErrorWithHelp {
+    val: String,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(code = "E0123", slug = "foo")]
+#[help = "bar"]
+struct ErrorWithHelpCustom {
+    val: String,
+}
+
+#[derive(SessionDiagnostic)]
+#[help]
+//~^ ERROR `#[help]` must come after `#[error(..)]` or `#[warn(..)]`
+#[error(code = "E0123", slug = "foo")]
+struct ErrorWithHelpWrongOrder {
+    val: String,
+}
+
+#[derive(SessionDiagnostic)]
+#[help = "bar"]
+//~^ ERROR `#[help = ...]` must come after `#[error(..)]` or `#[warn(..)]`
+#[error(code = "E0123", slug = "foo")]
+struct ErrorWithHelpCustomWrongOrder {
+    val: String,
+}
+
+#[derive(SessionDiagnostic)]
+#[note]
+//~^ ERROR `#[note]` must come after `#[error(..)]` or `#[warn(..)]`
+#[error(code = "E0123", slug = "foo")]
+struct ErrorWithNoteWrongOrder {
+    val: String,
+}
+
+#[derive(SessionDiagnostic)]
+#[note = "bar"]
+//~^ ERROR `#[note = ...]` must come after `#[error(..)]` or `#[warn(..)]`
+#[error(code = "E0123", slug = "foo")]
+struct ErrorWithNoteCustomWrongOrder {
+    val: String,
+}
diff --git a/src/test/ui-fulldeps/session-derive-errors.stderr b/src/test/ui-fulldeps/session-derive-errors.stderr
index c7853f5275e..a528ae1607f 100644
--- a/src/test/ui-fulldeps/session-derive-errors.stderr
+++ b/src/test/ui-fulldeps/session-derive-errors.stderr
@@ -1,7 +1,7 @@
 error: `#[derive(SessionDiagnostic)]` can only be used on structs
-  --> $DIR/session-derive-errors.rs:34:1
+  --> $DIR/session-derive-errors.rs:37:1
    |
-LL | / #[error = "E0123"]
+LL | / #[error(code = "E0123", slug = "foo")]
 LL | |
 LL | | enum SessionDiagnosticOnEnum {
 LL | |     Foo,
@@ -9,132 +9,285 @@ LL | |     Bar,
 LL | | }
    | |_^
 
-error: `#[label = ...]` is not a valid SessionDiagnostic struct attribute
-  --> $DIR/session-derive-errors.rs:43:1
+error: `#[error = ...]` is not a valid `SessionDiagnostic` struct attribute
+  --> $DIR/session-derive-errors.rs:46:1
    |
-LL | #[label = "This is in the wrong place"]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[error = "E0123"]
+   | ^^^^^^^^^^^^^^^^^^
+
+error: `#[nonsense(...)]` is not a valid `SessionDiagnostic` struct attribute
+  --> $DIR/session-derive-errors.rs:51:1
+   |
+LL | #[nonsense(code = "E0123", slug = "foo")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: diagnostic kind not specified
+  --> $DIR/session-derive-errors.rs:51:1
+   |
+LL | / #[nonsense(code = "E0123", slug = "foo")]
+LL | |
+LL | |
+LL | |
+LL | | struct InvalidStructAttr {}
+   | |___________________________^
+   |
+   = help: use the `#[error(...)]` attribute to create an error
+
+error: `#[error("...")]` is not a valid `SessionDiagnostic` struct attribute
+  --> $DIR/session-derive-errors.rs:58:9
+   |
+LL | #[error("E0123")]
+   |         ^^^^^^^
+
+error: `slug` not specified
+  --> $DIR/session-derive-errors.rs:58:1
+   |
+LL | / #[error("E0123")]
+LL | |
+LL | |
+LL | | struct InvalidLitNestedAttr {}
+   | |______________________________^
+   |
+   = help: use the `#[error(slug = "...")]` attribute to set this diagnostic's slug
+
+error: `#[error(nonsense)]` is not a valid `SessionDiagnostic` struct attribute
+  --> $DIR/session-derive-errors.rs:64:9
+   |
+LL | #[error(nonsense, code = "E0123", slug = "foo")]
+   |         ^^^^^^^^
+
+error: `#[error(nonsense(...))]` is not a valid `SessionDiagnostic` struct attribute
+  --> $DIR/session-derive-errors.rs:69:9
+   |
+LL | #[error(nonsense("foo"), code = "E0123", slug = "foo")]
+   |         ^^^^^^^^^^^^^^^
+
+error: `#[error(nonsense = ...)]` is not a valid `SessionDiagnostic` struct attribute
+  --> $DIR/session-derive-errors.rs:74:9
+   |
+LL | #[error(nonsense = "...", code = "E0123", slug = "foo")]
+   |         ^^^^^^^^^^^^^^^^
 
-error: `#[suggestion = ...]` is not a valid SessionDiagnostic field attribute
-  --> $DIR/session-derive-errors.rs:50:5
+error: `#[error(nonsense = ...)]` is not a valid `SessionDiagnostic` struct attribute
+  --> $DIR/session-derive-errors.rs:79:9
    |
-LL |     #[suggestion = "this is the wrong kind of attribute"]
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[error(nonsense = 4, code = "E0123", slug = "foo")]
+   |         ^^^^^^^^^^^^
+   |
+   = help: value must be a string
+
+error: `#[suggestion = ...]` is not a valid `SessionDiagnostic` field attribute
+  --> $DIR/session-derive-errors.rs:86:5
+   |
+LL |     #[suggestion = "bar"]
+   |     ^^^^^^^^^^^^^^^^^^^^^
 
 error: `error` specified multiple times
-  --> $DIR/session-derive-errors.rs:58:11
+  --> $DIR/session-derive-errors.rs:93:1
+   |
+LL | #[error(code = "E0456", slug = "bar")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/session-derive-errors.rs:92:1
+   |
+LL | #[error(code = "E0123", slug = "foo")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: `warning` specified when `error` was already specified
+  --> $DIR/session-derive-errors.rs:98:1
+   |
+LL | #[warning(code = "E0293", slug = "bar")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/session-derive-errors.rs:97:1
+   |
+LL | #[error(code = "E0123", slug = "foo")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: `code` specified multiple times
+  --> $DIR/session-derive-errors.rs:103:32
    |
-LL | #[error = "E0456"]
-   |           ^^^^^^^
+LL | #[error(code = "E0456", code = "E0457", slug = "bar")]
+   |                                ^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/session-derive-errors.rs:103:16
+   |
+LL | #[error(code = "E0456", code = "E0457", slug = "bar")]
+   |                ^^^^^^^
 
-error: `lint` specified when `error` was already specified
-  --> $DIR/session-derive-errors.rs:64:10
+error: `slug` specified multiple times
+  --> $DIR/session-derive-errors.rs:107:46
+   |
+LL | #[error(code = "E0456", slug = "foo", slug = "bar")]
+   |                                              ^^^^^
    |
-LL | #[lint = "some_useful_lint"]
-   |          ^^^^^^^^^^^^^^^^^^
+note: previously specified here
+  --> $DIR/session-derive-errors.rs:107:32
+   |
+LL | #[error(code = "E0456", slug = "foo", slug = "bar")]
+   |                                ^^^^^
 
-error: `code` not specified
-  --> $DIR/session-derive-errors.rs:73:1
+error: diagnostic kind not specified
+  --> $DIR/session-derive-errors.rs:111:1
+   |
+LL | struct KindNotProvided {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-LL | struct ErrorCodeNotProvided {}
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   = help: use the `#[error(...)]` attribute to create an error
+
+error: `slug` not specified
+  --> $DIR/session-derive-errors.rs:114:1
+   |
+LL | / #[error(code = "E0456")]
+LL | | struct SlugNotProvided {}
+   | |_________________________^
+   |
+   = help: use the `#[error(slug = "...")]` attribute to set this diagnostic's slug
+
+error: the `#[primary_span]` attribute can only be applied to fields of type `Span`
+  --> $DIR/session-derive-errors.rs:124:5
    |
-   = help: use the [code = "..."] attribute to set this diagnostic's error code 
+LL |     #[primary_span]
+   |     ^^^^^^^^^^^^^^^
 
-error: the `#[message = "..."]` attribute can only be applied to fields of type Span
-  --> $DIR/session-derive-errors.rs:101:5
+error: `#[nonsense]` is not a valid `SessionDiagnostic` field attribute
+  --> $DIR/session-derive-errors.rs:132:5
    |
-LL |     #[message = "this message is applied to a String field"]
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     #[nonsense]
+   |     ^^^^^^^^^^^
+
+error: the `#[label = ...]` attribute can only be applied to fields of type `Span`
+  --> $DIR/session-derive-errors.rs:149:5
+   |
+LL |     #[label = "bar"]
+   |     ^^^^^^^^^^^^^^^^
 
 error: `name` doesn't refer to a field on this type
-  --> $DIR/session-derive-errors.rs:108:1
+  --> $DIR/session-derive-errors.rs:157:42
    |
-LL | #[message = "This error has a field, and references {name}"]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     #[suggestion(message = "bar", code = "{name}")]
+   |                                          ^^^^^^^^
 
 error: invalid format string: expected `'}'` but string was terminated
-  --> $DIR/session-derive-errors.rs:116:1
+  --> $DIR/session-derive-errors.rs:162:16
    |
 LL | #[derive(SessionDiagnostic)]
-   |          ----------------- in this derive macro expansion
-LL | #[error = "E0123"]
-   |               - because of this opening brace
-LL | #[message = "This is missing a closing brace: {name"]
-   | ^ expected `'}'` in format string
+   |           -    ^ expected `'}'` in format string
+   |           |
+   |           because of this opening brace
    |
    = note: if you intended to print `{`, you can escape it using `{{`
    = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: invalid format string: unmatched `}` found
-  --> $DIR/session-derive-errors.rs:125:1
+  --> $DIR/session-derive-errors.rs:172:15
    |
 LL | #[derive(SessionDiagnostic)]
-   |          ----------------- in this derive macro expansion
-LL | #[error = "E0123"]
-LL | #[message = "This is missing an opening brace: name}"]
-   | ^ unmatched `}` in format string
+   |               ^ unmatched `}` in format string
    |
    = note: if you intended to print `}`, you can escape it using `}}`
    = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error: The `#[label = ...]` attribute can only be applied to fields of type Span
-  --> $DIR/session-derive-errors.rs:144:5
+error: the `#[label = ...]` attribute can only be applied to fields of type `Span`
+  --> $DIR/session-derive-errors.rs:192:5
    |
-LL |     #[label = "See here"]
-   |     ^^^^^^^^^^^^^^^^^^^^^
-
-error: `nonsense` is not a valid key for `#[suggestion(...)]`
-  --> $DIR/session-derive-errors.rs:169:18
-   |
-LL |     #[suggestion(nonsense = "This is nonsense")]
-   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     #[label = "bar"]
+   |     ^^^^^^^^^^^^^^^^
 
-error: `msg` is not a valid key for `#[suggestion(...)]`
-  --> $DIR/session-derive-errors.rs:177:18
+error: `#[suggestion(nonsense = ...)]` is not a valid `SessionDiagnostic` field attribute
+  --> $DIR/session-derive-errors.rs:217:18
    |
-LL |     #[suggestion(msg = "This is a suggestion")]
-   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     #[suggestion(nonsense = "bar")]
+   |                  ^^^^^^^^^^^^^^^^
 
-error: missing suggestion message
-  --> $DIR/session-derive-errors.rs:185:7
-   |
-LL |     #[suggestion(code = "This is suggested code")]
-   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: `#[suggestion(msg = ...)]` is not a valid `SessionDiagnostic` field attribute
+  --> $DIR/session-derive-errors.rs:225:18
    |
-   = help: provide a suggestion message using #[suggestion(message = "...")]
+LL |     #[suggestion(msg = "bar")]
+   |                  ^^^^^^^^^^^
 
 error: wrong field type for suggestion
-  --> $DIR/session-derive-errors.rs:200:5
+  --> $DIR/session-derive-errors.rs:247:5
    |
-LL | /     #[suggestion(message = "This is a message", code = "This is suggested code")]
+LL | /     #[suggestion(message = "bar", code = "This is suggested code")]
 LL | |
 LL | |     suggestion: Applicability,
    | |_____________________________^
    |
-   = help: #[suggestion(...)] should be applied to fields of type Span or (Span, Applicability)
+   = help: `#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)`
 
-error: type of field annotated with `#[suggestion(...)]` contains more than one Span
-  --> $DIR/session-derive-errors.rs:215:5
+error: type of field annotated with `#[suggestion(...)]` contains more than one `Span`
+  --> $DIR/session-derive-errors.rs:262:5
    |
-LL | /     #[suggestion(message = "This is a message", code = "This is suggested code")]
+LL | /     #[suggestion(message = "bar", code = "This is suggested code")]
 LL | |
 LL | |     suggestion: (Span, Span, Applicability),
    | |___________________________________________^
 
 error: type of field annotated with `#[suggestion(...)]` contains more than one Applicability
-  --> $DIR/session-derive-errors.rs:223:5
+  --> $DIR/session-derive-errors.rs:270:5
    |
-LL | /     #[suggestion(message = "This is a message", code = "This is suggested code")]
+LL | /     #[suggestion(message = "bar", code = "This is suggested code")]
 LL | |
 LL | |     suggestion: (Applicability, Applicability, Span),
    | |____________________________________________________^
 
-error: invalid annotation list `#[label(...)]`
-  --> $DIR/session-derive-errors.rs:231:7
+error: `#[label(...)]` is not a valid `SessionDiagnostic` field attribute
+  --> $DIR/session-derive-errors.rs:278:5
    |
-LL |     #[label("wrong kind of annotation for label")]
-   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     #[label("bar")]
+   |     ^^^^^^^^^^^^^^^
+
+error: `#[help]` must come after `#[error(..)]` or `#[warn(..)]`
+  --> $DIR/session-derive-errors.rs:399:1
+   |
+LL | #[help]
+   | ^^^^^^^
+
+error: `#[help = ...]` must come after `#[error(..)]` or `#[warn(..)]`
+  --> $DIR/session-derive-errors.rs:407:1
+   |
+LL | #[help = "bar"]
+   | ^^^^^^^^^^^^^^^
+
+error: `#[note]` must come after `#[error(..)]` or `#[warn(..)]`
+  --> $DIR/session-derive-errors.rs:415:1
+   |
+LL | #[note]
+   | ^^^^^^^
+
+error: `#[note = ...]` must come after `#[error(..)]` or `#[warn(..)]`
+  --> $DIR/session-derive-errors.rs:423:1
+   |
+LL | #[note = "bar"]
+   | ^^^^^^^^^^^^^^^
+
+error: cannot find attribute `nonsense` in this scope
+  --> $DIR/session-derive-errors.rs:51:3
+   |
+LL | #[nonsense(code = "E0123", slug = "foo")]
+   |   ^^^^^^^^
+
+error: cannot find attribute `nonsense` in this scope
+  --> $DIR/session-derive-errors.rs:132:7
+   |
+LL |     #[nonsense]
+   |       ^^^^^^^^
+
+error[E0599]: no method named `into_diagnostic_arg` found for struct `Hello` in the current scope
+  --> $DIR/session-derive-errors.rs:322:10
+   |
+LL | struct Hello {}
+   | ------------ method `into_diagnostic_arg` not found for this
+...
+LL | #[derive(SessionDiagnostic)]
+   |          ^^^^^^^^^^^^^^^^^ method not found in `Hello`
+   |
+   = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error: aborting due to 18 previous errors
+error: aborting due to 37 previous errors
 
+For more information about this error, try `rustc --explain E0599`.
diff --git a/src/test/ui/associated-types/cache/project-fn-ret-invariant.krisskross.nll.stderr b/src/test/ui/associated-types/cache/project-fn-ret-invariant.krisskross.nll.stderr
index 01f800811ab..55532d6b9b3 100644
--- a/src/test/ui/associated-types/cache/project-fn-ret-invariant.krisskross.nll.stderr
+++ b/src/test/ui/associated-types/cache/project-fn-ret-invariant.krisskross.nll.stderr
@@ -10,8 +10,8 @@ LL |     (a, b)
    |     ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of the type Type<'_>, which makes the generic argument '_ invariant
-   = note: the struct Type<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Type<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Type<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -26,8 +26,8 @@ LL |     (a, b)
    |     ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of the type Type<'_>, which makes the generic argument '_ invariant
-   = note: the struct Type<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Type<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Type<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 help: `'a` and `'b` must be the same: replace one with the other
diff --git a/src/test/ui/associated-types/cache/project-fn-ret-invariant.oneuse.nll.stderr b/src/test/ui/associated-types/cache/project-fn-ret-invariant.oneuse.nll.stderr
index e925a424c37..fd9eb05473a 100644
--- a/src/test/ui/associated-types/cache/project-fn-ret-invariant.oneuse.nll.stderr
+++ b/src/test/ui/associated-types/cache/project-fn-ret-invariant.oneuse.nll.stderr
@@ -10,8 +10,8 @@ LL |     let a = bar(f, x);
    |             ^^^^^^^^^ argument requires that `'a` must outlive `'b`
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of the type Type<'_>, which makes the generic argument '_ invariant
-   = note: the struct Type<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Type<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Type<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -26,8 +26,8 @@ LL |     let b = bar(f, y);
    |             ^^^^^^^^^ argument requires that `'b` must outlive `'a`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of the type Type<'_>, which makes the generic argument '_ invariant
-   = note: the struct Type<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Type<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Type<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 help: `'a` and `'b` must be the same: replace one with the other
diff --git a/src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.nll.stderr b/src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.nll.stderr
index 0457f142e19..5a7ead72949 100644
--- a/src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.nll.stderr
+++ b/src/test/ui/associated-types/cache/project-fn-ret-invariant.transmute.nll.stderr
@@ -7,8 +7,8 @@ LL | fn baz<'a, 'b>(x: Type<'a>) -> Type<'static> {
 LL |     bar(foo, x)
    |     ^^^^^^^^^^^ returning this value requires that `'a` must outlive `'static`
    |
-   = note: requirement occurs because of the type Type<'_>, which makes the generic argument '_ invariant
-   = note: the struct Type<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Type<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Type<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/c-variadic/variadic-ffi-4.stderr b/src/test/ui/c-variadic/variadic-ffi-4.stderr
index ff4da5251a9..6f8e53298ac 100644
--- a/src/test/ui/c-variadic/variadic-ffi-4.stderr
+++ b/src/test/ui/c-variadic/variadic-ffi-4.stderr
@@ -8,8 +8,8 @@ LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaListImpl<'f
 LL |     ap
    |     ^^ function was supposed to return data with lifetime `'1` but it is returning data with lifetime `'f`
    |
-   = note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
-   = note: the struct VaListImpl<'f> is invariant over the parameter 'f
+   = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -22,8 +22,8 @@ LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaListImpl<'f
 LL |     ap
    |     ^^ function was supposed to return data with lifetime `'f` but it is returning data with lifetime `'1`
    |
-   = note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
-   = note: the struct VaListImpl<'f> is invariant over the parameter 'f
+   = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -34,8 +34,8 @@ LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaListImpl<'stati
 LL |     ap
    |     ^^ returning this value requires that `'1` must outlive `'static`
    |
-   = note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
-   = note: the struct VaListImpl<'f> is invariant over the parameter 'f
+   = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -57,8 +57,8 @@ LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaListImpl, mut
 LL |     *ap0 = ap1;
    |     ^^^^ assignment requires that `'1` must outlive `'2`
    |
-   = note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
-   = note: the struct VaListImpl<'f> is invariant over the parameter 'f
+   = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -71,8 +71,8 @@ LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaListImpl, mut
 LL |     *ap0 = ap1;
    |     ^^^^ assignment requires that `'2` must outlive `'1`
    |
-   = note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
-   = note: the struct VaListImpl<'f> is invariant over the parameter 'f
+   = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -85,7 +85,7 @@ LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaListImpl, mut
 LL |     ap0 = &mut ap1;
    |     ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2`
    |
-   = note: requirement occurs because of a mutable reference to VaListImpl<'_>
+   = note: requirement occurs because of a mutable reference to `VaListImpl<'_>`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
@@ -99,7 +99,7 @@ LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaListImpl, mut
 LL |     ap0 = &mut ap1;
    |     ^^^^^^^^^^^^^^ assignment requires that `'2` must outlive `'1`
    |
-   = note: requirement occurs because of a mutable reference to VaListImpl<'_>
+   = note: requirement occurs because of a mutable reference to `VaListImpl<'_>`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
@@ -127,8 +127,8 @@ LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaListImpl, mut
 LL |     *ap0 = ap1.clone();
    |            ^^^^^^^^^^^ argument requires that `'1` must outlive `'2`
    |
-   = note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
-   = note: the struct VaListImpl<'f> is invariant over the parameter 'f
+   = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -141,8 +141,8 @@ LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaListImpl, mut
 LL |     *ap0 = ap1.clone();
    |            ^^^^^^^^^^^ argument requires that `'2` must outlive `'1`
    |
-   = note: requirement occurs because of the type VaListImpl<'_>, which makes the generic argument '_ invariant
-   = note: the struct VaListImpl<'f> is invariant over the parameter 'f
+   = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to 11 previous errors
diff --git a/src/test/ui/error-codes/E0184.stderr b/src/test/ui/error-codes/E0184.stderr
index 5bfeaa58bdf..bb3017b6ec2 100644
--- a/src/test/ui/error-codes/E0184.stderr
+++ b/src/test/ui/error-codes/E0184.stderr
@@ -2,7 +2,7 @@ error[E0184]: the trait `Copy` may not be implemented for this type; the type ha
   --> $DIR/E0184.rs:1:10
    |
 LL | #[derive(Copy)]
-   |          ^^^^ Copy not allowed on types with destructors
+   |          ^^^^ `Copy` not allowed on types with destructors
    |
    = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)
 
diff --git a/src/test/ui/exclusive-drop-and-copy.stderr b/src/test/ui/exclusive-drop-and-copy.stderr
index 36ee6570e42..8649c8abbfa 100644
--- a/src/test/ui/exclusive-drop-and-copy.stderr
+++ b/src/test/ui/exclusive-drop-and-copy.stderr
@@ -2,7 +2,7 @@ error[E0184]: the trait `Copy` may not be implemented for this type; the type ha
   --> $DIR/exclusive-drop-and-copy.rs:3:10
    |
 LL | #[derive(Copy, Clone)]
-   |          ^^^^ Copy not allowed on types with destructors
+   |          ^^^^ `Copy` not allowed on types with destructors
    |
    = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)
 
@@ -10,7 +10,7 @@ error[E0184]: the trait `Copy` may not be implemented for this type; the type ha
   --> $DIR/exclusive-drop-and-copy.rs:10:10
    |
 LL | #[derive(Copy, Clone)]
-   |          ^^^^ Copy not allowed on types with destructors
+   |          ^^^^ `Copy` not allowed on types with destructors
    |
    = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)
 
diff --git a/src/test/ui/hr-subtype/hr-subtype.free_inv_x_vs_free_inv_y.nll.stderr b/src/test/ui/hr-subtype/hr-subtype.free_inv_x_vs_free_inv_y.nll.stderr
index b4c54d52e5c..f5db68e8be1 100644
--- a/src/test/ui/hr-subtype/hr-subtype.free_inv_x_vs_free_inv_y.nll.stderr
+++ b/src/test/ui/hr-subtype/hr-subtype.free_inv_x_vs_free_inv_y.nll.stderr
@@ -13,8 +13,8 @@ LL | | fn(Inv<'y>)) }
    | |______________- in this macro invocation
    |
    = help: consider adding the following bound: `'x: 'y`
-   = note: requirement occurs because of the type Inv<'_>, which makes the generic argument '_ invariant
-   = note: the struct Inv<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Inv<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Inv<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
    = note: this error originates in the macro `check` (in Nightly builds, run with -Z macro-backtrace for more info)
 
@@ -33,8 +33,8 @@ LL | | fn(Inv<'y>)) }
    | |______________- in this macro invocation
    |
    = help: consider adding the following bound: `'x: 'y`
-   = note: requirement occurs because of the type Inv<'_>, which makes the generic argument '_ invariant
-   = note: the struct Inv<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Inv<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Inv<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
    = note: this error originates in the macro `check` (in Nightly builds, run with -Z macro-backtrace for more info)
 
diff --git a/src/test/ui/match/match-ref-mut-invariance.nll.stderr b/src/test/ui/match/match-ref-mut-invariance.nll.stderr
index c8a7876dc54..3b7e53cd527 100644
--- a/src/test/ui/match/match-ref-mut-invariance.nll.stderr
+++ b/src/test/ui/match/match-ref-mut-invariance.nll.stderr
@@ -9,7 +9,7 @@ LL |         match self.0 { ref mut x => x }
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of a mutable reference to &i32
+   = note: requirement occurs because of a mutable reference to `&i32`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
diff --git a/src/test/ui/match/match-ref-mut-let-invariance.nll.stderr b/src/test/ui/match/match-ref-mut-let-invariance.nll.stderr
index 11ddf1487dd..f4d1cea670b 100644
--- a/src/test/ui/match/match-ref-mut-let-invariance.nll.stderr
+++ b/src/test/ui/match/match-ref-mut-let-invariance.nll.stderr
@@ -10,7 +10,7 @@ LL |         x
    |         ^ associated function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of a mutable reference to &i32
+   = note: requirement occurs because of a mutable reference to `&i32`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
diff --git a/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr b/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr
index cf563072dff..ec2c220b6b8 100644
--- a/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr
+++ b/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr
@@ -52,8 +52,8 @@ LL | |     });
    | |______`cell_a` escapes the function body here
    |        argument requires that `'a` must outlive `'static`
    |
-   = note: requirement occurs because of the type Cell<&'_#10r u32>, which makes the generic argument &'_#10r u32 invariant
-   = note: the struct Cell<T> is invariant over the parameter T
+   = note: requirement occurs because of the type `Cell<&'_#10r u32>`, which makes the generic argument `&'_#10r u32` invariant
+   = note: the struct `Cell<T>` is invariant over the parameter `T`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr b/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr
index 453f6801d7e..234212c8876 100644
--- a/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr
+++ b/src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr
@@ -52,8 +52,8 @@ LL | |     });
    | |______`cell_a` escapes the function body here
    |        argument requires that `'a` must outlive `'static`
    |
-   = note: requirement occurs because of the type Cell<&'_#11r u32>, which makes the generic argument &'_#11r u32 invariant
-   = note: the struct Cell<T> is invariant over the parameter T
+   = note: requirement occurs because of the type `Cell<&'_#11r u32>`, which makes the generic argument `&'_#11r u32` invariant
+   = note: the struct `Cell<T>` is invariant over the parameter `T`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/nll/issue-95272.rs b/src/test/ui/nll/issue-95272.rs
new file mode 100644
index 00000000000..5b5308fb8c2
--- /dev/null
+++ b/src/test/ui/nll/issue-95272.rs
@@ -0,0 +1,17 @@
+#![feature(nll)]
+
+use std::cell::Cell;
+
+fn check<'a, 'b>(x: Cell<&'a ()>, y: Cell<&'b ()>)
+where
+    'a: 'b,
+{
+}
+
+fn test<'a, 'b>(x: Cell<&'a ()>, y: Cell<&'b ()>) {
+    let f = check;
+    //~^ ERROR lifetime may not live long enough
+    f(x, y);
+}
+
+fn main() {}
diff --git a/src/test/ui/nll/issue-95272.stderr b/src/test/ui/nll/issue-95272.stderr
new file mode 100644
index 00000000000..41346a4c699
--- /dev/null
+++ b/src/test/ui/nll/issue-95272.stderr
@@ -0,0 +1,17 @@
+error: lifetime may not live long enough
+  --> $DIR/issue-95272.rs:12:13
+   |
+LL | fn test<'a, 'b>(x: Cell<&'a ()>, y: Cell<&'b ()>) {
+   |         --  -- lifetime `'b` defined here
+   |         |
+   |         lifetime `'a` defined here
+LL |     let f = check;
+   |             ^^^^^ assignment requires that `'a` must outlive `'b`
+   |
+   = help: consider adding the following bound: `'a: 'b`
+   = note: requirement occurs because of a function pointer to `check`
+   = note: the function `check` is invariant over the parameter `'a`
+   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/nll/type-check-pointer-coercions.stderr b/src/test/ui/nll/type-check-pointer-coercions.stderr
index b392c2007d3..24b07cabbac 100644
--- a/src/test/ui/nll/type-check-pointer-coercions.stderr
+++ b/src/test/ui/nll/type-check-pointer-coercions.stderr
@@ -34,7 +34,7 @@ LL |     x
    |     ^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of a mutable pointer to &i32
+   = note: requirement occurs because of a mutable pointer to `&i32`
    = note: mutable pointers are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
@@ -50,7 +50,7 @@ LL |     x
    |     ^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of a mutable pointer to &i32
+   = note: requirement occurs because of a mutable pointer to `&i32`
    = note: mutable pointers are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
diff --git a/src/test/ui/nll/type-check-pointer-comparisons.stderr b/src/test/ui/nll/type-check-pointer-comparisons.stderr
index b488af820b8..8c88b229039 100644
--- a/src/test/ui/nll/type-check-pointer-comparisons.stderr
+++ b/src/test/ui/nll/type-check-pointer-comparisons.stderr
@@ -9,7 +9,7 @@ LL |     x == y;
    |     ^ requires that `'a` must outlive `'b`
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of a mutable reference to &i32
+   = note: requirement occurs because of a mutable reference to `&i32`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
@@ -24,7 +24,7 @@ LL |     x == y;
    |          ^ requires that `'b` must outlive `'a`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of a mutable reference to &i32
+   = note: requirement occurs because of a mutable reference to `&i32`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
@@ -41,7 +41,7 @@ LL |     x == y;
    |     ^ requires that `'a` must outlive `'b`
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of a mutable pointer to &i32
+   = note: requirement occurs because of a mutable pointer to `&i32`
    = note: mutable pointers are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
@@ -56,7 +56,7 @@ LL |     x == y;
    |          ^ requires that `'b` must outlive `'a`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of a mutable pointer to &i32
+   = note: requirement occurs because of a mutable pointer to `&i32`
    = note: mutable pointers are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
@@ -73,7 +73,7 @@ LL |     f == g;
    |     ^ requires that `'a` must outlive `'b`
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of a mutable reference to &i32
+   = note: requirement occurs because of a mutable reference to `&i32`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
@@ -88,7 +88,7 @@ LL |     f == g;
    |          ^ requires that `'b` must outlive `'a`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of a mutable reference to &i32
+   = note: requirement occurs because of a mutable reference to `&i32`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
diff --git a/src/test/ui/nll/where_clauses_in_structs.stderr b/src/test/ui/nll/where_clauses_in_structs.stderr
index 952667d518d..b88c90e8f54 100644
--- a/src/test/ui/nll/where_clauses_in_structs.stderr
+++ b/src/test/ui/nll/where_clauses_in_structs.stderr
@@ -9,8 +9,8 @@ LL |     Foo { x, y };
    |           ^ this usage requires that `'a` must outlive `'b`
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of the type Cell<&u32>, which makes the generic argument &u32 invariant
-   = note: the struct Cell<T> is invariant over the parameter T
+   = note: requirement occurs because of the type `Cell<&u32>`, which makes the generic argument `&u32` invariant
+   = note: the struct `Cell<T>` is invariant over the parameter `T`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/regions/region-invariant-static-error-reporting.nll.stderr b/src/test/ui/regions/region-invariant-static-error-reporting.nll.stderr
index 376534bf573..6e7eb734a50 100644
--- a/src/test/ui/regions/region-invariant-static-error-reporting.nll.stderr
+++ b/src/test/ui/regions/region-invariant-static-error-reporting.nll.stderr
@@ -12,8 +12,8 @@ LL |         x.unwrap()
    |         `x` escapes the function body here
    |         argument requires that `'a` must outlive `'static`
    |
-   = note: requirement occurs because of the type Invariant<'_>, which makes the generic argument '_ invariant
-   = note: the struct Invariant<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Invariant<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Invariant<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/regions/region-lifetime-bounds-on-fns-where-clause.nll.stderr b/src/test/ui/regions/region-lifetime-bounds-on-fns-where-clause.nll.stderr
index a64ad46ef46..233a040491c 100644
--- a/src/test/ui/regions/region-lifetime-bounds-on-fns-where-clause.nll.stderr
+++ b/src/test/ui/regions/region-lifetime-bounds-on-fns-where-clause.nll.stderr
@@ -23,7 +23,7 @@ LL |     a(x, y);
    |     ^^^^^^^ argument requires that `'b` must outlive `'a`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of a mutable reference to &isize
+   = note: requirement occurs because of a mutable reference to `&isize`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
diff --git a/src/test/ui/regions/region-multiple-lifetime-bounds-on-fns-where-clause.nll.stderr b/src/test/ui/regions/region-multiple-lifetime-bounds-on-fns-where-clause.nll.stderr
index ce5e7d01723..00119743acd 100644
--- a/src/test/ui/regions/region-multiple-lifetime-bounds-on-fns-where-clause.nll.stderr
+++ b/src/test/ui/regions/region-multiple-lifetime-bounds-on-fns-where-clause.nll.stderr
@@ -23,7 +23,7 @@ LL |     a(x, y, z);
    |     ^^^^^^^^^^ argument requires that `'b` must outlive `'a`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of a mutable reference to &isize
+   = note: requirement occurs because of a mutable reference to `&isize`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
diff --git a/src/test/ui/regions/regions-bounded-method-type-parameters-cross-crate.nll.stderr b/src/test/ui/regions/regions-bounded-method-type-parameters-cross-crate.nll.stderr
index 32f3080ea37..6193bf02f6d 100644
--- a/src/test/ui/regions/regions-bounded-method-type-parameters-cross-crate.nll.stderr
+++ b/src/test/ui/regions/regions-bounded-method-type-parameters-cross-crate.nll.stderr
@@ -10,8 +10,8 @@ LL |     a.bigger_region(b)
    |     ^^^^^^^^^^^^^^^^^^ argument requires that `'y` must outlive `'x`
    |
    = help: consider adding the following bound: `'y: 'x`
-   = note: requirement occurs because of the type Inv<'_>, which makes the generic argument '_ invariant
-   = note: the struct Inv<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Inv<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Inv<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/regions/regions-bounded-method-type-parameters-trait-bound.nll.stderr b/src/test/ui/regions/regions-bounded-method-type-parameters-trait-bound.nll.stderr
index 246b6483c21..0e0086be9ea 100644
--- a/src/test/ui/regions/regions-bounded-method-type-parameters-trait-bound.nll.stderr
+++ b/src/test/ui/regions/regions-bounded-method-type-parameters-trait-bound.nll.stderr
@@ -10,8 +10,8 @@ LL |     f.method(b);
    |     ^^^^^^^^^^^ argument requires that `'b` must outlive `'a`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of the type Inv<'_>, which makes the generic argument '_ invariant
-   = note: the struct Inv<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Inv<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Inv<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/regions/regions-infer-invariance-due-to-decl.nll.stderr b/src/test/ui/regions/regions-infer-invariance-due-to-decl.nll.stderr
index fede5f2d779..c8c7808e06c 100644
--- a/src/test/ui/regions/regions-infer-invariance-due-to-decl.nll.stderr
+++ b/src/test/ui/regions/regions-infer-invariance-due-to-decl.nll.stderr
@@ -6,8 +6,8 @@ LL | fn to_longer_lifetime<'r>(b_isize: Invariant<'r>) -> Invariant<'static> {
 LL |     b_isize
    |     ^^^^^^^ returning this value requires that `'r` must outlive `'static`
    |
-   = note: requirement occurs because of the type Invariant<'_>, which makes the generic argument '_ invariant
-   = note: the struct Invariant<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Invariant<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Invariant<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/regions/regions-infer-invariance-due-to-mutability-3.nll.stderr b/src/test/ui/regions/regions-infer-invariance-due-to-mutability-3.nll.stderr
index 8f5f3667453..1165011c1f4 100644
--- a/src/test/ui/regions/regions-infer-invariance-due-to-mutability-3.nll.stderr
+++ b/src/test/ui/regions/regions-infer-invariance-due-to-mutability-3.nll.stderr
@@ -6,8 +6,8 @@ LL | fn to_longer_lifetime<'r>(b_isize: Invariant<'r>) -> Invariant<'static> {
 LL |     b_isize
    |     ^^^^^^^ returning this value requires that `'r` must outlive `'static`
    |
-   = note: requirement occurs because of the type Invariant<'_>, which makes the generic argument '_ invariant
-   = note: the struct Invariant<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Invariant<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Invariant<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/regions/regions-infer-invariance-due-to-mutability-4.nll.stderr b/src/test/ui/regions/regions-infer-invariance-due-to-mutability-4.nll.stderr
index 8079fb0ef0d..f3973a93bad 100644
--- a/src/test/ui/regions/regions-infer-invariance-due-to-mutability-4.nll.stderr
+++ b/src/test/ui/regions/regions-infer-invariance-due-to-mutability-4.nll.stderr
@@ -6,8 +6,8 @@ LL | fn to_longer_lifetime<'r>(b_isize: Invariant<'r>) -> Invariant<'static> {
 LL |     b_isize
    |     ^^^^^^^ returning this value requires that `'r` must outlive `'static`
    |
-   = note: requirement occurs because of the type Invariant<'_>, which makes the generic argument '_ invariant
-   = note: the struct Invariant<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Invariant<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Invariant<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/regions/regions-infer-not-param.nll.stderr b/src/test/ui/regions/regions-infer-not-param.nll.stderr
index 3183aee23d9..f4875e49c3d 100644
--- a/src/test/ui/regions/regions-infer-not-param.nll.stderr
+++ b/src/test/ui/regions/regions-infer-not-param.nll.stderr
@@ -17,8 +17,8 @@ LL | fn take_indirect2<'a,'b>(p: Indirect2<'a>) -> Indirect2<'b> { p }
    |                   lifetime `'a` defined here
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of the type Indirect2<'_>, which makes the generic argument '_ invariant
-   = note: the struct Indirect2<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Indirect2<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Indirect2<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -30,8 +30,8 @@ LL | fn take_indirect2<'a,'b>(p: Indirect2<'a>) -> Indirect2<'b> { p }
    |                   lifetime `'a` defined here
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of the type Indirect2<'_>, which makes the generic argument '_ invariant
-   = note: the struct Indirect2<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Indirect2<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Indirect2<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 help: `'b` and `'a` must be the same: replace one with the other
diff --git a/src/test/ui/regions/regions-lifetime-bounds-on-fns.nll.stderr b/src/test/ui/regions/regions-lifetime-bounds-on-fns.nll.stderr
index cae692ad2f6..ee3dcef1cb5 100644
--- a/src/test/ui/regions/regions-lifetime-bounds-on-fns.nll.stderr
+++ b/src/test/ui/regions/regions-lifetime-bounds-on-fns.nll.stderr
@@ -23,7 +23,7 @@ LL |     a(x, y);
    |     ^^^^^^^ argument requires that `'b` must outlive `'a`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of a mutable reference to &isize
+   = note: requirement occurs because of a mutable reference to `&isize`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
diff --git a/src/test/ui/regions/regions-trait-object-subtyping.nll.stderr b/src/test/ui/regions/regions-trait-object-subtyping.nll.stderr
index 26f0fcae638..1b3a116d508 100644
--- a/src/test/ui/regions/regions-trait-object-subtyping.nll.stderr
+++ b/src/test/ui/regions/regions-trait-object-subtyping.nll.stderr
@@ -10,7 +10,7 @@ LL |     x
    |     ^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
    |
    = help: consider adding the following bound: `'a: 'b`
-   = note: requirement occurs because of a mutable reference to dyn Dummy
+   = note: requirement occurs because of a mutable reference to `dyn Dummy`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
@@ -26,7 +26,7 @@ LL |     x
    |     ^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
    |
    = help: consider adding the following bound: `'b: 'a`
-   = note: requirement occurs because of a mutable reference to dyn Dummy
+   = note: requirement occurs because of a mutable reference to `dyn Dummy`
    = note: mutable references are invariant over their type parameter
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
diff --git a/src/test/ui/regions/regions-variance-invariant-use-contravariant.nll.stderr b/src/test/ui/regions/regions-variance-invariant-use-contravariant.nll.stderr
index 8e8ca8e47cc..b35a2cb905d 100644
--- a/src/test/ui/regions/regions-variance-invariant-use-contravariant.nll.stderr
+++ b/src/test/ui/regions/regions-variance-invariant-use-contravariant.nll.stderr
@@ -10,8 +10,8 @@ LL |     let _: Invariant<'short> = c;
    |            ^^^^^^^^^^^^^^^^^ type annotation requires that `'short` must outlive `'long`
    |
    = help: consider adding the following bound: `'short: 'long`
-   = note: requirement occurs because of the type Invariant<'_>, which makes the generic argument '_ invariant
-   = note: the struct Invariant<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Invariant<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Invariant<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/regions/regions-variance-invariant-use-covariant.nll.stderr b/src/test/ui/regions/regions-variance-invariant-use-covariant.nll.stderr
index f9a3d727f7a..761e78d179e 100644
--- a/src/test/ui/regions/regions-variance-invariant-use-covariant.nll.stderr
+++ b/src/test/ui/regions/regions-variance-invariant-use-covariant.nll.stderr
@@ -7,8 +7,8 @@ LL | fn use_<'b>(c: Invariant<'b>) {
 LL |     let _: Invariant<'static> = c;
    |            ^^^^^^^^^^^^^^^^^^ type annotation requires that `'b` must outlive `'static`
    |
-   = note: requirement occurs because of the type Invariant<'_>, which makes the generic argument '_ invariant
-   = note: the struct Invariant<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Invariant<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Invariant<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/variance/variance-btree-invariant-types.nll.stderr b/src/test/ui/variance/variance-btree-invariant-types.nll.stderr
index 867d9f8238a..0d9815cf788 100644
--- a/src/test/ui/variance/variance-btree-invariant-types.nll.stderr
+++ b/src/test/ui/variance/variance-btree-invariant-types.nll.stderr
@@ -6,8 +6,8 @@ LL | fn iter_cov_key<'a, 'new>(v: IterMut<'a, &'static (), ()>) -> IterMut<'a, &
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::IterMut<'_, &(), ()>, which makes the generic argument &() invariant
-   = note: the struct std::collections::btree_map::IterMut<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::IterMut<'_, &(), ()>`, which makes the generic argument `&()` invariant
+   = note: the struct `std::collections::btree_map::IterMut<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -18,8 +18,8 @@ LL | fn iter_cov_val<'a, 'new>(v: IterMut<'a, (), &'static ()>) -> IterMut<'a, (
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::IterMut<'_, (), &()>, which makes the generic argument () invariant
-   = note: the struct std::collections::btree_map::IterMut<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::IterMut<'_, (), &()>`, which makes the generic argument `()` invariant
+   = note: the struct `std::collections::btree_map::IterMut<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -30,8 +30,8 @@ LL | fn iter_contra_key<'a, 'new>(v: IterMut<'a, &'new (), ()>) -> IterMut<'a, &
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::IterMut<'_, &(), ()>, which makes the generic argument &() invariant
-   = note: the struct std::collections::btree_map::IterMut<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::IterMut<'_, &(), ()>`, which makes the generic argument `&()` invariant
+   = note: the struct `std::collections::btree_map::IterMut<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -42,8 +42,8 @@ LL | fn iter_contra_val<'a, 'new>(v: IterMut<'a, (), &'new ()>) -> IterMut<'a, (
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::IterMut<'_, (), &()>, which makes the generic argument () invariant
-   = note: the struct std::collections::btree_map::IterMut<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::IterMut<'_, (), &()>`, which makes the generic argument `()` invariant
+   = note: the struct `std::collections::btree_map::IterMut<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -54,8 +54,8 @@ LL | fn range_cov_key<'a, 'new>(v: RangeMut<'a, &'static (), ()>) -> RangeMut<'a
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type RangeMut<'_, &(), ()>, which makes the generic argument &() invariant
-   = note: the struct RangeMut<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `RangeMut<'_, &(), ()>`, which makes the generic argument `&()` invariant
+   = note: the struct `RangeMut<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -66,8 +66,8 @@ LL | fn range_cov_val<'a, 'new>(v: RangeMut<'a, (), &'static ()>) -> RangeMut<'a
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type RangeMut<'_, (), &()>, which makes the generic argument () invariant
-   = note: the struct RangeMut<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `RangeMut<'_, (), &()>`, which makes the generic argument `()` invariant
+   = note: the struct `RangeMut<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -78,8 +78,8 @@ LL | fn range_contra_key<'a, 'new>(v: RangeMut<'a, &'new (), ()>) -> RangeMut<'a
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type RangeMut<'_, &(), ()>, which makes the generic argument &() invariant
-   = note: the struct RangeMut<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `RangeMut<'_, &(), ()>`, which makes the generic argument `&()` invariant
+   = note: the struct `RangeMut<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -90,8 +90,8 @@ LL | fn range_contra_val<'a, 'new>(v: RangeMut<'a, (), &'new ()>) -> RangeMut<'a
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type RangeMut<'_, (), &()>, which makes the generic argument () invariant
-   = note: the struct RangeMut<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `RangeMut<'_, (), &()>`, which makes the generic argument `()` invariant
+   = note: the struct `RangeMut<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -103,8 +103,8 @@ LL |                          -> OccupiedEntry<'a, &'new (), ()> {
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::OccupiedEntry<'_, &(), ()>, which makes the generic argument &() invariant
-   = note: the struct std::collections::btree_map::OccupiedEntry<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::OccupiedEntry<'_, &(), ()>`, which makes the generic argument `&()` invariant
+   = note: the struct `std::collections::btree_map::OccupiedEntry<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -116,8 +116,8 @@ LL |                          -> OccupiedEntry<'a, (), &'new ()> {
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::OccupiedEntry<'_, (), &()>, which makes the generic argument () invariant
-   = note: the struct std::collections::btree_map::OccupiedEntry<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::OccupiedEntry<'_, (), &()>`, which makes the generic argument `()` invariant
+   = note: the struct `std::collections::btree_map::OccupiedEntry<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -129,8 +129,8 @@ LL |                             -> OccupiedEntry<'a, &'static (), ()> {
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::OccupiedEntry<'_, &(), ()>, which makes the generic argument &() invariant
-   = note: the struct std::collections::btree_map::OccupiedEntry<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::OccupiedEntry<'_, &(), ()>`, which makes the generic argument `&()` invariant
+   = note: the struct `std::collections::btree_map::OccupiedEntry<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -142,8 +142,8 @@ LL |                             -> OccupiedEntry<'a, (), &'static ()> {
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::OccupiedEntry<'_, (), &()>, which makes the generic argument () invariant
-   = note: the struct std::collections::btree_map::OccupiedEntry<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::OccupiedEntry<'_, (), &()>`, which makes the generic argument `()` invariant
+   = note: the struct `std::collections::btree_map::OccupiedEntry<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -155,8 +155,8 @@ LL |                          -> VacantEntry<'a, &'new (), ()> {
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::VacantEntry<'_, &(), ()>, which makes the generic argument &() invariant
-   = note: the struct std::collections::btree_map::VacantEntry<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::VacantEntry<'_, &(), ()>`, which makes the generic argument `&()` invariant
+   = note: the struct `std::collections::btree_map::VacantEntry<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -168,8 +168,8 @@ LL |                          -> VacantEntry<'a, (), &'new ()> {
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::VacantEntry<'_, (), &()>, which makes the generic argument () invariant
-   = note: the struct std::collections::btree_map::VacantEntry<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::VacantEntry<'_, (), &()>`, which makes the generic argument `()` invariant
+   = note: the struct `std::collections::btree_map::VacantEntry<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -181,8 +181,8 @@ LL |                             -> VacantEntry<'a, &'static (), ()> {
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::VacantEntry<'_, &(), ()>, which makes the generic argument &() invariant
-   = note: the struct std::collections::btree_map::VacantEntry<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::VacantEntry<'_, &(), ()>`, which makes the generic argument `&()` invariant
+   = note: the struct `std::collections::btree_map::VacantEntry<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -194,8 +194,8 @@ LL |                             -> VacantEntry<'a, (), &'static ()> {
 LL |     v
    |     ^ returning this value requires that `'new` must outlive `'static`
    |
-   = note: requirement occurs because of the type std::collections::btree_map::VacantEntry<'_, (), &()>, which makes the generic argument () invariant
-   = note: the struct std::collections::btree_map::VacantEntry<'a, K, V> is invariant over the parameter K
+   = note: requirement occurs because of the type `std::collections::btree_map::VacantEntry<'_, (), &()>`, which makes the generic argument `()` invariant
+   = note: the struct `std::collections::btree_map::VacantEntry<'a, K, V>` is invariant over the parameter `K`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to 16 previous errors
diff --git a/src/test/ui/variance/variance-cell-is-invariant.nll.stderr b/src/test/ui/variance/variance-cell-is-invariant.nll.stderr
index d6a85680141..ab5435d1656 100644
--- a/src/test/ui/variance/variance-cell-is-invariant.nll.stderr
+++ b/src/test/ui/variance/variance-cell-is-invariant.nll.stderr
@@ -10,8 +10,8 @@ LL |     let _: Foo<'long> = c;
    |            ^^^^^^^^^^ type annotation requires that `'short` must outlive `'long`
    |
    = help: consider adding the following bound: `'short: 'long`
-   = note: requirement occurs because of the type Foo<'_>, which makes the generic argument '_ invariant
-   = note: the struct Foo<'a> is invariant over the parameter 'a
+   = note: requirement occurs because of the type `Foo<'_>`, which makes the generic argument `'_` invariant
+   = note: the struct `Foo<'a>` is invariant over the parameter `'a`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to previous error
diff --git a/src/test/ui/variance/variance-use-invariant-struct-1.nll.stderr b/src/test/ui/variance/variance-use-invariant-struct-1.nll.stderr
index f1df2a88b6b..600b245c1f7 100644
--- a/src/test/ui/variance/variance-use-invariant-struct-1.nll.stderr
+++ b/src/test/ui/variance/variance-use-invariant-struct-1.nll.stderr
@@ -10,8 +10,8 @@ LL |     v
    |     ^ function was supposed to return data with lifetime `'max` but it is returning data with lifetime `'min`
    |
    = help: consider adding the following bound: `'min: 'max`
-   = note: requirement occurs because of the type SomeStruct<&()>, which makes the generic argument &() invariant
-   = note: the struct SomeStruct<T> is invariant over the parameter T
+   = note: requirement occurs because of the type `SomeStruct<&()>`, which makes the generic argument `&()` invariant
+   = note: the struct `SomeStruct<T>` is invariant over the parameter `T`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: lifetime may not live long enough
@@ -26,8 +26,8 @@ LL |     v
    |     ^ function was supposed to return data with lifetime `'max` but it is returning data with lifetime `'min`
    |
    = help: consider adding the following bound: `'min: 'max`
-   = note: requirement occurs because of the type SomeStruct<&()>, which makes the generic argument &() invariant
-   = note: the struct SomeStruct<T> is invariant over the parameter T
+   = note: requirement occurs because of the type `SomeStruct<&()>`, which makes the generic argument `&()` invariant
+   = note: the struct `SomeStruct<T>` is invariant over the parameter `T`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
 
 error: aborting due to 2 previous errors
diff --git a/src/tools/clippy/clippy_lints/src/collapsible_match.rs b/src/tools/clippy/clippy_lints/src/collapsible_match.rs
index c71e9f10f79..cc354b50afa 100644
--- a/src/tools/clippy/clippy_lints/src/collapsible_match.rs
+++ b/src/tools/clippy/clippy_lints/src/collapsible_match.rs
@@ -3,11 +3,12 @@ use clippy_utils::higher::IfLetOrMatch;
 use clippy_utils::visitors::is_local_used;
 use clippy_utils::{is_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq};
 use if_chain::if_chain;
+use rustc_errors::MultiSpan;
 use rustc_hir::LangItem::OptionNone;
 use rustc_hir::{Arm, Expr, Guard, HirId, Pat, PatKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
-use rustc_span::{MultiSpan, Span};
+use rustc_span::Span;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -129,8 +130,8 @@ fn check_arm<'tcx>(
                 &msg,
                 |diag| {
                     let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
-                    help_span.push_span_label(binding_span, "replace this binding".into());
-                    help_span.push_span_label(inner_then_pat.span, "with this pattern".into());
+                    help_span.push_span_label(binding_span, "replace this binding");
+                    help_span.push_span_label(inner_then_pat.span, "with this pattern");
                     diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
                 },
             );
diff --git a/src/tools/clippy/clippy_lints/src/doc.rs b/src/tools/clippy/clippy_lints/src/doc.rs
index 703aa458f44..92cf82bcd6a 100644
--- a/src/tools/clippy/clippy_lints/src/doc.rs
+++ b/src/tools/clippy/clippy_lints/src/doc.rs
@@ -11,7 +11,7 @@ use rustc_ast::token::CommentKind;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::emitter::EmitterWriter;
-use rustc_errors::{Applicability, Handler, SuggestionStyle};
+use rustc_errors::{Applicability, Handler, MultiSpan, SuggestionStyle};
 use rustc_hir as hir;
 use rustc_hir::intravisit::{self, Visitor};
 use rustc_hir::{AnonConst, Expr};
@@ -25,7 +25,7 @@ use rustc_session::parse::ParseSess;
 use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::def_id::LocalDefId;
 use rustc_span::edition::Edition;
-use rustc_span::source_map::{BytePos, FilePathMapping, MultiSpan, SourceMap, Span};
+use rustc_span::source_map::{BytePos, FilePathMapping, SourceMap, Span};
 use rustc_span::{sym, FileName, Pos};
 use std::io;
 use std::ops::Range;
@@ -621,7 +621,19 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
                 let filename = FileName::anon_source_code(&code);
 
                 let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
-                let emitter = EmitterWriter::new(Box::new(io::sink()), None, false, false, false, None, false);
+                let fallback_bundle = rustc_errors::fallback_fluent_bundle(false)
+                    .expect("failed to load fallback fluent bundle");
+                let emitter = EmitterWriter::new(
+                    Box::new(io::sink()),
+                    None,
+                    None,
+                    fallback_bundle,
+                    false,
+                    false,
+                    false,
+                    None,
+                    false,
+                );
                 let handler = Handler::with_emitter(false, None, Box::new(emitter));
                 let sess = ParseSess::with_span_handler(handler, sm);
 
diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_collect.rs b/src/tools/clippy/clippy_lints/src/loops/needless_collect.rs
index 06190850bb0..ddaffc75188 100644
--- a/src/tools/clippy/clippy_lints/src/loops/needless_collect.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/needless_collect.rs
@@ -6,7 +6,7 @@ use clippy_utils::ty::is_type_diagnostic_item;
 use clippy_utils::{can_move_expr_to_closure, is_trait_method, path_to_local, path_to_local_id, CaptureKind};
 use if_chain::if_chain;
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::Applicability;
+use rustc_errors::{Applicability, MultiSpan};
 use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
 use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, Local, Mutability, Node, PatKind, Stmt, StmtKind};
 use rustc_lint::LateContext;
@@ -14,7 +14,7 @@ use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::subst::GenericArgKind;
 use rustc_middle::ty::{self, Ty};
 use rustc_span::sym;
-use rustc_span::{MultiSpan, Span};
+use rustc_span::Span;
 
 const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
 
@@ -102,7 +102,7 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo
 
                     // Suggest replacing iter_call with iter_replacement, and removing stmt
                     let mut span = MultiSpan::from_span(method_name.ident.span);
-                    span.push_span_label(iter_call.span, "the iterator could be used here instead".into());
+                    span.push_span_label(iter_call.span, "the iterator could be used here instead");
                     span_lint_hir_and_then(
                         cx,
                         super::NEEDLESS_COLLECT,
diff --git a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs
index ecc9acf4445..06209bfe7b0 100644
--- a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs
+++ b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs
@@ -148,7 +148,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
 
         if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv.as_ref()) {
             if cx.tcx.is_const_fn_raw(def_id.to_def_id()) {
-                cx.tcx.sess.span_err(span, &err);
+                cx.tcx.sess.span_err(span, err.as_ref());
             }
         } else {
             span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`");
diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
index 5eb7b0f0521..d29d07da7b0 100644
--- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
@@ -235,11 +235,12 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
                                 for (span, suggestion) in clone_spans {
                                     diag.span_suggestion(
                                         span,
-                                        &snippet_opt(cx, span)
+                                        snippet_opt(cx, span)
                                             .map_or(
                                                 "change the call to".into(),
                                                 |x| Cow::from(format!("change `{}` to", x)),
-                                            ),
+                                            )
+                                            .as_ref(),
                                         suggestion.into(),
                                         Applicability::Unspecified,
                                     );
@@ -264,11 +265,12 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
                                 for (span, suggestion) in clone_spans {
                                     diag.span_suggestion(
                                         span,
-                                        &snippet_opt(cx, span)
+                                        snippet_opt(cx, span)
                                             .map_or(
                                                 "change the call to".into(),
                                                 |x| Cow::from(format!("change `{}` to", x))
-                                            ),
+                                            )
+                                            .as_ref(),
                                         suggestion.into(),
                                         Applicability::Unspecified,
                                     );
diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs
index 9d4313827f7..5f453dc1655 100644
--- a/src/tools/clippy/clippy_lints/src/ptr.rs
+++ b/src/tools/clippy/clippy_lints/src/ptr.rs
@@ -5,7 +5,7 @@ use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::expr_sig;
 use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
 use if_chain::if_chain;
-use rustc_errors::Applicability;
+use rustc_errors::{Applicability, MultiSpan};
 use rustc_hir::def_id::DefId;
 use rustc_hir::hir_id::HirIdMap;
 use rustc_hir::intravisit::{walk_expr, Visitor};
@@ -19,8 +19,8 @@ use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::{self, Ty};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::source_map::Span;
+use rustc_span::sym;
 use rustc_span::symbol::Symbol;
-use rustc_span::{sym, MultiSpan};
 use std::fmt;
 use std::iter;
 
diff --git a/src/tools/clippy/clippy_utils/src/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs
index 625a53899df..b142397f71b 100644
--- a/src/tools/clippy/clippy_utils/src/diagnostics.rs
+++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs
@@ -8,10 +8,10 @@
 //! Thank you!
 //! ~The `INTERNAL_METADATA_COLLECTOR` lint
 
-use rustc_errors::{emitter::MAX_SUGGESTION_HIGHLIGHT_LINES, Applicability, Diagnostic};
+use rustc_errors::{emitter::MAX_SUGGESTION_HIGHLIGHT_LINES, Applicability, Diagnostic, MultiSpan};
 use rustc_hir::HirId;
 use rustc_lint::{LateContext, Lint, LintContext};
-use rustc_span::source_map::{MultiSpan, Span};
+use rustc_span::source_map::Span;
 use std::env;
 
 fn docs_link(diag: &mut Diagnostic, lint: &'static Lint) {
@@ -155,7 +155,13 @@ where
     });
 }
 
-pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
+pub fn span_lint_hir(
+    cx: &LateContext<'_>,
+    lint: &'static Lint,
+    hir_id: HirId,
+    sp: Span,
+    msg: &str,
+) {
     cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
         let mut diag = diag.build(msg);
         docs_link(&mut diag, lint);
@@ -272,9 +278,14 @@ pub fn span_lint_and_sugg_for_edges(
         let sugg_lines_count = sugg.lines().count();
         if sugg_lines_count > MAX_SUGGESTION_HIGHLIGHT_LINES {
             let sm = cx.sess().source_map();
-            if let (Ok(line_upper), Ok(line_bottom)) = (sm.lookup_line(sp.lo()), sm.lookup_line(sp.hi())) {
+            if let (Ok(line_upper), Ok(line_bottom)) =
+                (sm.lookup_line(sp.lo()), sm.lookup_line(sp.hi()))
+            {
                 let split_idx = MAX_SUGGESTION_HIGHLIGHT_LINES / 2;
-                let span_upper = sm.span_until_char(sp.with_hi(line_upper.sf.lines[line_upper.line + split_idx]), '\n');
+                let span_upper = sm.span_until_char(
+                    sp.with_hi(line_upper.sf.lines[line_upper.line + split_idx]),
+                    '\n',
+                );
                 let span_bottom = sp.with_lo(line_bottom.sf.lines[line_bottom.line - split_idx]);
 
                 let sugg_lines_vec = sugg.lines().collect::<Vec<&str>>();
diff --git a/src/tools/clippy/src/driver.rs b/src/tools/clippy/src/driver.rs
index 855a6a6ef6a..bc1b0d74575 100644
--- a/src/tools/clippy/src/driver.rs
+++ b/src/tools/clippy/src/driver.rs
@@ -165,9 +165,13 @@ fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
     // Separate the output with an empty line
     eprintln!();
 
+    let fallback_bundle = rustc_errors::fallback_fluent_bundle(false)
+        .expect("failed to load fallback fluent bundle");
     let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
         rustc_errors::ColorConfig::Auto,
         None,
+        None,
+        fallback_bundle,
         false,
         false,
         None,
@@ -191,7 +195,7 @@ fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
     ];
 
     for note in &xs {
-        handler.note_without_error(note);
+        handler.note_without_error(note.as_ref());
     }
 
     // If backtraces are enabled, also print the query stack
diff --git a/src/tools/rustfmt/src/parse/session.rs b/src/tools/rustfmt/src/parse/session.rs
index 7571e6d078a..7125b2ee685 100644
--- a/src/tools/rustfmt/src/parse/session.rs
+++ b/src/tools/rustfmt/src/parse/session.rs
@@ -33,6 +33,12 @@ impl Emitter for SilentEmitter {
         None
     }
     fn emit_diagnostic(&mut self, _db: &Diagnostic) {}
+    fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
+        None
+    }
+    fn fallback_fluent_bundle(&self) -> &Lrc<rustc_errors::FluentBundle> {
+        panic!("silent emitter attempted to translate a diagnostic");
+    }
 }
 
 fn silent_emitter() -> Box<dyn Emitter + Send> {
@@ -82,6 +88,14 @@ impl Emitter for SilentOnIgnoredFilesEmitter {
         }
         self.handle_non_ignoreable_error(db);
     }
+
+    fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
+        self.emitter.fluent_bundle()
+    }
+
+    fn fallback_fluent_bundle(&self) -> &Lrc<rustc_errors::FluentBundle> {
+        self.emitter.fallback_fluent_bundle()
+    }
 }
 
 fn default_handler(
@@ -100,9 +114,13 @@ fn default_handler(
     let emitter = if hide_parse_errors {
         silent_emitter()
     } else {
+        let fallback_bundle = rustc_errors::fallback_fluent_bundle(false)
+            .expect("failed to load fallback fluent bundle");
         Box::new(EmitterWriter::stderr(
             color_cfg,
             Some(source_map.clone()),
+            None,
+            fallback_bundle,
             false,
             false,
             None,
@@ -313,7 +331,8 @@ mod tests {
         use super::*;
         use crate::config::IgnoreList;
         use crate::utils::mk_sp;
-        use rustc_span::{FileName as SourceMapFileName, MultiSpan, RealFileName};
+        use rustc_errors::MultiSpan;
+        use rustc_span::{FileName as SourceMapFileName, RealFileName};
         use std::path::PathBuf;
         use std::sync::atomic::AtomicU32;
 
@@ -328,6 +347,12 @@ mod tests {
             fn emit_diagnostic(&mut self, _db: &Diagnostic) {
                 self.num_emitted_errors.fetch_add(1, Ordering::Release);
             }
+            fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
+                None
+            }
+            fn fallback_fluent_bundle(&self) -> &Lrc<rustc_errors::FluentBundle> {
+                panic!("test emitter attempted to translate a diagnostic");
+            }
         }
 
         fn build_diagnostic(level: DiagnosticLevel, span: Option<MultiSpan>) -> Diagnostic {
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index ab4be43e495..ea6e01e577c 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -38,6 +38,8 @@ const EXCEPTIONS: &[(&str, &str)] = &[
     ("bitmaps", "MPL-2.0+"),          // cargo via im-rc
     ("instant", "BSD-3-Clause"),      // rustc_driver/tracing-subscriber/parking_lot
     ("snap", "BSD-3-Clause"),         // rustc
+    ("fluent-langneg", "Apache-2.0"), // rustc (fluent translations)
+    ("self_cell", "Apache-2.0"),      // rustc (fluent translations)
     // FIXME: this dependency violates the documentation comment above:
     ("fortanix-sgx-abi", "MPL-2.0"), // libstd but only for `sgx` target
 ];
@@ -113,6 +115,9 @@ const PERMITTED_DEPENDENCIES: &[&str] = &[
     "filetime",
     "fixedbitset",
     "flate2",
+    "fluent-bundle",
+    "fluent-langneg",
+    "fluent-syntax",
     "fortanix-sgx-abi",
     "generic-array",
     "getopts",
@@ -125,6 +130,8 @@ const PERMITTED_DEPENDENCIES: &[&str] = &[
     "if_chain",
     "indexmap",
     "instant",
+    "intl-memoizer",
+    "intl_pluralrules",
     "itertools",
     "itoa",
     "jobserver",
@@ -157,6 +164,7 @@ const PERMITTED_DEPENDENCIES: &[&str] = &[
     "pkg-config",
     "polonius-engine",
     "ppv-lite86",
+    "proc-macro-hack",
     "proc-macro2",
     "psm",
     "punycode",
@@ -184,6 +192,7 @@ const PERMITTED_DEPENDENCIES: &[&str] = &[
     "ryu",
     "scoped-tls",
     "scopeguard",
+    "self_cell",
     "semver",
     "serde",
     "serde_derive",
@@ -200,9 +209,12 @@ const PERMITTED_DEPENDENCIES: &[&str] = &[
     "tempfile",
     "termcolor",
     "termize",
+    "thiserror",
+    "thiserror-impl",
     "thorin-dwp",
     "thread_local",
     "time",
+    "tinystr",
     "tinyvec",
     "tracing",
     "tracing-attributes",
@@ -210,11 +222,16 @@ const PERMITTED_DEPENDENCIES: &[&str] = &[
     "tracing-log",
     "tracing-subscriber",
     "tracing-tree",
+    "type-map",
     "typenum",
     "unic-char-property",
     "unic-char-range",
     "unic-common",
     "unic-emoji-char",
+    "unic-langid",
+    "unic-langid-impl",
+    "unic-langid-macros",
+    "unic-langid-macros-impl",
     "unic-ucd-version",
     "unicode-normalization",
     "unicode-script",
@@ -228,7 +245,8 @@ const PERMITTED_DEPENDENCIES: &[&str] = &[
     "winapi-i686-pc-windows-gnu",
     "winapi-util",
     "winapi-x86_64-pc-windows-gnu",
-    // this is a false-positive: it's only used by rustfmt, but because it's enabled through a feature, tidy thinks it's used by rustc as well.
+    // this is a false-positive: it's only used by rustfmt, but because it's enabled through a
+    // feature, tidy thinks it's used by rustc as well.
     "yansi-term",
 ];