summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_macros/src/diagnostics
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_macros/src/diagnostics')
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs197
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs743
-rw-r--r--compiler/rustc_macros/src/diagnostics/fluent.rs65
-rw-r--r--compiler/rustc_macros/src/diagnostics/mod.rs36
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs492
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs421
6 files changed, 1057 insertions, 897 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
index cf1c59455..ef1985b96 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
@@ -2,94 +2,77 @@
use crate::diagnostics::diagnostic_builder::{DiagnosticDeriveBuilder, DiagnosticDeriveKind};
use crate::diagnostics::error::{span_err, DiagnosticDeriveError};
-use crate::diagnostics::utils::{build_field_mapping, SetOnce};
+use crate::diagnostics::utils::SetOnce;
use proc_macro2::TokenStream;
use quote::quote;
-use syn::spanned::Spanned;
use synstructure::Structure;
/// The central struct for constructing the `into_diagnostic` method from an annotated struct.
-pub(crate) struct SessionDiagnosticDerive<'a> {
+pub(crate) struct DiagnosticDerive<'a> {
structure: Structure<'a>,
- sess: syn::Ident,
builder: DiagnosticDeriveBuilder,
}
-impl<'a> SessionDiagnosticDerive<'a> {
- pub(crate) fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self {
+impl<'a> DiagnosticDerive<'a> {
+ pub(crate) fn new(diag: syn::Ident, handler: syn::Ident, structure: Structure<'a>) -> Self {
Self {
builder: DiagnosticDeriveBuilder {
diag,
- fields: build_field_mapping(&structure),
- kind: DiagnosticDeriveKind::SessionDiagnostic,
- code: None,
- slug: None,
+ kind: DiagnosticDeriveKind::Diagnostic { handler },
},
- sess,
structure,
}
}
pub(crate) fn into_tokens(self) -> TokenStream {
- let SessionDiagnosticDerive { mut structure, sess, mut builder } = self;
-
- let ast = structure.ast();
- let implementation = {
- if let syn::Data::Struct(..) = ast.data {
- let preamble = builder.preamble(&structure);
- let (attrs, args) = builder.body(&mut structure);
-
- let span = ast.span().unwrap();
- let diag = &builder.diag;
- let init = match builder.slug.value() {
- None => {
- span_err(span, "diagnostic slug not specified")
- .help(&format!(
- "specify the slug as the first argument to the `#[diag(...)]` attribute, \
- such as `#[diag(typeck::example_error)]`",
- ))
- .emit();
- return DiagnosticDeriveError::ErrorHandled.to_compile_error();
- }
- Some(slug) => {
- quote! {
- let mut #diag = #sess.struct_diagnostic(rustc_errors::fluent::#slug);
- }
- }
- };
-
- quote! {
- #init
- #preamble
- match self {
- #attrs
- }
- match self {
- #args
+ let DiagnosticDerive { mut structure, mut builder } = self;
+
+ let implementation = builder.each_variant(&mut structure, |mut builder, variant| {
+ let preamble = builder.preamble(&variant);
+ let body = builder.body(&variant);
+
+ let diag = &builder.parent.diag;
+ let DiagnosticDeriveKind::Diagnostic { handler } = &builder.parent.kind else {
+ unreachable!()
+ };
+ let init = match builder.slug.value_ref() {
+ None => {
+ span_err(builder.span, "diagnostic slug not specified")
+ .help(&format!(
+ "specify the slug as the first argument to the `#[diag(...)]` \
+ attribute, such as `#[diag(hir_analysis_example_error)]`",
+ ))
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ Some(slug) => {
+ quote! {
+ let mut #diag = #handler.struct_diagnostic(rustc_errors::fluent::#slug);
}
- #diag
}
- } else {
- span_err(
- ast.span().unwrap(),
- "`#[derive(SessionDiagnostic)]` can only be used on structs",
- )
- .emit();
-
- DiagnosticDeriveError::ErrorHandled.to_compile_error()
+ };
+
+ let formatting_init = &builder.formatting_init;
+ quote! {
+ #init
+ #formatting_init
+ #preamble
+ #body
+ #diag
}
- };
+ });
+ let DiagnosticDeriveKind::Diagnostic { handler } = &builder.kind else { unreachable!() };
structure.gen_impl(quote! {
- gen impl<'__session_diagnostic_sess, G>
- rustc_session::SessionDiagnostic<'__session_diagnostic_sess, G>
+ gen impl<'__diagnostic_handler_sess, G>
+ rustc_errors::IntoDiagnostic<'__diagnostic_handler_sess, G>
for @Self
where G: rustc_errors::EmissionGuarantee
{
fn into_diagnostic(
self,
- #sess: &'__session_diagnostic_sess rustc_errors::Handler
- ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, G> {
+ #handler: &'__diagnostic_handler_sess rustc_errors::Handler
+ ) -> rustc_errors::DiagnosticBuilder<'__diagnostic_handler_sess, G> {
use rustc_errors::IntoDiagnosticArg;
#implementation
}
@@ -107,13 +90,7 @@ pub(crate) struct LintDiagnosticDerive<'a> {
impl<'a> LintDiagnosticDerive<'a> {
pub(crate) fn new(diag: syn::Ident, structure: Structure<'a>) -> Self {
Self {
- builder: DiagnosticDeriveBuilder {
- diag,
- fields: build_field_mapping(&structure),
- kind: DiagnosticDeriveKind::LintDiagnostic,
- code: None,
- slug: None,
- },
+ builder: DiagnosticDeriveBuilder { diag, kind: DiagnosticDeriveKind::LintDiagnostic },
structure,
}
}
@@ -121,62 +98,52 @@ impl<'a> LintDiagnosticDerive<'a> {
pub(crate) fn into_tokens(self) -> TokenStream {
let LintDiagnosticDerive { mut structure, mut builder } = self;
- let ast = structure.ast();
- let implementation = {
- if let syn::Data::Struct(..) = ast.data {
- let preamble = builder.preamble(&structure);
- let (attrs, args) = builder.body(&mut structure);
-
- let diag = &builder.diag;
- let span = ast.span().unwrap();
- let init = match builder.slug.value() {
- None => {
- span_err(span, "diagnostic slug not specified")
- .help(&format!(
- "specify the slug as the first argument to the attribute, such as \
- `#[diag(typeck::example_error)]`",
- ))
- .emit();
- return DiagnosticDeriveError::ErrorHandled.to_compile_error();
- }
- Some(slug) => {
- quote! {
- let mut #diag = #diag.build(rustc_errors::fluent::#slug);
- }
- }
- };
-
- let implementation = quote! {
- #init
- #preamble
- match self {
- #attrs
- }
- match self {
- #args
- }
- #diag.emit();
- };
-
- implementation
- } else {
- span_err(
- ast.span().unwrap(),
- "`#[derive(LintDiagnostic)]` can only be used on structs",
- )
- .emit();
-
- DiagnosticDeriveError::ErrorHandled.to_compile_error()
+ let implementation = builder.each_variant(&mut structure, |mut builder, variant| {
+ let preamble = builder.preamble(&variant);
+ let body = builder.body(&variant);
+
+ let diag = &builder.parent.diag;
+ let formatting_init = &builder.formatting_init;
+ quote! {
+ #preamble
+ #formatting_init
+ #body
+ #diag
}
- };
+ });
+
+ let msg = builder.each_variant(&mut structure, |mut builder, variant| {
+ // Collect the slug by generating the preamble.
+ let _ = builder.preamble(&variant);
+
+ match builder.slug.value_ref() {
+ None => {
+ span_err(builder.span, "diagnostic slug not specified")
+ .help(&format!(
+ "specify the slug as the first argument to the attribute, such as \
+ `#[diag(compiletest_example)]`",
+ ))
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ Some(slug) => quote! { rustc_errors::fluent::#slug.into() },
+ }
+ });
let diag = &builder.diag;
structure.gen_impl(quote! {
gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
- fn decorate_lint(self, #diag: rustc_errors::LintDiagnosticBuilder<'__a, ()>) {
+ fn decorate_lint<'__b>(
+ self,
+ #diag: &'__b mut rustc_errors::DiagnosticBuilder<'__a, ()>
+ ) -> &'__b mut rustc_errors::DiagnosticBuilder<'__a, ()> {
use rustc_errors::IntoDiagnosticArg;
#implementation
}
+
+ fn msg(&self) -> rustc_errors::DiagnosticMessage {
+ #msg
+ }
}
})
}
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
index 2a4fe48a8..3ea83fd09 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -5,56 +5,124 @@ use crate::diagnostics::error::{
DiagnosticDeriveError,
};
use crate::diagnostics::utils::{
- report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path,
- Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
+ build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error,
+ should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy, FieldMap,
+ HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
-use std::collections::HashMap;
-use std::str::FromStr;
use syn::{
- parse_quote, spanned::Spanned, Attribute, Field, Meta, MetaList, MetaNameValue, NestedMeta,
- Path, Type,
+ parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
};
-use synstructure::{BindingInfo, Structure};
+use synstructure::{BindingInfo, Structure, VariantInfo};
/// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
-#[derive(Copy, Clone, PartialEq, Eq)]
+#[derive(Clone, PartialEq, Eq)]
pub(crate) enum DiagnosticDeriveKind {
- SessionDiagnostic,
+ Diagnostic { handler: syn::Ident },
LintDiagnostic,
}
-/// Tracks persistent information required for building up individual calls to diagnostic methods
-/// for generated diagnostic derives - both `SessionDiagnostic` for fatal/errors/warnings and
-/// `LintDiagnostic` for lints.
+/// Tracks persistent information required for the entire type when building up individual calls to
+/// diagnostic methods for generated diagnostic derives - both `Diagnostic` for
+/// fatal/errors/warnings and `LintDiagnostic` for lints.
pub(crate) struct DiagnosticDeriveBuilder {
/// The identifier to use for the generated `DiagnosticBuilder` instance.
pub diag: syn::Ident,
+ /// Kind of diagnostic that should be derived.
+ pub kind: DiagnosticDeriveKind,
+}
+
+/// Tracks persistent information required for a specific variant when building up individual calls
+/// to diagnostic methods for generated diagnostic derives - both `Diagnostic` for
+/// fatal/errors/warnings and `LintDiagnostic` for lints.
+pub(crate) struct DiagnosticDeriveVariantBuilder<'parent> {
+ /// The parent builder for the entire type.
+ pub parent: &'parent DiagnosticDeriveBuilder,
+
+ /// Initialization of format strings for code suggestions.
+ pub formatting_init: TokenStream,
+
+ /// Span of the struct or the enum variant.
+ pub span: proc_macro::Span,
/// Store a map of field name to its corresponding field. This is built on construction of the
/// derive builder.
- pub fields: HashMap<String, TokenStream>,
+ pub field_map: FieldMap,
- /// Kind of diagnostic that should be derived.
- pub kind: DiagnosticDeriveKind,
/// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
/// has the actual diagnostic message.
- pub slug: Option<(Path, proc_macro::Span)>,
+ pub slug: SpannedOption<Path>,
/// Error codes are a optional part of the struct attribute - this is only set to detect
/// multiple specifications.
- pub code: Option<(String, proc_macro::Span)>,
+ pub code: SpannedOption<()>,
}
-impl HasFieldMap for DiagnosticDeriveBuilder {
+impl<'a> HasFieldMap for DiagnosticDeriveVariantBuilder<'a> {
fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
- self.fields.get(field)
+ self.field_map.get(field)
}
}
impl DiagnosticDeriveBuilder {
- pub fn preamble<'s>(&mut self, structure: &Structure<'s>) -> TokenStream {
+ /// Call `f` for the struct or for each variant of the enum, returning a `TokenStream` with the
+ /// tokens from `f` wrapped in an `match` expression. Emits errors for use of derive on unions
+ /// or attributes on the type itself when input is an enum.
+ pub fn each_variant<'s, F>(&mut self, structure: &mut Structure<'s>, f: F) -> TokenStream
+ where
+ F: for<'a, 'v> Fn(DiagnosticDeriveVariantBuilder<'a>, &VariantInfo<'v>) -> TokenStream,
+ {
let ast = structure.ast();
+ let span = ast.span().unwrap();
+ match ast.data {
+ syn::Data::Struct(..) | syn::Data::Enum(..) => (),
+ syn::Data::Union(..) => {
+ span_err(span, "diagnostic derives can only be used on structs and enums");
+ }
+ }
+
+ if matches!(ast.data, syn::Data::Enum(..)) {
+ for attr in &ast.attrs {
+ span_err(
+ attr.span().unwrap(),
+ "unsupported type attribute for diagnostic derive enum",
+ )
+ .emit();
+ }
+ }
+
+ structure.bind_with(|_| synstructure::BindStyle::Move);
+ let variants = structure.each_variant(|variant| {
+ let span = match structure.ast().data {
+ syn::Data::Struct(..) => span,
+ // There isn't a good way to get the span of the variant, so the variant's
+ // name will need to do.
+ _ => variant.ast().ident.span().unwrap(),
+ };
+ let builder = DiagnosticDeriveVariantBuilder {
+ parent: &self,
+ span,
+ field_map: build_field_mapping(variant),
+ formatting_init: TokenStream::new(),
+ slug: None,
+ code: None,
+ };
+ f(builder, variant)
+ });
+
+ quote! {
+ match self {
+ #variants
+ }
+ }
+ }
+}
+
+impl<'a> DiagnosticDeriveVariantBuilder<'a> {
+ /// Generates calls to `code` and similar functions based on the attributes on the type or
+ /// variant.
+ pub fn preamble<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
+ let ast = variant.ast();
let attrs = &ast.attrs;
let preamble = attrs.iter().map(|attr| {
self.generate_structure_code_for_attr(attr).unwrap_or_else(|v| v.to_compile_error())
@@ -65,66 +133,48 @@ impl DiagnosticDeriveBuilder {
}
}
- pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, TokenStream) {
- // Keep track of which fields need to be handled with a by-move binding.
- let mut needs_moved = std::collections::HashSet::new();
-
- // Generates calls to `span_label` and similar functions based on the attributes
- // on fields. Code for suggestions uses formatting machinery and the value of
- // other fields - because any given field can be referenced multiple times, it
- // should be accessed through a borrow. When passing fields to `add_subdiagnostic`
- // or `set_arg` (which happens below) for Fluent, we want to move the data, so that
- // has to happen in a separate pass over the fields.
- let attrs = structure
- .clone()
- .filter(|field_binding| {
- let ast = &field_binding.ast();
- !self.needs_move(ast) || {
- needs_moved.insert(field_binding.binding.clone());
- false
- }
- })
- .each(|field_binding| self.generate_field_attrs_code(field_binding));
-
- structure.bind_with(|_| synstructure::BindStyle::Move);
- // When a field has attributes like `#[label]` or `#[note]` then it doesn't
- // need to be passed as an argument to the diagnostic. But when a field has no
- // attributes or a `#[subdiagnostic]` attribute then it must be passed as an
- // argument to the diagnostic so that it can be referred to by Fluent messages.
- let args = structure
- .filter(|field_binding| needs_moved.contains(&field_binding.binding))
- .each(|field_binding| self.generate_field_attrs_code(field_binding));
-
- (attrs, args)
+ /// Generates calls to `span_label` and similar functions based on the attributes on fields or
+ /// calls to `set_arg` when no attributes are present.
+ pub fn body<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
+ let mut body = quote! {};
+ // Generate `set_arg` calls first..
+ for binding in variant.bindings().iter().filter(|bi| should_generate_set_arg(bi.ast())) {
+ body.extend(self.generate_field_code(binding));
+ }
+ // ..and then subdiagnostic additions.
+ for binding in variant.bindings().iter().filter(|bi| !should_generate_set_arg(bi.ast())) {
+ body.extend(self.generate_field_attrs_code(binding));
+ }
+ body
}
- /// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
- /// call (like `span_label`).
- fn should_generate_set_arg(&self, field: &Field) -> bool {
- field.attrs.is_empty()
- }
+ /// Parse a `SubdiagnosticKind` from an `Attribute`.
+ fn parse_subdiag_attribute(
+ &self,
+ attr: &Attribute,
+ ) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
+ let Some((subdiag, slug)) = SubdiagnosticKind::from_attr(attr, self)? else {
+ // Some attributes aren't errors - like documentation comments - but also aren't
+ // subdiagnostics.
+ return Ok(None);
+ };
- /// Returns `true` if `field` needs to have code generated in the by-move branch of the
- /// generated derive rather than the by-ref branch.
- fn needs_move(&self, field: &Field) -> bool {
- let generates_set_arg = self.should_generate_set_arg(field);
- let is_multispan = type_matches_path(&field.ty, &["rustc_errors", "MultiSpan"]);
- // FIXME(davidtwco): better support for one field needing to be in the by-move and
- // by-ref branches.
- let is_subdiagnostic = field
- .attrs
- .iter()
- .map(|attr| attr.path.segments.last().unwrap().ident.to_string())
- .any(|attr| attr == "subdiagnostic");
-
- // `set_arg` calls take their argument by-move..
- generates_set_arg
- // If this is a `MultiSpan` field then it needs to be moved to be used by any
- // attribute..
- || is_multispan
- // If this a `#[subdiagnostic]` then it needs to be moved as the other diagnostic is
- // unlikely to be `Copy`..
- || is_subdiagnostic
+ if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag {
+ let meta = attr.parse_meta()?;
+ throw_invalid_attr!(attr, &meta, |diag| diag
+ .help("consider creating a `Subdiagnostic` instead"));
+ }
+
+ let slug = slug.unwrap_or_else(|| match subdiag {
+ SubdiagnosticKind::Label => parse_quote! { _subdiag::label },
+ SubdiagnosticKind::Note => parse_quote! { _subdiag::note },
+ SubdiagnosticKind::Help => parse_quote! { _subdiag::help },
+ SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn },
+ SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion },
+ SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
+ });
+
+ Ok(Some((subdiag, slug)))
}
/// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
@@ -134,99 +184,70 @@ impl DiagnosticDeriveBuilder {
&mut self,
attr: &Attribute,
) -> Result<TokenStream, DiagnosticDeriveError> {
- let diag = &self.diag;
- let span = attr.span().unwrap();
+ let diag = &self.parent.diag;
+
+ // Always allow documentation comments.
+ if is_doc_comment(attr) {
+ return Ok(quote! {});
+ }
let name = attr.path.segments.last().unwrap().ident.to_string();
let name = name.as_str();
let meta = attr.parse_meta()?;
- let is_diag = name == "diag";
-
- let nested = match meta {
- // Most attributes are lists, like `#[diag(..)]` for most cases or
- // `#[help(..)]`/`#[note(..)]` when the user is specifying a alternative slug.
- Meta::List(MetaList { ref nested, .. }) => nested,
- // Subdiagnostics without spans can be applied to the type too, and these are just
- // paths: `#[help]`, `#[note]` and `#[warning]`
- Meta::Path(_) if !is_diag => {
- let fn_name = if name == "warning" {
- Ident::new("warn", attr.span())
- } else {
- Ident::new(name, attr.span())
- };
- return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::_subdiag::#fn_name); });
- }
- _ => throw_invalid_attr!(attr, &meta),
- };
-
- // Check the kind before doing any further processing so that there aren't misleading
- // "no kind specified" errors if there are failures later.
- match name {
- "error" | "lint" => throw_invalid_attr!(attr, &meta, |diag| {
- diag.help("`error` and `lint` have been replaced by `diag`")
- }),
- "warn_" => throw_invalid_attr!(attr, &meta, |diag| {
- diag.help("`warn_` have been replaced by `warning`")
- }),
- "diag" | "help" | "note" | "warning" => (),
- _ => throw_invalid_attr!(attr, &meta, |diag| {
- diag.help("only `diag`, `help`, `note` and `warning` are valid attributes")
- }),
- }
+ if name == "diag" {
+ let Meta::List(MetaList { ref nested, .. }) = meta else {
+ throw_invalid_attr!(
+ attr,
+ &meta
+ );
+ };
- // First nested element should always be the path, e.g. `#[diag(typeck::invalid)]` or
- // `#[help(typeck::another_help)]`.
- let mut nested_iter = nested.into_iter();
- if let Some(nested_attr) = nested_iter.next() {
- // Report an error if there are any other list items after the path.
- if !is_diag && nested_iter.next().is_some() {
- throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
- diag.help(
- "`help`, `note` and `warning` struct attributes can only have one argument",
- )
- });
- }
+ let mut nested_iter = nested.into_iter().peekable();
- match nested_attr {
- NestedMeta::Meta(Meta::Path(path)) => {
- if is_diag {
- self.slug.set_once((path.clone(), span));
- } else {
- let fn_name = proc_macro2::Ident::new(name, attr.span());
- return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); });
- }
- }
- NestedMeta::Meta(meta @ Meta::NameValue(_))
- if is_diag && meta.path().segments.last().unwrap().ident == "code" =>
- {
- // don't error for valid follow-up attributes
+ match nested_iter.peek() {
+ Some(NestedMeta::Meta(Meta::Path(slug))) => {
+ self.slug.set_once(slug.clone(), slug.span().unwrap());
+ nested_iter.next();
}
- nested_attr => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
- diag.help("first argument of the attribute should be the diagnostic slug")
- }),
+ Some(NestedMeta::Meta(Meta::NameValue { .. })) => {}
+ Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| diag
+ .help("a diagnostic slug is required as the first argument")),
+ None => throw_invalid_attr!(attr, &meta, |diag| diag
+ .help("a diagnostic slug is required as the first argument")),
};
- }
- // Remaining attributes are optional, only `code = ".."` at the moment.
- let mut tokens = Vec::new();
- for nested_attr in nested_iter {
- let meta = match nested_attr {
- syn::NestedMeta::Meta(meta) => meta,
- _ => throw_invalid_nested_attr!(attr, &nested_attr),
- };
+ // Remaining attributes are optional, only `code = ".."` at the moment.
+ let mut tokens = TokenStream::new();
+ for nested_attr in nested_iter {
+ let (value, path) = match nested_attr {
+ NestedMeta::Meta(Meta::NameValue(MetaNameValue {
+ lit: syn::Lit::Str(value),
+ path,
+ ..
+ })) => (value, path),
+ NestedMeta::Meta(Meta::Path(_)) => {
+ invalid_nested_attr(attr, &nested_attr)
+ .help("diagnostic slug must be the first argument")
+ .emit();
+ continue;
+ }
+ _ => {
+ invalid_nested_attr(attr, &nested_attr).emit();
+ continue;
+ }
+ };
- let path = meta.path();
- let nested_name = path.segments.last().unwrap().ident.to_string();
- // Struct attributes are only allowed to be applied once, and the diagnostic
- // changes will be set in the initialisation code.
- if let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) = &meta {
- let span = s.span().unwrap();
+ let nested_name = path.segments.last().unwrap().ident.to_string();
+ // Struct attributes are only allowed to be applied once, and the diagnostic
+ // changes will be set in the initialisation code.
+ let span = value.span().unwrap();
match nested_name.as_str() {
"code" => {
- self.code.set_once((s.value(), span));
- let code = &self.code.as_ref().map(|(v, _)| v);
- tokens.push(quote! {
+ self.code.set_once((), span);
+
+ let code = value.value();
+ tokens.extend(quote! {
#diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
});
}
@@ -234,46 +255,68 @@ impl DiagnosticDeriveBuilder {
.help("only `code` is a valid nested attributes following the slug")
.emit(),
}
- } else {
- invalid_nested_attr(attr, &nested_attr).emit()
}
+ return Ok(tokens);
}
- Ok(tokens.into_iter().collect())
+ let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
+ // Some attributes aren't errors - like documentation comments - but also aren't
+ // subdiagnostics.
+ return Ok(quote! {});
+ };
+ let fn_ident = format_ident!("{}", subdiag);
+ match subdiag {
+ SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
+ Ok(self.add_subdiagnostic(&fn_ident, slug))
+ }
+ SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => {
+ throw_invalid_attr!(attr, &meta, |diag| diag
+ .help("`#[label]` and `#[suggestion]` can only be applied to fields"));
+ }
+ SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
+ }
}
- fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
+ fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
+ let diag = &self.parent.diag;
+
let field = binding_info.ast();
let field_binding = &binding_info.binding;
- if self.should_generate_set_arg(&field) {
- let diag = &self.diag;
- let ident = field.ident.as_ref().unwrap();
- return quote! {
- #diag.set_arg(
- stringify!(#ident),
- #field_binding
- );
- };
+ let ident = field.ident.as_ref().unwrap();
+ let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
+
+ quote! {
+ #diag.set_arg(
+ stringify!(#ident),
+ #field_binding
+ );
}
+ }
+
+ fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
+ let field = binding_info.ast();
+ let field_binding = &binding_info.binding;
- let needs_move = self.needs_move(&field);
let inner_ty = FieldInnerTy::from_type(&field.ty);
field
.attrs
.iter()
.map(move |attr| {
+ // Always allow documentation comments.
+ if is_doc_comment(attr) {
+ return quote! {};
+ }
+
let name = attr.path.segments.last().unwrap().ident.to_string();
let needs_clone =
name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_));
let (binding, needs_destructure) = if needs_clone {
// `primary_span` can accept a `Vec<Span>` so don't destructure that.
(quote! { #field_binding.clone() }, false)
- } else if needs_move {
- (quote! { #field_binding }, true)
} else {
- (quote! { *#field_binding }, true)
+ (quote! { #field_binding }, true)
};
let generated_code = self
@@ -303,42 +346,23 @@ impl DiagnosticDeriveBuilder {
info: FieldInfo<'_>,
binding: TokenStream,
) -> Result<TokenStream, DiagnosticDeriveError> {
- let meta = attr.parse_meta()?;
- match meta {
- Meta::Path(_) => self.generate_inner_field_code_path(attr, info, binding),
- Meta::List(MetaList { .. }) => self.generate_inner_field_code_list(attr, info, binding),
- _ => throw_invalid_attr!(attr, &meta),
- }
- }
-
- fn generate_inner_field_code_path(
- &mut self,
- attr: &Attribute,
- info: FieldInfo<'_>,
- binding: TokenStream,
- ) -> Result<TokenStream, DiagnosticDeriveError> {
- assert!(matches!(attr.parse_meta()?, Meta::Path(_)));
- let diag = &self.diag;
-
+ let diag = &self.parent.diag;
let meta = attr.parse_meta()?;
let ident = &attr.path.segments.last().unwrap().ident;
let name = ident.to_string();
- let name = name.as_str();
- match name {
- "skip_arg" => {
- // Don't need to do anything - by virtue of the attribute existing, the
- // `set_arg` call will not be generated.
- Ok(quote! {})
- }
- "primary_span" => {
- match self.kind {
- DiagnosticDeriveKind::SessionDiagnostic => {
+ match (&meta, name.as_str()) {
+ // Don't need to do anything - by virtue of the attribute existing, the
+ // `set_arg` call will not be generated.
+ (Meta::Path(_), "skip_arg") => return Ok(quote! {}),
+ (Meta::Path(_), "primary_span") => {
+ match self.parent.kind {
+ DiagnosticDeriveKind::Diagnostic { .. } => {
report_error_if_not_applied_to_span(attr, &info)?;
- Ok(quote! {
+ return Ok(quote! {
#diag.set_span(#binding);
- })
+ });
}
DiagnosticDeriveKind::LintDiagnostic => {
throw_invalid_attr!(attr, &meta, |diag| {
@@ -347,188 +371,100 @@ impl DiagnosticDeriveBuilder {
}
}
}
- "label" => {
- report_error_if_not_applied_to_span(attr, &info)?;
- Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label }))
+ (Meta::Path(_), "subdiagnostic") => {
+ return Ok(quote! { #diag.subdiagnostic(#binding); });
}
- "note" | "help" | "warning" => {
- let warn_ident = Ident::new("warn", Span::call_site());
- let (ident, path) = match name {
- "note" => (ident, parse_quote! { _subdiag::note }),
- "help" => (ident, parse_quote! { _subdiag::help }),
- "warning" => (&warn_ident, parse_quote! { _subdiag::warn }),
- _ => unreachable!(),
- };
- if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
- Ok(self.add_spanned_subdiagnostic(binding, ident, path))
- } else if type_is_unit(&info.ty) {
- Ok(self.add_subdiagnostic(ident, path))
- } else {
- report_type_error(attr, "`Span` or `()`")?
- }
+ (Meta::NameValue(_), "subdiagnostic") => {
+ throw_invalid_attr!(attr, &meta, |diag| {
+ diag.help("`eager` is the only supported nested attribute for `subdiagnostic`")
+ })
}
- "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }),
- _ => throw_invalid_attr!(attr, &meta, |diag| {
- diag.help(
- "only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` \
- are valid field attributes",
- )
- }),
- }
- }
+ (Meta::List(MetaList { ref nested, .. }), "subdiagnostic") => {
+ if nested.len() != 1 {
+ throw_invalid_attr!(attr, &meta, |diag| {
+ diag.help(
+ "`eager` is the only supported nested attribute for `subdiagnostic`",
+ )
+ })
+ }
- fn generate_inner_field_code_list(
- &mut self,
- attr: &Attribute,
- info: FieldInfo<'_>,
- binding: TokenStream,
- ) -> Result<TokenStream, DiagnosticDeriveError> {
- let meta = attr.parse_meta()?;
- let Meta::List(MetaList { ref path, ref nested, .. }) = meta else { unreachable!() };
+ let handler = match &self.parent.kind {
+ DiagnosticDeriveKind::Diagnostic { handler } => handler,
+ DiagnosticDeriveKind::LintDiagnostic => {
+ throw_invalid_attr!(attr, &meta, |diag| {
+ diag.help("eager subdiagnostics are not supported on lints")
+ })
+ }
+ };
- let ident = &attr.path.segments.last().unwrap().ident;
- let name = path.segments.last().unwrap().ident.to_string();
- let name = name.as_ref();
- match name {
- "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => {
- return self.generate_inner_field_code_suggestion(attr, info);
+ let nested_attr = nested.first().expect("pop failed for single element list");
+ match nested_attr {
+ NestedMeta::Meta(meta @ Meta::Path(_))
+ if meta.path().segments.last().unwrap().ident.to_string().as_str()
+ == "eager" =>
+ {
+ return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); });
+ }
+ _ => {
+ throw_invalid_nested_attr!(attr, nested_attr, |diag| {
+ diag.help("`eager` is the only supported nested attribute for `subdiagnostic`")
+ })
+ }
+ }
}
- "label" | "help" | "note" | "warning" => (),
- _ => throw_invalid_attr!(attr, &meta, |diag| {
- diag.help(
- "only `label`, `help`, `note`, `warn` or `suggestion{,_short,_hidden,_verbose}` are \
- valid field attributes",
- )
- }),
+ _ => (),
}
- // For `#[label(..)]`, `#[note(..)]` and `#[help(..)]`, the first nested element must be a
- // path, e.g. `#[label(typeck::label)]`.
- let mut nested_iter = nested.into_iter();
- let msg = match nested_iter.next() {
- Some(NestedMeta::Meta(Meta::Path(path))) => path.clone(),
- Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr),
- None => throw_invalid_attr!(attr, &meta),
+ let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
+ // Some attributes aren't errors - like documentation comments - but also aren't
+ // subdiagnostics.
+ return Ok(quote! {});
};
-
- // None of these attributes should have anything following the slug.
- if nested_iter.next().is_some() {
- throw_invalid_attr!(attr, &meta);
- }
-
- match name {
- "label" => {
+ let fn_ident = format_ident!("{}", subdiag);
+ match subdiag {
+ SubdiagnosticKind::Label => {
report_error_if_not_applied_to_span(attr, &info)?;
- Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
+ Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
}
- "note" | "help" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => {
- Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
- }
- "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)),
- // `warning` must be special-cased because the attribute `warn` already has meaning and
- // so isn't used, despite the diagnostic API being named `warn`.
- "warning" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => Ok(self
- .add_spanned_subdiagnostic(binding, &Ident::new("warn", Span::call_site()), msg)),
- "warning" if type_is_unit(&info.ty) => {
- Ok(self.add_subdiagnostic(&Ident::new("warn", Span::call_site()), msg))
- }
- "note" | "help" | "warning" => report_type_error(attr, "`Span` or `()`")?,
- _ => unreachable!(),
- }
- }
-
- fn generate_inner_field_code_suggestion(
- &mut self,
- attr: &Attribute,
- info: FieldInfo<'_>,
- ) -> Result<TokenStream, DiagnosticDeriveError> {
- let diag = &self.diag;
-
- let mut meta = attr.parse_meta()?;
- let Meta::List(MetaList { ref path, ref mut nested, .. }) = meta else { unreachable!() };
-
- let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
-
- let mut msg = None;
- let mut code = None;
-
- let mut nested_iter = nested.into_iter().peekable();
- if let Some(nested_attr) = nested_iter.peek() {
- if let NestedMeta::Meta(Meta::Path(path)) = nested_attr {
- msg = Some(path.clone());
+ SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
+ if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
+ Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
+ } else if type_is_unit(&info.ty) {
+ Ok(self.add_subdiagnostic(&fn_ident, slug))
+ } else {
+ report_type_error(attr, "`Span` or `()`")?
+ }
}
- };
- // Move the iterator forward if a path was found (don't otherwise so that
- // code/applicability can be found or an error emitted).
- if msg.is_some() {
- let _ = nested_iter.next();
- }
-
- for nested_attr in nested_iter {
- let meta = match nested_attr {
- syn::NestedMeta::Meta(ref meta) => meta,
- syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr),
- };
-
- let nested_name = meta.path().segments.last().unwrap().ident.to_string();
- let nested_name = nested_name.as_str();
- match meta {
- Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
- let span = meta.span().unwrap();
- match nested_name {
- "code" => {
- let formatted_str = self.build_format(&s.value(), s.span());
- code = Some(formatted_str);
- }
- "applicability" => {
- applicability = match applicability {
- Some(v) => {
- span_err(
- span,
- "applicability cannot be set in both the field and \
- attribute",
- )
- .emit();
- Some(v)
- }
- None => match Applicability::from_str(&s.value()) {
- Ok(v) => Some(quote! { #v }),
- Err(()) => {
- span_err(span, "invalid applicability").emit();
- None
- }
- },
- }
- }
- _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
- diag.help(
- "only `message`, `code` and `applicability` are valid field \
- attributes",
- )
- }),
- }
+ SubdiagnosticKind::Suggestion {
+ suggestion_kind,
+ applicability: static_applicability,
+ code_field,
+ code_init,
+ } => {
+ let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
+
+ if let Some((static_applicability, span)) = static_applicability {
+ applicability.set_once(quote! { #static_applicability }, span);
}
- _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
- if matches!(meta, Meta::Path(_)) {
- diag.help("a diagnostic slug must be the first argument to the attribute")
- } else {
- diag
- }
- }),
+
+ let applicability = applicability
+ .value()
+ .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
+ let style = suggestion_kind.to_suggestion_style();
+
+ self.formatting_init.extend(code_init);
+ Ok(quote! {
+ #diag.span_suggestions_with_style(
+ #span_field,
+ rustc_errors::fluent::#slug,
+ #code_field,
+ #applicability,
+ #style
+ );
+ })
}
+ SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
}
-
- let applicability =
- applicability.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
-
- let name = path.segments.last().unwrap().ident.to_string();
- let method = format_ident!("span_{}", name);
-
- let msg = msg.unwrap_or_else(|| parse_quote! { _subdiag::suggestion });
- let msg = quote! { rustc_errors::fluent::#msg };
- let code = code.unwrap_or_else(|| quote! { String::new() });
-
- Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
}
/// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
@@ -539,7 +475,7 @@ impl DiagnosticDeriveBuilder {
kind: &Ident,
fluent_attr_identifier: Path,
) -> TokenStream {
- let diag = &self.diag;
+ let diag = &self.parent.diag;
let fn_name = format_ident!("span_{}", kind);
quote! {
#diag.#fn_name(
@@ -552,7 +488,7 @@ impl DiagnosticDeriveBuilder {
/// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
/// and `fluent_attr_identifier`.
fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream {
- let diag = &self.diag;
+ let diag = &self.parent.diag;
quote! {
#diag.#kind(rustc_errors::fluent::#fluent_attr_identifier);
}
@@ -561,58 +497,49 @@ impl DiagnosticDeriveBuilder {
fn span_and_applicability_of_ty(
&self,
info: FieldInfo<'_>,
- ) -> Result<(TokenStream, Option<TokenStream>), DiagnosticDeriveError> {
+ ) -> Result<(TokenStream, SpannedOption<TokenStream>), DiagnosticDeriveError> {
match &info.ty {
// If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
let binding = &info.binding.binding;
- Ok((quote!(*#binding), None))
+ Ok((quote!(#binding), None))
}
// If `ty` is `(Span, Applicability)` then return tokens accessing those.
Type::Tuple(tup) => {
let mut span_idx = None;
let mut applicability_idx = None;
+ fn type_err(span: &Span) -> Result<!, DiagnosticDeriveError> {
+ span_err(span.unwrap(), "wrong types for suggestion")
+ .help(
+ "`#[suggestion(...)]` on a tuple field must be applied to fields \
+ of type `(Span, Applicability)`",
+ )
+ .emit();
+ Err(DiagnosticDeriveError::ErrorHandled)
+ }
+
for (idx, elem) in tup.elems.iter().enumerate() {
if type_matches_path(elem, &["rustc_span", "Span"]) {
- if span_idx.is_none() {
- span_idx = Some(syn::Index::from(idx));
- } else {
- throw_span_err!(
- info.span.unwrap(),
- "type of field annotated with `#[suggestion(...)]` contains more \
- than one `Span`"
- );
- }
+ span_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
} else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
- if applicability_idx.is_none() {
- applicability_idx = Some(syn::Index::from(idx));
- } else {
- throw_span_err!(
- info.span.unwrap(),
- "type of field annotated with `#[suggestion(...)]` contains more \
- than one Applicability"
- );
- }
+ applicability_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
+ } else {
+ type_err(&elem.span())?;
}
}
- if let Some(span_idx) = span_idx {
- let binding = &info.binding.binding;
- let span = quote!(#binding.#span_idx);
- let applicability = applicability_idx
- .map(|applicability_idx| quote!(#binding.#applicability_idx))
- .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
-
- return Ok((span, Some(applicability)));
- }
+ let Some((span_idx, _)) = span_idx else {
+ type_err(&tup.span())?;
+ };
+ let Some((applicability_idx, applicability_span)) = applicability_idx else {
+ type_err(&tup.span())?;
+ };
+ let binding = &info.binding.binding;
+ let span = quote!(#binding.#span_idx);
+ let applicability = quote!(#binding.#applicability_idx);
- throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
- diag.help(
- "`#[suggestion(...)]` on a tuple field must be applied to fields of type \
- `(Span, Applicability)`",
- )
- });
+ Ok((span, Some((applicability, applicability_span))))
}
// If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
_ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs
index f7d8b494e..3e447c94e 100644
--- a/compiler/rustc_macros/src/diagnostics/fluent.rs
+++ b/compiler/rustc_macros/src/diagnostics/fluent.rs
@@ -25,18 +25,18 @@ use syn::{
use unic_langid::langid;
struct Resource {
- ident: Ident,
+ krate: Ident,
#[allow(dead_code)]
fat_arrow_token: token::FatArrow,
- resource: LitStr,
+ resource_path: LitStr,
}
impl Parse for Resource {
fn parse(input: ParseStream<'_>) -> Result<Self> {
Ok(Resource {
- ident: input.parse()?,
+ krate: input.parse()?,
fat_arrow_token: input.parse()?,
- resource: input.parse()?,
+ resource_path: input.parse()?,
})
}
}
@@ -94,19 +94,20 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
// diagnostics.
let mut previous_defns = HashMap::new();
+ // Set of Fluent attribute names already output, to avoid duplicate type errors - any given
+ // constant created for a given attribute is the same.
+ let mut previous_attrs = HashSet::new();
+
let mut includes = TokenStream::new();
let mut generated = TokenStream::new();
- for res in resources.0 {
- let ident_span = res.ident.span().unwrap();
- let path_span = res.resource.span().unwrap();
- // Set of Fluent attribute names already output, to avoid duplicate type errors - any given
- // constant created for a given attribute is the same.
- let mut previous_attrs = HashSet::new();
+ for res in resources.0 {
+ let krate_span = res.krate.span().unwrap();
+ let path_span = res.resource_path.span().unwrap();
- let relative_ftl_path = res.resource.value();
+ let relative_ftl_path = res.resource_path.value();
let absolute_ftl_path =
- invocation_relative_path_to_absolute(ident_span, &relative_ftl_path);
+ invocation_relative_path_to_absolute(krate_span, &relative_ftl_path);
// As this macro also outputs an `include_str!` for this file, the macro will always be
// re-executed when the file changes.
let mut resource_file = match File::open(absolute_ftl_path) {
@@ -185,7 +186,7 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
let mut constants = TokenStream::new();
for entry in resource.entries() {
- let span = res.ident.span();
+ let span = res.krate.span();
if let Entry::Message(Message { id: Identifier { name }, attributes, .. }) = entry {
let _ = previous_defns.entry(name.to_string()).or_insert(path_span);
@@ -199,29 +200,30 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
.emit();
}
- // `typeck_foo_bar` => `foo_bar` (in `typeck.ftl`)
- // `const_eval_baz` => `baz` (in `const_eval.ftl`)
+ // Require that the message name starts with the crate name
+ // `hir_typeck_foo_bar` (in `hir_typeck.ftl`)
+ // `const_eval_baz` (in `const_eval.ftl`)
// `const-eval-hyphen-having` => `hyphen_having` (in `const_eval.ftl`)
// The last case we error about above, but we want to fall back gracefully
// so that only the error is being emitted and not also one about the macro
// failing.
- let crate_prefix = format!("{}_", res.ident);
+ let crate_prefix = format!("{}_", res.krate);
let snake_name = name.replace('-', "_");
- let snake_name = match snake_name.strip_prefix(&crate_prefix) {
- Some(rest) => Ident::new(rest, span),
- None => {
- Diagnostic::spanned(
- path_span,
- Level::Error,
- format!("name `{name}` does not start with the crate name"),
- )
- .help(format!("prepend `{crate_prefix}` to the slug name: `{crate_prefix}{snake_name}`"))
- .emit();
- Ident::new(&snake_name, span)
- }
+ if !snake_name.starts_with(&crate_prefix) {
+ Diagnostic::spanned(
+ path_span,
+ Level::Error,
+ format!("name `{name}` does not start with the crate name"),
+ )
+ .help(format!(
+ "prepend `{crate_prefix}` to the slug name: `{crate_prefix}{snake_name}`"
+ ))
+ .emit();
};
+ let snake_name = Ident::new(&snake_name, span);
+
constants.extend(quote! {
pub const #snake_name: crate::DiagnosticMessage =
crate::DiagnosticMessage::FluentIdentifier(
@@ -275,12 +277,7 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
includes.extend(quote! { include_str!(#relative_ftl_path), });
- let ident = res.ident;
- generated.extend(quote! {
- pub mod #ident {
- #constants
- }
- });
+ generated.extend(constants);
}
quote! {
diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs
index 2ff21e18f..860340b43 100644
--- a/compiler/rustc_macros/src/diagnostics/mod.rs
+++ b/compiler/rustc_macros/src/diagnostics/mod.rs
@@ -5,14 +5,14 @@ mod fluent;
mod subdiagnostic;
mod utils;
-use diagnostic::{LintDiagnosticDerive, SessionDiagnosticDerive};
+use diagnostic::{DiagnosticDerive, LintDiagnosticDerive};
pub(crate) use fluent::fluent_messages;
use proc_macro2::TokenStream;
use quote::format_ident;
-use subdiagnostic::SessionSubdiagnosticDerive;
+use subdiagnostic::SubdiagnosticDeriveBuilder;
use synstructure::Structure;
-/// Implements `#[derive(SessionDiagnostic)]`, which allows for errors to be specified as a struct,
+/// Implements `#[derive(Diagnostic)]`, which allows for errors to be specified as a struct,
/// independent from the actual diagnostics emitting code.
///
/// ```ignore (rust)
@@ -22,15 +22,15 @@ use synstructure::Structure;
/// # use rustc_span::{symbol::Ident, Span};
/// # extern crate rust_middle;
/// # use rustc_middle::ty::Ty;
-/// #[derive(SessionDiagnostic)]
-/// #[diag(borrowck::move_out_of_borrow, code = "E0505")]
+/// #[derive(Diagnostic)]
+/// #[diag(borrowck_move_out_of_borrow, code = "E0505")]
/// pub struct MoveOutOfBorrowError<'tcx> {
/// pub name: Ident,
/// pub ty: Ty<'tcx>,
/// #[primary_span]
/// #[label]
/// pub span: Span,
-/// #[label(borrowck::first_borrow_label)]
+/// #[label(first_borrow_label)]
/// pub first_borrow_span: Span,
/// #[suggestion(code = "{name}.clone()")]
/// pub clone_sugg: Option<(Span, Applicability)>
@@ -56,10 +56,10 @@ use synstructure::Structure;
/// });
/// ```
///
-/// See rustc dev guide for more examples on using the `#[derive(SessionDiagnostic)]`:
+/// See rustc dev guide for more examples on using the `#[derive(Diagnostic)]`:
/// <https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html>
pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
- SessionDiagnosticDerive::new(format_ident!("diag"), format_ident!("sess"), s).into_tokens()
+ DiagnosticDerive::new(format_ident!("diag"), format_ident!("handler"), s).into_tokens()
}
/// Implements `#[derive(LintDiagnostic)]`, which allows for lints to be specified as a struct,
@@ -67,14 +67,14 @@ pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
///
/// ```ignore (rust)
/// #[derive(LintDiagnostic)]
-/// #[diag(lint::atomic_ordering_invalid_fail_success)]
+/// #[diag(lint_atomic_ordering_invalid_fail_success)]
/// pub struct AtomicOrderingInvalidLint {
/// method: Symbol,
/// success_ordering: Symbol,
/// fail_ordering: Symbol,
-/// #[label(lint::fail_label)]
+/// #[label(fail_label)]
/// fail_order_arg_span: Span,
-/// #[label(lint::success_label)]
+/// #[label(success_label)]
/// #[suggestion(
/// code = "std::sync::atomic::Ordering::{success_suggestion}",
/// applicability = "maybe-incorrect"
@@ -103,24 +103,24 @@ pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
/// ```
///
/// See rustc dev guide for more examples on using the `#[derive(LintDiagnostic)]`:
-/// <https://rustc-dev-guide.rust-lang.org/diagnostics/sessiondiagnostic.html>
+/// <https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html#reference>
pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream {
LintDiagnosticDerive::new(format_ident!("diag"), s).into_tokens()
}
-/// Implements `#[derive(SessionSubdiagnostic)]`, which allows for labels, notes, helps and
+/// Implements `#[derive(Subdiagnostic)]`, which allows for labels, notes, helps and
/// suggestions to be specified as a structs or enums, independent from the actual diagnostics
/// emitting code or diagnostic derives.
///
/// ```ignore (rust)
-/// #[derive(SessionSubdiagnostic)]
+/// #[derive(Subdiagnostic)]
/// pub enum ExpectedIdentifierLabel<'tcx> {
-/// #[label(parser::expected_identifier)]
+/// #[label(expected_identifier)]
/// WithoutFound {
/// #[primary_span]
/// span: Span,
/// }
-/// #[label(parser::expected_identifier_found)]
+/// #[label(expected_identifier_found)]
/// WithFound {
/// #[primary_span]
/// span: Span,
@@ -128,7 +128,7 @@ pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream {
/// }
/// }
///
-/// #[derive(SessionSubdiagnostic)]
+/// #[derive(Subdiagnostic)]
/// #[suggestion_verbose(parser::raw_identifier)]
/// pub struct RawIdentifierSuggestion<'tcx> {
/// #[primary_span]
@@ -155,5 +155,5 @@ pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream {
/// diag.subdiagnostic(RawIdentifierSuggestion { span, applicability, ident });
/// ```
pub fn session_subdiagnostic_derive(s: Structure<'_>) -> TokenStream {
- SessionSubdiagnosticDerive::new(s).into_tokens()
+ SubdiagnosticDeriveBuilder::new().into_tokens(s)
}
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index dce5d3cfb..fa0ca5a52 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -1,116 +1,35 @@
#![deny(unused_must_use)]
use crate::diagnostics::error::{
- span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
+ invalid_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
+ DiagnosticDeriveError,
};
use crate::diagnostics::utils::{
- report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
- Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
+ build_field_mapping, is_doc_comment, new_code_ident,
+ report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span, FieldInfo,
+ FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
-use std::collections::HashMap;
-use std::fmt;
-use std::str::FromStr;
-use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path};
+use syn::{spanned::Spanned, Attribute, Meta, MetaList, NestedMeta, Path};
use synstructure::{BindingInfo, Structure, VariantInfo};
-/// Which kind of suggestion is being created?
-#[derive(Clone, Copy)]
-enum SubdiagnosticSuggestionKind {
- /// `#[suggestion]`
- Normal,
- /// `#[suggestion_short]`
- Short,
- /// `#[suggestion_hidden]`
- Hidden,
- /// `#[suggestion_verbose]`
- Verbose,
-}
-
-impl FromStr for SubdiagnosticSuggestionKind {
- type Err = ();
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s {
- "" => Ok(SubdiagnosticSuggestionKind::Normal),
- "_short" => Ok(SubdiagnosticSuggestionKind::Short),
- "_hidden" => Ok(SubdiagnosticSuggestionKind::Hidden),
- "_verbose" => Ok(SubdiagnosticSuggestionKind::Verbose),
- _ => Err(()),
- }
- }
-}
-
-impl SubdiagnosticSuggestionKind {
- pub fn to_suggestion_style(&self) -> TokenStream {
- match self {
- SubdiagnosticSuggestionKind::Normal => {
- quote! { rustc_errors::SuggestionStyle::ShowCode }
- }
- SubdiagnosticSuggestionKind::Short => {
- quote! { rustc_errors::SuggestionStyle::HideCodeInline }
- }
- SubdiagnosticSuggestionKind::Hidden => {
- quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
- }
- SubdiagnosticSuggestionKind::Verbose => {
- quote! { rustc_errors::SuggestionStyle::ShowAlways }
- }
- }
- }
-}
-
-/// Which kind of subdiagnostic is being created from a variant?
-#[derive(Clone)]
-enum SubdiagnosticKind {
- /// `#[label(...)]`
- Label,
- /// `#[note(...)]`
- Note,
- /// `#[help(...)]`
- Help,
- /// `#[warning(...)]`
- Warn,
- /// `#[suggestion{,_short,_hidden,_verbose}]`
- Suggestion { suggestion_kind: SubdiagnosticSuggestionKind, code: TokenStream },
- /// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
- MultipartSuggestion { suggestion_kind: SubdiagnosticSuggestionKind },
-}
-
-impl quote::IdentFragment for SubdiagnosticKind {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- SubdiagnosticKind::Label => write!(f, "label"),
- SubdiagnosticKind::Note => write!(f, "note"),
- SubdiagnosticKind::Help => write!(f, "help"),
- SubdiagnosticKind::Warn => write!(f, "warn"),
- SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestion_with_style"),
- SubdiagnosticKind::MultipartSuggestion { .. } => {
- write!(f, "multipart_suggestion_with_style")
- }
- }
- }
-
- fn span(&self) -> Option<proc_macro2::Span> {
- None
- }
-}
+use super::utils::{build_suggestion_code, AllowMultipleAlternatives};
/// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
-pub(crate) struct SessionSubdiagnosticDerive<'a> {
- structure: Structure<'a>,
+pub(crate) struct SubdiagnosticDeriveBuilder {
diag: syn::Ident,
+ f: syn::Ident,
}
-impl<'a> SessionSubdiagnosticDerive<'a> {
- pub(crate) fn new(structure: Structure<'a>) -> Self {
+impl SubdiagnosticDeriveBuilder {
+ pub(crate) fn new() -> Self {
let diag = format_ident!("diag");
- Self { structure, diag }
+ let f = format_ident!("f");
+ Self { diag, f }
}
- pub(crate) fn into_tokens(self) -> TokenStream {
- let SessionSubdiagnosticDerive { mut structure, diag } = self;
+ pub(crate) fn into_tokens<'a>(self, mut structure: Structure<'a>) -> TokenStream {
let implementation = {
let ast = structure.ast();
let span = ast.span().unwrap();
@@ -119,13 +38,19 @@ impl<'a> SessionSubdiagnosticDerive<'a> {
syn::Data::Union(..) => {
span_err(
span,
- "`#[derive(SessionSubdiagnostic)]` can only be used on structs and enums",
+ "`#[derive(Subdiagnostic)]` can only be used on structs and enums",
);
}
}
- if matches!(ast.data, syn::Data::Enum(..)) {
+ let is_enum = matches!(ast.data, syn::Data::Enum(..));
+ if is_enum {
for attr in &ast.attrs {
+ // Always allow documentation comments.
+ if is_doc_comment(attr) {
+ continue;
+ }
+
span_err(
attr.span().unwrap(),
"unsupported type attribute for subdiagnostic enum",
@@ -136,24 +61,16 @@ impl<'a> SessionSubdiagnosticDerive<'a> {
structure.bind_with(|_| synstructure::BindStyle::Move);
let variants_ = structure.each_variant(|variant| {
- // Build the mapping of field names to fields. This allows attributes to peek
- // values from other fields.
- let mut fields_map = HashMap::new();
- for binding in variant.bindings() {
- let field = binding.ast();
- if let Some(ident) = &field.ident {
- fields_map.insert(ident.to_string(), quote! { #binding });
- }
- }
-
- let mut builder = SessionSubdiagnosticDeriveBuilder {
- diag: &diag,
+ let mut builder = SubdiagnosticDeriveVariantBuilder {
+ parent: &self,
variant,
span,
- fields: fields_map,
+ formatting_init: TokenStream::new(),
+ fields: build_field_mapping(variant),
span_field: None,
applicability: None,
has_suggestion_parts: false,
+ is_enum,
};
builder.into_tokens().unwrap_or_else(|v| v.to_compile_error())
});
@@ -165,9 +82,17 @@ impl<'a> SessionSubdiagnosticDerive<'a> {
}
};
+ let diag = &self.diag;
+ let f = &self.f;
let ret = structure.gen_impl(quote! {
- gen impl rustc_errors::AddSubdiagnostic for @Self {
- fn add_to_diagnostic(self, #diag: &mut rustc_errors::Diagnostic) {
+ gen impl rustc_errors::AddToDiagnostic for @Self {
+ fn add_to_diagnostic_with<__F>(self, #diag: &mut rustc_errors::Diagnostic, #f: __F)
+ where
+ __F: core::ops::Fn(
+ &mut rustc_errors::Diagnostic,
+ rustc_errors::SubdiagnosticMessage
+ ) -> rustc_errors::SubdiagnosticMessage,
+ {
use rustc_errors::{Applicability, IntoDiagnosticArg};
#implementation
}
@@ -178,34 +103,40 @@ impl<'a> SessionSubdiagnosticDerive<'a> {
}
/// Tracks persistent information required for building up the call to add to the diagnostic
-/// for the final generated method. This is a separate struct to `SessionSubdiagnosticDerive`
+/// for the final generated method. This is a separate struct to `SubdiagnosticDerive`
/// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
/// double mut borrow later on.
-struct SessionSubdiagnosticDeriveBuilder<'a> {
+struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
/// The identifier to use for the generated `DiagnosticBuilder` instance.
- diag: &'a syn::Ident,
+ parent: &'parent SubdiagnosticDeriveBuilder,
/// Info for the current variant (or the type if not an enum).
variant: &'a VariantInfo<'a>,
/// Span for the entire type.
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
/// derive builder.
- fields: HashMap<String, TokenStream>,
+ fields: FieldMap,
/// Identifier for the binding to the `#[primary_span]` field.
- span_field: Option<(proc_macro2::Ident, proc_macro::Span)>,
- /// If a suggestion, the identifier for the binding to the `#[applicability]` field or a
- /// `rustc_errors::Applicability::*` variant directly.
- applicability: Option<(TokenStream, proc_macro::Span)>,
+ span_field: SpannedOption<proc_macro2::Ident>,
+
+ /// The binding to the `#[applicability]` field, if present.
+ applicability: SpannedOption<TokenStream>,
/// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error
/// during finalization if still `false`.
has_suggestion_parts: bool,
+
+ /// Set to true when this variant is an enum variant rather than just the body of a struct.
+ is_enum: bool,
}
-impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
+impl<'parent, 'a> HasFieldMap for SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
self.fields.get(field)
}
@@ -217,6 +148,7 @@ struct KindsStatistics {
has_multipart_suggestion: bool,
all_multipart_suggestions: bool,
has_normal_suggestion: bool,
+ all_applicabilities_static: bool,
}
impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
@@ -225,8 +157,15 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
has_multipart_suggestion: false,
all_multipart_suggestions: true,
has_normal_suggestion: false,
+ all_applicabilities_static: true,
};
+
for kind in kinds {
+ if let SubdiagnosticKind::MultipartSuggestion { applicability: None, .. }
+ | SubdiagnosticKind::Suggestion { applicability: None, .. } = kind
+ {
+ ret.all_applicabilities_static = false;
+ }
if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
ret.has_multipart_suggestion = true;
} else {
@@ -241,134 +180,23 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
}
}
-impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
+impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
fn identify_kind(&mut self) -> Result<Vec<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
let mut kind_slugs = vec![];
for attr in self.variant.ast().attrs {
- let span = attr.span().unwrap();
-
- let name = attr.path.segments.last().unwrap().ident.to_string();
- let name = name.as_str();
-
- let meta = attr.parse_meta()?;
- let Meta::List(MetaList { ref nested, .. }) = meta else {
- throw_invalid_attr!(attr, &meta);
+ let Some((kind, slug)) = SubdiagnosticKind::from_attr(attr, self)? else {
+ // Some attributes aren't errors - like documentation comments - but also aren't
+ // subdiagnostics.
+ continue;
};
- let mut kind = match name {
- "label" => SubdiagnosticKind::Label,
- "note" => SubdiagnosticKind::Note,
- "help" => SubdiagnosticKind::Help,
- "warning" => SubdiagnosticKind::Warn,
- _ => {
- if let Some(suggestion_kind) =
- name.strip_prefix("suggestion").and_then(|s| s.parse().ok())
- {
- SubdiagnosticKind::Suggestion { suggestion_kind, code: TokenStream::new() }
- } else if let Some(suggestion_kind) =
- name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
- {
- SubdiagnosticKind::MultipartSuggestion { suggestion_kind }
- } else {
- throw_invalid_attr!(attr, &meta);
- }
- }
- };
-
- let mut slug = None;
- let mut code = None;
-
- let mut nested_iter = nested.into_iter();
- if let Some(nested_attr) = nested_iter.next() {
- match nested_attr {
- NestedMeta::Meta(Meta::Path(path)) => {
- slug.set_once((path.clone(), span));
- }
- NestedMeta::Meta(meta @ Meta::NameValue(_))
- if matches!(
- meta.path().segments.last().unwrap().ident.to_string().as_str(),
- "code" | "applicability"
- ) =>
- {
- // Don't error for valid follow-up attributes.
- }
- nested_attr => {
- throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
- diag.help(
- "first argument of the attribute should be the diagnostic \
- slug",
- )
- })
- }
- };
- }
-
- for nested_attr in nested_iter {
- let meta = match nested_attr {
- NestedMeta::Meta(ref meta) => meta,
- _ => throw_invalid_nested_attr!(attr, &nested_attr),
- };
-
- let span = meta.span().unwrap();
- let nested_name = meta.path().segments.last().unwrap().ident.to_string();
- let nested_name = nested_name.as_str();
-
- let value = match meta {
- Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => value,
- Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
- diag.help("a diagnostic slug must be the first argument to the attribute")
- }),
- _ => throw_invalid_nested_attr!(attr, &nested_attr),
- };
-
- match nested_name {
- "code" => {
- if matches!(kind, SubdiagnosticKind::Suggestion { .. }) {
- let formatted_str = self.build_format(&value.value(), value.span());
- code.set_once((formatted_str, span));
- } else {
- span_err(
- span,
- &format!(
- "`code` is not a valid nested attribute of a `{}` attribute",
- name
- ),
- )
- .emit();
- }
- }
- "applicability" => {
- if matches!(
- kind,
- SubdiagnosticKind::Suggestion { .. }
- | SubdiagnosticKind::MultipartSuggestion { .. }
- ) {
- let value =
- Applicability::from_str(&value.value()).unwrap_or_else(|()| {
- span_err(span, "invalid applicability").emit();
- Applicability::Unspecified
- });
- self.applicability.set_once((quote! { #value }, span));
- } else {
- span_err(
- span,
- &format!(
- "`applicability` is not a valid nested attribute of a `{}` attribute",
- name
- )
- ).emit();
- }
- }
- _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
- diag.help("only `code` and `applicability` are valid nested attributes")
- }),
- }
- }
+ let Some(slug) = slug else {
+ let name = attr.path.segments.last().unwrap().ident.to_string();
+ let name = name.as_str();
- let Some((slug, _)) = slug else {
throw_span_err!(
- span,
+ attr.span().unwrap(),
&format!(
"diagnostic slug must be first argument of a `#[{}(...)]` attribute",
name
@@ -376,21 +204,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
);
};
- match kind {
- SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
- let Some((code, _)) = code else {
- throw_span_err!(span, "suggestion without `code = \"...\"`");
- };
- *code_field = code;
- }
- SubdiagnosticKind::Label
- | SubdiagnosticKind::Note
- | SubdiagnosticKind::Help
- | SubdiagnosticKind::Warn
- | SubdiagnosticKind::MultipartSuggestion { .. } => {}
- }
-
- kind_slugs.push((kind, slug))
+ kind_slugs.push((kind, slug));
}
Ok(kind_slugs)
@@ -401,8 +215,11 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
let ast = binding.ast();
assert_eq!(ast.attrs.len(), 0, "field with attribute used as diagnostic arg");
- let diag = &self.diag;
+ let diag = &self.parent.diag;
let ident = ast.ident.as_ref().unwrap();
+ // strip `r#` prefix, if present
+ let ident = format_ident!("{}", ident);
+
quote! {
#diag.set_arg(
stringify!(#ident),
@@ -426,6 +243,11 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
ast.attrs
.iter()
.map(|attr| {
+ // Always allow documentation comments.
+ if is_doc_comment(attr) {
+ return quote! {};
+ }
+
let info = FieldInfo {
binding,
ty: inner_ty.inner_type().unwrap_or(&ast.ty),
@@ -433,7 +255,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
};
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());
inner_ty.with(binding, generated)
@@ -446,13 +268,18 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
kind_stats: KindsStatistics,
attr: &Attribute,
info: FieldInfo<'_>,
+ clone_suggestion_code: bool,
) -> Result<TokenStream, DiagnosticDeriveError> {
let meta = attr.parse_meta()?;
match meta {
Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path),
- Meta::List(list @ MetaList { .. }) => {
- self.generate_field_code_inner_list(kind_stats, attr, info, list)
- }
+ Meta::List(list @ MetaList { .. }) => self.generate_field_code_inner_list(
+ kind_stats,
+ attr,
+ info,
+ list,
+ clone_suggestion_code,
+ ),
_ => throw_invalid_attr!(attr, &meta),
}
}
@@ -474,18 +301,20 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
"skip_arg" => Ok(quote! {}),
"primary_span" => {
if kind_stats.has_multipart_suggestion {
- throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
- diag.help(
+ invalid_attr(attr, &Meta::Path(path))
+ .help(
"multipart suggestions use one or more `#[suggestion_part]`s rather \
than one `#[primary_span]`",
)
- })
- }
-
- report_error_if_not_applied_to_span(attr, &info)?;
+ .emit();
+ } else {
+ report_error_if_not_applied_to_span(attr, &info)?;
- let binding = info.binding.binding.clone();
- self.span_field.set_once((binding, span));
+ let binding = info.binding.binding.clone();
+ // FIXME(#100717): support `Option<Span>` on `primary_span` like in the
+ // diagnostic derive
+ self.span_field.set_once(binding, span);
+ }
Ok(quote! {})
}
@@ -495,28 +324,39 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
if kind_stats.has_multipart_suggestion {
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
.emit();
- Ok(quote! {})
} else {
- throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
- diag.help(
- "`#[suggestion_part(...)]` is only valid in multipart suggestions, use `#[primary_span]` instead",
- )
- });
+ invalid_attr(attr, &Meta::Path(path))
+ .help(
+ "`#[suggestion_part(...)]` is only valid in multipart suggestions, \
+ use `#[primary_span]` instead",
+ )
+ .emit();
}
+
+ Ok(quote! {})
}
"applicability" => {
if kind_stats.has_multipart_suggestion || kind_stats.has_normal_suggestion {
report_error_if_not_applied_to_applicability(attr, &info)?;
+ if kind_stats.all_applicabilities_static {
+ span_err(
+ span,
+ "`#[applicability]` has no effect if all `#[suggestion]`/\
+ `#[multipart_suggestion]` attributes have a static \
+ `applicability = \"...\"`",
+ )
+ .emit();
+ }
let binding = info.binding.binding.clone();
- self.applicability.set_once((quote! { #binding }, span));
+ self.applicability.set_once(quote! { #binding }, span);
} else {
span_err(span, "`#[applicability]` is only valid on suggestions").emit();
}
Ok(quote! {})
}
- _ => throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
+ _ => {
let mut span_attrs = vec![];
if kind_stats.has_multipart_suggestion {
span_attrs.push("suggestion_part");
@@ -524,11 +364,16 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
if !kind_stats.all_multipart_suggestions {
span_attrs.push("primary_span")
}
- diag.help(format!(
- "only `{}`, `applicability` and `skip_arg` are valid field attributes",
- span_attrs.join(", ")
- ))
- }),
+
+ invalid_attr(attr, &Meta::Path(path))
+ .help(format!(
+ "only `{}`, `applicability` and `skip_arg` are valid field attributes",
+ span_attrs.join(", ")
+ ))
+ .emit();
+
+ Ok(quote! {})
+ }
}
}
@@ -540,6 +385,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
attr: &Attribute,
info: FieldInfo<'_>,
list: MetaList,
+ clone_suggestion_code: bool,
) -> Result<TokenStream, DiagnosticDeriveError> {
let span = attr.span().unwrap();
let ident = &list.path.segments.last().unwrap().ident;
@@ -570,14 +416,16 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
let nested_name = meta.path().segments.last().unwrap().ident.to_string();
let nested_name = nested_name.as_str();
- let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) = meta else {
- throw_invalid_nested_attr!(attr, &nested_attr);
- };
-
match nested_name {
"code" => {
- let formatted_str = self.build_format(&value.value(), value.span());
- code.set_once((formatted_str, span));
+ let code_field = new_code_ident();
+ let formatting_init = build_suggestion_code(
+ &code_field,
+ meta,
+ self,
+ AllowMultipleAlternatives::No,
+ );
+ code.set_once((code_field, formatting_init), span);
}
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
diag.help("`code` is the only valid nested attribute")
@@ -585,14 +433,20 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
}
}
- let Some((code, _)) = code else {
+ let Some((code_field, formatting_init)) = code.value() else {
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
.emit();
return Ok(quote! {});
};
let binding = info.binding;
- Ok(quote! { suggestions.push((#binding, #code)); })
+ self.formatting_init.extend(formatting_init);
+ 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| {
let mut span_attrs = vec![];
@@ -613,10 +467,16 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
let kind_slugs = self.identify_kind()?;
if kind_slugs.is_empty() {
- throw_span_err!(
- self.variant.ast().ident.span().unwrap(),
- "subdiagnostic kind not specified"
- );
+ if self.is_enum {
+ // It's okay for a variant to not be a subdiagnostic at all..
+ return Ok(quote! {});
+ } else {
+ // ..but structs should always be _something_.
+ throw_span_err!(
+ self.variant.ast().ident.span().unwrap(),
+ "subdiagnostic kind not specified"
+ );
+ }
};
let kind_stats: KindsStatistics = kind_slugs.iter().map(|(kind, _slug)| kind).collect();
@@ -635,29 +495,46 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
.map(|binding| self.generate_field_attr_code(binding, kind_stats))
.collect();
- let span_field = self.span_field.as_ref().map(|(span, _)| span);
- let applicability = self.applicability.take().map_or_else(
- || quote! { rustc_errors::Applicability::Unspecified },
- |(applicability, _)| applicability,
- );
+ let span_field = self.span_field.value_ref();
- let diag = &self.diag;
+ let diag = &self.parent.diag;
+ let f = &self.parent.f;
let mut calls = TokenStream::new();
for (kind, slug) in kind_slugs {
+ let message = format_ident!("__message");
+ calls.extend(quote! { let #message = #f(#diag, rustc_errors::fluent::#slug.into()); });
+
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
- let message = quote! { rustc_errors::fluent::#slug };
let call = match kind {
- SubdiagnosticKind::Suggestion { suggestion_kind, code } => {
+ SubdiagnosticKind::Suggestion {
+ suggestion_kind,
+ applicability,
+ code_init,
+ code_field,
+ } => {
+ self.formatting_init.extend(code_init);
+
+ let applicability = applicability
+ .value()
+ .map(|a| quote! { #a })
+ .or_else(|| self.applicability.take().value())
+ .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
+
if let Some(span) = span_field {
let style = suggestion_kind.to_suggestion_style();
-
- quote! { #diag.#name(#span, #message, #code, #applicability, #style); }
+ quote! { #diag.#name(#span, #message, #code_field, #applicability, #style); }
} else {
span_err(self.span, "suggestion without `#[primary_span]` field").emit();
quote! { unreachable!(); }
}
}
- SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => {
+ SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability } => {
+ let applicability = applicability
+ .value()
+ .map(|a| quote! { #a })
+ .or_else(|| self.applicability.take().value())
+ .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
+
if !self.has_suggestion_parts {
span_err(
self.span,
@@ -686,6 +563,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
}
}
};
+
calls.extend(call);
}
@@ -697,11 +575,13 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
.map(|binding| self.generate_field_set_arg(binding))
.collect();
+ let formatting_init = &self.formatting_init;
Ok(quote! {
#init
+ #formatting_init
#attr_args
- #calls
#plain_args
+ #calls
})
}
}
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
index ad9ecd39b..374c795d0 100644
--- a/compiler/rustc_macros/src/diagnostics/utils.rs
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -1,11 +1,31 @@
-use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError};
+use crate::diagnostics::error::{
+ span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
+};
use proc_macro::Span;
-use proc_macro2::TokenStream;
+use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote, ToTokens};
+use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap};
+use std::fmt;
use std::str::FromStr;
-use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
-use synstructure::{BindingInfo, Structure};
+use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple};
+use syn::{MetaList, MetaNameValue, NestedMeta, Path};
+use synstructure::{BindingInfo, VariantInfo};
+
+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`.
///
@@ -135,6 +155,15 @@ impl<'ty> FieldInnerTy<'ty> {
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.
pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
match self {
@@ -172,13 +201,17 @@ pub(crate) struct FieldInfo<'a> {
/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
/// for error reporting if they are set more than once.
pub(crate) trait SetOnce<T> {
- fn set_once(&mut self, _: (T, Span));
+ fn set_once(&mut self, value: T, span: Span);
fn value(self) -> Option<T>;
+ fn value_ref(&self) -> Option<&T>;
}
-impl<T> SetOnce<T> for Option<(T, Span)> {
- fn set_once(&mut self, (value, span): (T, Span)) {
+/// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`].
+pub(super) type SpannedOption<T> = Option<(T, Span)>;
+
+impl<T> SetOnce<T> for SpannedOption<T> {
+ fn set_once(&mut self, value: T, span: Span) {
match self {
None => {
*self = Some((value, span));
@@ -194,8 +227,14 @@ impl<T> SetOnce<T> for Option<(T, Span)> {
fn value(self) -> Option<T> {
self.map(|(v, _)| v)
}
+
+ fn value_ref(&self) -> Option<&T> {
+ self.as_ref().map(|(v, _)| v)
+ }
}
+pub(super) type FieldMap = HashMap<String, TokenStream>;
+
pub(crate) trait HasFieldMap {
/// Returns the binding for the field with the given name, if it exists on the type.
fn get_field_binding(&self, field: &String) -> Option<&TokenStream>;
@@ -303,6 +342,7 @@ pub(crate) trait HasFieldMap {
/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
/// the user's selection of applicability if specified in an attribute.
+#[derive(Clone, Copy)]
pub(crate) enum Applicability {
MachineApplicable,
MaybeIncorrect,
@@ -345,17 +385,366 @@ impl quote::ToTokens for Applicability {
/// Build the mapping of field names to fields. This allows attributes to peek values from
/// other fields.
-pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap<String, TokenStream> {
- let mut fields_map = HashMap::new();
-
- let ast = structure.ast();
- if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
- for field in fields.iter() {
- if let Some(ident) = &field.ident {
- fields_map.insert(ident.to_string(), quote! { &self.#ident });
+pub(super) fn build_field_mapping<'v>(variant: &VariantInfo<'v>) -> HashMap<String, TokenStream> {
+ let mut fields_map = FieldMap::new();
+ for binding in variant.bindings() {
+ if let Some(ident) = &binding.ast().ident {
+ fields_map.insert(ident.to_string(), quote! { #binding });
+ }
+ }
+ fields_map
+}
+
+#[derive(Copy, Clone, Debug)]
+pub(super) enum AllowMultipleAlternatives {
+ No,
+ Yes,
+}
+
+/// Constructs the `format!()` invocation(s) necessary for a `#[suggestion*(code = "foo")]` or
+/// `#[suggestion*(code("foo", "bar"))]` attribute field
+pub(super) fn build_suggestion_code(
+ code_field: &Ident,
+ meta: &Meta,
+ fields: &impl HasFieldMap,
+ allow_multiple: AllowMultipleAlternatives,
+) -> TokenStream {
+ let values = match meta {
+ // `code = "foo"`
+ Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => vec![s],
+ // `code("foo", "bar")`
+ Meta::List(MetaList { nested, .. }) => {
+ if let AllowMultipleAlternatives::No = allow_multiple {
+ span_err(
+ meta.span().unwrap(),
+ "expected exactly one string literal for `code = ...`",
+ )
+ .emit();
+ vec![]
+ } else if nested.is_empty() {
+ span_err(
+ meta.span().unwrap(),
+ "expected at least one string literal for `code(...)`",
+ )
+ .emit();
+ vec![]
+ } else {
+ nested
+ .into_iter()
+ .filter_map(|item| {
+ if let NestedMeta::Lit(syn::Lit::Str(s)) = item {
+ Some(s)
+ } else {
+ span_err(
+ item.span().unwrap(),
+ "`code(...)` must contain only string literals",
+ )
+ .emit();
+ None
+ }
+ })
+ .collect()
}
}
+ _ => {
+ span_err(
+ meta.span().unwrap(),
+ r#"`code = "..."`/`code(...)` must contain only string literals"#,
+ )
+ .emit();
+ vec![]
+ }
+ };
+
+ if let AllowMultipleAlternatives::Yes = allow_multiple {
+ let formatted_strings: Vec<_> = values
+ .into_iter()
+ .map(|value| fields.build_format(&value.value(), value.span()))
+ .collect();
+ quote! { let #code_field = [#(#formatted_strings),*].into_iter(); }
+ } else if let [value] = values.as_slice() {
+ let formatted_str = fields.build_format(&value.value(), value.span());
+ quote! { let #code_field = #formatted_str; }
+ } else {
+ // error handled previously
+ quote! { let #code_field = String::new(); }
}
+}
- fields_map
+/// Possible styles for suggestion subdiagnostics.
+#[derive(Clone, Copy)]
+pub(super) enum SuggestionKind {
+ /// `#[suggestion]`
+ Normal,
+ /// `#[suggestion_short]`
+ Short,
+ /// `#[suggestion_hidden]`
+ Hidden,
+ /// `#[suggestion_verbose]`
+ Verbose,
+}
+
+impl FromStr for SuggestionKind {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "" => Ok(SuggestionKind::Normal),
+ "_short" => Ok(SuggestionKind::Short),
+ "_hidden" => Ok(SuggestionKind::Hidden),
+ "_verbose" => Ok(SuggestionKind::Verbose),
+ _ => Err(()),
+ }
+ }
+}
+
+impl SuggestionKind {
+ pub fn to_suggestion_style(&self) -> TokenStream {
+ match self {
+ SuggestionKind::Normal => {
+ quote! { rustc_errors::SuggestionStyle::ShowCode }
+ }
+ SuggestionKind::Short => {
+ quote! { rustc_errors::SuggestionStyle::HideCodeInline }
+ }
+ SuggestionKind::Hidden => {
+ quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
+ }
+ SuggestionKind::Verbose => {
+ quote! { rustc_errors::SuggestionStyle::ShowAlways }
+ }
+ }
+ }
+}
+
+/// Types of subdiagnostics that can be created using attributes
+#[derive(Clone)]
+pub(super) enum SubdiagnosticKind {
+ /// `#[label(...)]`
+ Label,
+ /// `#[note(...)]`
+ Note,
+ /// `#[help(...)]`
+ Help,
+ /// `#[warning(...)]`
+ Warn,
+ /// `#[suggestion{,_short,_hidden,_verbose}]`
+ Suggestion {
+ suggestion_kind: SuggestionKind,
+ applicability: SpannedOption<Applicability>,
+ /// 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}]`
+ MultipartSuggestion {
+ suggestion_kind: SuggestionKind,
+ applicability: SpannedOption<Applicability>,
+ },
+}
+
+impl SubdiagnosticKind {
+ /// Constructs a `SubdiagnosticKind` from a field or type attribute such as `#[note]`,
+ /// `#[error(parser::add_paren)]` or `#[suggestion(code = "...")]`. Returns the
+ /// `SubdiagnosticKind` and the diagnostic slug, if specified.
+ pub(super) fn from_attr(
+ attr: &Attribute,
+ fields: &impl HasFieldMap,
+ ) -> Result<Option<(SubdiagnosticKind, Option<Path>)>, DiagnosticDeriveError> {
+ // Always allow documentation comments.
+ if is_doc_comment(attr) {
+ return Ok(None);
+ }
+
+ let span = attr.span().unwrap();
+
+ let name = attr.path.segments.last().unwrap().ident.to_string();
+ let name = name.as_str();
+
+ let meta = attr.parse_meta()?;
+ let mut kind = match name {
+ "label" => SubdiagnosticKind::Label,
+ "note" => SubdiagnosticKind::Note,
+ "help" => SubdiagnosticKind::Help,
+ "warning" => SubdiagnosticKind::Warn,
+ _ => {
+ if let Some(suggestion_kind) =
+ name.strip_prefix("suggestion").and_then(|s| s.parse().ok())
+ {
+ SubdiagnosticKind::Suggestion {
+ suggestion_kind,
+ applicability: None,
+ code_field: new_code_ident(),
+ code_init: TokenStream::new(),
+ }
+ } else if let Some(suggestion_kind) =
+ name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
+ {
+ SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability: None }
+ } else {
+ throw_invalid_attr!(attr, &meta);
+ }
+ }
+ };
+
+ let nested = match meta {
+ Meta::List(MetaList { ref nested, .. }) => {
+ // An attribute with properties, such as `#[suggestion(code = "...")]` or
+ // `#[error(some::slug)]`
+ nested
+ }
+ Meta::Path(_) => {
+ // An attribute without a slug or other properties, such as `#[note]` - return
+ // without further processing.
+ //
+ // Only allow this if there are no mandatory properties, such as `code = "..."` in
+ // `#[suggestion(...)]`
+ match kind {
+ SubdiagnosticKind::Label
+ | SubdiagnosticKind::Note
+ | SubdiagnosticKind::Help
+ | SubdiagnosticKind::Warn
+ | SubdiagnosticKind::MultipartSuggestion { .. } => {
+ return Ok(Some((kind, None)));
+ }
+ SubdiagnosticKind::Suggestion { .. } => {
+ throw_span_err!(span, "suggestion without `code = \"...\"`")
+ }
+ }
+ }
+ _ => {
+ throw_invalid_attr!(attr, &meta)
+ }
+ };
+
+ let mut code = None;
+
+ let mut nested_iter = nested.into_iter().peekable();
+
+ // Peek at the first nested attribute: if it's a slug path, consume it.
+ let slug = if let Some(NestedMeta::Meta(Meta::Path(path))) = nested_iter.peek() {
+ let path = path.clone();
+ // Advance the iterator.
+ nested_iter.next();
+ Some(path)
+ } else {
+ None
+ };
+
+ for nested_attr in nested_iter {
+ let meta = match nested_attr {
+ NestedMeta::Meta(ref meta) => meta,
+ NestedMeta::Lit(_) => {
+ invalid_nested_attr(attr, &nested_attr).emit();
+ continue;
+ }
+ };
+
+ let span = meta.span().unwrap();
+ let nested_name = meta.path().segments.last().unwrap().ident.to_string();
+ let nested_name = nested_name.as_str();
+
+ let string_value = match meta {
+ Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => Some(value),
+
+ Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ diag.help("a diagnostic slug must be the first argument to the attribute")
+ }),
+ _ => None,
+ };
+
+ match (nested_name, &mut kind) {
+ ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
+ let code_init = build_suggestion_code(
+ code_field,
+ meta,
+ fields,
+ AllowMultipleAlternatives::Yes,
+ );
+ code.set_once(code_init, span);
+ }
+ (
+ "applicability",
+ SubdiagnosticKind::Suggestion { ref mut applicability, .. }
+ | SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. },
+ ) => {
+ let Some(value) = string_value else {
+ invalid_nested_attr(attr, &nested_attr).emit();
+ continue;
+ };
+
+ let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| {
+ span_err(span, "invalid applicability").emit();
+ Applicability::Unspecified
+ });
+ applicability.set_once(value, span);
+ }
+
+ // Invalid nested attribute
+ (_, SubdiagnosticKind::Suggestion { .. }) => {
+ invalid_nested_attr(attr, &nested_attr)
+ .help("only `code` and `applicability` are valid nested attributes")
+ .emit();
+ }
+ (_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
+ invalid_nested_attr(attr, &nested_attr)
+ .help("only `applicability` is a valid nested attributes")
+ .emit()
+ }
+ _ => {
+ invalid_nested_attr(attr, &nested_attr).emit();
+ }
+ }
+ }
+
+ match kind {
+ SubdiagnosticKind::Suggestion { ref code_field, ref mut code_init, .. } => {
+ *code_init = if let Some(init) = code.value() {
+ init
+ } else {
+ span_err(span, "suggestion without `code = \"...\"`").emit();
+ quote! { let #code_field = std::iter::empty(); }
+ };
+ }
+ SubdiagnosticKind::Label
+ | SubdiagnosticKind::Note
+ | SubdiagnosticKind::Help
+ | SubdiagnosticKind::Warn
+ | SubdiagnosticKind::MultipartSuggestion { .. } => {}
+ }
+
+ Ok(Some((kind, slug)))
+ }
+}
+
+impl quote::IdentFragment for SubdiagnosticKind {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ SubdiagnosticKind::Label => write!(f, "label"),
+ SubdiagnosticKind::Note => write!(f, "note"),
+ SubdiagnosticKind::Help => write!(f, "help"),
+ SubdiagnosticKind::Warn => write!(f, "warn"),
+ SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestions_with_style"),
+ SubdiagnosticKind::MultipartSuggestion { .. } => {
+ write!(f, "multipart_suggestion_with_style")
+ }
+ }
+ }
+
+ fn span(&self) -> Option<proc_macro2::Span> {
+ None
+ }
+}
+
+/// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
+/// call (like `span_label`).
+pub(super) fn should_generate_set_arg(field: &Field) -> bool {
+ field.attrs.is_empty()
+}
+
+pub(super) fn is_doc_comment(attr: &Attribute) -> bool {
+ attr.path.segments.last().unwrap().ident.to_string() == "doc"
}