about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-05-30 22:49:36 +0000
committerbors <bors@rust-lang.org>2022-05-30 22:49:36 +0000
commit7be9ec27652f2c3b820d341158b0e005f42e248e (patch)
tree3d29d2a2f6f99b65f916dc0d7cd461b02de695eb
parentc35035cefc709abddabfb28ecc6a326458d46ce2 (diff)
parentf669b78ffc9db8352c859d8c83c244975dbf0397 (diff)
downloadrust-7be9ec27652f2c3b820d341158b0e005f42e248e.tar.gz
rust-7be9ec27652f2c3b820d341158b0e005f42e248e.zip
Auto merge of #97357 - davidtwco:diagnostic-translation-typed-subdiagnostic-simplification, r=oli-obk
errors: simplify referring to fluent attributes

To render the message of a Fluent attribute, the identifier of the Fluent message must be known. `DiagnosticMessage::FluentIdentifier` contains both the message's identifier and optionally the identifier of an attribute. Generated constants for each attribute would therefore need to be named uniquely (amongst all error messages) or be able to refer to only the attribute identifier which will be combined with a message identifier later. In this commit, the latter strategy is implemented as part of the `Diagnostic` type's functions for adding subdiagnostics of various kinds.

r? `@oli-obk`
-rw-r--r--compiler/rustc_error_messages/src/lib.rs72
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs86
-rw-r--r--compiler/rustc_errors/src/diagnostic_builder.rs47
-rw-r--r--compiler/rustc_errors/src/lib.rs3
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs36
-rw-r--r--compiler/rustc_macros/src/diagnostics/fluent.rs20
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs2
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs11
-rw-r--r--compiler/rustc_typeck/src/errors.rs11
-rw-r--r--src/test/ui-fulldeps/fluent-messages/test.rs6
-rw-r--r--src/test/ui-fulldeps/fluent-messages/test.stderr10
-rw-r--r--src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs4
-rw-r--r--src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr32
13 files changed, 190 insertions, 150 deletions
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index 7faf14a2472..02d076c95ca 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -234,6 +234,48 @@ pub fn fallback_fluent_bundle(
 /// Identifier for the Fluent message/attribute corresponding to a diagnostic message.
 type FluentId = Cow<'static, str>;
 
+/// Abstraction over a message in a subdiagnostic (i.e. label, note, help, etc) to support both
+/// translatable and non-translatable diagnostic messages.
+///
+/// Translatable messages for subdiagnostics are typically attributes attached to a larger Fluent
+/// message so messages of this type must be combined with a `DiagnosticMessage` (using
+/// `DiagnosticMessage::with_subdiagnostic_message`) before rendering. However, subdiagnostics from
+/// the `SessionSubdiagnostic` derive refer to Fluent identifiers directly.
+pub enum SubdiagnosticMessage {
+    /// Non-translatable diagnostic message.
+    // FIXME(davidtwco): can a `Cow<'static, str>` be used here?
+    Str(String),
+    /// Identifier of a Fluent message. Instances of this variant are generated by the
+    /// `SessionSubdiagnostic` derive.
+    FluentIdentifier(FluentId),
+    /// Attribute of a Fluent message. Needs to be combined with a Fluent identifier to produce an
+    /// actual translated message. Instances of this variant are generated by the `fluent_messages`
+    /// macro.
+    ///
+    /// <https://projectfluent.org/fluent/guide/attributes.html>
+    FluentAttr(FluentId),
+}
+
+impl SubdiagnosticMessage {
+    /// Create a `SubdiagnosticMessage` for the provided Fluent attribute.
+    pub fn attr(id: impl Into<FluentId>) -> Self {
+        SubdiagnosticMessage::FluentAttr(id.into())
+    }
+
+    /// Create a `SubdiagnosticMessage` for the provided Fluent identifier.
+    pub fn message(id: impl Into<FluentId>) -> Self {
+        SubdiagnosticMessage::FluentIdentifier(id.into())
+    }
+}
+
+/// `From` impl that enables existing diagnostic calls to functions which now take
+/// `impl Into<SubdiagnosticMessage>` to continue to work as before.
+impl<S: Into<String>> From<S> for SubdiagnosticMessage {
+    fn from(s: S) -> Self {
+        SubdiagnosticMessage::Str(s.into())
+    }
+}
+
 /// Abstraction over a message in a diagnostic to support both translatable and non-translatable
 /// diagnostic messages.
 ///
@@ -252,6 +294,29 @@ pub enum DiagnosticMessage {
 }
 
 impl DiagnosticMessage {
+    /// Given a `SubdiagnosticMessage` which may contain a Fluent attribute, create a new
+    /// `DiagnosticMessage` that combines that attribute with the Fluent identifier of `self`.
+    ///
+    /// - If the `SubdiagnosticMessage` is non-translatable then return the message as a
+    /// `DiagnosticMessage`.
+    /// - If `self` is non-translatable then return `self`'s message.
+    pub fn with_subdiagnostic_message(&self, sub: SubdiagnosticMessage) -> Self {
+        let attr = match sub {
+            SubdiagnosticMessage::Str(s) => return DiagnosticMessage::Str(s.clone()),
+            SubdiagnosticMessage::FluentIdentifier(id) => {
+                return DiagnosticMessage::FluentIdentifier(id, None);
+            }
+            SubdiagnosticMessage::FluentAttr(attr) => attr,
+        };
+
+        match self {
+            DiagnosticMessage::Str(s) => DiagnosticMessage::Str(s.clone()),
+            DiagnosticMessage::FluentIdentifier(id, _) => {
+                DiagnosticMessage::FluentIdentifier(id.clone(), Some(attr))
+            }
+        }
+    }
+
     /// 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.
@@ -266,14 +331,9 @@ impl DiagnosticMessage {
     }
 
     /// Create a `DiagnosticMessage` for the provided Fluent identifier.
-    pub fn fluent(id: impl Into<FluentId>) -> Self {
+    pub fn new(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
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index f130b5aa9a6..643f3c12134 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -1,7 +1,7 @@
 use crate::snippet::Style;
 use crate::{
-    CodeSuggestion, DiagnosticMessage, Level, MultiSpan, Substitution, SubstitutionPart,
-    SuggestionStyle,
+    CodeSuggestion, DiagnosticMessage, Level, MultiSpan, SubdiagnosticMessage, Substitution,
+    SubstitutionPart, SuggestionStyle,
 };
 use rustc_data_structures::stable_map::FxHashMap;
 use rustc_error_messages::FluentValue;
@@ -283,8 +283,8 @@ 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(&mut self, span: Span, label: impl Into<DiagnosticMessage>) -> &mut Self {
-        self.span.push_span_label(span, label.into());
+    pub fn span_label(&mut self, span: Span, label: impl Into<SubdiagnosticMessage>) -> &mut Self {
+        self.span.push_span_label(span, self.subdiagnostic_message_to_diagnostic_message(label));
         self
     }
 
@@ -401,12 +401,12 @@ impl Diagnostic {
     }
 
     /// Add a note attached to this diagnostic.
-    pub fn note(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
+    pub fn note(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::Note, msg, MultiSpan::new(), None);
         self
     }
 
-    pub fn highlighted_note<M: Into<DiagnosticMessage>>(
+    pub fn highlighted_note<M: Into<SubdiagnosticMessage>>(
         &mut self,
         msg: Vec<(M, Style)>,
     ) -> &mut Self {
@@ -416,7 +416,7 @@ impl Diagnostic {
 
     /// 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: impl Into<DiagnosticMessage>) -> &mut Self {
+    pub fn note_once(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::OnceNote, msg, MultiSpan::new(), None);
         self
     }
@@ -426,7 +426,7 @@ impl Diagnostic {
     pub fn span_note<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self {
         self.sub(Level::Note, msg, sp.into(), None);
         self
@@ -437,14 +437,14 @@ impl Diagnostic {
     pub fn span_note_once<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self {
         self.sub(Level::OnceNote, msg, sp.into(), None);
         self
     }
 
     /// Add a warning attached to this diagnostic.
-    pub fn warn(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
+    pub fn warn(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::Warning, msg, MultiSpan::new(), None);
         self
     }
@@ -454,14 +454,14 @@ impl Diagnostic {
     pub fn span_warn<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
     ) -> &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: impl Into<DiagnosticMessage>) -> &mut Self {
+    pub fn help(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self {
         self.sub(Level::Help, msg, MultiSpan::new(), None);
         self
     }
@@ -477,7 +477,7 @@ impl Diagnostic {
     pub fn span_help<S: Into<MultiSpan>>(
         &mut self,
         sp: S,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self {
         self.sub(Level::Help, msg, sp.into(), None);
         self
@@ -514,7 +514,7 @@ impl Diagnostic {
     /// In other words, multiple changes need to be applied as part of this suggestion.
     pub fn multipart_suggestion(
         &mut self,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self {
@@ -530,7 +530,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: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self {
@@ -544,7 +544,7 @@ impl Diagnostic {
     /// [`Diagnostic::multipart_suggestion()`] but you can set the [`SuggestionStyle`].
     pub fn multipart_suggestion_with_style(
         &mut self,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
         style: SuggestionStyle,
@@ -557,7 +557,7 @@ impl Diagnostic {
                     .map(|(span, snippet)| SubstitutionPart { snippet, span })
                     .collect(),
             }],
-            msg: msg.into(),
+            msg: self.subdiagnostic_message_to_diagnostic_message(msg),
             style,
             applicability,
         });
@@ -572,7 +572,7 @@ impl Diagnostic {
     /// improve understandability.
     pub fn tool_only_multipart_suggestion(
         &mut self,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self {
@@ -584,7 +584,7 @@ impl Diagnostic {
                     .map(|(span, snippet)| SubstitutionPart { snippet, span })
                     .collect(),
             }],
-            msg: msg.into(),
+            msg: self.subdiagnostic_message_to_diagnostic_message(msg),
             style: SuggestionStyle::CompletelyHidden,
             applicability,
         });
@@ -611,7 +611,7 @@ impl Diagnostic {
     pub fn span_suggestion(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
     ) -> &mut Self {
@@ -629,7 +629,7 @@ impl Diagnostic {
     pub fn span_suggestion_with_style(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
         style: SuggestionStyle,
@@ -638,7 +638,7 @@ impl Diagnostic {
             substitutions: vec![Substitution {
                 parts: vec![SubstitutionPart { snippet: suggestion.to_string(), span: sp }],
             }],
-            msg: msg.into(),
+            msg: self.subdiagnostic_message_to_diagnostic_message(msg),
             style,
             applicability,
         });
@@ -649,7 +649,7 @@ impl Diagnostic {
     pub fn span_suggestion_verbose(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
     ) -> &mut Self {
@@ -668,7 +668,7 @@ impl Diagnostic {
     pub fn span_suggestions(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestions: impl Iterator<Item = String>,
         applicability: Applicability,
     ) -> &mut Self {
@@ -680,7 +680,7 @@ impl Diagnostic {
             .collect();
         self.push_suggestion(CodeSuggestion {
             substitutions,
-            msg: msg.into(),
+            msg: self.subdiagnostic_message_to_diagnostic_message(msg),
             style: SuggestionStyle::ShowCode,
             applicability,
         });
@@ -691,7 +691,7 @@ impl Diagnostic {
     /// See also [`Diagnostic::span_suggestion()`].
     pub fn multipart_suggestions(
         &mut self,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestions: impl Iterator<Item = Vec<(Span, String)>>,
         applicability: Applicability,
     ) -> &mut Self {
@@ -704,7 +704,7 @@ impl Diagnostic {
                         .collect(),
                 })
                 .collect(),
-            msg: msg.into(),
+            msg: self.subdiagnostic_message_to_diagnostic_message(msg),
             style: SuggestionStyle::ShowCode,
             applicability,
         });
@@ -717,7 +717,7 @@ impl Diagnostic {
     pub fn span_suggestion_short(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
     ) -> &mut Self {
@@ -740,7 +740,7 @@ impl Diagnostic {
     pub fn span_suggestion_hidden(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
     ) -> &mut Self {
@@ -761,7 +761,7 @@ impl Diagnostic {
     pub fn tool_only_span_suggestion(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
     ) -> &mut Self {
@@ -831,6 +831,18 @@ impl Diagnostic {
         &self.message
     }
 
+    /// Helper function that takes a `SubdiagnosticMessage` and returns a `DiagnosticMessage` by
+    /// combining it with the primary message of the diagnostic (if translatable, otherwise it just
+    /// passes the user's string along).
+    fn subdiagnostic_message_to_diagnostic_message(
+        &self,
+        attr: impl Into<SubdiagnosticMessage>,
+    ) -> DiagnosticMessage {
+        let msg =
+            self.message.iter().map(|(msg, _)| msg).next().expect("diagnostic with no messages");
+        msg.with_subdiagnostic_message(attr.into())
+    }
+
     /// Convenience function for internal use, clients should use one of the
     /// public methods above.
     ///
@@ -838,13 +850,16 @@ impl Diagnostic {
     pub fn sub(
         &mut self,
         level: Level,
-        message: impl Into<DiagnosticMessage>,
+        message: impl Into<SubdiagnosticMessage>,
         span: MultiSpan,
         render_span: Option<MultiSpan>,
     ) {
         let sub = SubDiagnostic {
             level,
-            message: vec![(message.into(), Style::NoStyle)],
+            message: vec![(
+                self.subdiagnostic_message_to_diagnostic_message(message),
+                Style::NoStyle,
+            )],
             span,
             render_span,
         };
@@ -853,14 +868,17 @@ impl Diagnostic {
 
     /// Convenience function for internal use, clients should use one of the
     /// public methods above.
-    fn sub_with_highlights<M: Into<DiagnosticMessage>>(
+    fn sub_with_highlights<M: Into<SubdiagnosticMessage>>(
         &mut self,
         level: Level,
         mut message: Vec<(M, Style)>,
         span: MultiSpan,
         render_span: Option<MultiSpan>,
     ) {
-        let message = message.drain(..).map(|m| (m.0.into(), m.1)).collect();
+        let message = message
+            .drain(..)
+            .map(|m| (self.subdiagnostic_message_to_diagnostic_message(m.0), m.1))
+            .collect();
         let sub = SubDiagnostic { level, message, span, render_span };
         self.children.push(sub);
     }
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index 6ef2c832c65..9e0a99849a3 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -1,5 +1,8 @@
 use crate::diagnostic::IntoDiagnosticArg;
-use crate::{Diagnostic, DiagnosticId, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed};
+use crate::{
+    Diagnostic, DiagnosticId, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed,
+    SubdiagnosticMessage,
+};
 use crate::{Handler, Level, MultiSpan, StashKey};
 use rustc_lint_defs::Applicability;
 
@@ -395,7 +398,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<DiagnosticMessage>) -> &mut Self);
+    pub fn span_label(&mut self, span: Span, label: impl Into<SubdiagnosticMessage>) -> &mut Self);
 
     forward!(
     /// Labels all the given spans with the provided label.
@@ -430,25 +433,29 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
         found: DiagnosticStyledString,
     ) -> &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 note(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
+    forward!(pub fn note_once(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
     forward!(pub fn span_note(
         &mut self,
         sp: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self);
     forward!(pub fn span_note_once(
         &mut self,
         sp: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
     ) -> &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: impl Into<DiagnosticMessage>) -> &mut Self);
+    forward!(pub fn warn(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
+    forward!(pub fn span_warn(
+        &mut self,
+        sp: impl Into<MultiSpan>,
+        msg: impl Into<SubdiagnosticMessage>,
+    ) -> &mut Self);
+    forward!(pub fn help(&mut self, msg: impl Into<SubdiagnosticMessage>) -> &mut Self);
     forward!(pub fn span_help(
         &mut self,
         sp: impl Into<MultiSpan>,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
     ) -> &mut Self);
     forward!(pub fn help_use_latest_edition(&mut self,) -> &mut Self);
     forward!(pub fn set_is_lint(&mut self,) -> &mut Self);
@@ -457,67 +464,67 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
 
     forward!(pub fn multipart_suggestion(
         &mut self,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn multipart_suggestion_verbose(
         &mut self,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn tool_only_multipart_suggestion(
         &mut self,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: Vec<(Span, String)>,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn span_suggestion(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn span_suggestions(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestions: impl Iterator<Item = String>,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn multipart_suggestions(
         &mut self,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestions: impl Iterator<Item = Vec<(Span, String)>>,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn span_suggestion_short(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn span_suggestion_verbose(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn span_suggestion_hidden(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
     ) -> &mut Self);
     forward!(pub fn tool_only_span_suggestion(
         &mut self,
         sp: Span,
-        msg: impl Into<DiagnosticMessage>,
+        msg: impl Into<SubdiagnosticMessage>,
         suggestion: impl ToString,
         applicability: Applicability,
     ) -> &mut Self);
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 5b9b65da343..fb02f1d68eb 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -32,7 +32,8 @@ use rustc_data_structures::sync::{self, Lock, Lrc};
 use rustc_data_structures::AtomicRef;
 pub use rustc_error_messages::{
     fallback_fluent_bundle, fluent, fluent_bundle, DiagnosticMessage, FluentBundle,
-    LanguageIdentifier, LazyFallbackBundle, MultiSpan, SpanLabel, DEFAULT_LOCALE_RESOURCES,
+    LanguageIdentifier, LazyFallbackBundle, MultiSpan, SpanLabel, SubdiagnosticMessage,
+    DEFAULT_LOCALE_RESOURCES,
 };
 pub use rustc_lint_defs::{pluralize, Applicability};
 use rustc_span::source_map::SourceMap;
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
index d7daee64d79..95ee0d4a060 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
@@ -126,14 +126,14 @@ impl<'a> SessionDiagnosticDerive<'a> {
                     (Some((SessionDiagnosticKind::Error, _)), Some((slug, _))) => {
                         quote! {
                             let mut #diag = #sess.struct_err(
-                                rustc_errors::DiagnosticMessage::fluent(#slug),
+                                rustc_errors::DiagnosticMessage::new(#slug),
                             );
                         }
                     }
                     (Some((SessionDiagnosticKind::Warn, _)), Some((slug, _))) => {
                         quote! {
                             let mut #diag = #sess.struct_warn(
-                                rustc_errors::DiagnosticMessage::fluent(#slug),
+                                rustc_errors::DiagnosticMessage::new(#slug),
                             );
                         }
                     }
@@ -254,21 +254,6 @@ impl SessionDiagnosticDeriveBuilder {
 
         if matches!(name, "help" | "note") && matches!(meta, Meta::Path(_) | 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 {
-                            Meta::Path(_) => "",
-                            Meta::NameValue(_) => " = ...",
-                            _ => unreachable!(),
-                        }
-                    )
-                ),
-            };
             let id = match meta {
                 Meta::Path(..) => quote! { #name },
                 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
@@ -279,7 +264,7 @@ impl SessionDiagnosticDeriveBuilder {
             let fn_name = proc_macro2::Ident::new(name, attr.span());
 
             return Ok(quote! {
-                #diag.#fn_name(rustc_errors::DiagnosticMessage::fluent_attr(#slug, #id));
+                #diag.#fn_name(rustc_errors::SubdiagnosticMessage::attr(#id));
             });
         }
 
@@ -525,13 +510,8 @@ impl SessionDiagnosticDeriveBuilder {
 
                 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 msg = quote! { rustc_errors::SubdiagnosticMessage::attr(#msg) };
                 let code = code.unwrap_or_else(|| quote! { String::new() });
 
                 Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
@@ -549,14 +529,11 @@ impl SessionDiagnosticDeriveBuilder {
         fluent_attr_identifier: &str,
     ) -> 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)
+                rustc_errors::SubdiagnosticMessage::attr(#fluent_attr_identifier)
             );
         }
     }
@@ -565,9 +542,8 @@ impl SessionDiagnosticDeriveBuilder {
     /// and `fluent_attr_identifier`.
     fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: &str) -> TokenStream {
         let diag = &self.diag;
-        let slug = self.slug.as_ref().map(|(slug, _)| slug.as_str()).unwrap_or("missing-slug");
         quote! {
-            #diag.#kind(rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier));
+            #diag.#kind(rustc_errors::SubdiagnosticMessage::attr(#fluent_attr_identifier));
         }
     }
 
diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs
index 8523d7fa9f9..42a9bf477a4 100644
--- a/compiler/rustc_macros/src/diagnostics/fluent.rs
+++ b/compiler/rustc_macros/src/diagnostics/fluent.rs
@@ -11,7 +11,7 @@ use proc_macro::{Diagnostic, Level, Span};
 use proc_macro2::TokenStream;
 use quote::quote;
 use std::{
-    collections::HashMap,
+    collections::{HashMap, HashSet},
     fs::File,
     io::Read,
     path::{Path, PathBuf},
@@ -100,6 +100,10 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
         let ident_span = res.ident.span().unwrap();
         let path_span = res.resource.span().unwrap();
 
+        // Set of Fluent attribute names already output, to avoid duplicate type errors - any given
+        // constant created for a given attribute is the same.
+        let mut previous_attrs = HashSet::new();
+
         let relative_ftl_path = res.resource.value();
         let absolute_ftl_path =
             invocation_relative_path_to_absolute(ident_span, &relative_ftl_path);
@@ -199,13 +203,15 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
                 });
 
                 for Attribute { id: Identifier { name: attr_name }, .. } in attributes {
-                    let attr_snake_name = attr_name.replace("-", "_");
-                    let snake_name = Ident::new(&format!("{snake_name}_{attr_snake_name}"), span);
+                    let snake_name = Ident::new(&attr_name.replace("-", "_"), span);
+                    if !previous_attrs.insert(snake_name.clone()) {
+                        continue;
+                    }
+
                     constants.extend(quote! {
-                        pub const #snake_name: crate::DiagnosticMessage =
-                            crate::DiagnosticMessage::FluentIdentifier(
-                                std::borrow::Cow::Borrowed(#name),
-                                Some(std::borrow::Cow::Borrowed(#attr_name))
+                        pub const #snake_name: crate::SubdiagnosticMessage =
+                            crate::SubdiagnosticMessage::FluentAttr(
+                                std::borrow::Cow::Borrowed(#attr_name)
                             );
                     });
                 }
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index df01419c82a..9aeb484bfd5 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -397,7 +397,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
 
         let diag = &self.diag;
         let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
-        let message = quote! { rustc_errors::DiagnosticMessage::fluent(#slug) };
+        let message = quote! { rustc_errors::SubdiagnosticMessage::message(#slug) };
         let call = if matches!(kind, SubdiagnosticKind::Suggestion(..)) {
             if let Some(span) = span_field {
                 quote! { #diag.#name(#span, #message, #code, #applicability); }
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index c9da58aae5c..12302315e90 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -17,10 +17,10 @@ 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, DiagnosticMessage, Handler, MultiSpan, PResult,
+    fluent, Applicability, DiagnosticBuilder, DiagnosticMessage, Handler, MultiSpan, PResult,
 };
+use rustc_errors::{pluralize, struct_span_err, Diagnostic, EmissionGuarantee, ErrorGuaranteed};
 use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic};
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::{kw, Ident};
@@ -658,13 +658,10 @@ impl<'a> Parser<'a> {
                     err.delay_as_bug();
                     self.struct_span_err(
                         expr.span,
-                        DiagnosticMessage::fluent("parser-struct-literal-body-without-path"),
+                        fluent::parser::struct_literal_body_without_path,
                     )
                     .multipart_suggestion(
-                        DiagnosticMessage::fluent_attr(
-                            "parser-struct-literal-body-without-path",
-                            "suggestion",
-                        ),
+                        fluent::parser::suggestion,
                         vec![
                             (expr.span.shrink_to_lo(), "{ SomeStruct ".to_string()),
                             (expr.span.shrink_to_hi(), " }".to_string()),
diff --git a/compiler/rustc_typeck/src/errors.rs b/compiler/rustc_typeck/src/errors.rs
index d9c9f2920b0..a7f736fed14 100644
--- a/compiler/rustc_typeck/src/errors.rs
+++ b/compiler/rustc_typeck/src/errors.rs
@@ -277,7 +277,7 @@ impl<'a> SessionDiagnostic<'a> for MissingTypeParams {
                 .join(", "),
         );
 
-        err.span_label(self.def_span, rustc_errors::fluent::typeck::missing_type_params_label);
+        err.span_label(self.def_span, rustc_errors::fluent::typeck::label);
 
         let mut suggested = false;
         if let (Ok(snippet), true) = (
@@ -295,7 +295,7 @@ impl<'a> SessionDiagnostic<'a> for MissingTypeParams {
                 // least we can clue them to the correct syntax `Iterator<Type>`.
                 err.span_suggestion(
                     self.span,
-                    rustc_errors::fluent::typeck::missing_type_params_suggestion,
+                    rustc_errors::fluent::typeck::suggestion,
                     format!("{}<{}>", snippet, self.missing_type_params.join(", ")),
                     Applicability::HasPlaceholders,
                 );
@@ -303,13 +303,10 @@ impl<'a> SessionDiagnostic<'a> for MissingTypeParams {
             }
         }
         if !suggested {
-            err.span_label(
-                self.span,
-                rustc_errors::fluent::typeck::missing_type_params_no_suggestion_label,
-            );
+            err.span_label(self.span, rustc_errors::fluent::typeck::no_suggestion_label);
         }
 
-        err.note(rustc_errors::fluent::typeck::missing_type_params_note);
+        err.note(rustc_errors::fluent::typeck::note);
         err
     }
 }
diff --git a/src/test/ui-fulldeps/fluent-messages/test.rs b/src/test/ui-fulldeps/fluent-messages/test.rs
index b05d3d08ccb..0390a07850b 100644
--- a/src/test/ui-fulldeps/fluent-messages/test.rs
+++ b/src/test/ui-fulldeps/fluent-messages/test.rs
@@ -12,6 +12,12 @@ pub enum DiagnosticMessage {
     FluentIdentifier(std::borrow::Cow<'static, str>, Option<std::borrow::Cow<'static, str>>),
 }
 
+/// Copy of the relevant `SubdiagnosticMessage` variant constructed by `fluent_messages` as it
+/// expects `crate::SubdiagnosticMessage` to exist.
+pub enum SubdiagnosticMessage {
+    FluentAttr(std::borrow::Cow<'static, str>),
+}
+
 mod missing_absolute {
     use super::fluent_messages;
 
diff --git a/src/test/ui-fulldeps/fluent-messages/test.stderr b/src/test/ui-fulldeps/fluent-messages/test.stderr
index f88d09bee6e..526bca43f69 100644
--- a/src/test/ui-fulldeps/fluent-messages/test.stderr
+++ b/src/test/ui-fulldeps/fluent-messages/test.stderr
@@ -1,5 +1,5 @@
 error: could not open Fluent resource
-  --> $DIR/test.rs:19:29
+  --> $DIR/test.rs:25:29
    |
 LL |         missing_absolute => "/definitely_does_not_exist.ftl",
    |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -7,7 +7,7 @@ LL |         missing_absolute => "/definitely_does_not_exist.ftl",
    = note: os-specific message
 
 error: could not open Fluent resource
-  --> $DIR/test.rs:28:29
+  --> $DIR/test.rs:34:29
    |
 LL |         missing_relative => "../definitely_does_not_exist.ftl",
    |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -15,7 +15,7 @@ LL |         missing_relative => "../definitely_does_not_exist.ftl",
    = note: os-specific message
 
 error: could not parse Fluent resource
-  --> $DIR/test.rs:37:28
+  --> $DIR/test.rs:43:28
    |
 LL |         missing_message => "./missing-message.ftl",
    |                            ^^^^^^^^^^^^^^^^^^^^^^^
@@ -30,13 +30,13 @@ error: expected a message field for "missing-message"
   |
 
 error: overrides existing message: `key`
-  --> $DIR/test.rs:47:9
+  --> $DIR/test.rs:53:9
    |
 LL |         b => "./duplicate-b.ftl",
    |         ^
    |
 help: previously defined in this resource
-  --> $DIR/test.rs:46:9
+  --> $DIR/test.rs:52:9
    |
 LL |         a => "./duplicate-a.ftl",
    |         ^
diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
index 1cdc5d18c28..84d5de17309 100644
--- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
+++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
@@ -404,7 +404,6 @@ struct ErrorWithHelpCustom {
 
 #[derive(SessionDiagnostic)]
 #[help]
-//~^ ERROR `#[help]` must come after `#[error(..)]` or `#[warn(..)]`
 #[error(code = "E0123", slug = "foo")]
 struct ErrorWithHelpWrongOrder {
     val: String,
@@ -412,7 +411,6 @@ struct ErrorWithHelpWrongOrder {
 
 #[derive(SessionDiagnostic)]
 #[help = "bar"]
-//~^ ERROR `#[help = ...]` must come after `#[error(..)]` or `#[warn(..)]`
 #[error(code = "E0123", slug = "foo")]
 struct ErrorWithHelpCustomWrongOrder {
     val: String,
@@ -420,7 +418,6 @@ struct ErrorWithHelpCustomWrongOrder {
 
 #[derive(SessionDiagnostic)]
 #[note]
-//~^ ERROR `#[note]` must come after `#[error(..)]` or `#[warn(..)]`
 #[error(code = "E0123", slug = "foo")]
 struct ErrorWithNoteWrongOrder {
     val: String,
@@ -428,7 +425,6 @@ struct ErrorWithNoteWrongOrder {
 
 #[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-diagnostic/diagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
index 2583363120a..85ea44ec278 100644
--- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
+++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
@@ -301,38 +301,14 @@ LL |     #[label("bar")]
    |
    = help: only `suggestion{,_short,_hidden,_verbose}` are valid field attributes
 
-error: `#[help]` must come after `#[error(..)]` or `#[warn(..)]`
-  --> $DIR/diagnostic-derive.rs:406:1
-   |
-LL | #[help]
-   | ^^^^^^^
-
-error: `#[help = ...]` must come after `#[error(..)]` or `#[warn(..)]`
-  --> $DIR/diagnostic-derive.rs:414:1
-   |
-LL | #[help = "bar"]
-   | ^^^^^^^^^^^^^^^
-
-error: `#[note]` must come after `#[error(..)]` or `#[warn(..)]`
-  --> $DIR/diagnostic-derive.rs:422:1
-   |
-LL | #[note]
-   | ^^^^^^^
-
-error: `#[note = ...]` must come after `#[error(..)]` or `#[warn(..)]`
-  --> $DIR/diagnostic-derive.rs:430:1
-   |
-LL | #[note = "bar"]
-   | ^^^^^^^^^^^^^^^
-
 error: applicability cannot be set in both the field and attribute
-  --> $DIR/diagnostic-derive.rs:440:49
+  --> $DIR/diagnostic-derive.rs:436:49
    |
 LL |     #[suggestion(message = "bar", code = "...", applicability = "maybe-incorrect")]
    |                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: invalid applicability
-  --> $DIR/diagnostic-derive.rs:448:49
+  --> $DIR/diagnostic-derive.rs:444:49
    |
 LL |     #[suggestion(message = "bar", code = "...", applicability = "batman")]
    |                                                 ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -363,12 +339,12 @@ LL | #[derive(SessionDiagnostic)]
              rustc_middle::ty::Ty<'tcx>
              usize
 note: required by a bound in `DiagnosticBuilder::<'a, G>::set_arg`
-  --> $COMPILER_DIR/rustc_errors/src/diagnostic_builder.rs:531:19
+  --> $COMPILER_DIR/rustc_errors/src/diagnostic_builder.rs:538:19
    |
 LL |         arg: impl IntoDiagnosticArg,
    |                   ^^^^^^^^^^^^^^^^^ required by this bound in `DiagnosticBuilder::<'a, G>::set_arg`
    = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error: aborting due to 43 previous errors
+error: aborting due to 39 previous errors
 
 For more information about this error, try `rustc --explain E0277`.