summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_macros/src/diagnostics
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /compiler/rustc_macros/src/diagnostics
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_macros/src/diagnostics')
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs225
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs630
-rw-r--r--compiler/rustc_macros/src/diagnostics/error.rs141
-rw-r--r--compiler/rustc_macros/src/diagnostics/fluent.rs277
-rw-r--r--compiler/rustc_macros/src/diagnostics/mod.rs159
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs491
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs356
7 files changed, 2279 insertions, 0 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
new file mode 100644
index 000000000..6b5b8b593
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
@@ -0,0 +1,225 @@
+#![deny(unused_must_use)]
+
+use crate::diagnostics::diagnostic_builder::{DiagnosticDeriveBuilder, DiagnosticDeriveKind};
+use crate::diagnostics::error::{span_err, DiagnosticDeriveError};
+use crate::diagnostics::utils::{build_field_mapping, 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> {
+ 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 {
+ Self {
+ builder: DiagnosticDeriveBuilder {
+ diag,
+ fields: build_field_mapping(&structure),
+ kind: None,
+ code: None,
+ slug: None,
+ },
+ sess,
+ structure,
+ }
+ }
+
+ pub(crate) fn into_tokens(self) -> TokenStream {
+ let SessionDiagnosticDerive { mut structure, sess, mut builder } = self;
+
+ let ast = structure.ast();
+ let (implementation, param_ty) = {
+ 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.kind.value(), builder.slug.value()) {
+ (None, _) => {
+ span_err(span, "diagnostic kind not specified")
+ .help("use the `#[error(...)]` attribute to create an error")
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(kind), None) => {
+ span_err(span, "diagnostic slug not specified")
+ .help(&format!(
+ "specify the slug as the first argument to the attribute, such as \
+ `#[{}(typeck::example_error)]`",
+ kind.descr()
+ ))
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(DiagnosticDeriveKind::Lint), _) => {
+ span_err(span, "only `#[error(..)]` and `#[warning(..)]` are supported")
+ .help("use the `#[error(...)]` attribute to create a error")
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(DiagnosticDeriveKind::Error), Some(slug)) => {
+ quote! {
+ let mut #diag = #sess.struct_err(rustc_errors::fluent::#slug);
+ }
+ }
+ (Some(DiagnosticDeriveKind::Warn), Some(slug)) => {
+ quote! {
+ let mut #diag = #sess.struct_warn(rustc_errors::fluent::#slug);
+ }
+ }
+ };
+
+ let implementation = quote! {
+ #init
+ #preamble
+ match self {
+ #attrs
+ }
+ match self {
+ #args
+ }
+ #diag
+ };
+ let param_ty = match builder.kind {
+ Some((DiagnosticDeriveKind::Error, _)) => {
+ quote! { rustc_errors::ErrorGuaranteed }
+ }
+ Some((DiagnosticDeriveKind::Lint | DiagnosticDeriveKind::Warn, _)) => {
+ quote! { () }
+ }
+ _ => unreachable!(),
+ };
+
+ (implementation, param_ty)
+ } else {
+ span_err(
+ ast.span().unwrap(),
+ "`#[derive(SessionDiagnostic)]` can only be used on structs",
+ )
+ .emit();
+
+ let implementation = DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ let param_ty = quote! { rustc_errors::ErrorGuaranteed };
+ (implementation, param_ty)
+ }
+ };
+
+ structure.gen_impl(quote! {
+ gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty>
+ for @Self
+ {
+ fn into_diagnostic(
+ self,
+ #sess: &'__session_diagnostic_sess rustc_session::parse::ParseSess
+ ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> {
+ use rustc_errors::IntoDiagnosticArg;
+ #implementation
+ }
+ }
+ })
+ }
+}
+
+/// The central struct for constructing the `decorate_lint` method from an annotated struct.
+pub(crate) struct LintDiagnosticDerive<'a> {
+ structure: Structure<'a>,
+ builder: DiagnosticDeriveBuilder,
+}
+
+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: None,
+ code: None,
+ slug: None,
+ },
+ structure,
+ }
+ }
+
+ 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.kind.value(), builder.slug.value()) {
+ (None, _) => {
+ span_err(span, "diagnostic kind not specified")
+ .help("use the `#[error(...)]` attribute to create an error")
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(kind), None) => {
+ span_err(span, "diagnostic slug not specified")
+ .help(&format!(
+ "specify the slug as the first argument to the attribute, such as \
+ `#[{}(typeck::example_error)]`",
+ kind.descr()
+ ))
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(DiagnosticDeriveKind::Error | DiagnosticDeriveKind::Warn), _) => {
+ span_err(span, "only `#[lint(..)]` is supported")
+ .help("use the `#[lint(...)]` attribute to create a lint")
+ .emit();
+ return DiagnosticDeriveError::ErrorHandled.to_compile_error();
+ }
+ (Some(DiagnosticDeriveKind::Lint), 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 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, ()>) {
+ use rustc_errors::IntoDiagnosticArg;
+ #implementation
+ }
+ }
+ })
+ }
+}
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
new file mode 100644
index 000000000..6c9561925
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -0,0 +1,630 @@
+#![deny(unused_must_use)]
+
+use crate::diagnostics::error::{
+ invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
+ 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,
+};
+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,
+};
+use synstructure::{BindingInfo, Structure};
+
+/// What kind of diagnostic is being derived - an error, a warning or a lint?
+#[derive(Copy, Clone)]
+pub(crate) enum DiagnosticDeriveKind {
+ /// `#[error(..)]`
+ Error,
+ /// `#[warn(..)]`
+ Warn,
+ /// `#[lint(..)]`
+ Lint,
+}
+
+impl DiagnosticDeriveKind {
+ /// Returns human-readable string corresponding to the kind.
+ pub fn descr(&self) -> &'static str {
+ match self {
+ DiagnosticDeriveKind::Error => "error",
+ DiagnosticDeriveKind::Warn => "warning",
+ DiagnosticDeriveKind::Lint => "lint",
+ }
+ }
+}
+
+/// Tracks persistent information required for building up individual calls to diagnostic methods
+/// for generated diagnostic derives - both `SessionDiagnostic` for errors/warnings and
+/// `LintDiagnostic` for lints.
+pub(crate) struct DiagnosticDeriveBuilder {
+ /// The identifier to use for the generated `DiagnosticBuilder` instance.
+ pub diag: syn::Ident,
+
+ /// Store a map of field name to its corresponding field. This is built on construction of the
+ /// derive builder.
+ pub fields: HashMap<String, TokenStream>,
+
+ /// Kind of diagnostic requested via the struct attribute.
+ pub kind: Option<(DiagnosticDeriveKind, proc_macro::Span)>,
+ /// 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)>,
+ /// 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)>,
+}
+
+impl HasFieldMap for DiagnosticDeriveBuilder {
+ fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
+ self.fields.get(field)
+ }
+}
+
+impl DiagnosticDeriveBuilder {
+ pub fn preamble<'s>(&mut self, structure: &Structure<'s>) -> TokenStream {
+ let ast = structure.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())
+ });
+
+ quote! {
+ #(#preamble)*;
+ }
+ }
+
+ 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)
+ }
+
+ /// 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()
+ }
+
+ /// 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
+ }
+
+ /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
+ /// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
+ /// diagnostic builder calls for setting error code and creating note/help messages.
+ fn generate_structure_code_for_attr(
+ &mut self,
+ attr: &Attribute,
+ ) -> Result<TokenStream, DiagnosticDeriveError> {
+ let diag = &self.diag;
+ 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 is_help_note_or_warn = matches!(name, "help" | "note" | "warn_");
+
+ let nested = match meta {
+ // Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` 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]` and `#[note]`
+ Meta::Path(_) if is_help_note_or_warn => {
+ let fn_name = if name == "warn_" {
+ 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" => self.kind.set_once((DiagnosticDeriveKind::Error, span)),
+ "warning" => self.kind.set_once((DiagnosticDeriveKind::Warn, span)),
+ "lint" => self.kind.set_once((DiagnosticDeriveKind::Lint, span)),
+ "help" | "note" | "warn_" => (),
+ _ => throw_invalid_attr!(attr, &meta, |diag| {
+ diag.help(
+ "only `error`, `warning`, `help`, `note` and `warn_` are valid attributes",
+ )
+ }),
+ }
+
+ // First nested element should always be the path, e.g. `#[error(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_help_note_or_warn && nested_iter.next().is_some() {
+ throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ diag.help(
+ "`help`, `note` and `warn_` struct attributes can only have one argument",
+ )
+ });
+ }
+
+ match nested_attr {
+ NestedMeta::Meta(Meta::Path(path)) if is_help_note_or_warn => {
+ let fn_name = proc_macro2::Ident::new(name, attr.span());
+ return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); });
+ }
+ NestedMeta::Meta(Meta::Path(path)) => {
+ self.slug.set_once((path.clone(), span));
+ }
+ NestedMeta::Meta(meta @ Meta::NameValue(_))
+ if !is_help_note_or_warn
+ && meta.path().segments.last().unwrap().ident == "code" =>
+ {
+ // 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")
+ }),
+ };
+ }
+
+ // 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),
+ };
+
+ 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();
+ 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! {
+ #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
+ });
+ }
+ _ => invalid_nested_attr(attr, &nested_attr)
+ .help("only `code` is a valid nested attributes following the slug")
+ .emit(),
+ }
+ } else {
+ invalid_nested_attr(attr, &nested_attr).emit()
+ }
+ }
+
+ Ok(tokens.drain(..).collect())
+ }
+
+ fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
+ 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 needs_move = self.needs_move(&field);
+ let inner_ty = FieldInnerTy::from_type(&field.ty);
+
+ field
+ .attrs
+ .iter()
+ .map(move |attr| {
+ 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)
+ };
+
+ let generated_code = self
+ .generate_inner_field_code(
+ attr,
+ FieldInfo {
+ binding: binding_info,
+ ty: inner_ty.inner_type().unwrap_or(&field.ty),
+ span: &field.span(),
+ },
+ binding,
+ )
+ .unwrap_or_else(|v| v.to_compile_error());
+
+ if needs_destructure {
+ inner_ty.with(field_binding, generated_code)
+ } else {
+ generated_code
+ }
+ })
+ .collect()
+ }
+
+ fn generate_inner_field_code(
+ &mut self,
+ attr: &Attribute,
+ 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 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" => {
+ report_error_if_not_applied_to_span(attr, &info)?;
+ Ok(quote! {
+ #diag.set_span(#binding);
+ })
+ }
+ "label" => {
+ report_error_if_not_applied_to_span(attr, &info)?;
+ Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label }))
+ }
+ "note" | "help" | "warn_" => {
+ 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 }),
+ "warn_" => (&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 `()`")?
+ }
+ }
+ "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",
+ )
+ }),
+ }
+ }
+
+ 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 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);
+ }
+ "label" | "help" | "note" | "warn_" => (),
+ _ => 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),
+ };
+
+ // None of these attributes should have anything following the slug.
+ if nested_iter.next().is_some() {
+ throw_invalid_attr!(attr, &meta);
+ }
+
+ match name {
+ "label" => {
+ report_error_if_not_applied_to_span(attr, &info)?;
+ Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
+ }
+ "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)),
+ // `warn_` must be special-cased because the attribute `warn` already has meaning and
+ // so isn't used, despite the diagnostic API being named `warn`.
+ "warn_" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => Ok(self
+ .add_spanned_subdiagnostic(binding, &Ident::new("warn", Span::call_site()), msg)),
+ "warn_" if type_is_unit(&info.ty) => {
+ Ok(self.add_subdiagnostic(&Ident::new("warn", Span::call_site()), msg))
+ }
+ "note" | "help" | "warn_" => 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());
+ }
+ };
+ // 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",
+ )
+ }),
+ }
+ }
+ _ => 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.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
+ /// and `fluent_attr_identifier`.
+ fn add_spanned_subdiagnostic(
+ &self,
+ field_binding: TokenStream,
+ kind: &Ident,
+ fluent_attr_identifier: Path,
+ ) -> TokenStream {
+ let diag = &self.diag;
+ let fn_name = format_ident!("span_{}", kind);
+ quote! {
+ #diag.#fn_name(
+ #field_binding,
+ rustc_errors::fluent::#fluent_attr_identifier
+ );
+ }
+ }
+
+ /// 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;
+ quote! {
+ #diag.#kind(rustc_errors::fluent::#fluent_attr_identifier);
+ }
+ }
+
+ fn span_and_applicability_of_ty(
+ &self,
+ info: FieldInfo<'_>,
+ ) -> Result<(TokenStream, Option<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))
+ }
+ // If `ty` is `(Span, Applicability)` then return tokens accessing those.
+ Type::Tuple(tup) => {
+ let mut span_idx = None;
+ let mut applicability_idx = None;
+
+ 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`"
+ );
+ }
+ } 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"
+ );
+ }
+ }
+ }
+
+ 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)));
+ }
+
+ 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)`",
+ )
+ });
+ }
+ // 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| {
+ diag.help(
+ "`#[suggestion(...)]` should be applied to fields of type `Span` or \
+ `(Span, Applicability)`",
+ )
+ }),
+ }
+ }
+}
diff --git a/compiler/rustc_macros/src/diagnostics/error.rs b/compiler/rustc_macros/src/diagnostics/error.rs
new file mode 100644
index 000000000..0b1ededa7
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/error.rs
@@ -0,0 +1,141 @@
+use proc_macro::{Diagnostic, Level, MultiSpan};
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{spanned::Spanned, Attribute, Error as SynError, Meta, NestedMeta};
+
+#[derive(Debug)]
+pub(crate) enum DiagnosticDeriveError {
+ SynError(SynError),
+ ErrorHandled,
+}
+
+impl DiagnosticDeriveError {
+ pub(crate) fn to_compile_error(self) -> TokenStream {
+ match self {
+ DiagnosticDeriveError::SynError(e) => e.to_compile_error(),
+ DiagnosticDeriveError::ErrorHandled => {
+ // Return ! to avoid having to create a blank DiagnosticBuilder to return when an
+ // error has already been emitted to the compiler.
+ quote! {
+ { unreachable!(); }
+ }
+ }
+ }
+ }
+}
+
+impl From<SynError> for DiagnosticDeriveError {
+ fn from(e: SynError) -> Self {
+ DiagnosticDeriveError::SynError(e)
+ }
+}
+
+/// Helper function for use with `throw_*` macros - constraints `$f` to an `impl FnOnce`.
+pub(crate) fn _throw_err(
+ diag: Diagnostic,
+ f: impl FnOnce(Diagnostic) -> Diagnostic,
+) -> DiagnosticDeriveError {
+ f(diag).emit();
+ DiagnosticDeriveError::ErrorHandled
+}
+
+/// Helper function for printing `syn::Path` - doesn't handle arguments in paths and these are
+/// unlikely to come up much in use of the macro.
+fn path_to_string(path: &syn::Path) -> String {
+ let mut out = String::new();
+ for (i, segment) in path.segments.iter().enumerate() {
+ if i > 0 || path.leading_colon.is_some() {
+ out.push_str("::");
+ }
+ out.push_str(&segment.ident.to_string());
+ }
+ out
+}
+
+/// Returns an error diagnostic on span `span` with msg `msg`.
+pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic {
+ Diagnostic::spanned(span, Level::Error, msg)
+}
+
+/// Emit a diagnostic on span `$span` with msg `$msg` (optionally performing additional decoration
+/// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
+///
+/// For methods that return a `Result<_, DiagnosticDeriveError>`:
+macro_rules! throw_span_err {
+ ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }};
+ ($span:expr, $msg:expr, $f:expr) => {{
+ let diag = span_err($span, $msg);
+ return Err(crate::diagnostics::error::_throw_err(diag, $f));
+ }};
+}
+
+pub(crate) use throw_span_err;
+
+/// Returns an error diagnostic for an invalid attribute.
+pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic {
+ let span = attr.span().unwrap();
+ let path = path_to_string(&attr.path);
+ match meta {
+ Meta::Path(_) => span_err(span, &format!("`#[{}]` is not a valid attribute", path)),
+ Meta::NameValue(_) => {
+ span_err(span, &format!("`#[{} = ...]` is not a valid attribute", path))
+ }
+ Meta::List(_) => span_err(span, &format!("`#[{}(...)]` is not a valid attribute", path)),
+ }
+}
+
+/// Emit a error diagnostic for an invalid attribute (optionally performing additional decoration
+/// using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
+///
+/// For methods that return a `Result<_, DiagnosticDeriveError>`:
+macro_rules! throw_invalid_attr {
+ ($attr:expr, $meta:expr) => {{ throw_invalid_attr!($attr, $meta, |diag| diag) }};
+ ($attr:expr, $meta:expr, $f:expr) => {{
+ let diag = crate::diagnostics::error::invalid_attr($attr, $meta);
+ return Err(crate::diagnostics::error::_throw_err(diag, $f));
+ }};
+}
+
+pub(crate) use throw_invalid_attr;
+
+/// Returns an error diagnostic for an invalid nested attribute.
+pub(crate) fn invalid_nested_attr(attr: &Attribute, nested: &NestedMeta) -> Diagnostic {
+ let name = attr.path.segments.last().unwrap().ident.to_string();
+ let name = name.as_str();
+
+ let span = nested.span().unwrap();
+ let meta = match nested {
+ syn::NestedMeta::Meta(meta) => meta,
+ syn::NestedMeta::Lit(_) => {
+ return span_err(span, &format!("`#[{}(\"...\")]` is not a valid attribute", name));
+ }
+ };
+
+ let span = meta.span().unwrap();
+ let path = path_to_string(meta.path());
+ match meta {
+ Meta::NameValue(..) => {
+ span_err(span, &format!("`#[{}({} = ...)]` is not a valid attribute", name, path))
+ }
+ Meta::Path(..) => {
+ span_err(span, &format!("`#[{}({})]` is not a valid attribute", name, path))
+ }
+ Meta::List(..) => {
+ span_err(span, &format!("`#[{}({}(...))]` is not a valid attribute", name, path))
+ }
+ }
+}
+
+/// Emit a error diagnostic for an invalid nested attribute (optionally performing additional
+/// decoration using the `FnOnce` passed in `diag`) and return `Err(ErrorHandled)`.
+///
+/// For methods that return a `Result<_, DiagnosticDeriveError>`:
+macro_rules! throw_invalid_nested_attr {
+ ($attr:expr, $nested_attr:expr) => {{ throw_invalid_nested_attr!($attr, $nested_attr, |diag| diag) }};
+ ($attr:expr, $nested_attr:expr, $f:expr) => {{
+ let diag = crate::diagnostics::error::invalid_nested_attr($attr, $nested_attr);
+ return Err(crate::diagnostics::error::_throw_err(diag, $f));
+ }};
+}
+
+pub(crate) use throw_invalid_nested_attr;
diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs
new file mode 100644
index 000000000..562d5e9f4
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/fluent.rs
@@ -0,0 +1,277 @@
+use annotate_snippets::{
+ display_list::DisplayList,
+ snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
+};
+use fluent_bundle::{FluentBundle, FluentError, FluentResource};
+use fluent_syntax::{
+ ast::{Attribute, Entry, Identifier, Message},
+ parser::ParserError,
+};
+use proc_macro::{Diagnostic, Level, Span};
+use proc_macro2::TokenStream;
+use quote::quote;
+use std::{
+ collections::{HashMap, HashSet},
+ fs::File,
+ io::Read,
+ path::{Path, PathBuf},
+};
+use syn::{
+ parse::{Parse, ParseStream},
+ parse_macro_input,
+ punctuated::Punctuated,
+ token, Ident, LitStr, Result,
+};
+use unic_langid::langid;
+
+struct Resource {
+ ident: Ident,
+ #[allow(dead_code)]
+ fat_arrow_token: token::FatArrow,
+ resource: LitStr,
+}
+
+impl Parse for Resource {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ Ok(Resource {
+ ident: input.parse()?,
+ fat_arrow_token: input.parse()?,
+ resource: input.parse()?,
+ })
+ }
+}
+
+struct Resources(Punctuated<Resource, token::Comma>);
+
+impl Parse for Resources {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let mut resources = Punctuated::new();
+ loop {
+ if input.is_empty() || input.peek(token::Brace) {
+ break;
+ }
+ let value = input.parse()?;
+ resources.push_value(value);
+ if !input.peek(token::Comma) {
+ break;
+ }
+ let punct = input.parse()?;
+ resources.push_punct(punct);
+ }
+ Ok(Resources(resources))
+ }
+}
+
+/// Helper function for returning an absolute path for macro-invocation relative file paths.
+///
+/// If the input is already absolute, then the input is returned. If the input is not absolute,
+/// then it is appended to the directory containing the source file with this macro invocation.
+fn invocation_relative_path_to_absolute(span: Span, path: &str) -> PathBuf {
+ let path = Path::new(path);
+ if path.is_absolute() {
+ path.to_path_buf()
+ } else {
+ // `/a/b/c/foo/bar.rs` contains the current macro invocation
+ let mut source_file_path = span.source_file().path();
+ // `/a/b/c/foo/`
+ source_file_path.pop();
+ // `/a/b/c/foo/../locales/en-US/example.ftl`
+ source_file_path.push(path);
+ source_file_path
+ }
+}
+
+/// See [rustc_macros::fluent_messages].
+pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let resources = parse_macro_input!(input as Resources);
+
+ // Cannot iterate over individual messages in a bundle, so do that using the
+ // `FluentResource` instead. Construct a bundle anyway to find out if there are conflicting
+ // messages in the resources.
+ let mut bundle = FluentBundle::new(vec![langid!("en-US")]);
+
+ // Map of Fluent identifiers to the `Span` of the resource that defined them, used for better
+ // diagnostics.
+ let mut previous_defns = HashMap::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();
+
+ let relative_ftl_path = res.resource.value();
+ let absolute_ftl_path =
+ invocation_relative_path_to_absolute(ident_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) {
+ Ok(resource_file) => resource_file,
+ Err(e) => {
+ Diagnostic::spanned(path_span, Level::Error, "could not open Fluent resource")
+ .note(e.to_string())
+ .emit();
+ continue;
+ }
+ };
+ let mut resource_contents = String::new();
+ if let Err(e) = resource_file.read_to_string(&mut resource_contents) {
+ Diagnostic::spanned(path_span, Level::Error, "could not read Fluent resource")
+ .note(e.to_string())
+ .emit();
+ continue;
+ }
+ let resource = match FluentResource::try_new(resource_contents) {
+ Ok(resource) => resource,
+ Err((this, errs)) => {
+ Diagnostic::spanned(path_span, Level::Error, "could not parse Fluent resource")
+ .help("see additional errors emitted")
+ .emit();
+ for ParserError { pos, slice: _, kind } in errs {
+ let mut err = kind.to_string();
+ // Entirely unnecessary string modification so that the error message starts
+ // with a lowercase as rustc errors do.
+ err.replace_range(
+ 0..1,
+ &err.chars().next().unwrap().to_lowercase().to_string(),
+ );
+
+ let line_starts: Vec<usize> = std::iter::once(0)
+ .chain(
+ this.source()
+ .char_indices()
+ .filter_map(|(i, c)| Some(i + 1).filter(|_| c == '\n')),
+ )
+ .collect();
+ let line_start = line_starts
+ .iter()
+ .enumerate()
+ .map(|(line, idx)| (line + 1, idx))
+ .filter(|(_, idx)| **idx <= pos.start)
+ .last()
+ .unwrap()
+ .0;
+
+ let snippet = Snippet {
+ title: Some(Annotation {
+ label: Some(&err),
+ id: None,
+ annotation_type: AnnotationType::Error,
+ }),
+ footer: vec![],
+ slices: vec![Slice {
+ source: this.source(),
+ line_start,
+ origin: Some(&relative_ftl_path),
+ fold: true,
+ annotations: vec![SourceAnnotation {
+ label: "",
+ annotation_type: AnnotationType::Error,
+ range: (pos.start, pos.end - 1),
+ }],
+ }],
+ opt: Default::default(),
+ };
+ let dl = DisplayList::from(snippet);
+ eprintln!("{}\n", dl);
+ }
+ continue;
+ }
+ };
+
+ let mut constants = TokenStream::new();
+ for entry in resource.entries() {
+ let span = res.ident.span();
+ if let Entry::Message(Message { id: Identifier { name }, attributes, .. }) = entry {
+ let _ = previous_defns.entry(name.to_string()).or_insert(ident_span);
+
+ // `typeck-foo-bar` => `foo_bar` (in `typeck.ftl`)
+ // `const-eval-baz` => `baz` (in `const_eval.ftl`)
+ let snake_name = Ident::new(
+ // FIXME: should probably trim prefix, not replace all occurrences
+ &name
+ .replace(&format!("{}-", res.ident).replace('_', "-"), "")
+ .replace('-', "_"),
+ span,
+ );
+ constants.extend(quote! {
+ pub const #snake_name: crate::DiagnosticMessage =
+ crate::DiagnosticMessage::FluentIdentifier(
+ std::borrow::Cow::Borrowed(#name),
+ None
+ );
+ });
+
+ for Attribute { id: Identifier { name: attr_name }, .. } in attributes {
+ let snake_name = Ident::new(&attr_name.replace('-', "_"), span);
+ if !previous_attrs.insert(snake_name.clone()) {
+ continue;
+ }
+
+ constants.extend(quote! {
+ pub const #snake_name: crate::SubdiagnosticMessage =
+ crate::SubdiagnosticMessage::FluentAttr(
+ std::borrow::Cow::Borrowed(#attr_name)
+ );
+ });
+ }
+ }
+ }
+
+ if let Err(errs) = bundle.add_resource(resource) {
+ for e in errs {
+ match e {
+ FluentError::Overriding { kind, id } => {
+ Diagnostic::spanned(
+ ident_span,
+ Level::Error,
+ format!("overrides existing {}: `{}`", kind, id),
+ )
+ .span_help(previous_defns[&id], "previously defined in this resource")
+ .emit();
+ }
+ FluentError::ResolverError(_) | FluentError::ParserError(_) => unreachable!(),
+ }
+ }
+ }
+
+ includes.extend(quote! { include_str!(#relative_ftl_path), });
+
+ let ident = res.ident;
+ generated.extend(quote! {
+ pub mod #ident {
+ #constants
+ }
+ });
+ }
+
+ quote! {
+ #[allow(non_upper_case_globals)]
+ #[doc(hidden)]
+ pub mod fluent_generated {
+ pub static DEFAULT_LOCALE_RESOURCES: &'static [&'static str] = &[
+ #includes
+ ];
+
+ #generated
+
+ pub mod _subdiag {
+ pub const help: crate::SubdiagnosticMessage =
+ crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("help"));
+ pub const note: crate::SubdiagnosticMessage =
+ crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("note"));
+ pub const warn: crate::SubdiagnosticMessage =
+ crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("warn"));
+ pub const label: crate::SubdiagnosticMessage =
+ crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("label"));
+ pub const suggestion: crate::SubdiagnosticMessage =
+ crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("suggestion"));
+ }
+ }
+ }
+ .into()
+}
diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs
new file mode 100644
index 000000000..399790026
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/mod.rs
@@ -0,0 +1,159 @@
+mod diagnostic;
+mod diagnostic_builder;
+mod error;
+mod fluent;
+mod subdiagnostic;
+mod utils;
+
+use diagnostic::{LintDiagnosticDerive, SessionDiagnosticDerive};
+pub(crate) use fluent::fluent_messages;
+use proc_macro2::TokenStream;
+use quote::format_ident;
+use subdiagnostic::SessionSubdiagnosticDerive;
+use synstructure::Structure;
+
+/// Implements `#[derive(SessionDiagnostic)]`, which allows for errors to be specified as a struct,
+/// independent from the actual diagnostics emitting code.
+///
+/// ```ignore (rust)
+/// # extern crate rustc_errors;
+/// # use rustc_errors::Applicability;
+/// # extern crate rustc_span;
+/// # use rustc_span::{symbol::Ident, Span};
+/// # extern crate rust_middle;
+/// # use rustc_middle::ty::Ty;
+/// #[derive(SessionDiagnostic)]
+/// #[error(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)]
+/// pub first_borrow_span: Span,
+/// #[suggestion(code = "{name}.clone()")]
+/// pub clone_sugg: Option<(Span, Applicability)>
+/// }
+/// ```
+///
+/// ```fluent
+/// move-out-of-borrow = cannot move out of {$name} because it is borrowed
+/// .label = cannot move out of borrow
+/// .first-borrow-label = `{$ty}` first borrowed here
+/// .suggestion = consider cloning here
+/// ```
+///
+/// Then, later, to emit the error:
+///
+/// ```ignore (rust)
+/// sess.emit_err(MoveOutOfBorrowError {
+/// expected,
+/// actual,
+/// span,
+/// first_borrow_span,
+/// clone_sugg: Some(suggestion, Applicability::MachineApplicable),
+/// });
+/// ```
+///
+/// See rustc dev guide for more examples on using the `#[derive(SessionDiagnostic)]`:
+/// <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()
+}
+
+/// Implements `#[derive(LintDiagnostic)]`, which allows for lints to be specified as a struct,
+/// independent from the actual lint emitting code.
+///
+/// ```ignore (rust)
+/// #[derive(LintDiagnostic)]
+/// #[lint(lint::atomic_ordering_invalid_fail_success)]
+/// pub struct AtomicOrderingInvalidLint {
+/// method: Symbol,
+/// success_ordering: Symbol,
+/// fail_ordering: Symbol,
+/// #[label(lint::fail_label)]
+/// fail_order_arg_span: Span,
+/// #[label(lint::success_label)]
+/// #[suggestion(
+/// code = "std::sync::atomic::Ordering::{success_suggestion}",
+/// applicability = "maybe-incorrect"
+/// )]
+/// success_order_arg_span: Span,
+/// }
+/// ```
+///
+/// ```fluent
+/// lint-atomic-ordering-invalid-fail-success = `{$method}`'s success ordering must be at least as strong as its failure ordering
+/// .fail-label = `{$fail_ordering}` failure ordering
+/// .success-label = `{$success_ordering}` success ordering
+/// .suggestion = consider using `{$success_suggestion}` success ordering instead
+/// ```
+///
+/// Then, later, to emit the error:
+///
+/// ```ignore (rust)
+/// cx.struct_span_lint(INVALID_ATOMIC_ORDERING, fail_order_arg_span, AtomicOrderingInvalidLint {
+/// method,
+/// success_ordering,
+/// fail_ordering,
+/// fail_order_arg_span,
+/// success_order_arg_span,
+/// });
+/// ```
+///
+/// See rustc dev guide for more examples on using the `#[derive(LintDiagnostic)]`:
+/// <https://rustc-dev-guide.rust-lang.org/diagnostics/sessiondiagnostic.html>
+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
+/// suggestions to be specified as a structs or enums, independent from the actual diagnostics
+/// emitting code or diagnostic derives.
+///
+/// ```ignore (rust)
+/// #[derive(SessionSubdiagnostic)]
+/// pub enum ExpectedIdentifierLabel<'tcx> {
+/// #[label(parser::expected_identifier)]
+/// WithoutFound {
+/// #[primary_span]
+/// span: Span,
+/// }
+/// #[label(parser::expected_identifier_found)]
+/// WithFound {
+/// #[primary_span]
+/// span: Span,
+/// found: String,
+/// }
+/// }
+///
+/// #[derive(SessionSubdiagnostic)]
+/// #[suggestion_verbose(parser::raw_identifier)]
+/// pub struct RawIdentifierSuggestion<'tcx> {
+/// #[primary_span]
+/// span: Span,
+/// #[applicability]
+/// applicability: Applicability,
+/// ident: Ident,
+/// }
+/// ```
+///
+/// ```fluent
+/// parser-expected-identifier = expected identifier
+///
+/// parser-expected-identifier-found = expected identifier, found {$found}
+///
+/// parser-raw-identifier = escape `{$ident}` to use it as an identifier
+/// ```
+///
+/// Then, later, to add the subdiagnostic:
+///
+/// ```ignore (rust)
+/// diag.subdiagnostic(ExpectedIdentifierLabel::WithoutFound { span });
+///
+/// diag.subdiagnostic(RawIdentifierSuggestion { span, applicability, ident });
+/// ```
+pub fn session_subdiagnostic_derive(s: Structure<'_>) -> TokenStream {
+ SessionSubdiagnosticDerive::new(s).into_tokens()
+}
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
new file mode 100644
index 000000000..edf4dbed9
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -0,0 +1,491 @@
+#![deny(unused_must_use)]
+
+use crate::diagnostics::error::{
+ 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,
+};
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+use std::collections::HashMap;
+use std::fmt;
+use std::str::FromStr;
+use syn::{parse_quote, spanned::Spanned, Meta, MetaList, MetaNameValue, 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,
+}
+
+/// Which kind of subdiagnostic is being created from a variant?
+#[derive(Clone, Copy)]
+enum SubdiagnosticKind {
+ /// `#[label(...)]`
+ Label,
+ /// `#[note(...)]`
+ Note,
+ /// `#[help(...)]`
+ Help,
+ /// `#[warn_(...)]`
+ Warn,
+ /// `#[suggestion{,_short,_hidden,_verbose}]`
+ Suggestion(SubdiagnosticSuggestionKind),
+}
+
+impl FromStr for SubdiagnosticKind {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "label" => Ok(SubdiagnosticKind::Label),
+ "note" => Ok(SubdiagnosticKind::Note),
+ "help" => Ok(SubdiagnosticKind::Help),
+ "warn_" => Ok(SubdiagnosticKind::Warn),
+ "suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)),
+ "suggestion_short" => {
+ Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short))
+ }
+ "suggestion_hidden" => {
+ Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden))
+ }
+ "suggestion_verbose" => {
+ Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose))
+ }
+ _ => Err(()),
+ }
+ }
+}
+
+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(SubdiagnosticSuggestionKind::Normal) => {
+ write!(f, "suggestion")
+ }
+ SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short) => {
+ write!(f, "suggestion_short")
+ }
+ SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden) => {
+ write!(f, "suggestion_hidden")
+ }
+ SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose) => {
+ write!(f, "suggestion_verbose")
+ }
+ }
+ }
+
+ fn span(&self) -> Option<proc_macro2::Span> {
+ None
+ }
+}
+
+/// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
+pub(crate) struct SessionSubdiagnosticDerive<'a> {
+ structure: Structure<'a>,
+ diag: syn::Ident,
+}
+
+impl<'a> SessionSubdiagnosticDerive<'a> {
+ pub(crate) fn new(structure: Structure<'a>) -> Self {
+ let diag = format_ident!("diag");
+ Self { structure, diag }
+ }
+
+ pub(crate) fn into_tokens(self) -> TokenStream {
+ let SessionSubdiagnosticDerive { mut structure, diag } = self;
+ let implementation = {
+ let ast = structure.ast();
+ let span = ast.span().unwrap();
+ match ast.data {
+ syn::Data::Struct(..) | syn::Data::Enum(..) => (),
+ syn::Data::Union(..) => {
+ span_err(
+ span,
+ "`#[derive(SessionSubdiagnostic)]` 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 subdiagnostic enum",
+ )
+ .emit();
+ }
+ }
+
+ 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,
+ variant,
+ span,
+ fields: fields_map,
+ kind: None,
+ slug: None,
+ code: None,
+ span_field: None,
+ applicability: None,
+ };
+ builder.into_tokens().unwrap_or_else(|v| v.to_compile_error())
+ });
+
+ quote! {
+ match self {
+ #variants_
+ }
+ }
+ };
+
+ let ret = structure.gen_impl(quote! {
+ gen impl rustc_errors::AddSubdiagnostic for @Self {
+ fn add_to_diagnostic(self, #diag: &mut rustc_errors::Diagnostic) {
+ use rustc_errors::{Applicability, IntoDiagnosticArg};
+ #implementation
+ }
+ }
+ });
+ ret
+ }
+}
+
+/// 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`
+/// 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> {
+ /// The identifier to use for the generated `DiagnosticBuilder` instance.
+ diag: &'a syn::Ident,
+
+ /// 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,
+
+ /// Store a map of field name to its corresponding field. This is built on construction of the
+ /// derive builder.
+ fields: HashMap<String, TokenStream>,
+
+ /// Subdiagnostic kind of the type/variant.
+ kind: Option<(SubdiagnosticKind, proc_macro::Span)>,
+
+ /// Slug of the subdiagnostic - corresponds to the Fluent identifier for the message - from the
+ /// `#[kind(slug)]` attribute on the type or variant.
+ slug: Option<(Path, proc_macro::Span)>,
+ /// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]`
+ /// attribute on the type or variant.
+ code: Option<(TokenStream, proc_macro::Span)>,
+
+ /// 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)>,
+}
+
+impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
+ fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
+ self.fields.get(field)
+ }
+}
+
+impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
+ fn identify_kind(&mut self) -> Result<(), DiagnosticDeriveError> {
+ 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 kind = match meta {
+ Meta::List(MetaList { ref nested, .. }) => {
+ let mut nested_iter = nested.into_iter();
+ if let Some(nested_attr) = nested_iter.next() {
+ match nested_attr {
+ NestedMeta::Meta(Meta::Path(path)) => {
+ self.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();
+
+ match meta {
+ Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
+ match nested_name {
+ "code" => {
+ let formatted_str = self.build_format(&s.value(), s.span());
+ self.code.set_once((formatted_str, span));
+ }
+ "applicability" => {
+ let value = match Applicability::from_str(&s.value()) {
+ Ok(v) => v,
+ Err(()) => {
+ span_err(span, "invalid applicability").emit();
+ Applicability::Unspecified
+ }
+ };
+ self.applicability.set_once((quote! { #value }, span));
+ }
+ _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ diag.help(
+ "only `code` and `applicability` are valid nested \
+ attributes",
+ )
+ }),
+ }
+ }
+ _ => 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 Ok(kind) = SubdiagnosticKind::from_str(name) else {
+ throw_invalid_attr!(attr, &meta)
+ };
+
+ kind
+ }
+ _ => throw_invalid_attr!(attr, &meta),
+ };
+
+ if matches!(
+ kind,
+ SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note
+ ) && self.code.is_some()
+ {
+ throw_span_err!(
+ span,
+ &format!("`code` is not a valid nested attribute of a `{}` attribute", name)
+ );
+ }
+
+ if matches!(
+ kind,
+ SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note
+ ) && self.applicability.is_some()
+ {
+ throw_span_err!(
+ span,
+ &format!(
+ "`applicability` is not a valid nested attribute of a `{}` attribute",
+ name
+ )
+ );
+ }
+
+ if self.slug.is_none() {
+ throw_span_err!(
+ span,
+ &format!(
+ "diagnostic slug must be first argument of a `#[{}(...)]` attribute",
+ name
+ )
+ );
+ }
+
+ self.kind.set_once((kind, span));
+ }
+
+ Ok(())
+ }
+
+ fn generate_field_code(
+ &mut self,
+ binding: &BindingInfo<'_>,
+ is_suggestion: bool,
+ ) -> Result<TokenStream, DiagnosticDeriveError> {
+ let ast = binding.ast();
+
+ let inner_ty = FieldInnerTy::from_type(&ast.ty);
+ let info = FieldInfo {
+ binding: binding,
+ ty: inner_ty.inner_type().unwrap_or(&ast.ty),
+ span: &ast.span(),
+ };
+
+ for attr in &ast.attrs {
+ let name = attr.path.segments.last().unwrap().ident.to_string();
+ let name = name.as_str();
+ let span = attr.span().unwrap();
+
+ let meta = attr.parse_meta()?;
+ match meta {
+ Meta::Path(_) => match name {
+ "primary_span" => {
+ report_error_if_not_applied_to_span(attr, &info)?;
+ self.span_field.set_once((binding.binding.clone(), span));
+ return Ok(quote! {});
+ }
+ "applicability" if is_suggestion => {
+ report_error_if_not_applied_to_applicability(attr, &info)?;
+ let binding = binding.binding.clone();
+ self.applicability.set_once((quote! { #binding }, span));
+ return Ok(quote! {});
+ }
+ "applicability" => {
+ span_err(span, "`#[applicability]` is only valid on suggestions").emit();
+ return Ok(quote! {});
+ }
+ "skip_arg" => {
+ return Ok(quote! {});
+ }
+ _ => throw_invalid_attr!(attr, &meta, |diag| {
+ diag.help(
+ "only `primary_span`, `applicability` and `skip_arg` are valid field \
+ attributes",
+ )
+ }),
+ },
+ _ => throw_invalid_attr!(attr, &meta),
+ }
+ }
+
+ let ident = ast.ident.as_ref().unwrap();
+
+ let diag = &self.diag;
+ let generated = quote! {
+ #diag.set_arg(
+ stringify!(#ident),
+ #binding
+ );
+ };
+
+ Ok(inner_ty.with(binding, generated))
+ }
+
+ fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
+ self.identify_kind()?;
+ let Some(kind) = self.kind.map(|(kind, _)| kind) else {
+ throw_span_err!(
+ self.variant.ast().ident.span().unwrap(),
+ "subdiagnostic kind not specified"
+ );
+ };
+
+ let is_suggestion = matches!(kind, SubdiagnosticKind::Suggestion(_));
+
+ let mut args = TokenStream::new();
+ for binding in self.variant.bindings() {
+ let arg = self
+ .generate_field_code(binding, is_suggestion)
+ .unwrap_or_else(|v| v.to_compile_error());
+ args.extend(arg);
+ }
+
+ // Missing slug errors will already have been reported.
+ let slug = self
+ .slug
+ .as_ref()
+ .map(|(slug, _)| slug.clone())
+ .unwrap_or_else(|| parse_quote! { you::need::to::specify::a::slug });
+ let code = match self.code.as_ref() {
+ Some((code, _)) => Some(quote! { #code }),
+ None if is_suggestion => {
+ span_err(self.span, "suggestion without `code = \"...\"`").emit();
+ Some(quote! { /* macro error */ "..." })
+ }
+ None => None,
+ };
+
+ let span_field = self.span_field.as_ref().map(|(span, _)| span);
+ let applicability = match self.applicability.clone() {
+ Some((applicability, _)) => Some(applicability),
+ None if is_suggestion => {
+ span_err(self.span, "suggestion without `applicability`").emit();
+ Some(quote! { rustc_errors::Applicability::Unspecified })
+ }
+ None => None,
+ };
+
+ let diag = &self.diag;
+ let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
+ let message = quote! { rustc_errors::fluent::#slug };
+ let call = if matches!(kind, SubdiagnosticKind::Suggestion(..)) {
+ if let Some(span) = span_field {
+ quote! { #diag.#name(#span, #message, #code, #applicability); }
+ } else {
+ span_err(self.span, "suggestion without `#[primary_span]` field").emit();
+ quote! { unreachable!(); }
+ }
+ } else if matches!(kind, SubdiagnosticKind::Label) {
+ if let Some(span) = span_field {
+ quote! { #diag.#name(#span, #message); }
+ } else {
+ span_err(self.span, "label without `#[primary_span]` field").emit();
+ quote! { unreachable!(); }
+ }
+ } else {
+ if let Some(span) = span_field {
+ quote! { #diag.#name(#span, #message); }
+ } else {
+ quote! { #diag.#name(#message); }
+ }
+ };
+
+ Ok(quote! {
+ #call
+ #args
+ })
+ }
+}
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
new file mode 100644
index 000000000..002abb152
--- /dev/null
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -0,0 +1,356 @@
+use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError};
+use proc_macro::Span;
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote, ToTokens};
+use std::collections::{BTreeSet, HashMap};
+use std::str::FromStr;
+use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
+use synstructure::{BindingInfo, Structure};
+
+/// 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
+/// `a::b::c::Foo`. This reasonably allows qualified names to be used in the macro.
+pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool {
+ if let Type::Path(ty) = ty {
+ ty.path
+ .segments
+ .iter()
+ .map(|s| s.ident.to_string())
+ .rev()
+ .zip(name.iter().rev())
+ .all(|(x, y)| &x.as_str() == y)
+ } else {
+ false
+ }
+}
+
+/// Checks whether the type `ty` is `()`.
+pub(crate) fn type_is_unit(ty: &Type) -> bool {
+ if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false }
+}
+
+/// Reports a type error for field with `attr`.
+pub(crate) fn report_type_error(
+ attr: &Attribute,
+ ty_name: &str,
+) -> Result<!, DiagnosticDeriveError> {
+ let name = attr.path.segments.last().unwrap().ident.to_string();
+ let meta = attr.parse_meta()?;
+
+ throw_span_err!(
+ attr.span().unwrap(),
+ &format!(
+ "the `#[{}{}]` attribute can only be applied to fields of type {}",
+ name,
+ match meta {
+ Meta::Path(_) => "",
+ Meta::NameValue(_) => " = ...",
+ Meta::List(_) => "(...)",
+ },
+ ty_name
+ )
+ );
+}
+
+/// Reports an error if the field's type does not match `path`.
+fn report_error_if_not_applied_to_ty(
+ attr: &Attribute,
+ info: &FieldInfo<'_>,
+ path: &[&str],
+ ty_name: &str,
+) -> Result<(), DiagnosticDeriveError> {
+ if !type_matches_path(&info.ty, path) {
+ report_type_error(attr, ty_name)?;
+ }
+
+ Ok(())
+}
+
+/// Reports an error if the field's type is not `Applicability`.
+pub(crate) fn report_error_if_not_applied_to_applicability(
+ attr: &Attribute,
+ info: &FieldInfo<'_>,
+) -> Result<(), DiagnosticDeriveError> {
+ report_error_if_not_applied_to_ty(
+ attr,
+ info,
+ &["rustc_errors", "Applicability"],
+ "`Applicability`",
+ )
+}
+
+/// Reports an error if the field's type is not `Span`.
+pub(crate) fn report_error_if_not_applied_to_span(
+ attr: &Attribute,
+ info: &FieldInfo<'_>,
+) -> Result<(), DiagnosticDeriveError> {
+ if !type_matches_path(&info.ty, &["rustc_span", "Span"])
+ && !type_matches_path(&info.ty, &["rustc_errors", "MultiSpan"])
+ {
+ report_type_error(attr, "`Span` or `MultiSpan`")?;
+ }
+
+ Ok(())
+}
+
+/// Inner type of a field and type of wrapper.
+pub(crate) enum FieldInnerTy<'ty> {
+ /// Field is wrapped in a `Option<$inner>`.
+ Option(&'ty Type),
+ /// Field is wrapped in a `Vec<$inner>`.
+ Vec(&'ty Type),
+ /// Field isn't wrapped in an outer type.
+ None,
+}
+
+impl<'ty> FieldInnerTy<'ty> {
+ /// Returns inner type for a field, if there is one.
+ ///
+ /// - If `ty` is an `Option`, returns `FieldInnerTy::Option { inner: (inner type) }`.
+ /// - If `ty` is a `Vec`, returns `FieldInnerTy::Vec { inner: (inner type) }`.
+ /// - Otherwise returns `None`.
+ pub(crate) fn from_type(ty: &'ty Type) -> Self {
+ let variant: &dyn Fn(&'ty Type) -> FieldInnerTy<'ty> =
+ if type_matches_path(ty, &["std", "option", "Option"]) {
+ &FieldInnerTy::Option
+ } else if type_matches_path(ty, &["std", "vec", "Vec"]) {
+ &FieldInnerTy::Vec
+ } else {
+ return FieldInnerTy::None;
+ };
+
+ if let Type::Path(ty_path) = ty {
+ let path = &ty_path.path;
+ let ty = path.segments.iter().last().unwrap();
+ if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments {
+ if bracketed.args.len() == 1 {
+ if let syn::GenericArgument::Type(ty) = &bracketed.args[0] {
+ return variant(ty);
+ }
+ }
+ }
+ }
+
+ unreachable!();
+ }
+
+ /// Returns `Option` containing inner type if there is one.
+ pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
+ match self {
+ FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) => Some(inner),
+ FieldInnerTy::None => None,
+ }
+ }
+
+ /// Surrounds `inner` with destructured wrapper type, exposing inner type as `binding`.
+ pub(crate) fn with(&self, binding: impl ToTokens, inner: impl ToTokens) -> TokenStream {
+ match self {
+ FieldInnerTy::Option(..) => quote! {
+ if let Some(#binding) = #binding {
+ #inner
+ }
+ },
+ FieldInnerTy::Vec(..) => quote! {
+ for #binding in #binding {
+ #inner
+ }
+ },
+ FieldInnerTy::None => quote! { #inner },
+ }
+ }
+}
+
+/// Field information passed to the builder. Deliberately omits attrs to discourage the
+/// `generate_*` methods from walking the attributes themselves.
+pub(crate) struct FieldInfo<'a> {
+ pub(crate) binding: &'a BindingInfo<'a>,
+ pub(crate) ty: &'a Type,
+ pub(crate) span: &'a proc_macro2::Span,
+}
+
+/// 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 value(self) -> Option<T>;
+}
+
+impl<T> SetOnce<T> for Option<(T, Span)> {
+ fn set_once(&mut self, (value, span): (T, Span)) {
+ match self {
+ None => {
+ *self = Some((value, span));
+ }
+ Some((_, prev_span)) => {
+ span_err(span, "specified multiple times")
+ .span_note(*prev_span, "previously specified here")
+ .emit();
+ }
+ }
+ }
+
+ fn value(self) -> Option<T> {
+ self.map(|(v, _)| v)
+ }
+}
+
+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>;
+
+ /// In the strings in the attributes supplied to this macro, we want callers to be able to
+ /// reference fields in the format string. For example:
+ ///
+ /// ```ignore (not-usage-example)
+ /// /// Suggest `==` when users wrote `===`.
+ /// #[suggestion(slug = "parser-not-javascript-eq", code = "{lhs} == {rhs}")]
+ /// struct NotJavaScriptEq {
+ /// #[primary_span]
+ /// span: Span,
+ /// lhs: Ident,
+ /// rhs: Ident,
+ /// }
+ /// ```
+ ///
+ /// We want to automatically pick up that `{lhs}` refers `self.lhs` and `{rhs}` refers to
+ /// `self.rhs`, then generate this call to `format!`:
+ ///
+ /// ```ignore (not-usage-example)
+ /// format!("{lhs} == {rhs}", lhs = self.lhs, rhs = self.rhs)
+ /// ```
+ ///
+ /// This function builds the entire call to `format!`.
+ fn build_format(&self, input: &str, span: proc_macro2::Span) -> TokenStream {
+ // This set is used later to generate the final format string. To keep builds reproducible,
+ // the iteration order needs to be deterministic, hence why we use a `BTreeSet` here
+ // instead of a `HashSet`.
+ let mut referenced_fields: BTreeSet<String> = BTreeSet::new();
+
+ // At this point, we can start parsing the format string.
+ let mut it = input.chars().peekable();
+
+ // Once the start of a format string has been found, process the format string and spit out
+ // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so
+ // the next call to `it.next()` retrieves the next character.
+ while let Some(c) = it.next() {
+ if c == '{' && *it.peek().unwrap_or(&'\0') != '{' {
+ let mut eat_argument = || -> Option<String> {
+ let mut result = String::new();
+ // Format specifiers look like:
+ //
+ // format := '{' [ argument ] [ ':' format_spec ] '}' .
+ //
+ // Therefore, we only need to eat until ':' or '}' to find the argument.
+ while let Some(c) = it.next() {
+ result.push(c);
+ let next = *it.peek().unwrap_or(&'\0');
+ if next == '}' {
+ break;
+ } else if next == ':' {
+ // Eat the ':' character.
+ assert_eq!(it.next().unwrap(), ':');
+ break;
+ }
+ }
+ // Eat until (and including) the matching '}'
+ while it.next()? != '}' {
+ continue;
+ }
+ Some(result)
+ };
+
+ if let Some(referenced_field) = eat_argument() {
+ referenced_fields.insert(referenced_field);
+ }
+ }
+ }
+
+ // At this point, `referenced_fields` contains a set of the unique fields that were
+ // referenced in the format string. Generate the corresponding "x = self.x" format
+ // string parameters:
+ let args = referenced_fields.into_iter().map(|field: String| {
+ let field_ident = format_ident!("{}", field);
+ let value = match self.get_field_binding(&field) {
+ Some(value) => value.clone(),
+ // This field doesn't exist. Emit a diagnostic.
+ None => {
+ span_err(
+ span.unwrap(),
+ &format!("`{}` doesn't refer to a field on this type", field),
+ )
+ .emit();
+ quote! {
+ "{#field}"
+ }
+ }
+ };
+ quote! {
+ #field_ident = #value
+ }
+ });
+ quote! {
+ format!(#input #(,#args)*)
+ }
+ }
+}
+
+/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
+/// the user's selection of applicability if specified in an attribute.
+pub(crate) enum Applicability {
+ MachineApplicable,
+ MaybeIncorrect,
+ HasPlaceholders,
+ Unspecified,
+}
+
+impl FromStr for Applicability {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "machine-applicable" => Ok(Applicability::MachineApplicable),
+ "maybe-incorrect" => Ok(Applicability::MaybeIncorrect),
+ "has-placeholders" => Ok(Applicability::HasPlaceholders),
+ "unspecified" => Ok(Applicability::Unspecified),
+ _ => Err(()),
+ }
+ }
+}
+
+impl quote::ToTokens for Applicability {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ tokens.extend(match self {
+ Applicability::MachineApplicable => {
+ quote! { rustc_errors::Applicability::MachineApplicable }
+ }
+ Applicability::MaybeIncorrect => {
+ quote! { rustc_errors::Applicability::MaybeIncorrect }
+ }
+ Applicability::HasPlaceholders => {
+ quote! { rustc_errors::Applicability::HasPlaceholders }
+ }
+ Applicability::Unspecified => {
+ quote! { rustc_errors::Applicability::Unspecified }
+ }
+ });
+ }
+}
+
+/// 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 });
+ }
+ }
+ }
+
+ fields_map
+}