Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit dc9f6f3

Browse files
authoredOct 12, 2022
Rollup merge of #102623 - davidtwco:translation-eager, r=compiler-errors
translation: eager translation Part of #100717. See [Zulip thread](https://rust-lang.zulipchat.com/#narrow/stream/336883-i18n/topic/.23100717.20lists!/near/295010720) for additional context. - **Store diagnostic arguments in a `HashMap`**: Eager translation will enable subdiagnostics to be translated multiple times with different arguments - this requires the ability to replace the value of one argument with a new value, which is better suited to a `HashMap` than the previous storage, a `Vec`. - **Add `AddToDiagnostic::add_to_diagnostic_with`**: `AddToDiagnostic::add_to_diagnostic_with` is similar to the previous `AddToDiagnostic::add_to_diagnostic` but takes a function that can be used by the caller to modify diagnostic messages originating from the subdiagnostic (such as performing translation eagerly). `add_to_diagnostic` now just calls `add_to_diagnostic_with` with an empty closure. - **Add `DiagnosticMessage::Eager`**: Add variant of `DiagnosticMessage` for eagerly translated messages (messages in the target language which don't need translated by the emitter during emission). Also adds `eager_subdiagnostic` function which is intended to be invoked by the diagnostic derive for subdiagnostic fields which are marked as needing eager translation. - **Support `#[subdiagnostic(eager)]`**: Add support for `eager` argument to the `subdiagnostic` attribute which generates a call to `eager_subdiagnostic`. - **Finish migrating `rustc_query_system`**: Using eager translation, migrate the remaining repeated cycle stack diagnostic. - **Split formatting initialization and use in diagnostic derives**: Diagnostic derives have previously had to take special care when ordering the generated code so that fields were not used after a move. This is unlikely for most fields because a field is either annotated with a subdiagnostic attribute and is thus likely a `Span` and copiable, or is a argument, in which case it is only used once by `set_arg` anyway. However, format strings for code in suggestions can result in fields being used after being moved if not ordered carefully. As a result, the derive currently puts `set_arg` calls last (just before emission), such as: let diag = { /* create diagnostic */ }; diag.span_suggestion_with_style( span, fluent::crate::slug, format!("{}", __binding_0), Applicability::Unknown, SuggestionStyle::ShowAlways ); /* + other subdiagnostic additions */ diag.set_arg("foo", __binding_0); /* + other `set_arg` calls */ diag.emit(); For eager translation, this doesn't work, as the message being translated eagerly can assume that all arguments are available - so arguments _must_ be set first. Format strings for suggestion code are now separated into two parts - an initialization line that performs the formatting into a variable, and a usage in the subdiagnostic addition. By separating these parts, the initialization can happen before arguments are set, preserving the desired order so that code compiles, while still enabling arguments to be set before subdiagnostics are added. let diag = { /* create diagnostic */ }; let __code_0 = format!("{}", __binding_0); /* + other formatting */ diag.set_arg("foo", __binding_0); /* + other `set_arg` calls */ diag.span_suggestion_with_style( span, fluent::crate::slug, __code_0, Applicability::Unknown, SuggestionStyle::ShowAlways ); /* + other subdiagnostic additions */ diag.emit(); - **Remove field ordering logic in diagnostic derive:** Following the approach taken in earlier commits to separate formatting initialization from use in the subdiagnostic derive, simplify the diagnostic derive by removing the field-ordering logic that previously solved this problem. r? ```@compiler-errors```
2 parents 50f6d33 + fbac1f2 commit dc9f6f3

File tree

26 files changed

+539
-235
lines changed

26 files changed

+539
-235
lines changed
 

‎compiler/rustc_ast_lowering/src/errors.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use rustc_errors::{fluent, AddToDiagnostic, Applicability, Diagnostic, DiagnosticArgFromDisplay};
1+
use rustc_errors::{
2+
fluent, AddToDiagnostic, Applicability, Diagnostic, DiagnosticArgFromDisplay,
3+
SubdiagnosticMessage,
4+
};
25
use rustc_macros::{Diagnostic, Subdiagnostic};
36
use rustc_span::{symbol::Ident, Span, Symbol};
47

@@ -19,7 +22,10 @@ pub struct UseAngleBrackets {
1922
}
2023

