about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorDylan DPC <99973273+Dylan-DPC@users.noreply.github.com>2022-04-05 09:33:22 +0200
committerGitHub <noreply@github.com>2022-04-05 09:33:22 +0200
commitd4730244d7be72b2c1f0e528bb3c41a91ebdf924 (patch)
treef4ab3bdc4e725db3cebbae49d440e4d6762977ec /src
parent73eab351093a16637298fc501ded798d26419d73 (diff)
parentccd482032672a223ceaa8ab8d7637180a357fdea (diff)
downloadrust-d4730244d7be72b2c1f0e528bb3c41a91ebdf924.tar.gz
rust-d4730244d7be72b2c1f0e528bb3c41a91ebdf924.zip
Rollup merge of #95512 - davidtwco:diagnostic-translation, r=oli-obk
diagnostics: translation infrastructure

An implementation of the infrastructure required to have translatable diagnostic messages.

- Introduces a `DiagnosticMessage` type which can represent both the current non-translatable messages and identifiers for [Fluent](https://projectfluent.org/).
- Modifies current diagnostic API so that existing calls still work but `DiagnosticMessage`s can be provided too.
- Adds support for always loading a "fallback bundle" containing the English diagnostic messages, which are used when a `DiagnosticMessage::FluentIdentifier` is used in a diagnostic being emitted.
- Adds support for loading a "primary bundle" which contains the user's preferred language translation, and is used preferentially when it contains a diagnostic message being emitted. Primary bundles are loaded either from the path provided to `-Ztranslate-alternate-ftl` (for testing), or from the sysroot at `$sysroot/locale/$locale/*.ftl` given a locale with `-Ztranslate-lang` (which is parsed as a language identifier).
- Adds "diagnostic args" which enable normally-interpolated variables to be made available as variables for Fluent messages to use.
- Updates `#[derive(SessionDiagnostic)]` so that it can only be used for translatable diagnostics and update the handful of diagnostics which used the derive to be translatable.

For example, the following diagnostic...

```rust
#[derive(SessionDiagnostic)]
#[error = "E0195"]
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"]
    pub span: Span,
    #[label = "lifetimes in impl do not match this {item_kind} in trait"]
    pub generics_span: Option<Span>,
    pub item_kind: &'static str,
    pub ident: Ident,
}
```

...becomes...

```rust
#[derive(SessionDiagnostic)]
#[error(code = "E0195", slug = "typeck-lifetimes-or-bounds-mismatch-on-trait")]
pub struct LifetimesOrBoundsMismatchOnTrait {
    #[primary_span]
    #[label]
    pub span: Span,
    #[label = "generics-label"]
    pub generics_span: Option<Span>,
    pub item_kind: &'static str,
    pub ident: Ident,
}
```

```fluent
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
```

r? `@estebank`
cc `@oli-obk` `@Manishearth`
Diffstat (limited to 'src')
-rw-r--r--src/librustdoc/core.rs6
-rw-r--r--src/librustdoc/doctest.rs31
-rw-r--r--src/librustdoc/passes/check_code_block_syntax.rs16
-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/ui-fulldeps/session-derive-errors.rs358
-rw-r--r--src/test/ui-fulldeps/session-derive-errors.stderr299
-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/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
20 files changed, 684 insertions, 206 deletions
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/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/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/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/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/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",
 ];