diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs
index af1c6594446..5d497d4a188 100644
--- a/compiler/rustc_codegen_ssa/src/back/write.rs
+++ b/compiler/rustc_codegen_ssa/src/back/write.rs
@@ -14,7 +14,7 @@ use rustc_data_structures::memmap::Mmap;
 use rustc_data_structures::profiling::{SelfProfilerRef, VerboseTimingGuard};
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::emitter::Emitter;
-use rustc_errors::{translation::Translate, DiagCtxt, DiagnosticId, FatalError, Level};
+use rustc_errors::{translation::Translate, DiagCtxt, FatalError, Level};
 use rustc_errors::{DiagnosticBuilder, DiagnosticMessage, Style};
 use rustc_fs_util::link_or_copy;
 use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
@@ -1000,7 +1000,7 @@ type DiagnosticArgName<'source> = Cow<'source, str>;
 struct Diagnostic {
     msgs: Vec<(DiagnosticMessage, Style)>,
     args: FxHashMap<DiagnosticArgName<'static>, rustc_errors::DiagnosticArgValue<'static>>,
-    code: Option<DiagnosticId>,
+    code: Option<String>,
     lvl: Level,
 }
 
diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
index 648c9118400..f0699a56f98 100644
--- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
+++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
@@ -9,8 +9,8 @@ use crate::emitter::FileWithAnnotatedLines;
 use crate::snippet::Line;
 use crate::translation::{to_fluent_args, Translate};
 use crate::{
-    CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, Emitter, FluentBundle,
-    LazyFallbackBundle, Level, MultiSpan, Style, SubDiagnostic,
+    CodeSuggestion, Diagnostic, DiagnosticMessage, Emitter, FluentBundle, LazyFallbackBundle,
+    Level, MultiSpan, Style, SubDiagnostic,
 };
 use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation};
 use rustc_data_structures::sync::Lrc;
