Skip to content

Commit 7e20929

Browse files
committed
macros: separate suggestion fmt'ing and emission
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: ```rust 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. ```rust 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(); ``` Signed-off-by: David Wood <[email protected]>
1 parent 113e943 commit 7e20929

File tree

4 files changed

+105
-23
lines changed

4 files changed

+105
-23
lines changed

compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,8 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
426426
SubdiagnosticKind::Suggestion {
427427
suggestion_kind,
428428
applicability: static_applicability,
429-
code,
429+
code_field,
430+
code_init,
430431
} => {
431432
let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
432433

@@ -440,10 +441,11 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
440441
let style = suggestion_kind.to_suggestion_style();
441442

442443
Ok(quote! {
444+
#code_init
443445
#diag.span_suggestion_with_style(
444446
#span_field,
445447
rustc_errors::fluent::#slug,
446-
#code,
448+
#code_field,
447449
#applicability,
448450
#style
449451
);

compiler/rustc_macros/src/diagnostics/subdiagnostic.rs

+38-12
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
};
@@ -57,6 +57,7 @@ impl SubdiagnosticDeriveBuilder {
5757
parent: &self,
5858
variant,
5959
span,
60+
formatting_init: TokenStream::new(),
6061
fields: build_field_mapping(variant),
6162
span_field: None,
6263
applicability: None,
@@ -105,6 +106,9 @@ struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
105106
/// Span for the entire type.
106107
span: proc_macro::Span,
107108

109+
/// Initialization of format strings for code suggestions.
110+
formatting_init: TokenStream,
111+
108112
/// Store a map of field name to its corresponding field. This is built on construction of the
109113
/// derive builder.
110114
fields: FieldMap,
@@ -230,7 +234,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
230234
};
231235

232236
let generated = self
233-
.generate_field_code_inner(kind_stats, attr, info)
237+
.generate_field_code_inner(kind_stats, attr, info, inner_ty.will_iterate())
234238
.unwrap_or_else(|v| v.to_compile_error());
235239

236240
inner_ty.with(binding, generated)
@@ -243,13 +247,18 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
243247
kind_stats: KindsStatistics,
244248
attr: &Attribute,
245249
info: FieldInfo<'_>,
250+
clone_suggestion_code: bool,
246251
) -> Result<TokenStream, DiagnosticDeriveError> {
247252
let meta = attr.parse_meta()?;
248253
match meta {
249254
Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path),
250-
Meta::List(list @ MetaList { .. }) => {
251-
self.generate_field_code_inner_list(kind_stats, attr, info, list)
252-
}
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+
),
253262
_ => throw_invalid_attr!(attr, &meta),
254263
}
255264
}
@@ -353,6 +362,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
353362
attr: &Attribute,
354363
info: FieldInfo<'_>,
355364
list: MetaList,
365+
clone_suggestion_code: bool,
356366
) -> Result<TokenStream, DiagnosticDeriveError> {
357367
let span = attr.span().unwrap();
358368
let ident = &list.path.segments.last().unwrap().ident;
@@ -390,22 +400,29 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
390400
match nested_name {
391401
"code" => {
392402
let formatted_str = self.build_format(&value.value(), value.span());
393-
code.set_once(formatted_str, span);
403+
let code_field = new_code_ident();
404+
code.set_once((code_field, formatted_str), span);
394405
}
395406
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
396407
diag.help("`code` is the only valid nested attribute")
397408
}),
398409
}
399410
}
400411

401-
let Some((code, _)) = code else {
412+
let Some((code_field, formatted_str)) = code.value() else {
402413
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
403414
.emit();
404415
return Ok(quote! {});
405416
};
406417
let binding = info.binding;
407418

408-
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)); })
409426
}
410427
_ => throw_invalid_attr!(attr, &Meta::List(list), |diag| {
411428
let mut span_attrs = vec![];
@@ -459,7 +476,14 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
459476

460477
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
461478
let call = match kind {
462-
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+
463487
let applicability = applicability
464488
.value()
465489
.map(|a| quote! { #a })
@@ -468,8 +492,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
468492

469493
if let Some(span) = span_field {
470494
let style = suggestion_kind.to_suggestion_style();
471-
472-
quote! { #diag.#name(#span, #message, #code, #applicability, #style); }
495+
quote! { #diag.#name(#span, #message, #code_field, #applicability, #style); }
473496
} else {
474497
span_err(self.span, "suggestion without `#[primary_span]` field").emit();
475498
quote! { unreachable!(); }
@@ -510,6 +533,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
510533
}
511534
}
512535
};
536+
513537
calls.extend(call);
514538
}
515539

