about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_ast_lowering/src/errors.rs2
-rw-r--r--compiler/rustc_ast_passes/src/errors.rs4
-rw-r--r--compiler/rustc_builtin_macros/src/errors.rs2
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs6
-rw-r--r--compiler/rustc_errors/src/diagnostic_impls.rs66
-rw-r--r--compiler/rustc_errors/src/lib.rs33
-rw-r--r--compiler/rustc_hir_typeck/src/errors.rs6
-rw-r--r--compiler/rustc_infer/src/errors/mod.rs18
-rw-r--r--compiler/rustc_infer/src/errors/note_and_explain.rs2
-rw-r--r--compiler/rustc_lint/src/context/diagnostics.rs18
-rw-r--r--compiler/rustc_lint/src/errors.rs2
-rw-r--r--compiler/rustc_lint/src/lints.rs18
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs39
-rw-r--r--compiler/rustc_macros/src/lib.rs1
-rw-r--r--compiler/rustc_mir_build/src/errors.rs4
-rw-r--r--compiler/rustc_parse/src/errors.rs2
-rw-r--r--compiler/rustc_passes/src/errors.rs2
-rw-r--r--compiler/rustc_pattern_analysis/src/errors.rs4
-rw-r--r--compiler/rustc_resolve/src/errors.rs4
-rw-r--r--compiler/rustc_resolve/src/late.rs12
-rw-r--r--compiler/rustc_trait_selection/src/errors.rs2
-rw-r--r--tests/ui-fulldeps/internal-lints/diagnostics.rs4
-rw-r--r--tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs10
23 files changed, 146 insertions, 115 deletions
diff --git a/compiler/rustc_ast_lowering/src/errors.rs b/compiler/rustc_ast_lowering/src/errors.rs
index ca0821e2c9e..6799513a323 100644
--- a/compiler/rustc_ast_lowering/src/errors.rs
+++ b/compiler/rustc_ast_lowering/src/errors.rs
@@ -44,7 +44,7 @@ impl Subdiagnostic for InvalidAbiReason {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _: F,
+        _: &F,
     ) {
         #[allow(rustc::untranslatable_diagnostic)]
         diag.note(self.0);
diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs
index f397c949e04..25a125f8393 100644
--- a/compiler/rustc_ast_passes/src/errors.rs
+++ b/compiler/rustc_ast_passes/src/errors.rs
@@ -382,7 +382,7 @@ impl Subdiagnostic for EmptyLabelManySpans {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _: F,
+        _: &F,
     ) {
         diag.span_labels(self.0, "");
     }
@@ -751,7 +751,7 @@ impl Subdiagnostic for StableFeature {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _: F,
+        _: &F,
     ) {
         diag.arg("name", self.name);
         diag.arg("since", self.since);
diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs
index 6b6647ef085..9078dc07a31 100644
--- a/compiler/rustc_builtin_macros/src/errors.rs
+++ b/compiler/rustc_builtin_macros/src/errors.rs
@@ -601,7 +601,7 @@ impl Subdiagnostic for FormatUnusedArg {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        f: F,
+        f: &F,
     ) {
         diag.arg("named", self.named);
         let msg = f(diag, crate::fluent_generated::builtin_macros_format_unused_arg.into());
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index a17e0da47a5..6c84eeaf888 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -177,7 +177,7 @@ where
 {
     /// Add a subdiagnostic to an existing diagnostic.
     fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
-        self.add_to_diag_with(diag, |_, m| m);
+        self.add_to_diag_with(diag, &|_, m| m);
     }
 
     /// Add a subdiagnostic to an existing diagnostic where `f` is invoked on every message used
@@ -185,7 +185,7 @@ where
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        f: F,
+        f: &F,
     );
 }
 
@@ -1197,7 +1197,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
         dcx: &crate::DiagCtxt,
         subdiagnostic: impl Subdiagnostic,
     ) -> &mut Self {
-        subdiagnostic.add_to_diag_with(self, |diag, msg| {
+        subdiagnostic.add_to_diag_with(self, &|diag, msg| {
             let args = diag.args.iter();
             let msg = diag.subdiagnostic_message_to_diagnostic_message(msg);
             dcx.eagerly_translate(msg, args)
diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs
index 6c0551848d6..2b10fcd8d6a 100644
--- a/compiler/rustc_errors/src/diagnostic_impls.rs
+++ b/compiler/rustc_errors/src/diagnostic_impls.rs
@@ -227,6 +227,36 @@ impl IntoDiagArg for rustc_lint_defs::Level {
     }
 }
 
+impl<Id> IntoDiagArg for hir::def::Res<Id> {
+    fn into_diag_arg(self) -> DiagArgValue {
+        DiagArgValue::Str(Cow::Borrowed(self.descr()))
+    }
+}
+
+impl IntoDiagArg for DiagLocation {
+    fn into_diag_arg(self) -> DiagArgValue {
+        DiagArgValue::Str(Cow::from(self.to_string()))
+    }
+}
+
+impl IntoDiagArg for Backtrace {
+    fn into_diag_arg(self) -> DiagArgValue {
+        DiagArgValue::Str(Cow::from(self.to_string()))
+    }
+}
+
+impl IntoDiagArg for Level {
+    fn into_diag_arg(self) -> DiagArgValue {
+        DiagArgValue::Str(Cow::from(self.to_string()))
+    }
+}
+
+impl IntoDiagArg for type_ir::ClosureKind {
+    fn into_diag_arg(self) -> DiagArgValue {
+        DiagArgValue::Str(self.as_str().into())
+    }
+}
+
 #[derive(Clone)]
 pub struct DiagSymbolList(Vec<Symbol>);
 
@@ -244,12 +274,6 @@ impl IntoDiagArg for DiagSymbolList {
     }
 }
 
-impl<Id> IntoDiagArg for hir::def::Res<Id> {
-    fn into_diag_arg(self) -> DiagArgValue {
-        DiagArgValue::Str(Cow::Borrowed(self.descr()))
-    }
-}
-
 impl<G: EmissionGuarantee> Diagnostic<'_, G> for TargetDataLayoutErrors<'_> {
     fn into_diag(self, dcx: &DiagCtxt, level: Level) -> Diag<'_, G> {
         match self {
@@ -302,7 +326,7 @@ impl Subdiagnostic for SingleLabelManySpans {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _: F,
+        _: &F,
     ) {
         diag.span_labels(self.spans, self.label);
     }
@@ -316,24 +340,6 @@ pub struct ExpectedLifetimeParameter {
     pub count: usize,
 }
 
-impl IntoDiagArg for DiagLocation {
-    fn into_diag_arg(self) -> DiagArgValue {
-        DiagArgValue::Str(Cow::from(self.to_string()))
-    }
-}
-
-impl IntoDiagArg for Backtrace {
-    fn into_diag_arg(self) -> DiagArgValue {
-        DiagArgValue::Str(Cow::from(self.to_string()))
-    }
-}
-
-impl IntoDiagArg for Level {
-    fn into_diag_arg(self) -> DiagArgValue {
-        DiagArgValue::Str(Cow::from(self.to_string()))
-    }
-}
-
 #[derive(Subdiagnostic)]
 #[suggestion(errors_indicate_anonymous_lifetime, code = "{suggestion}", style = "verbose")]
 pub struct IndicateAnonymousLifetime {
@@ -343,8 +349,10 @@ pub struct IndicateAnonymousLifetime {
     pub suggestion: String,
 }
 
-impl IntoDiagArg for type_ir::ClosureKind {
-    fn into_diag_arg(self) -> DiagArgValue {
-        DiagArgValue::Str(self.as_str().into())
-    }
+#[derive(Subdiagnostic)]
+pub struct ElidedLifetimeInPathSubdiag {
+    #[subdiagnostic]
+    pub expected: ExpectedLifetimeParameter,
+    #[subdiagnostic]
+    pub indicate: Option<IndicateAnonymousLifetime>,
 }
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index e3363e89594..b3c1c6c8515 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -40,8 +40,8 @@ pub use diagnostic::{
     SubdiagMessageOp, Subdiagnostic,
 };
 pub use diagnostic_impls::{
-    DiagArgFromDisplay, DiagSymbolList, ExpectedLifetimeParameter, IndicateAnonymousLifetime,
-    SingleLabelManySpans,
+    DiagArgFromDisplay, DiagSymbolList, ElidedLifetimeInPathSubdiag, ExpectedLifetimeParameter,
+    IndicateAnonymousLifetime, SingleLabelManySpans,
 };
 pub use emitter::ColorConfig;
 pub use rustc_error_messages::{
@@ -1911,27 +1911,24 @@ impl Level {
 }
 
 // FIXME(eddyb) this doesn't belong here AFAICT, should be moved to callsite.
-pub fn add_elided_lifetime_in_path_suggestion<G: EmissionGuarantee>(
+pub fn elided_lifetime_in_path_suggestion(
     source_map: &SourceMap,
-    diag: &mut Diag<'_, G>,
     n: usize,
     path_span: Span,
     incl_angl_brckt: bool,
     insertion_span: Span,
-) {
-    diag.subdiagnostic(diag.dcx, ExpectedLifetimeParameter { span: path_span, count: n });
-    if !source_map.is_span_accessible(insertion_span) {
-        // Do not try to suggest anything if generated by a proc-macro.
-        return;
-    }
-    let anon_lts = vec!["'_"; n].join(", ");
-    let suggestion =
-        if incl_angl_brckt { format!("<{anon_lts}>") } else { format!("{anon_lts}, ") };
-
-    diag.subdiagnostic(
-        diag.dcx,
-        IndicateAnonymousLifetime { span: insertion_span.shrink_to_hi(), count: n, suggestion },
-    );
+) -> ElidedLifetimeInPathSubdiag {
+    let expected = ExpectedLifetimeParameter { span: path_span, count: n };
+    // Do not try to suggest anything if generated by a proc-macro.
+    let indicate = source_map.is_span_accessible(insertion_span).then(|| {
+        let anon_lts = vec!["'_"; n].join(", ");
+        let suggestion =
+            if incl_angl_brckt { format!("<{anon_lts}>") } else { format!("{anon_lts}, ") };
+
+        IndicateAnonymousLifetime { span: insertion_span.shrink_to_hi(), count: n, suggestion }
+    });
+
+    ElidedLifetimeInPathSubdiag { expected, indicate }
 }
 
 pub fn report_ambiguity_error<'a, G: EmissionGuarantee>(
diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs
index 3dc9c7b86f7..1c4d5657b17 100644
--- a/compiler/rustc_hir_typeck/src/errors.rs
+++ b/compiler/rustc_hir_typeck/src/errors.rs
@@ -191,7 +191,7 @@ impl Subdiagnostic for TypeMismatchFruTypo {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         diag.arg("expr", self.expr.as_deref().unwrap_or("NONE"));
 
@@ -370,7 +370,7 @@ impl Subdiagnostic for RemoveSemiForCoerce {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         let mut multispan: MultiSpan = self.semi.into();
         multispan.push_span_label(self.expr, fluent::hir_typeck_remove_semi_for_coerce_expr);
@@ -546,7 +546,7 @@ impl rustc_errors::Subdiagnostic for CastUnknownPointerSub {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        f: F,
+        f: &F,
     ) {
         match self {
             CastUnknownPointerSub::To(span) => {
diff --git a/compiler/rustc_infer/src/errors/mod.rs b/compiler/rustc_infer/src/errors/mod.rs
index 4593108edac..2acaeac2439 100644
--- a/compiler/rustc_infer/src/errors/mod.rs
+++ b/compiler/rustc_infer/src/errors/mod.rs
@@ -239,7 +239,7 @@ impl Subdiagnostic for RegionOriginNote<'_> {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         let mut label_or_note = |span, msg: DiagMessage| {
             let sub_count = diag.children.iter().filter(|d| d.span.is_dummy()).count();
@@ -304,7 +304,7 @@ impl Subdiagnostic for LifetimeMismatchLabels {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         match self {
             LifetimeMismatchLabels::InRet { param_span, ret_span, span, label_var1 } => {
@@ -352,7 +352,7 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         let mut mk_suggestion = || {
             let (
@@ -454,7 +454,7 @@ impl Subdiagnostic for IntroducesStaticBecauseUnmetLifetimeReq {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         mut self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         self.unmet_requirements
             .push_span_label(self.binding_span, fluent::infer_msl_introduces_static);
@@ -773,7 +773,7 @@ impl Subdiagnostic for ConsiderBorrowingParamHelp {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        f: F,
+        f: &F,
     ) {
         let mut type_param_span: MultiSpan = self.spans.clone().into();
         for &span in &self.spans {
@@ -818,7 +818,7 @@ impl Subdiagnostic for DynTraitConstraintSuggestion {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        f: F,
+        f: &F,
     ) {
         let mut multi_span: MultiSpan = vec![self.span].into();
         multi_span.push_span_label(self.span, fluent::infer_dtcs_has_lifetime_req_label);
@@ -865,7 +865,7 @@ impl Subdiagnostic for ReqIntroducedLocations {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         mut self,
         diag: &mut Diag<'_, G>,
-        f: F,
+        f: &F,
     ) {
         for sp in self.spans {
             self.span.push_span_label(sp, fluent::infer_ril_introduced_here);
@@ -888,7 +888,7 @@ impl Subdiagnostic for MoreTargeted {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         diag.code(E0772);
         diag.primary_message(fluent::infer_more_targeted);
@@ -1293,7 +1293,7 @@ impl Subdiagnostic for SuggestTuplePatternMany {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        f: F,
+        f: &F,
     ) {
         diag.arg("path", self.path);
         let message = f(diag, crate::fluent_generated::infer_stp_wrap_many.into());
diff --git a/compiler/rustc_infer/src/errors/note_and_explain.rs b/compiler/rustc_infer/src/errors/note_and_explain.rs
index 7b962b01408..70d94873530 100644
--- a/compiler/rustc_infer/src/errors/note_and_explain.rs
+++ b/compiler/rustc_infer/src/errors/note_and_explain.rs
@@ -163,7 +163,7 @@ impl Subdiagnostic for RegionExplanation<'_> {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        f: F,
+        f: &F,
     ) {
         diag.arg("pref_kind", self.prefix);
         diag.arg("suff_kind", self.suffix);
diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs
index b2403836397..8458b539335 100644
--- a/compiler/rustc_lint/src/context/diagnostics.rs
+++ b/compiler/rustc_lint/src/context/diagnostics.rs
@@ -2,7 +2,7 @@
 #![allow(rustc::untranslatable_diagnostic)]
 
 use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
-use rustc_errors::{add_elided_lifetime_in_path_suggestion, Diag};
+use rustc_errors::{elided_lifetime_in_path_suggestion, Diag};
 use rustc_errors::{Applicability, SuggestionStyle};
 use rustc_middle::middle::stability;
 use rustc_session::lint::BuiltinLintDiag;
@@ -74,13 +74,15 @@ pub(super) fn builtin(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Di
             diag.span_note(span_def, "the macro is defined here");
         }
         BuiltinLintDiag::ElidedLifetimesInPaths(n, path_span, incl_angl_brckt, insertion_span) => {
-            add_elided_lifetime_in_path_suggestion(
-                sess.source_map(),
-                diag,
-                n,
-                path_span,
-                incl_angl_brckt,
-                insertion_span,
+            diag.subdiagnostic(
+                sess.dcx(),
+                elided_lifetime_in_path_suggestion(
+                    sess.source_map(),
+                    n,
+                    path_span,
+                    incl_angl_brckt,
+                    insertion_span,
+                ),
             );
         }
         BuiltinLintDiag::UnknownCrateTypes(span, note, sugg) => {
diff --git a/compiler/rustc_lint/src/errors.rs b/compiler/rustc_lint/src/errors.rs
index ee99e824a54..c23d1221bc8 100644
--- a/compiler/rustc_lint/src/errors.rs
+++ b/compiler/rustc_lint/src/errors.rs
@@ -27,7 +27,7 @@ impl Subdiagnostic for OverruledAttributeSub {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         match self {
             OverruledAttributeSub::DefaultSource { id } => {
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index a034bebc85e..74a0a224dba 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -274,7 +274,7 @@ impl<'a, 'b> Subdiagnostic for SuggestChangingAssocTypes<'a, 'b> {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         // Access to associates types should use `<T as Bound>::Assoc`, which does not need a
         // bound. Let's see if this type does that.
@@ -330,7 +330,7 @@ impl Subdiagnostic for BuiltinTypeAliasGenericBoundsSuggestion {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         diag.multipart_suggestion(
             fluent::lint_suggestion,
@@ -451,7 +451,7 @@ impl Subdiagnostic for BuiltinUnpermittedTypeInitSub {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         let mut err = self.err;
         loop {
@@ -506,7 +506,7 @@ impl Subdiagnostic for BuiltinClashingExternSub<'_> {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         let mut expected_str = DiagStyledString::new();
         expected_str.push(self.expected.fn_sig(self.tcx).to_string(), false);
@@ -788,7 +788,7 @@ impl Subdiagnostic for HiddenUnicodeCodepointsDiagLabels {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         for (c, span) in self.spans {
             diag.span_label(span, format!("{c:?}"));
@@ -806,7 +806,7 @@ impl Subdiagnostic for HiddenUnicodeCodepointsDiagSub {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         match self {
             HiddenUnicodeCodepointsDiagSub::Escape { spans } => {
@@ -954,7 +954,7 @@ impl Subdiagnostic for NonBindingLetSub {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         let can_suggest_binding = self.drop_fn_start_end.is_some() || !self.is_assign_desugar;
 
@@ -1240,7 +1240,7 @@ impl Subdiagnostic for NonSnakeCaseDiagSub {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         match self {
             NonSnakeCaseDiagSub::Label { span } => {
@@ -1482,7 +1482,7 @@ impl Subdiagnostic for OverflowingBinHexSign {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         match self {
             OverflowingBinHexSign::Positive => {
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index ced782cdbc0..45236771bce 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -71,6 +71,7 @@ impl SubdiagnosticDerive {
                     span_field: None,
                     applicability: None,
                     has_suggestion_parts: false,
+                    has_subdiagnostic: false,
                     is_enum,
                 };
                 builder.into_tokens().unwrap_or_else(|v| v.to_compile_error())
@@ -90,7 +91,7 @@ impl SubdiagnosticDerive {
                 fn add_to_diag_with<__G, __F>(
                     self,
                     #diag: &mut rustc_errors::Diag<'_, __G>,
-                    #f: __F
+                    #f: &__F
                 ) where
                     __G: rustc_errors::EmissionGuarantee,
                     __F: rustc_errors::SubdiagMessageOp<__G>,
@@ -133,6 +134,10 @@ struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
     /// during finalization if still `false`.
     has_suggestion_parts: bool,
 
+    /// Set to true when a `#[subdiagnostic]` field is encountered, used to suppress the error
+    /// emitted when no subdiagnostic kinds are specified on the variant itself.
+    has_subdiagnostic: bool,
+
     /// Set to true when this variant is an enum variant rather than just the body of a struct.
     is_enum: bool,
 }
@@ -373,6 +378,13 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
 
                 Ok(quote! {})
             }
+            "subdiagnostic" => {
+                let f = &self.parent.f;
+                let diag = &self.parent.diag;
+                let binding = &info.binding;
+                self.has_subdiagnostic = true;
+                Ok(quote! { #binding.add_to_diag_with(#diag, #f); })
+            }
             _ => {
                 let mut span_attrs = vec![];
                 if kind_stats.has_multipart_suggestion {
@@ -480,18 +492,6 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
 
     pub(crate) fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
         let kind_slugs = self.identify_kind()?;
-        if kind_slugs.is_empty() {
-            if self.is_enum {
-                // It's okay for a variant to not be a subdiagnostic at all..
-                return Ok(quote! {});
-            } else {
-                // ..but structs should always be _something_.
-                throw_span_err!(
-                    self.variant.ast().ident.span().unwrap(),
-                    "subdiagnostic kind not specified"
-                );
-            }
-        };
 
         let kind_stats: KindsStatistics =
             kind_slugs.iter().map(|(kind, _slug, _no_span)| kind).collect();
@@ -510,6 +510,19 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
             .map(|binding| self.generate_field_attr_code(binding, kind_stats))
             .collect();
 
+        if kind_slugs.is_empty() {
+            if self.is_enum {
+                // It's okay for a variant to not be a subdiagnostic at all..
+                return Ok(quote! {});
+            } else if !self.has_subdiagnostic {
+                // ..but structs should always be _something_.
+                throw_span_err!(
+                    self.variant.ast().ident.span().unwrap(),
+                    "subdiagnostic kind not specified"
+                );
+            }
+        };
+
         let span_field = self.span_field.value_ref();
 
         let diag = &self.parent.diag;
diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs
index eedc508fb14..c7b7eadbd9d 100644
--- a/compiler/rustc_macros/src/lib.rs
+++ b/compiler/rustc_macros/src/lib.rs
@@ -144,6 +144,7 @@ decl_derive!(
         help,
         note,
         warning,
+        subdiagnostic,
         suggestion,
         suggestion_short,
         suggestion_hidden,
diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs
index 9ddfb12bf76..f67113afd6d 100644
--- a/compiler/rustc_mir_build/src/errors.rs
+++ b/compiler/rustc_mir_build/src/errors.rs
@@ -423,7 +423,7 @@ impl Subdiagnostic for UnsafeNotInheritedLintNote {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         diag.span_note(self.signature_span, fluent::mir_build_unsafe_fn_safe_body);
         let body_start = self.body_span.shrink_to_lo();
@@ -871,7 +871,7 @@ impl<'tcx> Subdiagnostic for AdtDefinedHere<'tcx> {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         diag.arg("ty", self.ty);
         let mut spans = MultiSpan::from(self.adt_def_span);
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index b77045ee34e..d06f03a7c17 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -1472,7 +1472,7 @@ impl Subdiagnostic for FnTraitMissingParen {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _: F,
+        _: &F,
     ) {
         diag.span_label(self.span, crate::fluent_generated::parse_fn_trait_missing_paren);
         let applicability = if self.machine_applicable {
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 19b5e5495d6..03a607348e8 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1765,7 +1765,7 @@ impl Subdiagnostic for UnusedVariableStringInterp {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         diag.span_label(self.lit, crate::fluent_generated::passes_maybe_string_interpolation);
         diag.multipart_suggestion(
diff --git a/compiler/rustc_pattern_analysis/src/errors.rs b/compiler/rustc_pattern_analysis/src/errors.rs
index 75b7b7c8f67..27f227e6d9c 100644
--- a/compiler/rustc_pattern_analysis/src/errors.rs
+++ b/compiler/rustc_pattern_analysis/src/errors.rs
@@ -65,7 +65,7 @@ impl<'tcx> Subdiagnostic for Overlap<'tcx> {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _: F,
+        _: &F,
     ) {
         let Overlap { span, range } = self;
 
@@ -113,7 +113,7 @@ impl<'tcx> Subdiagnostic for GappedRange<'tcx> {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _: F,
+        _: &F,
     ) {
         let GappedRange { span, gap, first_range } = self;
 
diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs
index b0329702d11..edfeacec7e3 100644
--- a/compiler/rustc_resolve/src/errors.rs
+++ b/compiler/rustc_resolve/src/errors.rs
@@ -1,4 +1,4 @@
-use rustc_errors::{codes::*, Applicability, MultiSpan};
+use rustc_errors::{codes::*, Applicability, ElidedLifetimeInPathSubdiag, MultiSpan};
 use rustc_macros::{Diagnostic, Subdiagnostic};
 use rustc_span::{
     symbol::{Ident, Symbol},
@@ -907,6 +907,8 @@ pub(crate) struct ExplicitAnonymousLivetimeReportError {
 pub(crate) struct ImplicitElidedLifetimeNotAllowedHere {
     #[primary_span]
     pub(crate) span: Span,
+    #[subdiagnostic]
+    pub(crate) subdiag: ElidedLifetimeInPathSubdiag,
 }
 
 #[derive(Diagnostic)]
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index 753ba09d886..c877ae5e21f 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -1883,20 +1883,18 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
                     //     async fn foo(_: std::cell::Ref<u32>) { ... }
                     LifetimeRibKind::AnonymousCreateParameter { report_in_path: true, .. }
                     | LifetimeRibKind::AnonymousWarn(_) => {
-                        let mut err =
-                            self.r.dcx().create_err(errors::ImplicitElidedLifetimeNotAllowedHere {
-                                span: path_span,
-                            });
                         let sess = self.r.tcx.sess;
-                        rustc_errors::add_elided_lifetime_in_path_suggestion(
+                        let subdiag = rustc_errors::elided_lifetime_in_path_suggestion(
                             sess.source_map(),
-                            &mut err,
                             expected_lifetimes,
                             path_span,
                             !segment.has_generic_args,
                             elided_lifetime_span,
                         );
-                        err.emit();
+                        self.r.dcx().emit_err(errors::ImplicitElidedLifetimeNotAllowedHere {
+                            span: path_span,
+                            subdiag,
+                        });
                         should_lint = false;
 
                         for id in node_ids {
diff --git a/compiler/rustc_trait_selection/src/errors.rs b/compiler/rustc_trait_selection/src/errors.rs
index b126062102e..9b08a86ef16 100644
--- a/compiler/rustc_trait_selection/src/errors.rs
+++ b/compiler/rustc_trait_selection/src/errors.rs
@@ -104,7 +104,7 @@ impl Subdiagnostic for AdjustSignatureBorrow {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        _f: F,
+        _f: &F,
     ) {
         match self {
             AdjustSignatureBorrow::Borrow { to_borrow } => {
diff --git a/tests/ui-fulldeps/internal-lints/diagnostics.rs b/tests/ui-fulldeps/internal-lints/diagnostics.rs
index 1d7417be58c..380b2b6c431 100644
--- a/tests/ui-fulldeps/internal-lints/diagnostics.rs
+++ b/tests/ui-fulldeps/internal-lints/diagnostics.rs
@@ -59,7 +59,7 @@ impl Subdiagnostic for UntranslatableInAddtoDiag {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        f: F,
+        f: &F,
     ) {
         diag.note("untranslatable diagnostic");
         //~^ ERROR diagnostics should be created using translatable messages
@@ -72,7 +72,7 @@ impl Subdiagnostic for TranslatableInAddtoDiag {
     fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
         self,
         diag: &mut Diag<'_, G>,
-        f: F,
+        f: &F,
     ) {
         diag.note(crate::fluent_generated::no_crate_note);
     }
diff --git a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
index 163af7ff0e2..659ae54f7a3 100644
--- a/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
+++ b/tests/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
@@ -827,3 +827,13 @@ struct PrimarySpanOnVec {
     //~| NOTE there must be exactly one primary span
     sub: Vec<Span>,
 }
+
+#[derive(Subdiagnostic)]
+struct NestedParent {
+    #[subdiagnostic]
+    single_sub: A,
+    #[subdiagnostic]
+    option_sub: Option<A>,
+    #[subdiagnostic]
+    vec_sub: Vec<A>,
+}