2022-06-30 08:57:45 +01:00
|
|
|
#![deny(unused_must_use)]
|
|
|
|
|
2022-07-11 18:46:24 +01:00
|
|
|
use proc_macro2::{Ident, Span, TokenStream};
|
2023-05-03 23:53:44 +00:00
|
|
|
use quote::{format_ident, quote, quote_spanned};
|
2023-03-27 13:44:06 +00:00
|
|
|
use syn::spanned::Spanned;
|
|
|
|
use syn::{Attribute, Meta, Path, Token, Type, parse_quote};
|
2022-09-23 12:49:02 +01:00
|
|
|
use synstructure::{BindingInfo, Structure, VariantInfo};
|
2024-07-29 08:13:50 +10:00
|
|
|
|
2023-05-17 10:30:14 +00:00
|
|
|
use super::utils::SubdiagnosticVariant;
|
2022-06-30 08:57:45 +01:00
|
|
|
use crate::diagnostics::error::{
|
2023-03-27 13:44:06 +00:00
|
|
|
DiagnosticDeriveError, span_err, throw_invalid_attr, throw_span_err,
|
2022-06-30 08:57:45 +01:00
|
|
|
};
|
|
|
|
use crate::diagnostics::utils::{
|
2023-12-24 09:08:41 +11:00
|
|
|
FieldInfo, FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
|
2022-10-14 11:00:46 +01:00
|
|
|
build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error,
|
2023-12-24 09:08:41 +11:00
|
|
|
should_generate_arg, type_is_bool, type_is_unit, type_matches_path,
|
2022-06-30 08:57:45 +01:00
|
|
|
};
|
2023-05-17 10:30:14 +00:00
|
|
|
|
2022-08-19 15:02:10 +02:00
|
|
|
/// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
|
2023-12-19 08:27:37 +11:00
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
2022-06-30 08:57:45 +01:00
|
|
|
pub(crate) enum DiagnosticDeriveKind {
|
2023-12-19 08:27:37 +11:00
|
|
|
Diagnostic,
|
2022-08-19 15:02:10 +02:00
|
|
|
LintDiagnostic,
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
|
2022-09-23 12:49:02 +01:00
|
|
|
/// 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.
|
2023-12-19 08:27:37 +11:00
|
|
|
pub(crate) struct DiagnosticDeriveVariantBuilder {
|
|
|
|
/// The kind for the entire type.
|
|
|
|
pub kind: DiagnosticDeriveKind,
|
2022-09-23 12:49:02 +01:00
|
|
|
|
2022-10-03 16:10:34 +01:00
|
|
|
/// Initialization of format strings for code suggestions.
|
|
|
|
pub formatting_init: TokenStream,
|
|
|
|
|
2022-09-23 12:49:02 +01:00
|
|
|
/// Span of the struct or the enum variant.
|
|
|
|
pub span: proc_macro::Span,
|
2022-06-30 08:57:45 +01:00
|
|
|
|
|
|
|
/// Store a map of field name to its corresponding field. This is built on construction of the
|
|
|
|
/// derive builder.
|
2022-09-23 12:49:02 +01:00
|
|
|
pub field_map: FieldMap,
|
2022-06-30 08:57:45 +01:00
|
|
|
|
|
|
|
/// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
|
|
|
|
/// has the actual diagnostic message.
|
2022-09-11 18:30:18 +02:00
|
|
|
pub slug: SpannedOption<Path>,
|
2023-11-08 18:27:19 +11:00
|
|
|
|
2022-06-30 08:57:45 +01:00
|
|
|
/// Error codes are a optional part of the struct attribute - this is only set to detect
|
|
|
|
/// multiple specifications.
|
2022-09-14 17:19:40 +02:00
|
|
|
pub code: SpannedOption<()>,
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 08:27:37 +11:00
|
|
|
impl HasFieldMap for DiagnosticDeriveVariantBuilder {
|
2022-06-30 08:57:45 +01:00
|
|
|
fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
|
2022-09-23 12:49:02 +01:00
|
|
|
self.field_map.get(field)
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-19 08:27:37 +11:00
|
|
|
impl DiagnosticDeriveKind {
|
2022-09-23 12:49:02 +01:00
|
|
|
/// 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.
|
2023-12-19 08:27:37 +11:00
|
|
|
pub(crate) fn each_variant<'s, F>(self, structure: &mut Structure<'s>, f: F) -> TokenStream
|
2022-09-23 12:49:02 +01:00
|
|
|
where
|
2023-12-19 08:27:37 +11:00
|
|
|
F: for<'v> Fn(DiagnosticDeriveVariantBuilder, &VariantInfo<'v>) -> TokenStream,
|
2022-09-23 12:49:02 +01:00
|
|
|
{
|
2022-06-30 08:57:45 +01:00
|
|
|
let ast = structure.ast();
|
2022-09-23 12:49:02 +01:00
|
|
|
let span = ast.span().unwrap();
|
|
|
|
match ast.data {
|
|
|
|
syn::Data::Struct(..) | syn::Data::Enum(..) => (),
|
|
|
|
syn::Data::Union(..) => {
|
2023-03-27 13:44:06 +00:00
|
|
|
span_err(span, "diagnostic derives can only be used on structs and enums").emit();
|
2022-09-23 12:49:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-03 16:10:34 +01:00
|
|
|
structure.bind_with(|_| synstructure::BindStyle::Move);
|
2022-09-23 12:49:02 +01:00
|
|
|
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 {
|
2023-12-19 08:27:37 +11:00
|
|
|
kind: self,
|
2022-09-23 12:49:02 +01:00
|
|
|
span,
|
|
|
|
field_map: build_field_mapping(variant),
|
2022-10-03 16:10:34 +01:00
|
|
|
formatting_init: TokenStream::new(),
|
2022-09-23 12:49:02 +01:00
|
|
|
slug: None,
|
|
|
|
code: None,
|
|
|
|
};
|
|
|
|
f(builder, variant)
|
|
|
|
});
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
match self {
|
|
|
|
#variants
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-19 08:27:37 +11:00
|
|
|
impl DiagnosticDeriveVariantBuilder {
|
2022-09-23 12:49:02 +01:00
|
|
|
/// Generates calls to `code` and similar functions based on the attributes on the type or
|
|
|
|
/// variant.
|
2023-11-08 18:27:19 +11:00
|
|
|
pub(crate) fn preamble(&mut self, variant: &VariantInfo<'_>) -> TokenStream {
|
2022-09-23 12:49:02 +01:00
|
|
|
let ast = variant.ast();
|
2022-06-30 08:57:45 +01:00
|
|
|
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)*;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-23 12:49:02 +01:00
|
|
|
/// Generates calls to `span_label` and similar functions based on the attributes on fields or
|
2023-12-24 09:08:41 +11:00
|
|
|
/// calls to `arg` when no attributes are present.
|
2023-11-08 18:27:19 +11:00
|
|
|
pub(crate) fn body(&mut self, variant: &VariantInfo<'_>) -> TokenStream {
|
2022-09-23 12:49:02 +01:00
|
|
|
let mut body = quote! {};
|
2023-12-24 09:08:41 +11:00
|
|
|
// Generate `arg` calls first..
|
|
|
|
for binding in variant.bindings().iter().filter(|bi| should_generate_arg(bi.ast())) {
|
2022-10-03 16:10:34 +01:00
|
|
|
body.extend(self.generate_field_code(binding));
|
|
|
|
}
|
|
|
|
// ..and then subdiagnostic additions.
|
2023-12-24 09:08:41 +11:00
|
|
|
for binding in variant.bindings().iter().filter(|bi| !should_generate_arg(bi.ast())) {
|
2022-09-23 12:49:02 +01:00
|
|
|
body.extend(self.generate_field_attrs_code(binding));
|
|
|
|
}
|
|
|
|
body
|
2022-07-11 17:15:31 +01:00
|
|
|
}
|
|
|
|
|
2022-09-23 12:49:02 +01:00
|
|
|
/// Parse a `SubdiagnosticKind` from an `Attribute`.
|
2022-09-14 17:19:40 +02:00
|
|
|
fn parse_subdiag_attribute(
|
|
|
|
&self,
|
|
|
|
attr: &Attribute,
|
2023-05-17 10:30:14 +00:00
|
|
|
) -> Result<Option<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> {
|
|
|
|
let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, self)? else {
|
2022-10-14 11:00:46 +01:00
|
|
|
// Some attributes aren't errors - like documentation comments - but also aren't
|
|
|
|
// subdiagnostics.
|
|
|
|
return Ok(None);
|
|
|
|
};
|
2022-09-14 17:19:40 +02:00
|
|
|
|
2023-05-17 10:30:14 +00:00
|
|
|
if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag.kind {
|
2023-03-27 13:44:06 +00:00
|
|
|
throw_invalid_attr!(attr, |diag| diag
|
2022-09-14 17:19:40 +02:00
|
|
|
.help("consider creating a `Subdiagnostic` instead"));
|
|
|
|
}
|
|
|
|
|
2023-05-17 10:30:14 +00:00
|
|
|
let slug = subdiag.slug.unwrap_or_else(|| match subdiag.kind {
|
2022-09-14 17:19:40 +02:00
|
|
|
SubdiagnosticKind::Label => parse_quote! { _subdiag::label },
|
|
|
|
SubdiagnosticKind::Note => parse_quote! { _subdiag::note },
|
2024-04-24 17:28:26 +00:00
|
|
|
SubdiagnosticKind::NoteOnce => parse_quote! { _subdiag::note_once },
|
2022-09-14 17:19:40 +02:00
|
|
|
SubdiagnosticKind::Help => parse_quote! { _subdiag::help },
|
2024-04-24 17:28:26 +00:00
|
|
|
SubdiagnosticKind::HelpOnce => parse_quote! { _subdiag::help_once },
|
2022-09-14 17:19:40 +02:00
|
|
|
SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn },
|
|
|
|
SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion },
|
|
|
|
SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
|
|
|
|
});
|
|
|
|
|
2023-05-17 10:30:14 +00:00
|
|
|
Ok(Some((subdiag.kind, slug, subdiag.no_span)))
|
2022-09-14 17:19:40 +02:00
|
|
|
}
|
|
|
|
|
2022-06-30 08:57:45 +01:00
|
|
|
/// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
|
2022-08-19 15:02:10 +02:00
|
|
|
/// attributes like `#[diag(..)]`, such as the slug and error code. Generates
|
2022-06-30 08:57:45 +01:00
|
|
|
/// 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> {
|
2022-10-14 11:00:46 +01:00
|
|
|
// Always allow documentation comments.
|
|
|
|
if is_doc_comment(attr) {
|
|
|
|
return Ok(quote! {});
|
|
|
|
}
|
|
|
|
|
2023-03-27 13:44:06 +00:00
|
|
|
let name = attr.path().segments.last().unwrap().ident.to_string();
|
2022-06-30 08:57:45 +01:00
|
|
|
let name = name.as_str();
|
|
|
|
|
2023-03-27 13:44:06 +00:00
|
|
|
let mut first = true;
|
2022-06-30 08:57:45 +01:00
|
|
|
|
2023-03-27 13:44:06 +00:00
|
|
|
if name == "diag" {
|
|
|
|
let mut tokens = TokenStream::new();
|
|
|
|
attr.parse_nested_meta(|nested| {
|
|
|
|
let path = &nested.path;
|
2022-06-30 08:57:45 +01:00
|
|
|
|
2023-03-27 13:44:06 +00:00
|
|
|
if first && (nested.input.is_empty() || nested.input.peek(Token![,])) {
|
|
|
|
self.slug.set_once(path.clone(), path.span().unwrap());
|
|
|
|
first = false;
|
|
|
|
return Ok(());
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
|
2023-03-27 13:44:06 +00:00
|
|
|
first = false;
|
|
|
|
|
|
|
|
let Ok(nested) = nested.value() else {
|
|
|
|
span_err(
|
|
|
|
nested.input.span().unwrap(),
|
|
|
|
"diagnostic slug must be the first argument",
|
2023-07-12 21:49:27 -04:00
|
|
|
)
|
2023-03-27 13:44:06 +00:00
|
|
|
.emit();
|
|
|
|
return Ok(());
|
2022-09-14 17:19:40 +02:00
|
|
|
};
|
2022-06-30 08:57:45 +01:00
|
|
|
|
2023-03-27 13:44:06 +00:00
|
|
|
if path.is_ident("code") {
|
|
|
|
self.code.set_once((), path.span().unwrap());
|
|
|
|
|
Stop using `String` for error codes.
Error codes are integers, but `String` is used everywhere to represent
them. Gross!
This commit introduces `ErrCode`, an integral newtype for error codes,
replacing `String`. It also introduces a constant for every error code,
e.g. `E0123`, and removes the `error_code!` macro. The constants are
imported wherever used with `use rustc_errors::codes::*`.
With the old code, we have three different ways to specify an error code
at a use point:
```
error_code!(E0123) // macro call
struct_span_code_err!(dcx, span, E0123, "msg"); // bare ident arg to macro call
\#[diag(name, code = "E0123")] // string
struct Diag;
```
With the new code, they all use the `E0123` constant.
```
E0123 // constant
struct_span_code_err!(dcx, span, E0123, "msg"); // constant
\#[diag(name, code = E0123)] // constant
struct Diag;
```
The commit also changes the structure of the error code definitions:
- `rustc_error_codes` now just defines a higher-order macro listing the
used error codes and nothing else.
- Because that's now the only thing in the `rustc_error_codes` crate, I
moved it into the `lib.rs` file and removed the `error_codes.rs` file.
- `rustc_errors` uses that macro to define everything, e.g. the error
code constants and the `DIAGNOSTIC_TABLES`. This is in its new
`codes.rs` file.
2024-01-14 10:57:07 +11:00
|
|
|
let code = nested.parse::<syn::Expr>()?;
|
2023-03-27 13:44:06 +00:00
|
|
|
tokens.extend(quote! {
|
Stop using `String` for error codes.
Error codes are integers, but `String` is used everywhere to represent
them. Gross!
This commit introduces `ErrCode`, an integral newtype for error codes,
replacing `String`. It also introduces a constant for every error code,
e.g. `E0123`, and removes the `error_code!` macro. The constants are
imported wherever used with `use rustc_errors::codes::*`.
With the old code, we have three different ways to specify an error code
at a use point:
```
error_code!(E0123) // macro call
struct_span_code_err!(dcx, span, E0123, "msg"); // bare ident arg to macro call
\#[diag(name, code = "E0123")] // string
struct Diag;
```
With the new code, they all use the `E0123` constant.
```
E0123 // constant
struct_span_code_err!(dcx, span, E0123, "msg"); // constant
\#[diag(name, code = E0123)] // constant
struct Diag;
```
The commit also changes the structure of the error code definitions:
- `rustc_error_codes` now just defines a higher-order macro listing the
used error codes and nothing else.
- Because that's now the only thing in the `rustc_error_codes` crate, I
moved it into the `lib.rs` file and removed the `error_codes.rs` file.
- `rustc_errors` uses that macro to define everything, e.g. the error
code constants and the `DIAGNOSTIC_TABLES`. This is in its new
`codes.rs` file.
2024-01-14 10:57:07 +11:00
|
|
|
diag.code(#code);
|
2023-03-27 13:44:06 +00:00
|
|
|
});
|
2023-04-03 14:36:50 +00:00
|
|
|
} else {
|
|
|
|
span_err(path.span().unwrap(), "unknown argument")
|
|
|
|
.note("only the `code` parameter is valid after the slug")
|
|
|
|
.emit();
|
|
|
|
|
|
|
|
// consume the buffer so we don't have syntax errors from syn
|
|
|
|
let _ = nested.parse::<TokenStream>();
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
2023-03-27 13:44:06 +00:00
|
|
|
Ok(())
|
|
|
|
})?;
|
2022-09-14 17:19:40 +02:00
|
|
|
return Ok(tokens);
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
|
2023-05-17 10:30:14 +00:00
|
|
|
let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else {
|
2022-10-14 11:00:46 +01:00
|
|
|
// Some attributes aren't errors - like documentation comments - but also aren't
|
|
|
|
// subdiagnostics.
|
|
|
|
return Ok(quote! {});
|
|
|
|
};
|
2022-09-14 17:19:40 +02:00
|
|
|
let fn_ident = format_ident!("{}", subdiag);
|
|
|
|
match subdiag {
|
2024-04-24 17:28:26 +00:00
|
|
|
SubdiagnosticKind::Note
|
|
|
|
| SubdiagnosticKind::NoteOnce
|
|
|
|
| SubdiagnosticKind::Help
|
|
|
|
| SubdiagnosticKind::HelpOnce
|
|
|
|
| SubdiagnosticKind::Warn => Ok(self.add_subdiagnostic(&fn_ident, slug)),
|
2022-09-14 17:19:40 +02:00
|
|
|
SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => {
|
2023-03-27 13:44:06 +00:00
|
|
|
throw_invalid_attr!(attr, |diag| diag
|
2022-09-14 17:19:40 +02:00
|
|
|
.help("`#[label]` and `#[suggestion]` can only be applied to fields"));
|
|
|
|
}
|
|
|
|
SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
|
|
|
|
}
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
|
2022-10-03 16:10:34 +01:00
|
|
|
fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
|
2022-06-30 08:57:45 +01:00
|
|
|
let field = binding_info.ast();
|
2023-05-03 23:53:44 +00:00
|
|
|
let mut field_binding = binding_info.binding.clone();
|
|
|
|
field_binding.set_span(field.ty.span());
|
2022-06-30 08:57:45 +01:00
|
|
|
|
2023-12-24 18:34:31 -05:00
|
|
|
let Some(ident) = field.ident.as_ref() else {
|
|
|
|
span_err(field.span().unwrap(), "tuple structs are not supported").emit();
|
|
|
|
return TokenStream::new();
|
|
|
|
};
|
2022-10-03 16:10:34 +01:00
|
|
|
let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
|
|
|
|
|
|
|
|
quote! {
|
2023-12-24 09:08:41 +11:00
|
|
|
diag.arg(
|
2022-10-03 16:10:34 +01:00
|
|
|
stringify!(#ident),
|
|
|
|
#field_binding
|
|
|
|
);
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
2022-10-03 16:10:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
|
|
|
|
let field = binding_info.ast();
|
|
|
|
let field_binding = &binding_info.binding;
|
2022-07-11 17:15:31 +01:00
|
|
|
|
|
|
|
let inner_ty = FieldInnerTy::from_type(&field.ty);
|
2024-07-10 18:55:45 -04:00
|
|
|
let mut seen_label = false;
|
2022-07-11 17:15:31 +01:00
|
|
|
|
|
|
|
field
|
|
|
|
.attrs
|
|
|
|
.iter()
|
|
|
|
.map(move |attr| {
|
2022-10-14 11:00:46 +01:00
|
|
|
// Always allow documentation comments.
|
|
|
|
if is_doc_comment(attr) {
|
|
|
|
return quote! {};
|
|
|
|
}
|
|
|
|
|
2023-03-27 13:44:06 +00:00
|
|
|
let name = attr.path().segments.last().unwrap().ident.to_string();
|
2024-07-10 18:55:45 -04:00
|
|
|
|
|
|
|
if name == "primary_span" && seen_label {
|
|
|
|
span_err(attr.span().unwrap(), format!("`#[primary_span]` must be placed before labels, since it overwrites the span of the diagnostic")).emit();
|
|
|
|
}
|
|
|
|
if name == "label" {
|
|
|
|
seen_label = true;
|
|
|
|
}
|
|
|
|
|
2022-07-11 17:15:31 +01:00
|
|
|
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.
|
2023-05-03 23:53:44 +00:00
|
|
|
(quote_spanned! {inner_ty.span()=> #field_binding.clone() }, false)
|
2022-07-11 17:15:31 +01:00
|
|
|
} else {
|
2023-05-03 23:53:44 +00:00
|
|
|
(quote_spanned! {inner_ty.span()=> #field_binding }, true)
|
2022-07-11 17:15:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
let generated_code = self
|
|
|
|
.generate_inner_field_code(
|
|
|
|
attr,
|
2022-10-16 16:15:39 +02:00
|
|
|
FieldInfo { binding: binding_info, ty: inner_ty, span: &field.span() },
|
2022-07-11 17:15:31 +01:00
|
|
|
binding,
|
|
|
|
)
|
|
|
|
.unwrap_or_else(|v| v.to_compile_error());
|
|
|
|
|
|
|
|
if needs_destructure {
|
|
|
|
inner_ty.with(field_binding, generated_code)
|
|
|
|
} else {
|
|
|
|
generated_code
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn generate_inner_field_code(
|
|
|
|
&mut self,
|
|
|
|
attr: &Attribute,
|
|
|
|
info: FieldInfo<'_>,
|
|
|
|
binding: TokenStream,
|
|
|
|
) -> Result<TokenStream, DiagnosticDeriveError> {
|
2023-03-27 13:44:06 +00:00
|
|
|
let ident = &attr.path().segments.last().unwrap().ident;
|
2022-10-03 14:24:17 +01:00
|
|
|
let name = ident.to_string();
|
2023-03-27 13:44:06 +00:00
|
|
|
match (&attr.meta, name.as_str()) {
|
2022-10-03 14:24:17 +01:00
|
|
|
// Don't need to do anything - by virtue of the attribute existing, the
|
2023-12-24 09:08:41 +11:00
|
|
|
// `arg` call will not be generated.
|
2022-10-03 14:24:17 +01:00
|
|
|
(Meta::Path(_), "skip_arg") => return Ok(quote! {}),
|
|
|
|
(Meta::Path(_), "primary_span") => {
|
2023-12-19 08:27:37 +11:00
|
|
|
match self.kind {
|
|
|
|
DiagnosticDeriveKind::Diagnostic => {
|
2022-08-19 15:04:34 +02:00
|
|
|
report_error_if_not_applied_to_span(attr, &info)?;
|
2022-08-19 15:02:10 +02:00
|
|
|
|
2022-09-14 17:19:40 +02:00
|
|
|
return Ok(quote! {
|
2023-12-24 09:08:41 +11:00
|
|
|
diag.span(#binding);
|
2022-09-14 17:19:40 +02:00
|
|
|
});
|
2022-08-19 15:04:34 +02:00
|
|
|
}
|
|
|
|
DiagnosticDeriveKind::LintDiagnostic => {
|
2023-03-27 13:44:06 +00:00
|
|
|
throw_invalid_attr!(attr, |diag| {
|
2022-08-19 15:04:34 +02:00
|
|
|
diag.help("the `primary_span` field attribute is not valid for lint diagnostics")
|
|
|
|
})
|
|
|
|
}
|
2022-10-03 14:24:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
(Meta::Path(_), "subdiagnostic") => {
|
2024-06-18 11:10:18 +00:00
|
|
|
return Ok(quote! { diag.subdiagnostic(#binding); });
|
2022-10-03 14:24:17 +01:00
|
|
|
}
|
|
|
|
_ => (),
|
2022-09-14 17:19:40 +02:00
|
|
|
}
|
|
|
|
|
2023-05-17 10:30:14 +00:00
|
|
|
let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else {
|
2022-10-14 11:00:46 +01:00
|
|
|
// Some attributes aren't errors - like documentation comments - but also aren't
|
|
|
|
// subdiagnostics.
|
|
|
|
return Ok(quote! {});
|
|
|
|
};
|
2022-09-14 17:19:40 +02:00
|
|
|
let fn_ident = format_ident!("{}", subdiag);
|
|
|
|
match subdiag {
|
|
|
|
SubdiagnosticKind::Label => {
|
2022-06-30 08:57:45 +01:00
|
|
|
report_error_if_not_applied_to_span(attr, &info)?;
|
2022-09-14 17:19:40 +02:00
|
|
|
Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
2024-04-24 17:28:26 +00:00
|
|
|
SubdiagnosticKind::Note
|
|
|
|
| SubdiagnosticKind::NoteOnce
|
|
|
|
| SubdiagnosticKind::Help
|
|
|
|
| SubdiagnosticKind::HelpOnce
|
|
|
|
| SubdiagnosticKind::Warn => {
|
2023-02-27 12:54:11 +00:00
|
|
|
let inner = info.ty.inner_type();
|
2023-04-08 20:37:41 +01:00
|
|
|
if type_matches_path(inner, &["rustc_span", "Span"])
|
|
|
|
|| type_matches_path(inner, &["rustc_span", "MultiSpan"])
|
|
|
|
{
|
2022-09-14 17:19:40 +02:00
|
|
|
Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
|
2023-02-27 12:54:11 +00:00
|
|
|
} else if type_is_unit(inner)
|
|
|
|
|| (matches!(info.ty, FieldInnerTy::Plain(_)) && type_is_bool(inner))
|
|
|
|
{
|
2022-09-14 17:19:40 +02:00
|
|
|
Ok(self.add_subdiagnostic(&fn_ident, slug))
|
2022-06-30 08:57:45 +01:00
|
|
|
} else {
|
2023-04-08 20:37:41 +01:00
|
|
|
report_type_error(attr, "`Span`, `MultiSpan`, `bool` or `()`")?
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
}
|
2022-09-14 17:19:40 +02:00
|
|
|
SubdiagnosticKind::Suggestion {
|
|
|
|
suggestion_kind,
|
|
|
|
applicability: static_applicability,
|
macros: separate suggestion fmt'ing and emission
Diagnostic derives have previously had to take special care when
ordering the generated code so that fields were not used after a move.
This is unlikely for most fields because a field is either annotated
with a subdiagnostic attribute and is thus likely a `Span` and copiable,
or is a argument, in which case it is only used once by `set_arg`
anyway.
However, format strings for code in suggestions can result in fields
being used after being moved if not ordered carefully. As a result, the
derive currently puts `set_arg` calls last (just before emission), such
as:
```rust
let diag = { /* create diagnostic */ };
diag.span_suggestion_with_style(
span,
fluent::crate::slug,
format!("{}", __binding_0),
Applicability::Unknown,
SuggestionStyle::ShowAlways
);
/* + other subdiagnostic additions */
diag.set_arg("foo", __binding_0);
/* + other `set_arg` calls */
diag.emit();
```
For eager translation, this doesn't work, as the message being
translated eagerly can assume that all arguments are available - so
arguments _must_ be set first.
Format strings for suggestion code are now separated into two parts - an
initialization line that performs the formatting into a variable, and a
usage in the subdiagnostic addition.
By separating these parts, the initialization can happen before
arguments are set, preserving the desired order so that code compiles,
while still enabling arguments to be set before subdiagnostics are
added.
```rust
let diag = { /* create diagnostic */ };
let __code_0 = format!("{}", __binding_0);
/* + other formatting */
diag.set_arg("foo", __binding_0);
/* + other `set_arg` calls */
diag.span_suggestion_with_style(
span,
fluent::crate::slug,
__code_0,
Applicability::Unknown,
SuggestionStyle::ShowAlways
);
/* + other subdiagnostic additions */
diag.emit();
```
Signed-off-by: David Wood <david.wood@huawei.com>
2022-10-03 14:28:02 +01:00
|
|
|
code_field,
|
|
|
|
code_init,
|
2022-09-14 17:19:40 +02:00
|
|
|
} => {
|
2022-10-16 16:15:39 +02:00
|
|
|
if let FieldInnerTy::Vec(_) = info.ty {
|
2023-03-27 13:44:06 +00:00
|
|
|
throw_invalid_attr!(attr, |diag| {
|
2022-10-16 16:15:39 +02:00
|
|
|
diag
|
|
|
|
.note("`#[suggestion(...)]` applied to `Vec` field is ambiguous")
|
|
|
|
.help("to show a suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]`")
|
|
|
|
.help("to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]`")
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-09-14 17:19:40 +02:00
|
|
|
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);
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
2022-09-14 17:19:40 +02:00
|
|
|
|
|
|
|
let applicability = applicability
|
|
|
|
.value()
|
|
|
|
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
|
|
|
|
let style = suggestion_kind.to_suggestion_style();
|
|
|
|
|
2022-10-03 16:10:34 +01:00
|
|
|
self.formatting_init.extend(code_init);
|
2022-09-14 17:19:40 +02:00
|
|
|
Ok(quote! {
|
2023-12-19 08:27:37 +11:00
|
|
|
diag.span_suggestions_with_style(
|
2022-09-14 17:19:40 +02:00
|
|
|
#span_field,
|
2022-10-13 10:13:02 +01:00
|
|
|
crate::fluent_generated::#slug,
|
macros: separate suggestion fmt'ing and emission
Diagnostic derives have previously had to take special care when
ordering the generated code so that fields were not used after a move.
This is unlikely for most fields because a field is either annotated
with a subdiagnostic attribute and is thus likely a `Span` and copiable,
or is a argument, in which case it is only used once by `set_arg`
anyway.
However, format strings for code in suggestions can result in fields
being used after being moved if not ordered carefully. As a result, the
derive currently puts `set_arg` calls last (just before emission), such
as:
```rust
let diag = { /* create diagnostic */ };
diag.span_suggestion_with_style(
span,
fluent::crate::slug,
format!("{}", __binding_0),
Applicability::Unknown,
SuggestionStyle::ShowAlways
);
/* + other subdiagnostic additions */
diag.set_arg("foo", __binding_0);
/* + other `set_arg` calls */
diag.emit();
```
For eager translation, this doesn't work, as the message being
translated eagerly can assume that all arguments are available - so
arguments _must_ be set first.
Format strings for suggestion code are now separated into two parts - an
initialization line that performs the formatting into a variable, and a
usage in the subdiagnostic addition.
By separating these parts, the initialization can happen before
arguments are set, preserving the desired order so that code compiles,
while still enabling arguments to be set before subdiagnostics are
added.
```rust
let diag = { /* create diagnostic */ };
let __code_0 = format!("{}", __binding_0);
/* + other formatting */
diag.set_arg("foo", __binding_0);
/* + other `set_arg` calls */
diag.span_suggestion_with_style(
span,
fluent::crate::slug,
__code_0,
Applicability::Unknown,
SuggestionStyle::ShowAlways
);
/* + other subdiagnostic additions */
diag.emit();
```
Signed-off-by: David Wood <david.wood@huawei.com>
2022-10-03 14:28:02 +01:00
|
|
|
#code_field,
|
2022-09-14 17:19:40 +02:00
|
|
|
#applicability,
|
|
|
|
#style
|
|
|
|
);
|
|
|
|
})
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
2022-09-14 17:19:40 +02:00
|
|
|
SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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 fn_name = format_ident!("span_{}", kind);
|
|
|
|
quote! {
|
2023-12-19 08:27:37 +11:00
|
|
|
diag.#fn_name(
|
2022-06-30 08:57:45 +01:00
|
|
|
#field_binding,
|
2022-10-13 10:13:02 +01:00
|
|
|
crate::fluent_generated::#fluent_attr_identifier
|
2022-06-30 08:57:45 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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 {
|
|
|
|
quote! {
|
2023-12-19 08:27:37 +11:00
|
|
|
diag.#kind(crate::fluent_generated::#fluent_attr_identifier);
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn span_and_applicability_of_ty(
|
|
|
|
&self,
|
|
|
|
info: FieldInfo<'_>,
|
2022-09-11 18:30:18 +02:00
|
|
|
) -> Result<(TokenStream, SpannedOption<TokenStream>), DiagnosticDeriveError> {
|
2022-10-16 16:15:39 +02:00
|
|
|
match &info.ty.inner_type() {
|
2022-06-30 08:57:45 +01:00
|
|
|
// 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;
|
2022-10-03 16:10:34 +01:00
|
|
|
Ok((quote!(#binding), None))
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
// If `ty` is `(Span, Applicability)` then return tokens accessing those.
|
|
|
|
Type::Tuple(tup) => {
|
|
|
|
let mut span_idx = None;
|
|
|
|
let mut applicability_idx = None;
|
|
|
|
|
2022-09-10 14:43:07 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2022-06-30 08:57:45 +01:00
|
|
|
for (idx, elem) in tup.elems.iter().enumerate() {
|
|
|
|
if type_matches_path(elem, &["rustc_span", "Span"]) {
|
2022-09-11 18:30:18 +02:00
|
|
|
span_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
|
2022-06-30 08:57:45 +01:00
|
|
|
} else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
|
2022-09-11 18:30:18 +02:00
|
|
|
applicability_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
|
2022-09-10 14:43:07 +02:00
|
|
|
} else {
|
|
|
|
type_err(&elem.span())?;
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-10 14:43:07 +02:00
|
|
|
let Some((span_idx, _)) = span_idx else {
|
|
|
|
type_err(&tup.span())?;
|
|
|
|
};
|
2022-09-10 14:48:01 +02:00
|
|
|
let Some((applicability_idx, applicability_span)) = applicability_idx else {
|
2022-09-10 14:43:07 +02:00
|
|
|
type_err(&tup.span())?;
|
|
|
|
};
|
|
|
|
let binding = &info.binding.binding;
|
|
|
|
let span = quote!(#binding.#span_idx);
|
|
|
|
let applicability = quote!(#binding.#applicability_idx);
|
2022-06-30 08:57:45 +01:00
|
|
|
|
2022-09-10 14:48:01 +02:00
|
|
|
Ok((span, Some((applicability, applicability_span))))
|
2022-06-30 08:57:45 +01:00
|
|
|
}
|
|
|
|
// 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)`",
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|