2124
impl AddToDiagnostic for UseAngleBrackets {
22-
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
25+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
26+
where
27+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
28+
{
2329
diag.multipart_suggestion(
2430
fluent::ast_lowering::use_angle_brackets,
2531
vec![(self.open_param, String::from("<")), (self.close_param, String::from(">"))],
@@ -69,7 +75,10 @@ pub enum AssocTyParenthesesSub {
6975
}
7076

7177
impl AddToDiagnostic for AssocTyParenthesesSub {
72-
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
78+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
79+
where
80+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
81+
{
7382
match self {
7483
Self::Empty { parentheses_span } => diag.multipart_suggestion(
7584
fluent::ast_lowering::remove_parentheses,

‎compiler/rustc_ast_passes/src/errors.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Errors emitted by ast_passes.
22
3-
use rustc_errors::{fluent, AddToDiagnostic, Applicability, Diagnostic};
3+
use rustc_errors::{fluent, AddToDiagnostic, Applicability, Diagnostic, SubdiagnosticMessage};
44
use rustc_macros::{Diagnostic, Subdiagnostic};
55
use rustc_span::{Span, Symbol};
66

@@ -17,7 +17,10 @@ pub struct ForbiddenLet {
1717
}
1818

1919
impl AddToDiagnostic for ForbiddenLetReason {
20-
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
20+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
21+
where
22+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
23+
{
2124
match self {
2225
Self::GenericForbidden => {}
2326
Self::NotSupportedOr(span) => {
@@ -228,7 +231,10 @@ pub struct ExternBlockSuggestion {
228231
}
229232

230233
impl AddToDiagnostic for ExternBlockSuggestion {
231-
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
234+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
235+
where
236+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
237+
{
232238
let start_suggestion = if let Some(abi) = self.abi {
233239
format!("extern \"{}\" {{", abi)
234240
} else {

‎compiler/rustc_codegen_ssa/src/back/write.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ use rustc_data_structures::profiling::TimingGuard;
1515
use rustc_data_structures::profiling::VerboseTimingGuard;
1616
use rustc_data_structures::sync::Lrc;
1717
use rustc_errors::emitter::Emitter;
18-
use rustc_errors::{translation::Translate, DiagnosticId, FatalError, Handler, Level};
18+
use rustc_errors::{
19+
translation::{to_fluent_args, Translate},
20+
DiagnosticId, FatalError, Handler, Level,
21+
};
1922
use rustc_fs_util::link_or_copy;
2023
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
2124
use rustc_incremental::{
@@ -1740,7 +1743,7 @@ impl Translate for SharedEmitter {
17401743

17411744
impl Emitter for SharedEmitter {
17421745
fn emit_diagnostic(&mut self, diag: &rustc_errors::Diagnostic) {
1743-
let fluent_args = self.to_fluent_args(diag.args());
1746+
let fluent_args = to_fluent_args(diag.args());
17441747
drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic {
17451748
msg: self.translate_messages(&diag.message, &fluent_args).to_string(),
17461749
code: diag.code.clone(),

‎compiler/rustc_error_messages/locales/en-US/query_system.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ query_system_cycle_usage = cycle used when {$usage}
1212
1313
query_system_cycle_stack_single = ...which immediately requires {$stack_bottom} again
1414
15+
query_system_cycle_stack_middle = ...which requires {$desc}...
16+
1517
query_system_cycle_stack_multiple = ...which again requires {$stack_bottom}, completing the cycle
1618
1719
query_system_cycle_recursive_ty_alias = type aliases cannot be recursive

‎compiler/rustc_error_messages/src/lib.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,18 @@ pub enum SubdiagnosticMessage {
277277
/// Non-translatable diagnostic message.
278278
// FIXME(davidtwco): can a `Cow<'static, str>` be used here?
279279
Str(String),
280+
/// Translatable message which has already been translated eagerly.
281+
///
282+
/// Some diagnostics have repeated subdiagnostics where the same interpolated variables would
283+
/// be instantiated multiple times with different values. As translation normally happens
284+
/// immediately prior to emission, after the diagnostic and subdiagnostic derive logic has run,
285+
/// the setting of diagnostic arguments in the derived code will overwrite previous variable
286+
/// values and only the final value will be set when translation occurs - resulting in
287+
/// incorrect diagnostics. Eager translation results in translation for a subdiagnostic
288+
/// happening immediately after the subdiagnostic derive's logic has been run. This variant
289+
/// stores messages which have been translated eagerly.
290+
// FIXME(#100717): can a `Cow<'static, str>` be used here?
291+
Eager(String),
280292
/// Identifier of a Fluent message. Instances of this variant are generated by the
281293
/// `Subdiagnostic` derive.
282294
FluentIdentifier(FluentId),
@@ -304,8 +316,20 @@ impl<S: Into<String>> From<S> for SubdiagnosticMessage {
304316
#[rustc_diagnostic_item = "DiagnosticMessage"]
305317
pub enum DiagnosticMessage {
306318
/// Non-translatable diagnostic message.
307-
// FIXME(davidtwco): can a `Cow<'static, str>` be used here?
319+
// FIXME(#100717): can a `Cow<'static, str>` be used here?
308320
Str(String),
321+
/// Translatable message which has already been translated eagerly.
322+
///
323+
/// Some diagnostics have repeated subdiagnostics where the same interpolated variables would
324+
/// be instantiated multiple times with different values. As translation normally happens
325+
/// immediately prior to emission, after the diagnostic and subdiagnostic derive logic has run,
326+
/// the setting of diagnostic arguments in the derived code will overwrite previous variable
327+
/// values and only the final value will be set when translation occurs - resulting in
328+
/// incorrect diagnostics. Eager translation results in translation for a subdiagnostic
329+
/// happening immediately after the subdiagnostic derive's logic has been run. This variant
330+
/// stores messages which have been translated eagerly.
331+
// FIXME(#100717): can a `Cow<'static, str>` be used here?
332+
Eager(String),
309333
/// Identifier for a Fluent message (with optional attribute) corresponding to the diagnostic
310334
/// message.
311335
///
@@ -324,6 +348,7 @@ impl DiagnosticMessage {
324348
pub fn with_subdiagnostic_message(&self, sub: SubdiagnosticMessage) -> Self {
325349
let attr = match sub {
326350
SubdiagnosticMessage::Str(s) => return DiagnosticMessage::Str(s),
351+
SubdiagnosticMessage::Eager(s) => return DiagnosticMessage::Eager(s),
327352
SubdiagnosticMessage::FluentIdentifier(id) => {
328353
return DiagnosticMessage::FluentIdentifier(id, None);
329354
}
@@ -332,6 +357,7 @@ impl DiagnosticMessage {
332357

333358
match self {
334359
DiagnosticMessage::Str(s) => DiagnosticMessage::Str(s.clone()),
360+
DiagnosticMessage::Eager(s) => DiagnosticMessage::Eager(s.clone()),
335361
DiagnosticMessage::FluentIdentifier(id, _) => {
336362
DiagnosticMessage::FluentIdentifier(id.clone(), Some(attr))
337363
}
@@ -367,6 +393,7 @@ impl Into<SubdiagnosticMessage> for DiagnosticMessage {
367393
fn into(self) -> SubdiagnosticMessage {
368394
match self {
369395
DiagnosticMessage::Str(s) => SubdiagnosticMessage::Str(s),
396+
DiagnosticMessage::Eager(s) => SubdiagnosticMessage::Eager(s),
370397
DiagnosticMessage::FluentIdentifier(id, None) => {
371398
SubdiagnosticMessage::FluentIdentifier(id)
372399
}

‎compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
88
use crate::emitter::FileWithAnnotatedLines;
99
use crate::snippet::Line;
10-
use crate::translation::Translate;
10+
use crate::translation::{to_fluent_args, Translate};
1111
use crate::{
1212
CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, Emitter, FluentBundle,
1313
LazyFallbackBundle, Level, MultiSpan, Style, SubDiagnostic,
@@ -46,7 +46,7 @@ impl Translate for AnnotateSnippetEmitterWriter {
4646
impl Emitter for AnnotateSnippetEmitterWriter {
4747
/// The entry point for the diagnostics generation
4848
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
49-
let fluent_args = self.to_fluent_args(diag.args());
49+
let fluent_args = to_fluent_args(diag.args());
5050

5151
let mut children = diag.children.clone();
5252
let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args);

‎compiler/rustc_errors/src/diagnostic.rs

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ pub struct SuggestionsDisabled;
2727
/// Simplified version of `FluentArg` that can implement `Encodable` and `Decodable`. Collection of
2828
/// `DiagnosticArg` are converted to `FluentArgs` (consuming the collection) at the start of
2929
/// diagnostic emission.
30-
pub type DiagnosticArg<'source> = (Cow<'source, str>, DiagnosticArgValue<'source>);
30+
pub type DiagnosticArg<'iter, 'source> =
31+
(&'iter DiagnosticArgName<'source>, &'iter DiagnosticArgValue<'source>);
32+
33+
/// Name of a diagnostic argument.
34+
pub type DiagnosticArgName<'source> = Cow<'source, str>;
3135

3236
/// Simplified version of `FluentValue` that can implement `Encodable` and `Decodable`. Converted
3337
/// to a `FluentValue` by the emitter to be used in diagnostic translation.
@@ -199,9 +203,20 @@ impl IntoDiagnosticArg for ast::token::TokenKind {
199203
/// `#[derive(Subdiagnostic)]` -- see [rustc_macros::Subdiagnostic].
200204
#[cfg_attr(bootstrap, rustc_diagnostic_item = "AddSubdiagnostic")]
201205
#[cfg_attr(not(bootstrap), rustc_diagnostic_item = "AddToDiagnostic")]
202-
pub trait AddToDiagnostic {
206+
pub trait AddToDiagnostic
207+
where
208+
Self: Sized,
209+
{
203210
/// Add a subdiagnostic to an existing diagnostic.
204-
fn add_to_diagnostic(self, diag: &mut Diagnostic);
211+
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
212+
self.add_to_diagnostic_with(diag, |_, m| m);
213+
}
214+
215+
/// Add a subdiagnostic to an existing diagnostic where `f` is invoked on every message used
216+
/// (to optionally perform eager translation).
217+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, f: F)
218+
where
219+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage;
205220
}
206221

207222
/// Trait implemented by lint types. This should not be implemented manually. Instead, use
@@ -229,7 +244,7 @@ pub struct Diagnostic {
229244
pub span: MultiSpan,
230245
pub children: Vec<SubDiagnostic>,
231246
pub suggestions: Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
232-
args: Vec<DiagnosticArg<'static>>,
247+
args: FxHashMap<DiagnosticArgName<'static>, DiagnosticArgValue<'static>>,
233248

234249
/// This is not used for highlighting or rendering any error message. Rather, it can be used
235250
/// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of
@@ -321,7 +336,7 @@ impl Diagnostic {
321336
span: MultiSpan::new(),
322337
children: vec![],
323338
suggestions: Ok(vec![]),
324-
args: vec![],
339+
args: Default::default(),
325340
sort_span: DUMMY_SP,
326341
is_lint: false,
327342
}
@@ -917,13 +932,30 @@ impl Diagnostic {
917932
self
918933
}
919934

920-
/// Add a subdiagnostic from a type that implements `Subdiagnostic` - see
921-
/// [rustc_macros::Subdiagnostic].
935+
/// Add a subdiagnostic from a type that implements `Subdiagnostic` (see
936+
/// [rustc_macros::Subdiagnostic]).
922937
pub fn subdiagnostic(&mut self, subdiagnostic: impl AddToDiagnostic) -> &mut Self {
923938
subdiagnostic.add_to_diagnostic(self);
924939
self
925940
}
926941

942+
/// Add a subdiagnostic from a type that implements `Subdiagnostic` (see
943+
/// [rustc_macros::Subdiagnostic]). Performs eager translation of any translatable messages
944+
/// used in the subdiagnostic, so suitable for use with repeated messages (i.e. re-use of
945+
/// interpolated variables).
946+
pub fn eager_subdiagnostic(
947+
&mut self,
948+
handler: &crate::Handler,
949+
subdiagnostic: impl AddToDiagnostic,
950+
) -> &mut Self {
951+
subdiagnostic.add_to_diagnostic_with(self, |diag, msg| {
952+
let args = diag.args();
953+
let msg = diag.subdiagnostic_message_to_diagnostic_message(msg);
954+
handler.eagerly_translate(msg, args)
955+
});
956+
self
957+
}
958+
927959
pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
928960
self.span = sp.into();
929961
if let Some(span) = self.span.primary_span() {
@@ -956,16 +988,19 @@ impl Diagnostic {
956988
self
957989
}
958990

959-
pub fn args(&self) -> &[DiagnosticArg<'static>] {
960-
&self.args
991+
// Exact iteration order of diagnostic arguments shouldn't make a difference to output because
992+
// they're only used in interpolation.
993+
#[allow(rustc::potential_query_instability)]
994+
pub fn args<'a>(&'a self) -> impl Iterator<Item = DiagnosticArg<'a, 'static>> {
995+
self.args.iter()
961996
}
962997

963998
pub fn set_arg(
964999
&mut self,
9651000
name: impl Into<Cow<'static, str>>,
9661001
arg: impl IntoDiagnosticArg,
9671002
) -> &mut Self {
968-
self.args.push((name.into(), arg.into_diagnostic_arg()));
1003+
self.args.insert(name.into(), arg.into_diagnostic_arg());
9691004
self
9701005
}
9711006

@@ -976,7 +1011,7 @@ impl Diagnostic {
9761011
/// Helper function that takes a `SubdiagnosticMessage` and returns a `DiagnosticMessage` by
9771012
/// combining it with the primary message of the diagnostic (if translatable, otherwise it just
9781013
/// passes the user's string along).
979-
fn subdiagnostic_message_to_diagnostic_message(
1014+
pub(crate) fn subdiagnostic_message_to_diagnostic_message(
9801015
&self,
9811016
attr: impl Into<SubdiagnosticMessage>,
9821017
) -> DiagnosticMessage {

‎compiler/rustc_errors/src/emitter.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_span::{FileLines, SourceFile, Span};
1414

1515
use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString};
1616
use crate::styled_buffer::StyledBuffer;
17-
use crate::translation::Translate;
17+
use crate::translation::{to_fluent_args, Translate};
1818
use crate::{
1919
CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, FluentBundle, Handler,
2020
LazyFallbackBundle, Level, MultiSpan, SubDiagnostic, SubstitutionHighlight, SuggestionStyle,
@@ -535,7 +535,7 @@ impl Emitter for EmitterWriter {
535535
}
536536

537537
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
538-
let fluent_args = self.to_fluent_args(diag.args());
538+
let fluent_args = to_fluent_args(diag.args());
539539

540540
let mut children = diag.children.clone();
541541
let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args);

‎compiler/rustc_errors/src/json.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_span::source_map::{FilePathMapping, SourceMap};
1313

1414
use crate::emitter::{Emitter, HumanReadableErrorType};
1515
use crate::registry::Registry;
16-
use crate::translation::Translate;
16+
use crate::translation::{to_fluent_args, Translate};
1717
use crate::DiagnosticId;
1818
use crate::{
1919
CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, SubDiagnostic,
@@ -312,7 +312,7 @@ struct UnusedExterns<'a, 'b, 'c> {
312312

313313
impl Diagnostic {
314314
fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
315-
let args = je.to_fluent_args(diag.args());
315+
let args = to_fluent_args(diag.args());
316316
let sugg = diag.suggestions.iter().flatten().map(|sugg| {
317317
let translated_message = je.translate_message(&sugg.msg, &args);
318318
Diagnostic {

‎compiler/rustc_errors/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,17 @@ impl Handler {
598598
}
599599
}
600600

601+
/// Translate `message` eagerly with `args`.
602+
pub fn eagerly_translate<'a>(
603+
&self,
604+
message: DiagnosticMessage,
605+
args: impl Iterator<Item = DiagnosticArg<'a, 'static>>,
606+
) -> SubdiagnosticMessage {
607+
let inner = self.inner.borrow();
608+
let args = crate::translation::to_fluent_args(args);
609+
SubdiagnosticMessage::Eager(inner.emitter.translate_message(&message, &args).to_string())
610+
}
611+
601612
// This is here to not allow mutation of flags;
602613
// as of this writing it's only used in tests in librustc_middle.
603614
pub fn can_emit_warnings(&self) -> bool {

‎compiler/rustc_errors/src/translation.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@ use rustc_data_structures::sync::Lrc;
44
use rustc_error_messages::FluentArgs;
55
use std::borrow::Cow;
66

7+
/// Convert diagnostic arguments (a rustc internal type that exists to implement
8+
/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
9+
///
10+
/// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
11+
/// passed around as a reference thereafter.
12+
pub fn to_fluent_args<'iter, 'arg: 'iter>(
13+
iter: impl Iterator<Item = DiagnosticArg<'iter, 'arg>>,
14+
) -> FluentArgs<'arg> {
15+
let mut args = if let Some(size) = iter.size_hint().1 {
16+
FluentArgs::with_capacity(size)
17+
} else {
18+
FluentArgs::new()
19+
};
20+
21+
for (k, v) in iter {
22+
args.set(k.clone(), v.clone());
23+
}
24+
25+
args
26+
}
27+
728
pub trait Translate {
829
/// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
930
/// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
@@ -15,15 +36,6 @@ pub trait Translate {
1536
/// unavailable for the requested locale.
1637
fn fallback_fluent_bundle(&self) -> &FluentBundle;
1738

18-
/// Convert diagnostic arguments (a rustc internal type that exists to implement
19-
/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
20-
///
21-
/// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
22-
/// passed around as a reference thereafter.
23-
fn to_fluent_args<'arg>(&self, args: &[DiagnosticArg<'arg>]) -> FluentArgs<'arg> {
24-
FromIterator::from_iter(args.iter().cloned())
25-
}
26-
2739
/// Convert `DiagnosticMessage`s to a string, performing translation if necessary.
2840
fn translate_messages(
2941
&self,
@@ -43,7 +55,9 @@ pub trait Translate {
4355
) -> Cow<'_, str> {
4456
trace!(?message, ?args);
4557
let (identifier, attr) = match message {
46-
DiagnosticMessage::Str(msg) => return Cow::Borrowed(&msg),
58+
DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
59+
return Cow::Borrowed(&msg);
60+
}
4761
DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
4862
};
4963

‎compiler/rustc_infer/src/errors/mod.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use hir::GenericParamKind;
22
use rustc_errors::{
3-
fluent, AddToDiagnostic, Applicability, DiagnosticMessage, DiagnosticStyledString, MultiSpan,
3+
fluent, AddToDiagnostic, Applicability, Diagnostic, DiagnosticMessage, DiagnosticStyledString,
4+
MultiSpan, SubdiagnosticMessage,
45
};
56
use rustc_hir as hir;
67
use rustc_hir::{FnRetTy, Ty};
@@ -229,7 +230,10 @@ pub enum RegionOriginNote<'a> {
229230
}
230231

231232
impl AddToDiagnostic for RegionOriginNote<'_> {
232-
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
233+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
234+
where
235+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
236+
{
233237
let mut label_or_note = |span, msg: DiagnosticMessage| {
234238
let sub_count = diag.children.iter().filter(|d| d.span.is_dummy()).count();
235239
let expanded_sub_count = diag.children.iter().filter(|d| !d.span.is_dummy()).count();
@@ -290,7 +294,10 @@ pub enum LifetimeMismatchLabels {
290294
}
291295

292296
impl AddToDiagnostic for LifetimeMismatchLabels {
293-
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
297+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
298+
where
299+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
300+
{
294301
match self {
295302
LifetimeMismatchLabels::InRet { param_span, ret_span, span, label_var1 } => {
296303
diag.span_label(param_span, fluent::infer::declared_different);
@@ -340,7 +347,10 @@ pub struct AddLifetimeParamsSuggestion<'a> {
340347
}
341348

342349
impl AddToDiagnostic for AddLifetimeParamsSuggestion<'_> {
343-
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
350+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
351+
where
352+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
353+
{
344354
let mut mk_suggestion = || {
345355
let (
346356
hir::Ty { kind: hir::TyKind::Rptr(lifetime_sub, _), .. },
@@ -439,7 +449,10 @@ pub struct IntroducesStaticBecauseUnmetLifetimeReq {
439449
}
440450

441451
impl AddToDiagnostic for IntroducesStaticBecauseUnmetLifetimeReq {
442-
fn add_to_diagnostic(mut self, diag: &mut rustc_errors::Diagnostic) {
452+
fn add_to_diagnostic_with<F>(mut self, diag: &mut Diagnostic, _: F)
453+
where
454+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
455+
{
443456
self.unmet_requirements
444457
.push_span_label(self.binding_span, fluent::infer::msl_introduces_static);
445458
diag.span_note(self.unmet_requirements, fluent::infer::msl_unmet_req);
@@ -451,7 +464,10 @@ pub struct ImplNote {
451464
}
452465

453466
impl AddToDiagnostic for ImplNote {
454-
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
467+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
468+
where
469+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
470+
{
455471
match self.impl_span {
456472
Some(span) => diag.span_note(span, fluent::infer::msl_impl_note),
457473
None => diag.note(fluent::infer::msl_impl_note),
@@ -466,7 +482,10 @@ pub enum TraitSubdiag {
466482

467483
// FIXME(#100717) used in `Vec<TraitSubdiag>` so requires eager translation/list support
468484
impl AddToDiagnostic for TraitSubdiag {
469-
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
485+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
486+
where
487+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
488+
{
470489
match self {
471490
TraitSubdiag::Note { span } => {
472491
diag.span_note(span, "this has an implicit `'static` lifetime requirement");

‎compiler/rustc_infer/src/errors/note_and_explain.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::infer::error_reporting::nice_region_error::find_anon_type;
2-
use rustc_errors::{self, fluent, AddToDiagnostic, IntoDiagnosticArg};
2+
use rustc_errors::{
3+
self, fluent, AddToDiagnostic, Diagnostic, IntoDiagnosticArg, SubdiagnosticMessage,
4+
};
35
use rustc_middle::ty::{self, TyCtxt};
46
use rustc_span::{symbol::kw, Span};
57

@@ -159,7 +161,10 @@ impl RegionExplanation<'_> {
159161
}
160162

161163
impl AddToDiagnostic for RegionExplanation<'_> {
162-
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
164+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
165+
where
166+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
167+
{
163168
if let Some(span) = self.desc.span {
164169
diag.span_note(span, fluent::infer::region_explanation);
165170
} else {

‎compiler/rustc_lint/src/errors.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use rustc_errors::{fluent, AddToDiagnostic, ErrorGuaranteed, Handler, IntoDiagnostic};
1+
use rustc_errors::{
2+
fluent, AddToDiagnostic, Diagnostic, ErrorGuaranteed, Handler, IntoDiagnostic,
3+
SubdiagnosticMessage,
4+
};
25
use rustc_macros::{Diagnostic, Subdiagnostic};
36
use rustc_session::lint::Level;
47
use rustc_span::{Span, Symbol};
@@ -23,7 +26,10 @@ pub enum OverruledAttributeSub {
2326
}
2427

2528
impl AddToDiagnostic for OverruledAttributeSub {
26-
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
29+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
30+
where
31+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
32+
{
2733
match self {
2834
OverruledAttributeSub::DefaultSource { id } => {
2935
diag.note(fluent::lint::default_source);
@@ -88,7 +94,10 @@ pub struct RequestedLevel {
8894
}
8995

9096
impl AddToDiagnostic for RequestedLevel {
91-
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
97+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
98+
where
99+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
100+
{
92101
diag.note(fluent::lint::requested_level);
93102
diag.set_arg(
94103
"level",

‎compiler/rustc_macros/src/diagnostics/diagnostic.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,31 @@ use synstructure::Structure;
1010
/// The central struct for constructing the `into_diagnostic` method from an annotated struct.
1111
pub(crate) struct DiagnosticDerive<'a> {
1212
structure: Structure<'a>,
13-
handler: syn::Ident,
1413
builder: DiagnosticDeriveBuilder,
1514
}
1615

1716
impl<'a> DiagnosticDerive<'a> {
1817
pub(crate) fn new(diag: syn::Ident, handler: syn::Ident, structure: Structure<'a>) -> Self {
1918
Self {
20-
builder: DiagnosticDeriveBuilder { diag, kind: DiagnosticDeriveKind::Diagnostic },
21-
handler,
19+
builder: DiagnosticDeriveBuilder {
20+
diag,
21+
kind: DiagnosticDeriveKind::Diagnostic { handler },
22+
},
2223
structure,
2324
}
2425
}
2526

2627
pub(crate) fn into_tokens(self) -> TokenStream {
27-
let DiagnosticDerive { mut structure, handler, mut builder } = self;
28+
let DiagnosticDerive { mut structure, mut builder } = self;
2829

2930
let implementation = builder.each_variant(&mut structure, |mut builder, variant| {
3031
let preamble = builder.preamble(&variant);
3132
let body = builder.body(&variant);
3233

3334
let diag = &builder.parent.diag;
35+
let DiagnosticDeriveKind::Diagnostic { handler } = &builder.parent.kind else {
36+
unreachable!()
37+
};
3438
let init = match builder.slug.value_ref() {
3539
None => {
3640
span_err(builder.span, "diagnostic slug not specified")
@@ -48,14 +52,17 @@ impl<'a> DiagnosticDerive<'a> {
4852
}
4953
};
5054

55+
let formatting_init = &builder.formatting_init;
5156
quote! {
5257
#init
58+
#formatting_init
5359
#preamble
5460
#body
5561
#diag
5662
}
5763
});
5864

65+
let DiagnosticDeriveKind::Diagnostic { handler } = &builder.kind else { unreachable!() };
5966
structure.gen_impl(quote! {
6067
gen impl<'__diagnostic_handler_sess, G>
6168
rustc_errors::IntoDiagnostic<'__diagnostic_handler_sess, G>
@@ -96,17 +103,18 @@ impl<'a> LintDiagnosticDerive<'a> {
96103
let body = builder.body(&variant);
97104

98105
let diag = &builder.parent.diag;
99-
106+
let formatting_init = &builder.formatting_init;
100107
quote! {
101108
#preamble
109+
#formatting_init
102110
#body
103111
#diag
104112
}
105113
});
106114

107115
let msg = builder.each_variant(&mut structure, |mut builder, variant| {
108-
// HACK(wafflelapkin): initialize slug (???)
109-
let _preamble = builder.preamble(&variant);
116+
// Collect the slug by generating the preamble.
117+
let _ = builder.preamble(&variant);
110118

111119
match builder.slug.value_ref() {
112120
None => {
@@ -125,7 +133,10 @@ impl<'a> LintDiagnosticDerive<'a> {
125133
let diag = &builder.diag;
126134
structure.gen_impl(quote! {
127135
gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
128-
fn decorate_lint<'__b>(self, #diag: &'__b mut rustc_errors::DiagnosticBuilder<'__a, ()>) -> &'__b mut rustc_errors::DiagnosticBuilder<'__a, ()> {
136+
fn decorate_lint<'__b>(
137+
self,
138+
#diag: &'__b mut rustc_errors::DiagnosticBuilder<'__a, ()>
139+
) -> &'__b mut rustc_errors::DiagnosticBuilder<'__a, ()> {
129140
use rustc_errors::IntoDiagnosticArg;
130141
#implementation
131142
}

‎compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs

Lines changed: 90 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use crate::diagnostics::error::{
55
DiagnosticDeriveError,
66
};
77
use crate::diagnostics::utils::{
8-
bind_style_of_field, build_field_mapping, report_error_if_not_applied_to_span,
9-
report_type_error, should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo,
10-
FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
8+
build_field_mapping, report_error_if_not_applied_to_span, report_type_error,
9+
should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy, FieldMap,
10+
HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
1111
};
1212
use proc_macro2::{Ident, Span, TokenStream};
1313
use quote::{format_ident, quote};
@@ -17,9 +17,9 @@ use syn::{
1717
use synstructure::{BindingInfo, Structure, VariantInfo};
1818

1919
/// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
20-
#[derive(Copy, Clone, PartialEq, Eq)]
20+
#[derive(Clone, PartialEq, Eq)]
2121
pub(crate) enum DiagnosticDeriveKind {
22-
Diagnostic,
22+
Diagnostic { handler: syn::Ident },
2323
LintDiagnostic,
2424
}
2525

@@ -40,6 +40,9 @@ pub(crate) struct DiagnosticDeriveVariantBuilder<'parent> {
4040
/// The parent builder for the entire type.
4141
pub parent: &'parent DiagnosticDeriveBuilder,
4242

43+
/// Initialization of format strings for code suggestions.
44+
pub formatting_init: TokenStream,
45+
4346
/// Span of the struct or the enum variant.
4447
pub span: proc_macro::Span,
4548

@@ -88,19 +91,7 @@ impl DiagnosticDeriveBuilder {
8891
}
8992
}
9093

91-
for variant in structure.variants_mut() {
92-
// First, change the binding style of each field based on the code that will be
93-
// generated for the field - e.g. `set_arg` calls needs by-move bindings, whereas
94-
// `set_primary_span` only needs by-ref.
95-
variant.bind_with(|bi| bind_style_of_field(bi.ast()).0);
96-
97-
// Then, perform a stable sort on bindings which generates code for by-ref bindings
98-
// before code generated for by-move bindings. Any code generated for the by-ref
99-
// bindings which creates a reference to the by-move fields will happen before the
100-
// by-move bindings move those fields and make them inaccessible.
101-
variant.bindings_mut().sort_by_cached_key(|bi| bind_style_of_field(bi.ast()));
102-
}
103-
94+
structure.bind_with(|_| synstructure::BindStyle::Move);
10495
let variants = structure.each_variant(|variant| {
10596
let span = match structure.ast().data {
10697
syn::Data::Struct(..) => span,
@@ -112,6 +103,7 @@ impl DiagnosticDeriveBuilder {
112103
parent: &self,
113104
span,
114105
field_map: build_field_mapping(variant),
106+
formatting_init: TokenStream::new(),
115107
slug: None,
116108
code: None,
117109
};
@@ -143,16 +135,14 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
143135

144136
/// Generates calls to `span_label` and similar functions based on the attributes on fields or
145137
/// calls to `set_arg` when no attributes are present.
146-
///
147-
/// Expects use of `Self::each_variant` which will have sorted bindings so that by-ref bindings
148-
/// (which may create references to by-move bindings) have their code generated first -
149-
/// necessary as code for suggestions uses formatting machinery and the value of other fields
150-
/// (any given field can be referenced multiple times, so must be accessed through a borrow);
151-
/// and when passing fields to `add_subdiagnostic` or `set_arg` for Fluent, fields must be
152-
/// accessed by-move.
153138
pub fn body<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
154139
let mut body = quote! {};
155-
for binding in variant.bindings() {
140+
// Generate `set_arg` calls first..
141+
for binding in variant.bindings().iter().filter(|bi| should_generate_set_arg(bi.ast())) {
142+
body.extend(self.generate_field_code(binding));
143+
}
144+
// ..and then subdiagnostic additions.
145+
for binding in variant.bindings().iter().filter(|bi| !should_generate_set_arg(bi.ast())) {
156146
body.extend(self.generate_field_attrs_code(binding));
157147
}
158148
body
@@ -274,24 +264,27 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
274264
}
275265
}
276266

277-
fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
267+
fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
268+
let diag = &self.parent.diag;
269+
278270
let field = binding_info.ast();
279271
let field_binding = &binding_info.binding;
280272

281-
if should_generate_set_arg(&field) {
282-
let diag = &self.parent.diag;
283-
let ident = field.ident.as_ref().unwrap();
284-
// strip `r#` prefix, if present
285-
let ident = format_ident!("{}", ident);
286-
return quote! {
287-
#diag.set_arg(
288-
stringify!(#ident),
289-
#field_binding
290-
);
291-
};
273+
let ident = field.ident.as_ref().unwrap();
274+
let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
275+
276+
quote! {
277+
#diag.set_arg(
278+
stringify!(#ident),
279+
#field_binding
280+
);
292281
}
282+
}
283+
284+
fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
285+
let field = binding_info.ast();
286+
let field_binding = &binding_info.binding;
293287

294-
let needs_move = bind_style_of_field(&field).is_move();
295288
let inner_ty = FieldInnerTy::from_type(&field.ty);
296289

297290
field
@@ -304,10 +297,8 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
304297
let (binding, needs_destructure) = if needs_clone {
305298
// `primary_span` can accept a `Vec<Span>` so don't destructure that.
306299
(quote! { #field_binding.clone() }, false)
307-
} else if needs_move {
308-
(quote! { #field_binding }, true)
309300
} else {
310-
(quote! { *#field_binding }, true)
301+
(quote! { #field_binding }, true)
311302
};
312303

313304
let generated_code = self
@@ -340,18 +331,15 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
340331
let diag = &self.parent.diag;
341332
let meta = attr.parse_meta()?;
342333

343-
if let Meta::Path(_) = meta {
344-
let ident = &attr.path.segments.last().unwrap().ident;
345-
let name = ident.to_string();
346-
let name = name.as_str();
347-
match name {
348-
"skip_arg" => {
349-
// Don't need to do anything - by virtue of the attribute existing, the
350-
// `set_arg` call will not be generated.
351-
return Ok(quote! {});
352-
}
353-
"primary_span" => match self.parent.kind {
354-
DiagnosticDeriveKind::Diagnostic => {
334+
let ident = &attr.path.segments.last().unwrap().ident;
335+
let name = ident.to_string();
336+
match (&meta, name.as_str()) {
337+
// Don't need to do anything - by virtue of the attribute existing, the
338+
// `set_arg` call will not be generated.
339+
(Meta::Path(_), "skip_arg") => return Ok(quote! {}),
340+
(Meta::Path(_), "primary_span") => {
341+
match self.parent.kind {
342+
DiagnosticDeriveKind::Diagnostic { .. } => {
355343
report_error_if_not_applied_to_span(attr, &info)?;
356344

357345
return Ok(quote! {
@@ -363,10 +351,50 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
363351
diag.help("the `primary_span` field attribute is not valid for lint diagnostics")
364352
})
365353
}
366-
},
367-
"subdiagnostic" => return Ok(quote! { #diag.subdiagnostic(#binding); }),
368-
_ => {}
354+
}
355+
}
356+
(Meta::Path(_), "subdiagnostic") => {
357+
return Ok(quote! { #diag.subdiagnostic(#binding); });
358+
}
359+
(Meta::NameValue(_), "subdiagnostic") => {
360+
throw_invalid_attr!(attr, &meta, |diag| {
361+
diag.help("`eager` is the only supported nested attribute for `subdiagnostic`")
362+
})
363+
}
364+
(Meta::List(MetaList { ref nested, .. }), "subdiagnostic") => {
365+
if nested.len() != 1 {
366+
throw_invalid_attr!(attr, &meta, |diag| {
367+
diag.help(
368+
"`eager` is the only supported nested attribute for `subdiagnostic`",
369+
)
370+
})
371+
}
372+
373+
let handler = match &self.parent.kind {
374+
DiagnosticDeriveKind::Diagnostic { handler } => handler,
375+
DiagnosticDeriveKind::LintDiagnostic => {
376+
throw_invalid_attr!(attr, &meta, |diag| {
377+
diag.help("eager subdiagnostics are not supported on lints")
378+
})
379+
}
380+
};
381+
382+
let nested_attr = nested.first().expect("pop failed for single element list");
383+
match nested_attr {
384+
NestedMeta::Meta(meta @ Meta::Path(_))
385+
if meta.path().segments.last().unwrap().ident.to_string().as_str()
386+
== "eager" =>
387+
{
388+
return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); });
389+
}
390+
_ => {
391+
throw_invalid_nested_attr!(attr, nested_attr, |diag| {
392+
diag.help("`eager` is the only supported nested attribute for `subdiagnostic`")
393+
})
394+
}
395+
}
369396
}
397+
_ => (),
370398
}
371399

372400
let (subdiag, slug) = self.parse_subdiag_attribute(attr)?;
@@ -389,7 +417,8 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
389417
SubdiagnosticKind::Suggestion {
390418
suggestion_kind,
391419
applicability: static_applicability,
392-
code,
420+
code_field,
421+
code_init,
393422
} => {
394423
let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
395424

@@ -402,11 +431,12 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
402431
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
403432
let style = suggestion_kind.to_suggestion_style();
404433

434+
self.formatting_init.extend(code_init);
405435
Ok(quote! {
406436
#diag.span_suggestion_with_style(
407437
#span_field,
408438
rustc_errors::fluent::#slug,
409-
#code,
439+
#code_field,
410440
#applicability,
411441
#style
412442
);
@@ -451,7 +481,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
451481
// If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
452482
ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
453483
let binding = &info.binding.binding;
454-
Ok((quote!(*#binding), None))
484+
Ok((quote!(#binding), None))
455485
}
456486
// If `ty` is `(Span, Applicability)` then return tokens accessing those.
457487
Type::Tuple(tup) => {

‎compiler/rustc_macros/src/diagnostics/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use diagnostic::{DiagnosticDerive, LintDiagnosticDerive};
99
pub(crate) use fluent::fluent_messages;
1010
use proc_macro2::TokenStream;
1111
use quote::format_ident;
12-
use subdiagnostic::SubdiagnosticDerive;
12+
use subdiagnostic::SubdiagnosticDeriveBuilder;
1313
use synstructure::Structure;
1414

1515
/// Implements `#[derive(Diagnostic)]`, which allows for errors to be specified as a struct,
@@ -155,5 +155,5 @@ pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream {
155155
/// diag.subdiagnostic(RawIdentifierSuggestion { span, applicability, ident });
156156
/// ```
157157
pub fn session_subdiagnostic_derive(s: Structure<'_>) -> TokenStream {
158-
SubdiagnosticDerive::new(s).into_tokens()
158+
SubdiagnosticDeriveBuilder::new().into_tokens(s)
159159
}

‎compiler/rustc_macros/src/diagnostics/subdiagnostic.rs

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::diagnostics::error::{
55
DiagnosticDeriveError,
66
};
77
use crate::diagnostics::utils::{
8-
build_field_mapping, report_error_if_not_applied_to_applicability,
8+
build_field_mapping, new_code_ident, report_error_if_not_applied_to_applicability,
99
report_error_if_not_applied_to_span, FieldInfo, FieldInnerTy, FieldMap, HasFieldMap, SetOnce,
1010
SpannedOption, SubdiagnosticKind,
1111
};
@@ -15,19 +15,19 @@ use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta
1515
use synstructure::{BindingInfo, Structure, VariantInfo};
1616

1717
/// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
18-
pub(crate) struct SubdiagnosticDerive<'a> {
19-
structure: Structure<'a>,
18+
pub(crate) struct SubdiagnosticDeriveBuilder {
2019
diag: syn::Ident,
20+
f: syn::Ident,
2121
}
2222

23-
impl<'a> SubdiagnosticDerive<'a> {
24-
pub(crate) fn new(structure: Structure<'a>) -> Self {
23+
impl SubdiagnosticDeriveBuilder {
24+
pub(crate) fn new() -> Self {
2525
let diag = format_ident!("diag");
26-
Self { structure, diag }
26+
let f = format_ident!("f");
27+
Self { diag, f }
2728
}
2829

29-
pub(crate) fn into_tokens(self) -> TokenStream {
30-
let SubdiagnosticDerive { mut structure, diag } = self;
30+
pub(crate) fn into_tokens<'a>(self, mut structure: Structure<'a>) -> TokenStream {
3131
let implementation = {
3232
let ast = structure.ast();
3333
let span = ast.span().unwrap();
@@ -53,10 +53,11 @@ impl<'a> SubdiagnosticDerive<'a> {
5353

5454
structure.bind_with(|_| synstructure::BindStyle::Move);
5555
let variants_ = structure.each_variant(|variant| {
56-
let mut builder = SubdiagnosticDeriveBuilder {
57-
diag: &diag,
56+
let mut builder = SubdiagnosticDeriveVariantBuilder {
57+
parent: &self,
5858
variant,
5959
span,
60+
formatting_init: TokenStream::new(),
6061
fields: build_field_mapping(variant),
6162
span_field: None,
6263
applicability: None,
@@ -72,9 +73,17 @@ impl<'a> SubdiagnosticDerive<'a> {
7273
}
7374
};
7475

76+
let diag = &self.diag;
77+
let f = &self.f;
7578
let ret = structure.gen_impl(quote! {
7679
gen impl rustc_errors::AddToDiagnostic for @Self {
77-
fn add_to_diagnostic(self, #diag: &mut rustc_errors::Diagnostic) {
80+
fn add_to_diagnostic_with<__F>(self, #diag: &mut rustc_errors::Diagnostic, #f: __F)
81+
where
82+
__F: Fn(
83+
&mut rustc_errors::Diagnostic,
84+
rustc_errors::SubdiagnosticMessage
85+
) -> rustc_errors::SubdiagnosticMessage,
86+
{
7887
use rustc_errors::{Applicability, IntoDiagnosticArg};
7988
#implementation
8089
}
@@ -88,15 +97,18 @@ impl<'a> SubdiagnosticDerive<'a> {
8897
/// for the final generated method. This is a separate struct to `SubdiagnosticDerive`
8998
/// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
9099
/// double mut borrow later on.
91-
struct SubdiagnosticDeriveBuilder<'a> {
100+
struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
92101
/// The identifier to use for the generated `DiagnosticBuilder` instance.
93-
diag: &'a syn::Ident,
102+
parent: &'parent SubdiagnosticDeriveBuilder,
94103

95104
/// Info for the current variant (or the type if not an enum).
96105
variant: &'a VariantInfo<'a>,
97106
/// Span for the entire type.
98107
span: proc_macro::Span,
99108

109+
/// Initialization of format strings for code suggestions.
110+
formatting_init: TokenStream,
111+
100112
/// Store a map of field name to its corresponding field. This is built on construction of the
101113
/// derive builder.
102114
fields: FieldMap,
@@ -112,7 +124,7 @@ struct SubdiagnosticDeriveBuilder<'a> {
112124
has_suggestion_parts: bool,
113125
}
114126

115-
impl<'a> HasFieldMap for SubdiagnosticDeriveBuilder<'a> {
127+
impl<'parent, 'a> HasFieldMap for SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
116128
fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
117129
self.fields.get(field)
118130
}
@@ -156,7 +168,7 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
156168
}
157169
}
158170

159-
impl<'a> SubdiagnosticDeriveBuilder<'a> {
171+
impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
160172
fn identify_kind(&mut self) -> Result<Vec<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
161173
let mut kind_slugs = vec![];
162174

@@ -187,7 +199,7 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
187199
let ast = binding.ast();
188200
assert_eq!(ast.attrs.len(), 0, "field with attribute used as diagnostic arg");
189201

190-
let diag = &self.diag;
202+
let diag = &self.parent.diag;
191203
let ident = ast.ident.as_ref().unwrap();
192204
// strip `r#` prefix, if present
193205
let ident = format_ident!("{}", ident);
@@ -222,7 +234,7 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
222234
};
223235

224236
let generated = self
225-
.generate_field_code_inner(kind_stats, attr, info)
237+
.generate_field_code_inner(kind_stats, attr, info, inner_ty.will_iterate())
226238
.unwrap_or_else(|v| v.to_compile_error());
227239

228240
inner_ty.with(binding, generated)
@@ -235,13 +247,18 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
235247
kind_stats: KindsStatistics,
236248
attr: &Attribute,
237249
info: FieldInfo<'_>,
250+
clone_suggestion_code: bool,
238251
) -> Result<TokenStream, DiagnosticDeriveError> {
239252
let meta = attr.parse_meta()?;
240253
match meta {
241254
Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path),
242-
Meta::List(list @ MetaList { .. }) => {
243-
self.generate_field_code_inner_list(kind_stats, attr, info, list)
244-
}
255+
Meta::List(list @ MetaList { .. }) => self.generate_field_code_inner_list(
256+
kind_stats,
257+
attr,
258+
info,
259+
list,
260+
clone_suggestion_code,
261+
),
245262
_ => throw_invalid_attr!(attr, &meta),
246263
}
247264
}
@@ -345,6 +362,7 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
345362
attr: &Attribute,
346363
info: FieldInfo<'_>,
347364
list: MetaList,
365+
clone_suggestion_code: bool,
348366
) -> Result<TokenStream, DiagnosticDeriveError> {
349367
let span = attr.span().unwrap();
350368
let ident = &list.path.segments.last().unwrap().ident;
@@ -382,22 +400,29 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
382400
match nested_name {
383401
"code" => {
384402
let formatted_str = self.build_format(&value.value(), value.span());
385-
code.set_once(formatted_str, span);
403+
let code_field = new_code_ident();
404+
code.set_once((code_field, formatted_str), span);
386405
}
387406
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
388407
diag.help("`code` is the only valid nested attribute")
389408
}),
390409
}
391410
}
392411

393-
let Some((code, _)) = code else {
412+
let Some((code_field, formatted_str)) = code.value() else {
394413
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
395414
.emit();
396415
return Ok(quote! {});
397416
};
398417
let binding = info.binding;
399418

400-
Ok(quote! { suggestions.push((#binding, #code)); })
419+
self.formatting_init.extend(quote! { let #code_field = #formatted_str; });
420+
let code_field = if clone_suggestion_code {
421+
quote! { #code_field.clone() }
422+
} else {
423+
quote! { #code_field }
424+
};
425+
Ok(quote! { suggestions.push((#binding, #code_field)); })
401426
}
402427
_ => throw_invalid_attr!(attr, &Meta::List(list), |diag| {
403428
let mut span_attrs = vec![];
@@ -442,13 +467,23 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
442467

443468
let span_field = self.span_field.value_ref();
444469

445-
let diag = &self.diag;
470+
let diag = &self.parent.diag;
471+
let f = &self.parent.f;
446472
let mut calls = TokenStream::new();
447473
for (kind, slug) in kind_slugs {
474+
let message = format_ident!("__message");
475+
calls.extend(quote! { let #message = #f(#diag, rustc_errors::fluent::#slug.into()); });
476+
448477
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
449-
let message = quote! { rustc_errors::fluent::#slug };
450478
let call = match kind {
451-
SubdiagnosticKind::Suggestion { suggestion_kind, applicability, code } => {
479+
SubdiagnosticKind::Suggestion {
480+
suggestion_kind,
481+
applicability,
482+
code_init,
483+
code_field,
484+
} => {
485+
self.formatting_init.extend(code_init);
486+
452487
let applicability = applicability
453488
.value()
454489
.map(|a| quote! { #a })
@@ -457,8 +492,7 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
457492

458493
if let Some(span) = span_field {
459494
let style = suggestion_kind.to_suggestion_style();
460-
461-
quote! { #diag.#name(#span, #message, #code, #applicability, #style); }
495+
quote! { #diag.#name(#span, #message, #code_field, #applicability, #style); }
462496
} else {
463497
span_err(self.span, "suggestion without `#[primary_span]` field").emit();
464498
quote! { unreachable!(); }
@@ -499,6 +533,7 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
499533
}
500534
}
501535
};
536+
502537
calls.extend(call);
503538
}
504539

@@ -510,11 +545,13 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
510545
.map(|binding| self.generate_field_set_arg(binding))
511546
.collect();
512547

548+
let formatting_init = &self.formatting_init;
513549
Ok(quote! {
514550
#init
551+
#formatting_init
515552
#attr_args
516-
#calls
517553
#plain_args
554+
#calls
518555
})
519556
}
520557
}

‎compiler/rustc_macros/src/diagnostics/utils.rs

Lines changed: 40 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,29 @@ use crate::diagnostics::error::{
44
use proc_macro::Span;
55
use proc_macro2::TokenStream;
66
use quote::{format_ident, quote, ToTokens};
7-
use std::cmp::Ordering;
7+
use std::cell::RefCell;
88
use std::collections::{BTreeSet, HashMap};
99
use std::fmt;
1010
use std::str::FromStr;
1111
use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple};
1212
use syn::{MetaList, MetaNameValue, NestedMeta, Path};
13-
use synstructure::{BindStyle, BindingInfo, VariantInfo};
13+
use synstructure::{BindingInfo, VariantInfo};
1414

1515
use super::error::invalid_nested_attr;
1616

17+
thread_local! {
18+
pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0);
19+
}
20+
21+
/// Returns an ident of the form `__code_N` where `N` is incremented once with every call.
22+
pub(crate) fn new_code_ident() -> syn::Ident {
23+
CODE_IDENT_COUNT.with(|count| {
24+
let ident = format_ident!("__code_{}", *count.borrow());
25+
*count.borrow_mut() += 1;
26+
ident
27+
})
28+
}
29+
1730
/// Checks whether the type name of `ty` matches `name`.
1831
///
1932
/// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
@@ -142,6 +155,15 @@ impl<'ty> FieldInnerTy<'ty> {
142155
unreachable!();
143156
}
144157

158+
/// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e.
159+
/// that cloning might be required for values moved in the loop body).
160+
pub(crate) fn will_iterate(&self) -> bool {
161+
match self {
162+
FieldInnerTy::Vec(..) => true,
163+
FieldInnerTy::Option(..) | FieldInnerTy::None => false,
164+
}
165+
}
166+
145167
/// Returns `Option` containing inner type if there is one.
146168
pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
147169
match self {
@@ -434,7 +456,12 @@ pub(super) enum SubdiagnosticKind {
434456
Suggestion {
435457
suggestion_kind: SuggestionKind,
436458
applicability: SpannedOption<Applicability>,
437-
code: TokenStream,
459+
/// Identifier for variable used for formatted code, e.g. `___code_0`. Enables separation
460+
/// of formatting and diagnostic emission so that `set_arg` calls can happen in-between..
461+
code_field: syn::Ident,
462+
/// Initialization logic for `code_field`'s variable, e.g.
463+
/// `let __formatted_code = /* whatever */;`
464+
code_init: TokenStream,
438465
},
439466
/// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
440467
MultipartSuggestion {
@@ -469,7 +496,8 @@ impl SubdiagnosticKind {
469496
SubdiagnosticKind::Suggestion {
470497
suggestion_kind,
471498
applicability: None,
472-
code: TokenStream::new(),
499+
code_field: new_code_ident(),
500+
code_init: TokenStream::new(),
473501
}
474502
} else if let Some(suggestion_kind) =
475503
name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
@@ -548,9 +576,10 @@ impl SubdiagnosticKind {
548576
};
549577

550578
match (nested_name, &mut kind) {
551-
("code", SubdiagnosticKind::Suggestion { .. }) => {
579+
("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
552580
let formatted_str = fields.build_format(&value.value(), value.span());
553-
code.set_once(formatted_str, span);
581+
let code_init = quote! { let #code_field = #formatted_str; };
582+
code.set_once(code_init, span);
554583
}
555584
(
556585
"applicability",
@@ -582,13 +611,13 @@ impl SubdiagnosticKind {
582611
}
583612

584613
match kind {
585-
SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
586-
*code_field = if let Some((code, _)) = code {
587-
code
614+
SubdiagnosticKind::Suggestion { ref code_field, ref mut code_init, .. } => {
615+
*code_init = if let Some(init) = code.value() {
616+
init
588617
} else {
589618
span_err(span, "suggestion without `code = \"...\"`").emit();
590-
quote! { "" }
591-
}
619+
quote! { let #code_field: String = unreachable!(); }
620+
};
592621
}
593622
SubdiagnosticKind::Label
594623
| SubdiagnosticKind::Note
@@ -620,65 +649,8 @@ impl quote::IdentFragment for SubdiagnosticKind {
620649
}
621650
}
622651

623-
/// Wrapper around `synstructure::BindStyle` which implements `Ord`.
624-
#[derive(PartialEq, Eq)]
625-
pub(super) struct OrderedBindStyle(pub(super) BindStyle);
626-
627-
impl OrderedBindStyle {
628-
/// Is `BindStyle::Move` or `BindStyle::MoveMut`?
629-
pub(super) fn is_move(&self) -> bool {
630-
matches!(self.0, BindStyle::Move | BindStyle::MoveMut)
631-
}
632-
}
633-
634-
impl Ord for OrderedBindStyle {
635-
fn cmp(&self, other: &Self) -> Ordering {
636-
match (self.is_move(), other.is_move()) {
637-
// If both `self` and `other` are the same, then ordering is equal.
638-
(true, true) | (false, false) => Ordering::Equal,
639-
// If `self` is not a move then it should be considered less than `other` (so that
640-
// references are sorted first).
641-
(false, _) => Ordering::Less,
642-
// If `self` is a move then it must be greater than `other` (again, so that references
643-
// are sorted first).
644-
(true, _) => Ordering::Greater,
645-
}
646-
}
647-
}
648-
649-
impl PartialOrd for OrderedBindStyle {
650-
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
651-
Some(self.cmp(other))
652-
}
653-
}
654-
655652
/// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
656653
/// call (like `span_label`).
657654
pub(super) fn should_generate_set_arg(field: &Field) -> bool {
658655
field.attrs.is_empty()
659656
}
660-
661-
/// Returns `true` if `field` needs to have code generated in the by-move branch of the
662-
/// generated derive rather than the by-ref branch.
663-
pub(super) fn bind_style_of_field(field: &Field) -> OrderedBindStyle {
664-
let generates_set_arg = should_generate_set_arg(field);
665-
let is_multispan = type_matches_path(&field.ty, &["rustc_errors", "MultiSpan"]);
666-
// FIXME(davidtwco): better support for one field needing to be in the by-move and
667-
// by-ref branches.
668-
let is_subdiagnostic = field
669-
.attrs
670-
.iter()
671-
.map(|attr| attr.path.segments.last().unwrap().ident.to_string())
672-
.any(|attr| attr == "subdiagnostic");
673-
674-
// `set_arg` calls take their argument by-move..
675-
let needs_move = generates_set_arg
676-
// If this is a `MultiSpan` field then it needs to be moved to be used by any
677-
// attribute..
678-
|| is_multispan
679-
// If this a `#[subdiagnostic]` then it needs to be moved as the other diagnostic is
680-
// unlikely to be `Copy`..
681-
|| is_subdiagnostic;
682-
683-
OrderedBindStyle(if needs_move { BindStyle::Move } else { BindStyle::Ref })
684-
}

‎compiler/rustc_query_system/src/error.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
1-
use rustc_errors::AddToDiagnostic;
21
use rustc_macros::{Diagnostic, Subdiagnostic};
32
use rustc_session::Limit;
43
use rustc_span::{Span, Symbol};
54

5+
#[derive(Subdiagnostic)]
6+
#[note(query_system::cycle_stack_middle)]
67
pub struct CycleStack {
8+
#[primary_span]
79
pub span: Span,
810
pub desc: String,
911
}
1012

11-
impl AddToDiagnostic for CycleStack {
12-
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
13-
diag.span_note(self.span, &format!("...which requires {}...", self.desc));
14-
}
15-
}
16-
1713
#[derive(Copy, Clone)]
1814
pub enum HandleCycleError {
1915
Error,
@@ -53,7 +49,7 @@ pub struct Cycle {
5349
#[primary_span]
5450
pub span: Span,
5551
pub stack_bottom: String,
56-
#[subdiagnostic]
52+
#[subdiagnostic(eager)]
5753
pub cycle_stack: Vec<CycleStack>,
5854
#[subdiagnostic]
5955
pub stack_count: StackCount,

‎compiler/rustc_query_system/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#![feature(min_specialization)]
55
#![feature(extern_types)]
66
#![allow(rustc::potential_query_instability)]
7-
// #![deny(rustc::untranslatable_diagnostic)]
7+
#![deny(rustc::untranslatable_diagnostic)]
88
#![deny(rustc::diagnostic_outside_of_impl)]
99

1010
#[macro_use]

‎src/librustdoc/passes/check_code_block_syntax.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! Validates syntax inside Rust code blocks (\`\`\`rust).
22
use rustc_data_structures::sync::{Lock, Lrc};
33
use rustc_errors::{
4-
emitter::Emitter, translation::Translate, Applicability, Diagnostic, Handler,
5-
LazyFallbackBundle,
4+
emitter::Emitter,
5+
translation::{to_fluent_args, Translate},
6+
Applicability, Diagnostic, Handler, LazyFallbackBundle,
67
};
78
use rustc_parse::parse_stream_from_source_str;
89
use rustc_session::parse::ParseSess;
@@ -193,7 +194,7 @@ impl Emitter for BufferEmitter {
193194
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
194195
let mut buffer = self.buffer.borrow_mut();
195196

196-
let fluent_args = self.to_fluent_args(diag.args());
197+
let fluent_args = to_fluent_args(diag.args());
197198
let translated_main_message = self.translate_message(&diag.message[0].0, &fluent_args);
198199

199200
buffer.messages.push(format!("error from rustc: {}", translated_main_message));

‎src/test/ui-fulldeps/internal-lints/diagnostics.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ extern crate rustc_span;
1313

1414
use rustc_errors::{
1515
AddToDiagnostic, IntoDiagnostic, Diagnostic, DiagnosticBuilder,
16-
ErrorGuaranteed, Handler, fluent
16+
ErrorGuaranteed, Handler, fluent, SubdiagnosticMessage,
1717
};
1818
use rustc_macros::{Diagnostic, Subdiagnostic};
1919
use rustc_span::Span;
@@ -52,7 +52,10 @@ impl<'a> IntoDiagnostic<'a, ErrorGuaranteed> for TranslatableInIntoDiagnostic {
5252
pub struct UntranslatableInAddToDiagnostic;
5353

5454
impl AddToDiagnostic for UntranslatableInAddToDiagnostic {
55-
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
55+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
56+
where
57+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
58+
{
5659
diag.note("untranslatable diagnostic");
5760
//~^ ERROR diagnostics should be created using translatable messages
5861
}
@@ -61,7 +64,10 @@ impl AddToDiagnostic for UntranslatableInAddToDiagnostic {
6164
pub struct TranslatableInAddToDiagnostic;
6265

6366
impl AddToDiagnostic for TranslatableInAddToDiagnostic {
64-
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
67+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
68+
where
69+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
70+
{
6571
diag.note(fluent::compiletest::note);
6672
}
6773
}

‎src/test/ui-fulldeps/internal-lints/diagnostics.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ LL | #![deny(rustc::untranslatable_diagnostic)]
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1212

1313
error: diagnostics should be created using translatable messages
14-
--> $DIR/diagnostics.rs:56:14
14+
--> $DIR/diagnostics.rs:59:14
1515
|
1616
LL | diag.note("untranslatable diagnostic");
1717
| ^^^^
1818

1919
error: diagnostics should only be created in `IntoDiagnostic`/`AddToDiagnostic` impls
20-
--> $DIR/diagnostics.rs:70:25
20+
--> $DIR/diagnostics.rs:76:25
2121
|
2222
LL | let _diag = handler.struct_err(fluent::compiletest::example);
2323
| ^^^^^^^^^^
@@ -29,13 +29,13 @@ LL | #![deny(rustc::diagnostic_outside_of_impl)]
2929
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3030

3131
error: diagnostics should only be created in `IntoDiagnostic`/`AddToDiagnostic` impls
32-
--> $DIR/diagnostics.rs:73:25
32+
--> $DIR/diagnostics.rs:79:25
3333
|
3434
LL | let _diag = handler.struct_err("untranslatable diagnostic");
3535
| ^^^^^^^^^^
3636

3737
error: diagnostics should be created using translatable messages
38-
--> $DIR/diagnostics.rs:73:25
38+
--> $DIR/diagnostics.rs:79:25
3939
|
4040
LL | let _diag = handler.struct_err("untranslatable diagnostic");
4141
| ^^^^^^^^^^

‎src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,3 +678,74 @@ enum ExampleEnum {
678678
struct RawIdentDiagnosticArg {
679679
pub r#type: String,
680680
}
681+
682+
#[derive(Diagnostic)]
683+
#[diag(compiletest::example)]
684+
struct SubdiagnosticBad {
685+
#[subdiagnostic(bad)]
686+
//~^ ERROR `#[subdiagnostic(bad)]` is not a valid attribute
687+
note: Note,
688+
}
689+
690+
#[derive(Diagnostic)]
691+
#[diag(compiletest::example)]
692+
struct SubdiagnosticBadStr {
693+
#[subdiagnostic = "bad"]
694+
//~^ ERROR `#[subdiagnostic = ...]` is not a valid attribute
695+
note: Note,
696+
}
697+
698+
#[derive(Diagnostic)]
699+
#[diag(compiletest::example)]
700+
struct SubdiagnosticBadTwice {
701+
#[subdiagnostic(bad, bad)]
702+
//~^ ERROR `#[subdiagnostic(...)]` is not a valid attribute
703+
note: Note,
704+
}
705+
706+
#[derive(Diagnostic)]
707+
#[diag(compiletest::example)]
708+
struct SubdiagnosticBadLitStr {
709+
#[subdiagnostic("bad")]
710+
//~^ ERROR `#[subdiagnostic("...")]` is not a valid attribute
711+
note: Note,
712+
}
713+
714+
#[derive(LintDiagnostic)]
715+
#[diag(compiletest::example)]
716+
struct SubdiagnosticEagerLint {
717+
#[subdiagnostic(eager)]
718+
//~^ ERROR `#[subdiagnostic(...)]` is not a valid attribute
719+
note: Note,
720+
}
721+
722+
#[derive(Diagnostic)]
723+
#[diag(compiletest::example)]
724+
struct SubdiagnosticEagerCorrect {
725+
#[subdiagnostic(eager)]
726+
note: Note,
727+
}
728+
729+
// Check that formatting of `correct` in suggestion doesn't move the binding for that field, making
730+
// the `set_arg` call a compile error; and that isn't worked around by moving the `set_arg` call
731+
// after the `span_suggestion` call - which breaks eager translation.
732+
733+
#[derive(Subdiagnostic)]
734+
#[suggestion_short(
735+
parser::use_instead,
736+
applicability = "machine-applicable",
737+
code = "{correct}"
738+
)]
739+
pub(crate) struct SubdiagnosticWithSuggestion {
740+
#[primary_span]
741+
span: Span,
742+
invalid: String,
743+
correct: String,
744+
}
745+
746+
#[derive(Diagnostic)]
747+
#[diag(compiletest::example)]
748+
struct SubdiagnosticEagerSuggestion {
749+
#[subdiagnostic(eager)]
750+
sub: SubdiagnosticWithSuggestion,
751+
}

‎src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,46 @@ LL | #[label]
533533
|
534534
= help: `#[label]` and `#[suggestion]` can only be applied to fields
535535

536+
error: `#[subdiagnostic(bad)]` is not a valid attribute
537+
--> $DIR/diagnostic-derive.rs:685:21
538+
|
539+
LL | #[subdiagnostic(bad)]
540+
| ^^^
541+
|
542+
= help: `eager` is the only supported nested attribute for `subdiagnostic`
543+
544+
error: `#[subdiagnostic = ...]` is not a valid attribute
545+
--> $DIR/diagnostic-derive.rs:693:5
546+
|
547+
LL | #[subdiagnostic = "bad"]
548+
| ^^^^^^^^^^^^^^^^^^^^^^^^
549+
|
550+
= help: `eager` is the only supported nested attribute for `subdiagnostic`
551+
552+
error: `#[subdiagnostic(...)]` is not a valid attribute
553+
--> $DIR/diagnostic-derive.rs:701:5
554+
|
555+
LL | #[subdiagnostic(bad, bad)]
556+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
557+
|
558+
= help: `eager` is the only supported nested attribute for `subdiagnostic`
559+
560+
error: `#[subdiagnostic("...")]` is not a valid attribute
561+
--> $DIR/diagnostic-derive.rs:709:21
562+
|
563+
LL | #[subdiagnostic("bad")]
564+
| ^^^^^
565+
|
566+
= help: `eager` is the only supported nested attribute for `subdiagnostic`
567+
568+
error: `#[subdiagnostic(...)]` is not a valid attribute
569+
--> $DIR/diagnostic-derive.rs:717:5
570+
|
571+
LL | #[subdiagnostic(eager)]
572+
| ^^^^^^^^^^^^^^^^^^^^^^^
573+
|
574+
= help: eager subdiagnostics are not supported on lints
575+
536576
error: cannot find attribute `nonsense` in this scope
537577
--> $DIR/diagnostic-derive.rs:55:3
538578
|
@@ -607,7 +647,7 @@ LL | arg: impl IntoDiagnosticArg,
607647
| ^^^^^^^^^^^^^^^^^ required by this bound in `DiagnosticBuilder::<'a, G>::set_arg`
608648
= note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
609649

610-
error: aborting due to 75 previous errors
650+
error: aborting due to 80 previous errors
611651

612652
Some errors have detailed explanations: E0277, E0425.
613653
For more information about an error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)
Please sign in to comment.