mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 06:44:35 +00:00
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 <david.wood@huawei.com>
This commit is contained in:
parent
113e94369c
commit
7e20929e55
@ -426,7 +426,8 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
|
|||||||
SubdiagnosticKind::Suggestion {
|
SubdiagnosticKind::Suggestion {
|
||||||
suggestion_kind,
|
suggestion_kind,
|
||||||
applicability: static_applicability,
|
applicability: static_applicability,
|
||||||
code,
|
code_field,
|
||||||
|
code_init,
|
||||||
} => {
|
} => {
|
||||||
let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
|
let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
|
||||||
|
|
||||||
@ -440,10 +441,11 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
|
|||||||
let style = suggestion_kind.to_suggestion_style();
|
let style = suggestion_kind.to_suggestion_style();
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
|
#code_init
|
||||||
#diag.span_suggestion_with_style(
|
#diag.span_suggestion_with_style(
|
||||||
#span_field,
|
#span_field,
|
||||||
rustc_errors::fluent::#slug,
|
rustc_errors::fluent::#slug,
|
||||||
#code,
|
#code_field,
|
||||||
#applicability,
|
#applicability,
|
||||||
#style
|
#style
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ use crate::diagnostics::error::{
|
|||||||
DiagnosticDeriveError,
|
DiagnosticDeriveError,
|
||||||
};
|
};
|
||||||
use crate::diagnostics::utils::{
|
use crate::diagnostics::utils::{
|
||||||
build_field_mapping, report_error_if_not_applied_to_applicability,
|
build_field_mapping, new_code_ident, report_error_if_not_applied_to_applicability,
|
||||||
report_error_if_not_applied_to_span, FieldInfo, FieldInnerTy, FieldMap, HasFieldMap, SetOnce,
|
report_error_if_not_applied_to_span, FieldInfo, FieldInnerTy, FieldMap, HasFieldMap, SetOnce,
|
||||||
SpannedOption, SubdiagnosticKind,
|
SpannedOption, SubdiagnosticKind,
|
||||||
};
|
};
|
||||||
@ -57,6 +57,7 @@ impl SubdiagnosticDeriveBuilder {
|
|||||||
parent: &self,
|
parent: &self,
|
||||||
variant,
|
variant,
|
||||||
span,
|
span,
|
||||||
|
formatting_init: TokenStream::new(),
|
||||||
fields: build_field_mapping(variant),
|
fields: build_field_mapping(variant),
|
||||||
span_field: None,
|
span_field: None,
|
||||||
applicability: None,
|
applicability: None,
|
||||||
@ -105,6 +106,9 @@ struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
|||||||
/// Span for the entire type.
|
/// Span for the entire type.
|
||||||
span: proc_macro::Span,
|
span: proc_macro::Span,
|
||||||
|
|
||||||
|
/// Initialization of format strings for code suggestions.
|
||||||
|
formatting_init: TokenStream,
|
||||||
|
|
||||||
/// Store a map of field name to its corresponding field. This is built on construction of the
|
/// Store a map of field name to its corresponding field. This is built on construction of the
|
||||||
/// derive builder.
|
/// derive builder.
|
||||||
fields: FieldMap,
|
fields: FieldMap,
|
||||||
@ -230,7 +234,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let generated = self
|
let generated = self
|
||||||
.generate_field_code_inner(kind_stats, attr, info)
|
.generate_field_code_inner(kind_stats, attr, info, inner_ty.will_iterate())
|
||||||
.unwrap_or_else(|v| v.to_compile_error());
|
.unwrap_or_else(|v| v.to_compile_error());
|
||||||
|
|
||||||
inner_ty.with(binding, generated)
|
inner_ty.with(binding, generated)
|
||||||
@ -243,13 +247,18 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
|||||||
kind_stats: KindsStatistics,
|
kind_stats: KindsStatistics,
|
||||||
attr: &Attribute,
|
attr: &Attribute,
|
||||||
info: FieldInfo<'_>,
|
info: FieldInfo<'_>,
|
||||||
|
clone_suggestion_code: bool,
|
||||||
) -> Result<TokenStream, DiagnosticDeriveError> {
|
) -> Result<TokenStream, DiagnosticDeriveError> {
|
||||||
let meta = attr.parse_meta()?;
|
let meta = attr.parse_meta()?;
|
||||||
match meta {
|
match meta {
|
||||||
Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path),
|
Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path),
|
||||||
Meta::List(list @ MetaList { .. }) => {
|
Meta::List(list @ MetaList { .. }) => self.generate_field_code_inner_list(
|
||||||
self.generate_field_code_inner_list(kind_stats, attr, info, list)
|
kind_stats,
|
||||||
}
|
attr,
|
||||||
|
info,
|
||||||
|
list,
|
||||||
|
clone_suggestion_code,
|
||||||
|
),
|
||||||
_ => throw_invalid_attr!(attr, &meta),
|
_ => throw_invalid_attr!(attr, &meta),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,6 +362,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
|||||||
attr: &Attribute,
|
attr: &Attribute,
|
||||||
info: FieldInfo<'_>,
|
info: FieldInfo<'_>,
|
||||||
list: MetaList,
|
list: MetaList,
|
||||||
|
clone_suggestion_code: bool,
|
||||||
) -> Result<TokenStream, DiagnosticDeriveError> {
|
) -> Result<TokenStream, DiagnosticDeriveError> {
|
||||||
let span = attr.span().unwrap();
|
let span = attr.span().unwrap();
|
||||||
let ident = &list.path.segments.last().unwrap().ident;
|
let ident = &list.path.segments.last().unwrap().ident;
|
||||||
@ -390,7 +400,8 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
|||||||
match nested_name {
|
match nested_name {
|
||||||
"code" => {
|
"code" => {
|
||||||
let formatted_str = self.build_format(&value.value(), value.span());
|
let formatted_str = self.build_format(&value.value(), value.span());
|
||||||
code.set_once(formatted_str, span);
|
let code_field = new_code_ident();
|
||||||
|
code.set_once((code_field, formatted_str), span);
|
||||||
}
|
}
|
||||||
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
|
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
|
||||||
diag.help("`code` is the only valid nested attribute")
|
diag.help("`code` is the only valid nested attribute")
|
||||||
@ -398,14 +409,20 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some((code, _)) = code else {
|
let Some((code_field, formatted_str)) = code.value() else {
|
||||||
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
|
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
|
||||||
.emit();
|
.emit();
|
||||||
return Ok(quote! {});
|
return Ok(quote! {});
|
||||||
};
|
};
|
||||||
let binding = info.binding;
|
let binding = info.binding;
|
||||||
|
|
||||||
Ok(quote! { suggestions.push((#binding, #code)); })
|
self.formatting_init.extend(quote! { let #code_field = #formatted_str; });
|
||||||
|
let code_field = if clone_suggestion_code {
|
||||||
|
quote! { #code_field.clone() }
|
||||||
|
} else {
|
||||||
|
quote! { #code_field }
|
||||||
|
};
|
||||||
|
Ok(quote! { suggestions.push((#binding, #code_field)); })
|
||||||
}
|
}
|
||||||
_ => throw_invalid_attr!(attr, &Meta::List(list), |diag| {
|
_ => throw_invalid_attr!(attr, &Meta::List(list), |diag| {
|
||||||
let mut span_attrs = vec![];
|
let mut span_attrs = vec![];
|
||||||
@ -459,7 +476,14 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
|||||||
|
|
||||||
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
|
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
|
||||||
let call = match kind {
|
let call = match kind {
|
||||||
SubdiagnosticKind::Suggestion { suggestion_kind, applicability, code } => {
|
SubdiagnosticKind::Suggestion {
|
||||||
|
suggestion_kind,
|
||||||
|
applicability,
|
||||||
|
code_init,
|
||||||
|
code_field,
|
||||||
|
} => {
|
||||||
|
self.formatting_init.extend(code_init);
|
||||||
|
|
||||||
let applicability = applicability
|
let applicability = applicability
|
||||||
.value()
|
.value()
|
||||||
.map(|a| quote! { #a })
|
.map(|a| quote! { #a })
|
||||||
@ -468,8 +492,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
|||||||
|
|
||||||
if let Some(span) = span_field {
|
if let Some(span) = span_field {
|
||||||
let style = suggestion_kind.to_suggestion_style();
|
let style = suggestion_kind.to_suggestion_style();
|
||||||
|
quote! { #diag.#name(#span, #message, #code_field, #applicability, #style); }
|
||||||
quote! { #diag.#name(#span, #message, #code, #applicability, #style); }
|
|
||||||
} else {
|
} else {
|
||||||
span_err(self.span, "suggestion without `#[primary_span]` field").emit();
|
span_err(self.span, "suggestion without `#[primary_span]` field").emit();
|
||||||
quote! { unreachable!(); }
|
quote! { unreachable!(); }
|
||||||
@ -510,6 +533,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
calls.extend(call);
|
calls.extend(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -521,11 +545,13 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
|||||||
.map(|binding| self.generate_field_set_arg(binding))
|
.map(|binding| self.generate_field_set_arg(binding))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let formatting_init = &self.formatting_init;
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#init
|
#init
|
||||||
|
#formatting_init
|
||||||
#attr_args
|
#attr_args
|
||||||
#calls
|
|
||||||
#plain_args
|
#plain_args
|
||||||
|
#calls
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use crate::diagnostics::error::{
|
|||||||
use proc_macro::Span;
|
use proc_macro::Span;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::{BTreeSet, HashMap};
|
use std::collections::{BTreeSet, HashMap};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@ -14,6 +15,19 @@ use synstructure::{BindStyle, BindingInfo, VariantInfo};
|
|||||||
|
|
||||||
use super::error::invalid_nested_attr;
|
use super::error::invalid_nested_attr;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an ident of the form `__code_N` where `N` is incremented once with every call.
|
||||||
|
pub(crate) fn new_code_ident() -> syn::Ident {
|
||||||
|
CODE_IDENT_COUNT.with(|count| {
|
||||||
|
let ident = format_ident!("__code_{}", *count.borrow());
|
||||||
|
*count.borrow_mut() += 1;
|
||||||
|
ident
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks whether the type name of `ty` matches `name`.
|
/// Checks whether the type name of `ty` matches `name`.
|
||||||
///
|
///
|
||||||
/// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
|
/// 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> {
|
|||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e.
|
||||||
|
/// that cloning might be required for values moved in the loop body).
|
||||||
|
pub(crate) fn will_iterate(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
FieldInnerTy::Vec(..) => true,
|
||||||
|
FieldInnerTy::Option(..) | FieldInnerTy::None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `Option` containing inner type if there is one.
|
/// Returns `Option` containing inner type if there is one.
|
||||||
pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
|
pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
|
||||||
match self {
|
match self {
|
||||||
@ -434,7 +457,12 @@ pub(super) enum SubdiagnosticKind {
|
|||||||
Suggestion {
|
Suggestion {
|
||||||
suggestion_kind: SuggestionKind,
|
suggestion_kind: SuggestionKind,
|
||||||
applicability: SpannedOption<Applicability>,
|
applicability: SpannedOption<Applicability>,
|
||||||
code: TokenStream,
|
/// Identifier for variable used for formatted code, e.g. `___code_0`. Enables separation
|
||||||
|
/// of formatting and diagnostic emission so that `set_arg` calls can happen in-between..
|
||||||
|
code_field: syn::Ident,
|
||||||
|
/// Initialization logic for `code_field`'s variable, e.g.
|
||||||
|
/// `let __formatted_code = /* whatever */;`
|
||||||
|
code_init: TokenStream,
|
||||||
},
|
},
|
||||||
/// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
|
/// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
|
||||||
MultipartSuggestion {
|
MultipartSuggestion {
|
||||||
@ -469,7 +497,8 @@ impl SubdiagnosticKind {
|
|||||||
SubdiagnosticKind::Suggestion {
|
SubdiagnosticKind::Suggestion {
|
||||||
suggestion_kind,
|
suggestion_kind,
|
||||||
applicability: None,
|
applicability: None,
|
||||||
code: TokenStream::new(),
|
code_field: new_code_ident(),
|
||||||
|
code_init: TokenStream::new(),
|
||||||
}
|
}
|
||||||
} else if let Some(suggestion_kind) =
|
} else if let Some(suggestion_kind) =
|
||||||
name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
|
name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
|
||||||
@ -548,9 +577,10 @@ impl SubdiagnosticKind {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match (nested_name, &mut kind) {
|
match (nested_name, &mut kind) {
|
||||||
("code", SubdiagnosticKind::Suggestion { .. }) => {
|
("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
|
||||||
let formatted_str = fields.build_format(&value.value(), value.span());
|
let formatted_str = fields.build_format(&value.value(), value.span());
|
||||||
code.set_once(formatted_str, span);
|
let code_init = quote! { let #code_field = #formatted_str; };
|
||||||
|
code.set_once(code_init, span);
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
"applicability",
|
"applicability",
|
||||||
@ -582,13 +612,13 @@ impl SubdiagnosticKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
|
SubdiagnosticKind::Suggestion { ref code_field, ref mut code_init, .. } => {
|
||||||
*code_field = if let Some((code, _)) = code {
|
*code_init = if let Some(init) = code.value() {
|
||||||
code
|
init
|
||||||
} else {
|
} else {
|
||||||
span_err(span, "suggestion without `code = \"...\"`").emit();
|
span_err(span, "suggestion without `code = \"...\"`").emit();
|
||||||
quote! { "" }
|
quote! { let #code_field: String = unreachable!(); }
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
SubdiagnosticKind::Label
|
SubdiagnosticKind::Label
|
||||||
| SubdiagnosticKind::Note
|
| SubdiagnosticKind::Note
|
||||||
|
@ -725,3 +725,27 @@ struct SubdiagnosticEagerCorrect {
|
|||||||
#[subdiagnostic(eager)]
|
#[subdiagnostic(eager)]
|
||||||
note: Note,
|
note: Note,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that formatting of `correct` in suggestion doesn't move the binding for that field, making
|
||||||
|
// the `set_arg` call a compile error; and that isn't worked around by moving the `set_arg` call
|
||||||
|
// after the `span_suggestion` call - which breaks eager translation.
|
||||||
|
|
||||||
|
#[derive(Subdiagnostic)]
|
||||||
|
#[suggestion_short(
|
||||||
|
parser::use_instead,
|
||||||
|
applicability = "machine-applicable",
|
||||||
|
code = "{correct}"
|
||||||
|
)]
|
||||||
|
pub(crate) struct SubdiagnosticWithSuggestion {
|
||||||
|
#[primary_span]
|
||||||
|
span: Span,
|
||||||
|
invalid: String,
|
||||||
|
correct: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Diagnostic)]
|
||||||
|
#[diag(compiletest::example)]
|
||||||
|
struct SubdiagnosticEagerSuggestion {
|
||||||
|
#[subdiagnostic(eager)]
|
||||||
|
sub: SubdiagnosticWithSuggestion,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user