@@ -521,11 +545,13 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
521545
.map(|binding| self.generate_field_set_arg(binding))
522546
.collect();
523547

548+
let formatting_init = &self.formatting_init;
524549
Ok(quote! {
525550
#init
551+
#formatting_init
526552
#attr_args
527-
#calls
528553
#plain_args
554+
#calls
529555
})
530556
}
531557
}

compiler/rustc_macros/src/diagnostics/utils.rs

+39-9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::diagnostics::error::{
44
use proc_macro::Span;
55
use proc_macro2::TokenStream;
66
use quote::{format_ident, quote, ToTokens};
7+
use std::cell::RefCell;
78
use std::cmp::Ordering;
89
use std::collections::{BTreeSet, HashMap};
910
use std::fmt;
@@ -14,6 +15,19 @@ use synstructure::{BindStyle, BindingInfo, VariantInfo};
1415

1516
use super::error::invalid_nested_attr;
1617

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

159+
/// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e.
160+
/// that cloning might be required for values moved in the loop body).
161+
pub(crate) fn will_iterate(&self) -> bool {
162+
match self {
163+
FieldInnerTy::Vec(..) => true,
164+
FieldInnerTy::Option(..) | FieldInnerTy::None => false,
165+
}
166+
}
167+
145168
/// Returns `Option` containing inner type if there is one.
146169
pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
147170
match self {
@@ -434,7 +457,12 @@ pub(super) enum SubdiagnosticKind {
434457
Suggestion {
435458
suggestion_kind: SuggestionKind,
436459
applicability: SpannedOption<Applicability>,
437-
code: TokenStream,
460+
/// Identifier for variable used for formatted code, e.g. `___code_0`. Enables separation
461+
/// of formatting and diagnostic emission so that `set_arg` calls can happen in-between..
462+
code_field: syn::Ident,
463+
/// Initialization logic for `code_field`'s variable, e.g.
464+
/// `let __formatted_code = /* whatever */;`
465+
code_init: TokenStream,
438466
},
439467
/// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
440468
MultipartSuggestion {
@@ -469,7 +497,8 @@ impl SubdiagnosticKind {
469497
SubdiagnosticKind::Suggestion {
470498
suggestion_kind,
471499
applicability: None,
472-
code: TokenStream::new(),
500+
code_field: new_code_ident(),
501+
code_init: TokenStream::new(),
473502
}
474503
} else if let Some(suggestion_kind) =
475504
name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
@@ -548,9 +577,10 @@ impl SubdiagnosticKind {
548577
};
549578

550579
match (nested_name, &mut kind) {
551-
("code", SubdiagnosticKind::Suggestion { .. }) => {
580+
("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
552581
let formatted_str = fields.build_format(&value.value(), value.span());
553-
code.set_once(formatted_str, span);
582+
let code_init = quote! { let #code_field = #formatted_str; };
583+
code.set_once(code_init, span);
554584
}
555585
(
556586
"applicability",
@@ -582,13 +612,13 @@ impl SubdiagnosticKind {
582612
}
583613

584614
match kind {
585-
SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
586-
*code_field = if let Some((code, _)) = code {
587-
code
615+
SubdiagnosticKind::Suggestion { ref code_field, ref mut code_init, .. } => {
616+
*code_init = if let Some(init) = code.value() {
617+
init
588618
} else {
589619
span_err(span, "suggestion without `code = \"...\"`").emit();
590-
quote! { "" }
591-
}
620+
quote! { let #code_field: String = unreachable!(); }
621+
};
592622
}
593623
SubdiagnosticKind::Label
594624
| SubdiagnosticKind::Note

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

+24
Original file line numberDiff line numberDiff line change
@@ -725,3 +725,27 @@ struct SubdiagnosticEagerCorrect {
725725
#[subdiagnostic(eager)]
726726
note: Note,
727727
}
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+
}

0 commit comments

Comments
 (0)