Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 45 additions & 43 deletions compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned};
use syn::parse::ParseStream;
use syn::spanned::Spanned;
use syn::{Attribute, Meta, Path, Token, Type, parse_quote};
use synstructure::{BindingInfo, Structure, VariantInfo};
Expand All @@ -11,7 +12,7 @@ use crate::diagnostics::error::{
DiagnosticDeriveError, span_err, throw_invalid_attr, throw_span_err,
};
use crate::diagnostics::utils::{
FieldInfo, FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
FieldInfo, FieldInnerTy, FieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error,
should_generate_arg, type_is_bool, type_is_unit, type_matches_path,
};
Expand Down Expand Up @@ -42,19 +43,13 @@ pub(crate) struct DiagnosticDeriveVariantBuilder {

/// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
/// has the actual diagnostic message.
pub slug: SpannedOption<Path>,
pub slug: Option<Path>,

/// Error codes are a optional part of the struct attribute - this is only set to detect
/// multiple specifications.
pub code: SpannedOption<()>,
}

impl HasFieldMap for DiagnosticDeriveVariantBuilder {
fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
self.field_map.get(field)
}
}

impl DiagnosticDeriveKind {
/// 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
Expand Down Expand Up @@ -111,7 +106,7 @@ impl DiagnosticDeriveKind {

impl DiagnosticDeriveVariantBuilder {
pub(crate) fn primary_message(&self) -> Option<&Path> {
match self.slug.value_ref() {
match self.slug.as_ref() {
None => {
span_err(self.span, "diagnostic slug not specified")
.help(
Expand Down Expand Up @@ -169,7 +164,7 @@ impl DiagnosticDeriveVariantBuilder {
&self,
attr: &Attribute,
) -> Result<Option<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> {
let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, self)? else {
let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, &self.field_map)? else {
// Some attributes aren't errors - like documentation comments - but also aren't
// subdiagnostics.
return Ok(None);
Expand All @@ -191,7 +186,7 @@ impl DiagnosticDeriveVariantBuilder {
SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
});

Ok(Some((subdiag.kind, slug, subdiag.no_span)))
Ok(Some((subdiag.kind, slug, false)))
}

/// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
Expand All @@ -209,47 +204,54 @@ impl DiagnosticDeriveVariantBuilder {
let name = attr.path().segments.last().unwrap().ident.to_string();
let name = name.as_str();

let mut first = true;

if name == "diag" {
let mut tokens = TokenStream::new();
attr.parse_nested_meta(|nested| {
let path = &nested.path;
attr.parse_args_with(|input: ParseStream<'_>| {
let mut input = &*input;
let slug_recovery_point = input.fork();

if first && (nested.input.is_empty() || nested.input.peek(Token![,])) {
self.slug.set_once(path.clone(), path.span().unwrap());
first = false;
return Ok(());
let slug = input.parse::<Path>()?;
if input.is_empty() || input.peek(Token![,]) {
self.slug = Some(slug);
} else {
input = &slug_recovery_point;
}

first = false;

let Ok(nested) = nested.value() else {
span_err(
nested.input.span().unwrap(),
"diagnostic slug must be the first argument",
)
.emit();
return Ok(());
};

if path.is_ident("code") {
self.code.set_once((), path.span().unwrap());

let code = nested.parse::<syn::Expr>()?;
tokens.extend(quote! {
diag.code(#code);
});
} else {
span_err(path.span().unwrap(), "unknown argument")
.note("only the `code` parameter is valid after the slug")
while !input.is_empty() {
input.parse::<Token![,]>()?;
// Allow trailing comma
if input.is_empty() {
break;
}
let arg_name: Path = input.parse::<Path>()?;
if input.peek(Token![,]) {
span_err(
arg_name.span().unwrap(),
"diagnostic slug must be the first argument",
)
.emit();

// consume the buffer so we don't have syntax errors from syn
let _ = nested.parse::<TokenStream>();
continue;
}
let arg_name = arg_name.require_ident()?;
input.parse::<Token![=]>()?;
let arg_value = input.parse::<syn::Expr>()?;
match arg_name.to_string().as_str() {
"code" => {
self.code.set_once((), arg_name.span().unwrap());
tokens.extend(quote! {
diag.code(#arg_value);
});
}
_ => {
span_err(arg_name.span().unwrap(), "unknown argument")
.note("only the `code` parameter is valid after the slug")
.emit();
}
}
}
Ok(())
})?;

return Ok(tokens);
}

Expand Down
90 changes: 44 additions & 46 deletions compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
#![deny(unused_must_use)]

use proc_macro2::TokenStream;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use syn::parse::ParseStream;
use syn::spanned::Spanned;
use syn::{Attribute, Meta, MetaList, Path};
use syn::{Attribute, Meta, MetaList, Path, Token};
use synstructure::{BindingInfo, Structure, VariantInfo};

use super::utils::SubdiagnosticVariant;
use crate::diagnostics::error::{
DiagnosticDeriveError, invalid_attr, span_err, throw_invalid_attr, throw_span_err,
};
use crate::diagnostics::utils::{
AllowMultipleAlternatives, FieldInfo, FieldInnerTy, FieldMap, HasFieldMap, SetOnce,
SpannedOption, SubdiagnosticKind, build_field_mapping, build_suggestion_code, is_doc_comment,
new_code_ident, report_error_if_not_applied_to_applicability,
report_error_if_not_applied_to_span, should_generate_arg,
AllowMultipleAlternatives, FieldInfo, FieldInnerTy, FieldMap, SetOnce, SpannedOption,
SubdiagnosticKind, build_field_mapping, build_suggestion_code, is_doc_comment, new_code_ident,
report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
should_generate_arg,
};

/// The central struct for constructing the `add_to_diag` method from an annotated struct.
Expand Down Expand Up @@ -142,12 +143,6 @@ struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
is_enum: bool,
}

impl<'parent, 'a> HasFieldMap for SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
self.fields.get(field)
}
}

/// Provides frequently-needed information about the diagnostic kinds being derived for this type.
#[derive(Clone, Copy, Debug)]
struct KindsStatistics {
Expand Down Expand Up @@ -187,14 +182,12 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
}

impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
fn identify_kind(
&mut self,
) -> Result<Vec<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> {
fn identify_kind(&mut self) -> Result<Vec<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
let mut kind_slugs = vec![];

for attr in self.variant.ast().attrs {
let Some(SubdiagnosticVariant { kind, slug, no_span }) =
SubdiagnosticVariant::from_attr(attr, self)?
let Some(SubdiagnosticVariant { kind, slug }) =
SubdiagnosticVariant::from_attr(attr, &self.fields)?
else {
// Some attributes aren't errors - like documentation comments - but also aren't
// subdiagnostics.
Expand All @@ -213,7 +206,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
);
};

kind_slugs.push((kind, slug, no_span));
kind_slugs.push((kind, slug));
}

Ok(kind_slugs)
Expand Down Expand Up @@ -437,23 +430,35 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {

let mut code = None;

list.parse_nested_meta(|nested| {
if nested.path.is_ident("code") {
let code_field = new_code_ident();
let span = nested.path.span().unwrap();
let formatting_init = build_suggestion_code(
&code_field,
nested,
self,
AllowMultipleAlternatives::No,
);
code.set_once((code_field, formatting_init), span);
} else {
span_err(
nested.path.span().unwrap(),
"`code` is the only valid nested attribute",
)
.emit();
list.parse_args_with(|input: ParseStream<'_>| {
while !input.is_empty() {
let arg_name = input.parse::<Ident>()?;
match arg_name.to_string().as_str() {
"code" => {
let code_field = new_code_ident();
let formatting_init = build_suggestion_code(
&code_field,
input,
&self.fields,
AllowMultipleAlternatives::No,
)?;
code.set_once(
(code_field, formatting_init),
arg_name.span().unwrap(),
);
}
_ => {
span_err(
arg_name.span().unwrap(),
"`code` is the only valid nested attribute",
)
.emit();
}
}
if input.is_empty() {
break;
}
input.parse::<Token![,]>()?;
}
Ok(())
})?;
Expand Down Expand Up @@ -492,8 +497,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
pub(crate) fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
let kind_slugs = self.identify_kind()?;

let kind_stats: KindsStatistics =
kind_slugs.iter().map(|(kind, _slug, _no_span)| kind).collect();
let kind_stats: KindsStatistics = kind_slugs.iter().map(|(kind, _slug)| kind).collect();

let init = if kind_stats.has_multipart_suggestion {
quote! { let mut suggestions = Vec::new(); }
Expand Down Expand Up @@ -526,17 +530,13 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {

let diag = &self.parent.diag;
let mut calls = TokenStream::new();
for (kind, slug, no_span) in kind_slugs {
for (kind, slug) in kind_slugs {
let message = format_ident!("__message");
calls.extend(
quote! { let #message = #diag.eagerly_translate(crate::fluent_generated::#slug); },
);

let name = format_ident!(
"{}{}",
if span_field.is_some() && !no_span { "span_" } else { "" },
kind
);
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
let call = match kind {
SubdiagnosticKind::Suggestion {
suggestion_kind,
Expand Down Expand Up @@ -588,9 +588,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
}
}
_ => {
if let Some(span) = span_field
&& !no_span
{
if let Some(span) = span_field {
quote! { #diag.#name(#span, #message); }
} else {
quote! { #diag.#name(#message); }
Expand Down
Loading
Loading