@@ -127,7 +127,7 @@ impl AnnotateSnippetEmitter {
         level: &Level,
         messages: &[(DiagnosticMessage, Style)],
         args: &FluentArgs<'_>,
-        code: &Option<DiagnosticId>,
+        code: &Option<String>,
         msp: &MultiSpan,
         _children: &[SubDiagnostic],
         _suggestions: &[CodeSuggestion],
@@ -181,11 +181,7 @@ impl AnnotateSnippetEmitter {
             let snippet = Snippet {
                 title: Some(Annotation {
                     label: Some(&message),
-                    id: code.as_ref().map(|c| match c {
-                        DiagnosticId::Error(val) | DiagnosticId::Lint { name: val, .. } => {
-                            val.as_str()
-                        }
-                    }),
+                    id: code.as_deref(),
                     annotation_type: annotation_type_for_level(*level),
                 }),
                 footer: vec![],
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 786aced5b4f..4934bc2450c 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -104,7 +104,7 @@ pub struct Diagnostic {
     pub(crate) level: Level,
 
     pub messages: Vec<(DiagnosticMessage, Style)>,
-    pub code: Option<DiagnosticId>,
+    pub code: Option<String>,
     pub span: MultiSpan,
     pub children: Vec<SubDiagnostic>,
     pub suggestions: Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
@@ -115,9 +115,9 @@ pub struct Diagnostic {
     /// `span` if there is one. Otherwise, it is `DUMMY_SP`.
     pub sort_span: Span,
 
-    /// If diagnostic is from Lint, custom hash function ignores notes
-    /// otherwise hash is based on the all the fields
-    pub is_lint: bool,
+    /// If diagnostic is from Lint, custom hash function ignores children.
+    /// Otherwise hash is based on the all the fields.
+    pub is_lint: Option<IsLint>,
 
     /// With `-Ztrack_diagnostics` enabled,
     /// we print where in rustc this error was emitted.
@@ -146,13 +146,11 @@ impl fmt::Display for DiagnosticLocation {
 }
 
 #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
-pub enum DiagnosticId {
-    Error(String),
-    Lint {
-        name: String,
-        /// Indicates whether this lint should show up in cargo's future breakage report.
-        has_future_breakage: bool,
-    },
+pub struct IsLint {
+    /// The lint name.
+    pub(crate) name: String,
+    /// Indicates whether this lint should show up in cargo's future breakage report.
+    has_future_breakage: bool,
 }
 
 /// A "sub"-diagnostic attached to a parent diagnostic.
@@ -231,7 +229,7 @@ impl Diagnostic {
             suggestions: Ok(vec![]),
             args: Default::default(),
             sort_span: DUMMY_SP,
-            is_lint: false,
+            is_lint: None,
             emitted_at: DiagnosticLocation::caller(),
         }
     }
@@ -288,16 +286,13 @@ impl Diagnostic {
 
     /// Indicates whether this diagnostic should show up in cargo's future breakage report.
     pub(crate) fn has_future_breakage(&self) -> bool {
-        match self.code {
-            Some(DiagnosticId::Lint { has_future_breakage, .. }) => has_future_breakage,
-            _ => false,
-        }
+        matches!(self.is_lint, Some(IsLint { has_future_breakage: true, .. }))
     }
 
     pub(crate) fn is_force_warn(&self) -> bool {
         match self.level {
             Level::ForceWarning(_) => {
-                assert!(self.is_lint);
+                assert!(self.is_lint.is_some());
                 true
             }
             _ => false,
@@ -893,12 +888,12 @@ impl Diagnostic {
         self
     }
 
-    pub fn is_lint(&mut self) -> &mut Self {
-        self.is_lint = true;
+    pub fn is_lint(&mut self, name: String, has_future_breakage: bool) -> &mut Self {
+        self.is_lint = Some(IsLint { name, has_future_breakage });
         self
     }
 
-    pub fn code(&mut self, s: DiagnosticId) -> &mut Self {
+    pub fn code(&mut self, s: String) -> &mut Self {
         self.code = Some(s);
         self
     }
@@ -908,8 +903,8 @@ impl Diagnostic {
         self
     }
 
-    pub fn get_code(&self) -> Option<DiagnosticId> {
-        self.code.clone()
+    pub fn get_code(&self) -> Option<&str> {
+        self.code.as_deref()
     }
 
     pub fn primary_message(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut Self {
@@ -995,7 +990,8 @@ impl Diagnostic {
         &Level,
         &[(DiagnosticMessage, Style)],
         Vec<(&Cow<'static, str>, &DiagnosticArgValue<'static>)>,
-        &Option<DiagnosticId>,
+        &Option<String>,
+        &Option<IsLint>,
         &MultiSpan,
         &Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
         Option<&[SubDiagnostic]>,
@@ -1005,9 +1001,10 @@ impl Diagnostic {
             &self.messages,
             self.args().collect(),
             &self.code,
+            &self.is_lint,
             &self.span,
             &self.suggestions,
-            (if self.is_lint { None } else { Some(&self.children) }),
+            (if self.is_lint.is_some() { None } else { Some(&self.children) }),
         )
     }
 }
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index bd7c58d904e..87e2d295c7f 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -1,8 +1,8 @@
 use crate::diagnostic::IntoDiagnosticArg;
 use crate::{DiagCtxt, Level, MultiSpan, StashKey};
 use crate::{
-    Diagnostic, DiagnosticId, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed,
-    ExplicitBug, SubdiagnosticMessage,
+    Diagnostic, DiagnosticMessage, DiagnosticStyledString, ErrorGuaranteed, ExplicitBug,
+    SubdiagnosticMessage,
 };
 use rustc_lint_defs::Applicability;
 use rustc_span::source_map::Spanned;
@@ -395,8 +395,11 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
     forward!((span, with_span)(
         sp: impl Into<MultiSpan>,
     ));
+    forward!((is_lint, with_is_lint)(
+        name: String, has_future_breakage: bool,
+    ));
     forward!((code, with_code)(
-        s: DiagnosticId,
+        s: String,
     ));
     forward!((arg, with_arg)(
         name: impl Into<Cow<'static, str>>, arg: impl IntoDiagnosticArg,
@@ -437,15 +440,11 @@ impl<G: EmissionGuarantee> Drop for DiagnosticBuilder<'_, G> {
 #[macro_export]
 macro_rules! struct_span_code_err {
     ($dcx:expr, $span:expr, $code:ident, $($message:tt)*) => ({
-        $dcx.struct_span_err(
-            $span,
-            format!($($message)*),
-        )
-        .with_code($crate::error_code!($code))
+        $dcx.struct_span_err($span, format!($($message)*)).with_code($crate::error_code!($code))
     })
 }
 
 #[macro_export]
 macro_rules! error_code {
-    ($code:ident) => {{ $crate::DiagnosticId::Error(stringify!($code).to_owned()) }};
+    ($code:ident) => {{ stringify!($code).to_owned() }};
 }
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 987832e6937..23efdaea0eb 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -16,9 +16,9 @@ use crate::snippet::{
 use crate::styled_buffer::StyledBuffer;
 use crate::translation::{to_fluent_args, Translate};
 use crate::{
-    diagnostic::DiagnosticLocation, CodeSuggestion, DiagCtxt, Diagnostic, DiagnosticId,
-    DiagnosticMessage, FluentBundle, LazyFallbackBundle, Level, MultiSpan, SubDiagnostic,
-    SubstitutionHighlight, SuggestionStyle, TerminalUrl,
+    diagnostic::DiagnosticLocation, CodeSuggestion, DiagCtxt, Diagnostic, DiagnosticMessage,
+    FluentBundle, LazyFallbackBundle, Level, MultiSpan, SubDiagnostic, SubstitutionHighlight,
+    SuggestionStyle, TerminalUrl,
 };
 use rustc_lint_defs::pluralize;
 
@@ -1309,7 +1309,7 @@ impl HumanEmitter {
         msp: &MultiSpan,
         msgs: &[(DiagnosticMessage, Style)],
         args: &FluentArgs<'_>,
-        code: &Option<DiagnosticId>,
+        code: &Option<String>,
         level: &Level,
         max_line_num_len: usize,
         is_secondary: bool,
@@ -1336,14 +1336,13 @@ impl HumanEmitter {
                 buffer.append(0, level.to_str(), Style::Level(*level));
                 label_width += level.to_str().len();
             }
-            // only render error codes, not lint codes
-            if let Some(DiagnosticId::Error(ref code)) = *code {
+            if let Some(code) = code {
                 buffer.append(0, "[", Style::Level(*level));
                 let code = if let TerminalUrl::Yes = self.terminal_url {
                     let path = "https://doc.rust-lang.org/error_codes";
-                    format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
+                    Cow::Owned(format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07"))
                 } else {
-                    code.clone()
+                    Cow::Borrowed(code)
                 };
                 buffer.append(0, &code, Style::Level(*level));
                 buffer.append(0, "]", Style::Level(*level));
@@ -2077,7 +2076,7 @@ impl HumanEmitter {
         level: &Level,
         messages: &[(DiagnosticMessage, Style)],
         args: &FluentArgs<'_>,
-        code: &Option<DiagnosticId>,
+        code: &Option<String>,
         span: &MultiSpan,
         children: &[SubDiagnostic],
         suggestions: &[CodeSuggestion],
diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
index 87bf9c23456..51b064f4c61 100644
--- a/compiler/rustc_errors/src/json.rs
+++ b/compiler/rustc_errors/src/json.rs
@@ -15,10 +15,9 @@ use termcolor::{ColorSpec, WriteColor};
 use crate::emitter::{should_show_source_code, Emitter, HumanReadableErrorType};
 use crate::registry::Registry;
 use crate::translation::{to_fluent_args, Translate};
-use crate::DiagnosticId;
 use crate::{
-    CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, SubDiagnostic,
-    TerminalUrl,
+    diagnostic::IsLint, CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel,
+    SubDiagnostic, TerminalUrl,
 };
 use rustc_lint_defs::Applicability;
 
@@ -301,7 +300,8 @@ struct DiagnosticSpanMacroExpansion {
 
 #[derive(Serialize)]
 struct DiagnosticCode {
-    /// The code itself.
+    /// The error code (e.g. "E1234"), if the diagnostic has one. Or the lint
+    /// name, if it's a lint without an error code.
     code: String,
     /// An explanation for the code.
     explanation: Option<&'static str>,
@@ -399,9 +399,21 @@ impl Diagnostic {
         let output = String::from_utf8(output).unwrap();
 
         let translated_message = je.translate_messages(&diag.messages, &args);
+
+        let code = if let Some(code) = &diag.code {
+            Some(DiagnosticCode {
+                code: code.to_string(),
+                explanation: je.registry.as_ref().unwrap().try_find_description(&code).ok(),
+            })
+        } else if let Some(IsLint { name, .. }) = &diag.is_lint {
+            Some(DiagnosticCode { code: name.to_string(), explanation: None })
+        } else {
+            None
+        };
+
         Diagnostic {
             message: translated_message.to_string(),
-            code: DiagnosticCode::map_opt_string(diag.code.clone(), je),
+            code,
             level: diag.level.to_str(),
             spans: DiagnosticSpan::from_multispan(&diag.span, &args, je),
             children: diag
@@ -592,18 +604,3 @@ impl DiagnosticSpanLine {
             .unwrap_or_else(|_| vec![])
     }
 }
-
-impl DiagnosticCode {
-    fn map_opt_string(s: Option<DiagnosticId>, je: &JsonEmitter) -> Option<DiagnosticCode> {
-        s.map(|s| {
-            let s = match s {
-                DiagnosticId::Error(s) => s,
-                DiagnosticId::Lint { name, .. } => name,
-            };
-            let je_result =
-                je.registry.as_ref().map(|registry| registry.try_find_description(&s)).unwrap();
-
-            DiagnosticCode { code: s, explanation: je_result.ok() }
-        })
-    }
-}
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 63391a0faa6..141547b537d 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -30,7 +30,7 @@ extern crate tracing;
 extern crate self as rustc_errors;
 
 pub use diagnostic::{
-    AddToDiagnostic, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId,
+    AddToDiagnostic, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgValue,
     DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic,
 };
 pub use diagnostic_builder::{
@@ -442,13 +442,13 @@ struct DiagCtxtInner {
     /// This is used for the `good_path_delayed_bugs` check.
     suppressed_expected_diag: bool,
 
-    /// This set contains the `DiagnosticId` of all emitted diagnostics to avoid
+    /// This set contains the code of all emitted diagnostics to avoid
     /// emitting the same diagnostic with extended help (`--teach`) twice, which
     /// would be unnecessary repetition.
-    taught_diagnostics: FxHashSet<DiagnosticId>,
+    taught_diagnostics: FxHashSet<String>,
 
     /// Used to suggest rustc --explain `<error code>`
-    emitted_diagnostic_codes: FxIndexSet<DiagnosticId>,
+    emitted_diagnostic_codes: FxIndexSet<String>,
 
     /// This set contains a hash of every diagnostic that has been emitted by
     /// this `DiagCtxt`. These hashes is used to avoid emitting the same error
@@ -676,7 +676,7 @@ impl DiagCtxt {
         let key = (span.with_parent(None), key);
 
         if diag.is_error() {
-            if diag.is_lint {
+            if diag.is_lint.is_some() {
                 inner.lint_err_count += 1;
             } else {
                 inner.err_count += 1;
@@ -695,7 +695,7 @@ impl DiagCtxt {
         let key = (span.with_parent(None), key);
         let diag = inner.stashed_diagnostics.remove(&key)?;
         if diag.is_error() {
-            if diag.is_lint {
+            if diag.is_lint.is_some() {
                 inner.lint_err_count -= 1;
             } else {
                 inner.err_count -= 1;
@@ -715,9 +715,7 @@ impl DiagCtxt {
 
     /// Construct a builder at the `Warning` level at the given `span` and with the `msg`.
     ///
-    /// Attempting to `.emit()` the builder will only emit if either:
-    /// * `can_emit_warnings` is `true`
-    /// * `is_force_warn` was set in `DiagnosticId::Lint`
+    /// An `emit` call on the builder will only emit if `can_emit_warnings` is `true`.
     #[rustc_lint_diagnostics]
     #[track_caller]
     pub fn struct_span_warn(
@@ -730,9 +728,7 @@ impl DiagCtxt {
 
     /// Construct a builder at the `Warning` level with the `msg`.
     ///
-    /// Attempting to `.emit()` the builder will only emit if either:
-    /// * `can_emit_warnings` is `true`
-    /// * `is_force_warn` was set in `DiagnosticId::Lint`
+    /// An `emit` call on the builder will only emit if `can_emit_warnings` is `true`.
     #[rustc_lint_diagnostics]
     #[track_caller]
     pub fn struct_warn(&self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'_, ()> {
@@ -1011,11 +1007,12 @@ impl DiagCtxt {
             let mut error_codes = inner
                 .emitted_diagnostic_codes
                 .iter()
-                .filter_map(|x| match &x {
-                    DiagnosticId::Error(s) if registry.try_find_description(s).is_ok() => {
-                        Some(s.clone())
+                .filter_map(|code| {
+                    if registry.try_find_description(code).is_ok().clone() {
+                        Some(code.clone())
+                    } else {
+                        None
                     }
-                    _ => None,
                 })
                 .collect::<Vec<_>>();
             if !error_codes.is_empty() {
@@ -1058,8 +1055,8 @@ impl DiagCtxt {
     ///
     /// Used to suppress emitting the same error multiple times with extended explanation when
     /// calling `-Zteach`.
-    pub fn must_teach(&self, code: &DiagnosticId) -> bool {
-        self.inner.borrow_mut().taught_diagnostics.insert(code.clone())
+    pub fn must_teach(&self, code: &str) -> bool {
+        self.inner.borrow_mut().taught_diagnostics.insert(code.to_string())
     }
 
     pub fn force_print_diagnostic(&self, db: Diagnostic) {
@@ -1231,7 +1228,7 @@ impl DiagCtxtInner {
         for diag in diags {
             // Decrement the count tracking the stash; emitting will increment it.
             if diag.is_error() {
-                if diag.is_lint {
+                if diag.is_lint.is_some() {
                     self.lint_err_count -= 1;
                 } else {
                     self.err_count -= 1;
@@ -1363,7 +1360,7 @@ impl DiagCtxtInner {
                 self.has_printed = true;
             }
             if diagnostic.is_error() {
-                if diagnostic.is_lint {
+                if diagnostic.is_lint.is_some() {
                     self.lint_err_count += 1;
                 } else {
                     self.err_count += 1;
diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs
index 469e7a6a13c..774feb94f7d 100644
--- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs
+++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs
@@ -2,7 +2,7 @@ use super::potentially_plural_count;
 use crate::errors::LifetimesOrBoundsMismatchOnTrait;
 use hir::def_id::{DefId, DefIdMap, LocalDefId};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
-use rustc_errors::{pluralize, struct_span_code_err, Applicability, DiagnosticId, ErrorGuaranteed};
+use rustc_errors::{pluralize, struct_span_code_err, Applicability, ErrorGuaranteed};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::intravisit;
@@ -1382,7 +1382,7 @@ fn compare_number_of_generics<'tcx>(
                     kind = kind,
                 ),
             );
-            err.code(DiagnosticId::Error("E0049".into()));
+            err.code("E0049".into());
 
             let msg =
                 format!("expected {trait_count} {kind} parameter{}", pluralize!(trait_count),);
diff --git a/compiler/rustc_hir_analysis/src/structured_errors.rs b/compiler/rustc_hir_analysis/src/structured_errors.rs
index 04d04304e70..b061d637138 100644
--- a/compiler/rustc_hir_analysis/src/structured_errors.rs
+++ b/compiler/rustc_hir_analysis/src/structured_errors.rs
@@ -6,13 +6,13 @@ pub use self::{
     missing_cast_for_variadic_arg::*, sized_unsized_cast::*, wrong_number_of_generic_args::*,
 };
 
-use rustc_errors::{DiagnosticBuilder, DiagnosticId};
+use rustc_errors::DiagnosticBuilder;
 use rustc_session::Session;
 
 pub trait StructuredDiagnostic<'tcx> {
     fn session(&self) -> &Session;
 
-    fn code(&self) -> DiagnosticId;
+    fn code(&self) -> String;
 
     fn diagnostic(&self) -> DiagnosticBuilder<'tcx> {
         let err = self.diagnostic_common();
diff --git a/compiler/rustc_hir_analysis/src/structured_errors/missing_cast_for_variadic_arg.rs b/compiler/rustc_hir_analysis/src/structured_errors/missing_cast_for_variadic_arg.rs
index 634904e3271..6f4435db411 100644
--- a/compiler/rustc_hir_analysis/src/structured_errors/missing_cast_for_variadic_arg.rs
+++ b/compiler/rustc_hir_analysis/src/structured_errors/missing_cast_for_variadic_arg.rs
@@ -1,5 +1,5 @@
 use crate::{errors, structured_errors::StructuredDiagnostic};
-use rustc_errors::{DiagnosticBuilder, DiagnosticId};
+use rustc_errors::DiagnosticBuilder;
 use rustc_middle::ty::{Ty, TypeVisitableExt};
 use rustc_session::Session;
 use rustc_span::Span;
@@ -16,7 +16,7 @@ impl<'tcx> StructuredDiagnostic<'tcx> for MissingCastForVariadicArg<'tcx, '_> {
         self.sess
     }
 
-    fn code(&self) -> DiagnosticId {
+    fn code(&self) -> String {
         rustc_errors::error_code!(E0617)
     }
 
diff --git a/compiler/rustc_hir_analysis/src/structured_errors/sized_unsized_cast.rs b/compiler/rustc_hir_analysis/src/structured_errors/sized_unsized_cast.rs
index c68d74969a5..19cac4a38aa 100644
--- a/compiler/rustc_hir_analysis/src/structured_errors/sized_unsized_cast.rs
+++ b/compiler/rustc_hir_analysis/src/structured_errors/sized_unsized_cast.rs
@@ -1,5 +1,5 @@
 use crate::{errors, structured_errors::StructuredDiagnostic};
-use rustc_errors::{DiagnosticBuilder, DiagnosticId};
+use rustc_errors::DiagnosticBuilder;
 use rustc_middle::ty::{Ty, TypeVisitableExt};
 use rustc_session::Session;
 use rustc_span::Span;
@@ -16,7 +16,7 @@ impl<'tcx> StructuredDiagnostic<'tcx> for SizedUnsizedCast<'tcx> {
         self.sess
     }
 
-    fn code(&self) -> DiagnosticId {
+    fn code(&self) -> String {
         rustc_errors::error_code!(E0607)
     }
 
diff --git a/compiler/rustc_hir_analysis/src/structured_errors/wrong_number_of_generic_args.rs b/compiler/rustc_hir_analysis/src/structured_errors/wrong_number_of_generic_args.rs
index 6657e3fd872..ba81e7f1f81 100644
--- a/compiler/rustc_hir_analysis/src/structured_errors/wrong_number_of_generic_args.rs
+++ b/compiler/rustc_hir_analysis/src/structured_errors/wrong_number_of_generic_args.rs
@@ -1,7 +1,5 @@
 use crate::structured_errors::StructuredDiagnostic;
-use rustc_errors::{
-    pluralize, Applicability, Diagnostic, DiagnosticBuilder, DiagnosticId, MultiSpan,
-};
+use rustc_errors::{pluralize, Applicability, Diagnostic, DiagnosticBuilder, MultiSpan};
 use rustc_hir as hir;
 use rustc_middle::ty::{self as ty, AssocItems, AssocKind, TyCtxt};
 use rustc_session::Session;
@@ -1107,7 +1105,7 @@ impl<'tcx> StructuredDiagnostic<'tcx> for WrongNumberOfGenericArgs<'_, 'tcx> {
         self.tcx.sess
     }
 
-    fn code(&self) -> DiagnosticId {
+    fn code(&self) -> String {
         rustc_errors::error_code!(E0107)
     }
 
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index af47455c16d..ac8701a33d4 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -26,7 +26,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_errors::{
     pluralize, struct_span_code_err, AddToDiagnostic, Applicability, Diagnostic, DiagnosticBuilder,
-    DiagnosticId, ErrorGuaranteed, StashKey,
+    ErrorGuaranteed, StashKey,
 };
 use rustc_hir as hir;
 use rustc_hir::def::{CtorKind, DefKind, Res};
@@ -941,7 +941,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
 
         let mut err = self.dcx().struct_span_err(op_span, "invalid left-hand side of assignment");
-        err.code(DiagnosticId::Error(err_code.into()));
+        err.code(err_code.into());
         err.span_label(lhs.span, "cannot assign to this expression");
 
         self.comes_from_while_condition(lhs.hir_id, |expr| {
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
index 4704d52abfc..f42f58322dc 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
@@ -12,9 +12,7 @@ use crate::{
 use itertools::Itertools;
 use rustc_ast as ast;
 use rustc_data_structures::fx::FxIndexSet;
-use rustc_errors::{
-    pluralize, Applicability, Diagnostic, DiagnosticId, ErrorGuaranteed, MultiSpan, StashKey,
-};
+use rustc_errors::{pluralize, Applicability, Diagnostic, ErrorGuaranteed, MultiSpan, StashKey};
 use rustc_hir as hir;
 use rustc_hir::def::{CtorOf, DefKind, Res};
 use rustc_hir::def_id::DefId;
@@ -679,7 +677,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                                 pluralize!("was", provided_args.len())
                             ),
                         );
-                        err.code(DiagnosticId::Error(err_code.to_owned()));
+                        err.code(err_code.to_owned());
                         err.multipart_suggestion_verbose(
                             "wrap these arguments in parentheses to construct a tuple",
                             vec![
@@ -829,7 +827,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         pluralize!("was", provided_args.len())
                     ),
                 )
-                .with_code(DiagnosticId::Error(err_code.to_owned()))
+                .with_code(err_code.to_owned())
         };
 
         // As we encounter issues, keep track of what we want to provide for the suggestion
diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs
index 36dd06d944c..67c35d717a1 100644
--- a/compiler/rustc_hir_typeck/src/lib.rs
+++ b/compiler/rustc_hir_typeck/src/lib.rs
@@ -52,7 +52,7 @@ use crate::expectation::Expectation;
 use crate::fn_ctxt::RawTy;
 use crate::gather_locals::GatherLocalsVisitor;
 use rustc_data_structures::unord::UnordSet;
-use rustc_errors::{struct_span_code_err, DiagnosticId, ErrorGuaranteed};
+use rustc_errors::{struct_span_code_err, ErrorGuaranteed};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::intravisit::Visitor;
@@ -369,7 +369,7 @@ fn report_unexpected_variant_res(
     let err = tcx
         .dcx()
         .struct_span_err(span, format!("expected {expected}, found {res_descr} `{path_str}`"))
-        .with_code(DiagnosticId::Error(err_code.into()));
+        .with_code(err_code.into());
     match res {
         Res::Def(DefKind::Fn | DefKind::AssocFn, _) if err_code == "E0164" => {
             let patterns_url = "https://doc.rust-lang.org/book/ch18-00-patterns.html";
diff --git a/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs b/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs
index 4aefadf590d..e85c940f1b5 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs
@@ -41,8 +41,8 @@ pub enum TypeAnnotationNeeded {
     E0284,
 }
 
-impl Into<rustc_errors::DiagnosticId> for TypeAnnotationNeeded {
-    fn into(self) -> rustc_errors::DiagnosticId {
+impl Into<String> for TypeAnnotationNeeded {
+    fn into(self) -> String {
         match self {
             Self::E0282 => rustc_errors::error_code!(E0282),
             Self::E0283 => rustc_errors::error_code!(E0283),
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
index fb908fe2db1..0b7ecff8148 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -211,7 +211,7 @@ impl DiagnosticDeriveVariantBuilder {
 
                     let code = nested.parse::<syn::LitStr>()?;
                     tokens.extend(quote! {
-                        diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
+                        diag.code(#code.to_string());
                     });
                 } else {
                     span_err(path.span().unwrap(), "unknown argument")
diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs
index c5e4dfaf19e..4ab16cf19ba 100644
--- a/compiler/rustc_middle/src/lint.rs
+++ b/compiler/rustc_middle/src/lint.rs
@@ -2,7 +2,7 @@ use std::cmp;
 
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::sorted_map::SortedMap;
-use rustc_errors::{Diagnostic, DiagnosticBuilder, DiagnosticId, DiagnosticMessage, MultiSpan};
+use rustc_errors::{Diagnostic, DiagnosticBuilder, DiagnosticMessage, MultiSpan};
 use rustc_hir::{HirId, ItemLocalId};
 use rustc_session::lint::{
     builtin::{self, FORBIDDEN_LINT_GROUPS},
@@ -322,8 +322,6 @@ pub fn struct_lint_level(
             err.span(span);
         }
 
-        err.is_lint();
-
         // If this code originates in a foreign macro, aka something that this crate
         // did not itself author, then it's likely that there's nothing this crate
         // can do about it. We probably want to skip the lint entirely.
@@ -351,8 +349,7 @@ pub fn struct_lint_level(
         // suppressed the lint due to macros.
         err.primary_message(msg);
 
-        let name = lint.name_lower();
-        err.code(DiagnosticId::Lint { name, has_future_breakage });
+        err.is_lint(lint.name_lower(), has_future_breakage);
 
         // Lint diagnostics that are covered by the expect level will not be emitted outside
         // the compiler. It is therefore not necessary to add any information for the user.
diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs
index 446f13feff0..2ee660ddc9b 100644
--- a/compiler/rustc_mir_transform/src/errors.rs
+++ b/compiler/rustc_mir_transform/src/errors.rs
@@ -66,7 +66,7 @@ impl<'a, G: EmissionGuarantee> IntoDiagnostic<'a, G> for RequiresUnsafe {
     #[track_caller]
     fn into_diagnostic(self, dcx: &'a DiagCtxt, level: Level) -> DiagnosticBuilder<'a, G> {
         let mut diag = DiagnosticBuilder::new(dcx, level, fluent::mir_transform_requires_unsafe);
-        diag.code(rustc_errors::DiagnosticId::Error("E0133".to_string()));
+        diag.code("E0133".to_string());
         diag.span(self.span);
         diag.span_label(self.span, self.details.label());
         let desc = dcx.eagerly_translate_to_string(self.details.label(), [].into_iter());
diff --git a/compiler/rustc_passes/src/entry.rs b/compiler/rustc_passes/src/entry.rs
index a9d0e0d2dd3..25ca685cbfa 100644
--- a/compiler/rustc_passes/src/entry.rs
+++ b/compiler/rustc_passes/src/entry.rs
@@ -180,8 +180,8 @@ fn no_main_err(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) {
         Default::default()
     });
     let main_def_opt = tcx.resolutions(()).main_def;
-    let diagnostic_id = error_code!(E0601);
-    let add_teach_note = tcx.sess.teach(&diagnostic_id);
+    let code = error_code!(E0601);
+    let add_teach_note = tcx.sess.teach(&code);
     // The file may be empty, which leads to the diagnostic machinery not emitting this
     // note. This is a relatively simple way to detect that case and emit a span-less
     // note instead.
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index 5102c096d4b..abd724a6cc2 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -16,7 +16,7 @@ use rustc_ast::ptr::P;
 use rustc_ast::visit::{self, AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor};
 use rustc_ast::*;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
-use rustc_errors::{Applicability, DiagnosticArgValue, DiagnosticId, IntoDiagnosticArg};
+use rustc_errors::{Applicability, DiagnosticArgValue, IntoDiagnosticArg};
 use rustc_hir::def::Namespace::{self, *};
 use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, PartialRes, PerNS};
 use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE};
@@ -533,7 +533,7 @@ impl<'a> PathSource<'a> {
         }
     }
 
-    fn error_code(self, has_unexpected_resolution: bool) -> DiagnosticId {
+    fn error_code(self, has_unexpected_resolution: bool) -> String {
         use rustc_errors::error_code;
         match (self, has_unexpected_resolution) {
             (PathSource::Trait(_), true) => error_code!(E0404),
@@ -3821,7 +3821,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
             swap(&mut err.span, &mut parent_err.span);
             err.children = take(&mut parent_err.children);
             err.sort_span = parent_err.sort_span;
-            err.is_lint = parent_err.is_lint;
+            err.is_lint = parent_err.is_lint.clone();
 
             // merge the parent's suggestions with the typo suggestions
             fn append_result<T, E>(res1: &mut Result<Vec<T>, E>, res2: Result<Vec<T>, E>) {
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 313a6b6c683..341c566d97f 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -257,7 +257,7 @@ enum ResolutionError<'a> {
         kind: &'static str,
         trait_path: String,
         trait_item_span: Span,
-        code: rustc_errors::DiagnosticId,
+        code: String,
     },
     /// Error E0201: multiple impl items for the same trait item.
     TraitImplDuplicate { name: Symbol, trait_item_span: Span, old_span: Span },
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
index c629c9884c8..16036e5be38 100644
--- a/compiler/rustc_session/src/parse.rs
+++ b/compiler/rustc_session/src/parse.rs
@@ -15,8 +15,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::sync::{AppendOnlyVec, Lock, Lrc};
 use rustc_errors::{emitter::SilentEmitter, DiagCtxt};
 use rustc_errors::{
-    fallback_fluent_bundle, Diagnostic, DiagnosticBuilder, DiagnosticId, DiagnosticMessage,
-    MultiSpan, StashKey,
+    fallback_fluent_bundle, Diagnostic, DiagnosticBuilder, DiagnosticMessage, MultiSpan, StashKey,
 };
 use rustc_feature::{find_feature_issue, GateIssue, UnstableFeatures};
 use rustc_span::edition::Edition;
@@ -148,7 +147,7 @@ pub fn feature_warn_issue(
     // Decorate this as a future-incompatibility lint as in rustc_middle::lint::struct_lint_level
     let lint = UNSTABLE_SYNTAX_PRE_EXPANSION;
     let future_incompatible = lint.future_incompatible.as_ref().unwrap();
-    err.code(DiagnosticId::Lint { name: lint.name_lower(), has_future_breakage: false });
+    err.is_lint(lint.name_lower(), /* has_future_breakage */ false);
     err.warn(lint.desc);
     err.note(format!("for more information, see {}", future_incompatible.reference));
 
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index 1c3d5a3cb88..3f6c70a18d3 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -20,9 +20,8 @@ use rustc_errors::emitter::{DynEmitter, HumanEmitter, HumanReadableErrorType};
 use rustc_errors::json::JsonEmitter;
 use rustc_errors::registry::Registry;
 use rustc_errors::{
-    error_code, fallback_fluent_bundle, DiagCtxt, DiagnosticBuilder, DiagnosticId,
-    DiagnosticMessage, ErrorGuaranteed, FatalAbort, FluentBundle, IntoDiagnostic,
-    LazyFallbackBundle, TerminalUrl,
+    error_code, fallback_fluent_bundle, DiagCtxt, DiagnosticBuilder, DiagnosticMessage,
+    ErrorGuaranteed, FatalAbort, FluentBundle, IntoDiagnostic, LazyFallbackBundle, TerminalUrl,
 };
 use rustc_macros::HashStable_Generic;
 pub use rustc_span::def_id::StableCrateId;
@@ -906,7 +905,7 @@ impl Session {
         CodegenUnits::Default(16)
     }
 
-    pub fn teach(&self, code: &DiagnosticId) -> bool {
+    pub fn teach(&self, code: &str) -> bool {
         self.opts.unstable_opts.teach && self.dcx().must_teach(code)
     }