summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_macros/src/diagnostics
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:20:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:20:39 +0000
commit1376c5a617be5c25655d0d7cb63e3beaa5a6e026 (patch)
tree3bb8d61aee02bc7a15eab3f36e3b921afc2075d0 /compiler/rustc_macros/src/diagnostics
parentReleasing progress-linux version 1.69.0+dfsg1-1~progress7.99u1. (diff)
downloadrustc-1376c5a617be5c25655d0d7cb63e3beaa5a6e026.tar.xz
rustc-1376c5a617be5c25655d0d7cb63e3beaa5a6e026.zip
Merging upstream version 1.70.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_builder.rs176
-rw-r--r--compiler/rustc_macros/src/diagnostics/error.rs55
-rw-r--r--compiler/rustc_macros/src/diagnostics/fluent.rs31
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs79
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs232
5 files changed, 270 insertions, 303 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
index 46068f8c8..427c82c41 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -1,19 +1,17 @@
#![deny(unused_must_use)]
use crate::diagnostics::error::{
- invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
- DiagnosticDeriveError,
+ span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError,
};
use crate::diagnostics::utils::{
build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error,
- should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy, FieldMap,
- HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
+ should_generate_set_arg, type_is_bool, type_is_unit, type_matches_path, FieldInfo,
+ FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
-use syn::{
- parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
-};
+use syn::Token;
+use syn::{parse_quote, spanned::Spanned, Attribute, Meta, Path, Type};
use synstructure::{BindingInfo, Structure, VariantInfo};
/// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
@@ -77,7 +75,7 @@ impl DiagnosticDeriveBuilder {
match ast.data {
syn::Data::Struct(..) | syn::Data::Enum(..) => (),
syn::Data::Union(..) => {
- span_err(span, "diagnostic derives can only be used on structs and enums");
+ span_err(span, "diagnostic derives can only be used on structs and enums").emit();
}
}
@@ -121,7 +119,7 @@ impl DiagnosticDeriveBuilder {
impl<'a> DiagnosticDeriveVariantBuilder<'a> {
/// Generates calls to `code` and similar functions based on the attributes on the type or
/// variant.
- pub fn preamble<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
+ pub fn preamble(&mut self, variant: &VariantInfo<'_>) -> TokenStream {
let ast = variant.ast();
let attrs = &ast.attrs;
let preamble = attrs.iter().map(|attr| {
@@ -135,7 +133,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
/// Generates calls to `span_label` and similar functions based on the attributes on fields or
/// calls to `set_arg` when no attributes are present.
- pub fn body<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
+ pub fn body(&mut self, variant: &VariantInfo<'_>) -> TokenStream {
let mut body = quote! {};
// Generate `set_arg` calls first..
for binding in variant.bindings().iter().filter(|bi| should_generate_set_arg(bi.ast())) {
@@ -160,8 +158,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
};
if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag {
- let meta = attr.parse_meta()?;
- throw_invalid_attr!(attr, &meta, |diag| diag
+ throw_invalid_attr!(attr, |diag| diag
.help("consider creating a `Subdiagnostic` instead"));
}
@@ -191,71 +188,44 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
return Ok(quote! {});
}
- let name = attr.path.segments.last().unwrap().ident.to_string();
+ let name = attr.path().segments.last().unwrap().ident.to_string();
let name = name.as_str();
- let meta = attr.parse_meta()?;
- if name == "diag" {
- let Meta::List(MetaList { ref nested, .. }) = meta else {
- throw_invalid_attr!(
- attr,
- &meta
- );
- };
+ let mut first = true;
- let mut nested_iter = nested.into_iter().peekable();
+ if name == "diag" {
+ let mut tokens = TokenStream::new();
+ attr.parse_nested_meta(|nested| {
+ let path = &nested.path;
- match nested_iter.peek() {
- Some(NestedMeta::Meta(Meta::Path(slug))) => {
- self.slug.set_once(slug.clone(), slug.span().unwrap());
- nested_iter.next();
+ if first && (nested.input.is_empty() || nested.input.peek(Token![,])) {
+ self.slug.set_once(path.clone(), path.span().unwrap());
+ first = false;
+ return Ok(())
}
- Some(NestedMeta::Meta(Meta::NameValue { .. })) => {}
- Some(nested_attr) => throw_invalid_nested_attr!(attr, nested_attr, |diag| diag
- .help("a diagnostic slug is required as the first argument")),
- None => throw_invalid_attr!(attr, &meta, |diag| diag
- .help("a diagnostic slug is required as the first argument")),
- };
- // Remaining attributes are optional, only `code = ".."` at the moment.
- let mut tokens = TokenStream::new();
- for nested_attr in nested_iter {
- let (value, path) = match nested_attr {
- NestedMeta::Meta(Meta::NameValue(MetaNameValue {
- lit: syn::Lit::Str(value),
- path,
- ..
- })) => (value, path),
- NestedMeta::Meta(Meta::Path(_)) => {
- invalid_nested_attr(attr, nested_attr)
- .help("diagnostic slug must be the first argument")
- .emit();
- continue;
- }
- _ => {
- invalid_nested_attr(attr, nested_attr).emit();
- continue;
- }
+ first = false;
+
+ let Ok(nested) = nested.value() else {
+ span_err(nested.input.span().unwrap(), "diagnostic slug must be the first argument").emit();
+ return Ok(())
};
- let nested_name = path.segments.last().unwrap().ident.to_string();
- // Struct attributes are only allowed to be applied once, and the diagnostic
- // changes will be set in the initialisation code.
- let span = value.span().unwrap();
- match nested_name.as_str() {
- "code" => {
- self.code.set_once((), span);
-
- let code = value.value();
- tokens.extend(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(),
+ if path.is_ident("code") {
+ self.code.set_once((), path.span().unwrap());
+
+ let code = nested.parse::<syn::LitStr>()?;
+ tokens.extend(quote! {
+ #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
+ });
+ } 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>();
}
- }
+ Ok(())
+ })?;
return Ok(tokens);
}
@@ -270,7 +240,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
Ok(self.add_subdiagnostic(&fn_ident, slug))
}
SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => {
- throw_invalid_attr!(attr, &meta, |diag| diag
+ throw_invalid_attr!(attr, |diag| diag
.help("`#[label]` and `#[suggestion]` can only be applied to fields"));
}
SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
@@ -309,7 +279,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
return quote! {};
}
- let name = attr.path.segments.last().unwrap().ident.to_string();
+ 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 {
@@ -343,11 +313,10 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
binding: TokenStream,
) -> Result<TokenStream, DiagnosticDeriveError> {
let diag = &self.parent.diag;
- let meta = attr.parse_meta()?;
- let ident = &attr.path.segments.last().unwrap().ident;
+ let ident = &attr.path().segments.last().unwrap().ident;
let name = ident.to_string();
- match (&meta, name.as_str()) {
+ match (&attr.meta, name.as_str()) {
// Don't need to do anything - by virtue of the attribute existing, the
// `set_arg` call will not be generated.
(Meta::Path(_), "skip_arg") => return Ok(quote! {}),
@@ -361,7 +330,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
});
}
DiagnosticDeriveKind::LintDiagnostic => {
- throw_invalid_attr!(attr, &meta, |diag| {
+ throw_invalid_attr!(attr, |diag| {
diag.help("the `primary_span` field attribute is not valid for lint diagnostics")
})
}
@@ -378,26 +347,34 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
return Ok(quote! { #diag.subdiagnostic(#binding); });
}
}
- (Meta::List(MetaList { ref nested, .. }), "subdiagnostic") => {
- if nested.len() == 1
- && let Some(NestedMeta::Meta(Meta::Path(path))) = nested.first()
- && path.is_ident("eager") {
- let handler = match &self.parent.kind {
- DiagnosticDeriveKind::Diagnostic { handler } => handler,
- DiagnosticDeriveKind::LintDiagnostic => {
- throw_invalid_attr!(attr, &meta, |diag| {
- diag.help("eager subdiagnostics are not supported on lints")
- })
- }
- };
- return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); });
- } else {
- throw_invalid_attr!(attr, &meta, |diag| {
- diag.help(
- "`eager` is the only supported nested attribute for `subdiagnostic`",
- )
- })
+ (Meta::List(meta_list), "subdiagnostic") => {
+ let err = || {
+ span_err(
+ meta_list.span().unwrap(),
+ "`eager` is the only supported nested attribute for `subdiagnostic`",
+ )
+ .emit();
+ };
+
+ let Ok(p): Result<Path, _> = meta_list.parse_args() else {
+ err();
+ return Ok(quote! {});
+ };
+
+ if !p.is_ident("eager") {
+ err();
+ return Ok(quote! {});
}
+
+ let handler = match &self.parent.kind {
+ DiagnosticDeriveKind::Diagnostic { handler } => handler,
+ DiagnosticDeriveKind::LintDiagnostic => {
+ throw_invalid_attr!(attr, |diag| {
+ diag.help("eager subdiagnostics are not supported on lints")
+ })
+ }
+ };
+ return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); });
}
_ => (),
}
@@ -414,12 +391,17 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
}
SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
- if type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"]) {
+ let inner = info.ty.inner_type();
+ if type_matches_path(inner, &["rustc_span", "Span"])
+ || type_matches_path(inner, &["rustc_span", "MultiSpan"])
+ {
Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
- } else if type_is_unit(info.ty.inner_type()) {
+ } else if type_is_unit(inner)
+ || (matches!(info.ty, FieldInnerTy::Plain(_)) && type_is_bool(inner))
+ {
Ok(self.add_subdiagnostic(&fn_ident, slug))
} else {
- report_type_error(attr, "`Span` or `()`")?
+ report_type_error(attr, "`Span`, `MultiSpan`, `bool` or `()`")?
}
}
SubdiagnosticKind::Suggestion {
@@ -429,7 +411,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
code_init,
} => {
if let FieldInnerTy::Vec(_) = info.ty {
- throw_invalid_attr!(attr, &meta, |diag| {
+ throw_invalid_attr!(attr, |diag| {
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(...)]`")
diff --git a/compiler/rustc_macros/src/diagnostics/error.rs b/compiler/rustc_macros/src/diagnostics/error.rs
index 2d62d5931..b37dc826d 100644
--- a/compiler/rustc_macros/src/diagnostics/error.rs
+++ b/compiler/rustc_macros/src/diagnostics/error.rs
@@ -1,7 +1,7 @@
use proc_macro::{Diagnostic, Level, MultiSpan};
use proc_macro2::TokenStream;
use quote::quote;
-use syn::{spanned::Spanned, Attribute, Error as SynError, Meta, NestedMeta};
+use syn::{spanned::Spanned, Attribute, Error as SynError, Meta};
#[derive(Debug)]
pub(crate) enum DiagnosticDeriveError {
@@ -53,6 +53,7 @@ fn path_to_string(path: &syn::Path) -> String {
}
/// Returns an error diagnostic on span `span` with msg `msg`.
+#[must_use]
pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic {
Diagnostic::spanned(span, Level::Error, msg)
}
@@ -72,10 +73,10 @@ macro_rules! throw_span_err {
pub(crate) use throw_span_err;
/// Returns an error diagnostic for an invalid attribute.
-pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic {
+pub(crate) fn invalid_attr(attr: &Attribute) -> Diagnostic {
let span = attr.span().unwrap();
- let path = path_to_string(&attr.path);
- match meta {
+ let path = path_to_string(attr.path());
+ match attr.meta {
Meta::Path(_) => span_err(span, &format!("`#[{path}]` is not a valid attribute")),
Meta::NameValue(_) => {
span_err(span, &format!("`#[{path} = ...]` is not a valid attribute"))
@@ -89,51 +90,11 @@ pub(crate) fn invalid_attr(attr: &Attribute, meta: &Meta) -> Diagnostic {
///
/// 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);
+ ($attr:expr) => {{ throw_invalid_attr!($attr, |diag| diag) }};
+ ($attr:expr, $f:expr) => {{
+ let diag = crate::diagnostics::error::invalid_attr($attr);
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!("`#[{name}(\"...\")]` is not a valid attribute"));
- }
- };
-
- let span = meta.span().unwrap();
- let path = path_to_string(meta.path());
- match meta {
- Meta::NameValue(..) => {
- span_err(span, &format!("`#[{name}({path} = ...)]` is not a valid attribute"))
- }
- Meta::Path(..) => span_err(span, &format!("`#[{name}({path})]` is not a valid attribute")),
- Meta::List(..) => {
- span_err(span, &format!("`#[{name}({path}(...))]` is not a valid attribute"))
- }
- }
-}
-
-/// Emit an 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
index 38c0f4895..607d51f56 100644
--- a/compiler/rustc_macros/src/diagnostics/fluent.rs
+++ b/compiler/rustc_macros/src/diagnostics/fluent.rs
@@ -15,8 +15,7 @@ use proc_macro2::TokenStream;
use quote::quote;
use std::{
collections::{HashMap, HashSet},
- fs::File,
- io::Read,
+ fs::read_to_string,
path::{Path, PathBuf},
};
use syn::{parse_macro_input, Ident, LitStr};
@@ -95,20 +94,28 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
// 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,
+ let resource_contents = match read_to_string(absolute_ftl_path) {
+ Ok(resource_contents) => resource_contents,
Err(e) => {
- Diagnostic::spanned(resource_span, Level::Error, "could not open Fluent resource")
- .note(e.to_string())
- .emit();
+ Diagnostic::spanned(
+ resource_span,
+ Level::Error,
+ format!("could not open Fluent resource: {e}"),
+ )
+ .emit();
return failed(&crate_name);
}
};
- let mut resource_contents = String::new();
- if let Err(e) = resource_file.read_to_string(&mut resource_contents) {
- Diagnostic::spanned(resource_span, Level::Error, "could not read Fluent resource")
- .note(e.to_string())
- .emit();
+ let mut bad = false;
+ for esc in ["\\n", "\\\"", "\\'"] {
+ for _ in resource_contents.matches(esc) {
+ bad = true;
+ Diagnostic::spanned(resource_span, Level::Error, format!("invalid escape `{esc}` in Fluent resource"))
+ .note("Fluent does not interpret these escape sequences (<https://projectfluent.org/fluent/guide/special.html>)")
+ .emit();
+ }
+ }
+ if bad {
return failed(&crate_name);
}
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index 90660fc1f..62d49c1c6 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -1,8 +1,7 @@
#![deny(unused_must_use)]
use crate::diagnostics::error::{
- invalid_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
- DiagnosticDeriveError,
+ invalid_attr, span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError,
};
use crate::diagnostics::utils::{
build_field_mapping, is_doc_comment, new_code_ident,
@@ -11,7 +10,7 @@ use crate::diagnostics::utils::{
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
-use syn::{spanned::Spanned, Attribute, Meta, MetaList, NestedMeta, Path};
+use syn::{spanned::Spanned, Attribute, Meta, MetaList, Path};
use synstructure::{BindingInfo, Structure, VariantInfo};
use super::utils::{build_suggestion_code, AllowMultipleAlternatives};
@@ -39,7 +38,8 @@ impl SubdiagnosticDeriveBuilder {
span_err(
span,
"`#[derive(Subdiagnostic)]` can only be used on structs and enums",
- );
+ )
+ .emit();
}
}
@@ -192,7 +192,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
};
let Some(slug) = slug else {
- let name = attr.path.segments.last().unwrap().ident.to_string();
+ let name = attr.path().segments.last().unwrap().ident.to_string();
let name = name.as_str();
throw_span_err!(
@@ -265,17 +265,18 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
info: FieldInfo<'_>,
clone_suggestion_code: bool,
) -> Result<TokenStream, DiagnosticDeriveError> {
- let meta = attr.parse_meta()?;
- match meta {
- Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path),
- Meta::List(list @ MetaList { .. }) => self.generate_field_code_inner_list(
+ match &attr.meta {
+ Meta::Path(path) => {
+ self.generate_field_code_inner_path(kind_stats, attr, info, path.clone())
+ }
+ Meta::List(list) => self.generate_field_code_inner_list(
kind_stats,
attr,
info,
list,
clone_suggestion_code,
),
- _ => throw_invalid_attr!(attr, &meta),
+ _ => throw_invalid_attr!(attr),
}
}
@@ -296,7 +297,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
"skip_arg" => Ok(quote! {}),
"primary_span" => {
if kind_stats.has_multipart_suggestion {
- invalid_attr(attr, &Meta::Path(path))
+ invalid_attr(attr)
.help(
"multipart suggestions use one or more `#[suggestion_part]`s rather \
than one `#[primary_span]`",
@@ -309,7 +310,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
// FIXME(#100717): support `Option<Span>` on `primary_span` like in the
// diagnostic derive
if !matches!(info.ty, FieldInnerTy::Plain(_)) {
- throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
+ throw_invalid_attr!(attr, |diag| {
let diag = diag.note("there must be exactly one primary span");
if kind_stats.has_normal_suggestion {
@@ -335,7 +336,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
.emit();
} else {
- invalid_attr(attr, &Meta::Path(path))
+ invalid_attr(attr)
.help(
"`#[suggestion_part(...)]` is only valid in multipart suggestions, \
use `#[primary_span]` instead",
@@ -375,7 +376,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
span_attrs.push("primary_span")
}
- invalid_attr(attr, &Meta::Path(path))
+ invalid_attr(attr)
.help(format!(
"only `{}`, `applicability` and `skip_arg` are valid field attributes",
span_attrs.join(", ")
@@ -394,7 +395,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
kind_stats: KindsStatistics,
attr: &Attribute,
info: FieldInfo<'_>,
- list: MetaList,
+ list: &MetaList,
clone_suggestion_code: bool,
) -> Result<TokenStream, DiagnosticDeriveError> {
let span = attr.span().unwrap();
@@ -405,7 +406,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
match name {
"suggestion_part" => {
if !kind_stats.has_multipart_suggestion {
- throw_invalid_attr!(attr, &Meta::List(list), |diag| {
+ throw_invalid_attr!(attr, |diag| {
diag.help(
"`#[suggestion_part(...)]` is only valid in multipart suggestions",
)
@@ -417,31 +418,27 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
report_error_if_not_applied_to_span(attr, &info)?;
let mut code = None;
- for nested_attr in list.nested.iter() {
- let NestedMeta::Meta(ref meta) = nested_attr else {
- 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 nested_name {
- "code" => {
- let code_field = new_code_ident();
- let formatting_init = build_suggestion_code(
- &code_field,
- meta,
- self,
- AllowMultipleAlternatives::No,
- );
- code.set_once((code_field, formatting_init), span);
- }
- _ => throw_invalid_nested_attr!(attr, nested_attr, |diag| {
- diag.help("`code` is the only valid nested attribute")
- }),
+
+ 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();
}
- }
+ Ok(())
+ })?;
let Some((code_field, formatting_init)) = code.value() else {
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
@@ -458,7 +455,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
};
Ok(quote! { suggestions.push((#binding, #code_field)); })
}
- _ => throw_invalid_attr!(attr, &Meta::List(list), |diag| {
+ _ => throw_invalid_attr!(attr, |diag| {
let mut span_attrs = vec![];
if kind_stats.has_multipart_suggestion {
span_attrs.push("suggestion_part");
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
index 27b8f676f..b9b09c662 100644
--- a/compiler/rustc_macros/src/diagnostics/utils.rs
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -1,5 +1,5 @@
use crate::diagnostics::error::{
- span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
+ span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError,
};
use proc_macro::Span;
use proc_macro2::{Ident, TokenStream};
@@ -8,11 +8,13 @@ use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap};
use std::fmt;
use std::str::FromStr;
+use syn::meta::ParseNestedMeta;
+use syn::punctuated::Punctuated;
+use syn::{parenthesized, LitStr, Path, Token};
use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple};
-use syn::{MetaList, MetaNameValue, NestedMeta, Path};
use synstructure::{BindingInfo, VariantInfo};
-use super::error::{invalid_attr, invalid_nested_attr};
+use super::error::invalid_attr;
thread_local! {
pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0);
@@ -50,13 +52,18 @@ pub(crate) fn type_is_unit(ty: &Type) -> bool {
if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false }
}
+/// Checks whether the type `ty` is `bool`.
+pub(crate) fn type_is_bool(ty: &Type) -> bool {
+ type_matches_path(ty, &["bool"])
+}
+
/// 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()?;
+ let name = attr.path().segments.last().unwrap().ident.to_string();
+ let meta = &attr.meta;
throw_span_err!(
attr.span().unwrap(),
@@ -192,6 +199,11 @@ impl<'ty> FieldInnerTy<'ty> {
#inner
}
},
+ FieldInnerTy::Plain(t) if type_is_bool(t) => quote! {
+ if #binding {
+ #inner
+ }
+ },
FieldInnerTy::Plain(..) => quote! { #inner },
}
}
@@ -408,59 +420,62 @@ pub(super) enum AllowMultipleAlternatives {
Yes,
}
+fn parse_suggestion_values(
+ nested: ParseNestedMeta<'_>,
+ allow_multiple: AllowMultipleAlternatives,
+) -> syn::Result<Vec<LitStr>> {
+ let values = if let Ok(val) = nested.value() {
+ vec![val.parse()?]
+ } else {
+ let content;
+ parenthesized!(content in nested.input);
+
+ if let AllowMultipleAlternatives::No = allow_multiple {
+ span_err(
+ nested.input.span().unwrap(),
+ "expected exactly one string literal for `code = ...`",
+ )
+ .emit();
+ vec![]
+ } else {
+ let literals = Punctuated::<LitStr, Token![,]>::parse_terminated(&content);
+
+ match literals {
+ Ok(p) if p.is_empty() => {
+ span_err(
+ content.span().unwrap(),
+ "expected at least one string literal for `code(...)`",
+ )
+ .emit();
+ vec![]
+ }
+ Ok(p) => p.into_iter().collect(),
+ Err(_) => {
+ span_err(
+ content.span().unwrap(),
+ "`code(...)` must contain only string literals",
+ )
+ .emit();
+ vec![]
+ }
+ }
+ }
+ };
+
+ Ok(values)
+}
+
/// Constructs the `format!()` invocation(s) necessary for a `#[suggestion*(code = "foo")]` or
/// `#[suggestion*(code("foo", "bar"))]` attribute field
pub(super) fn build_suggestion_code(
code_field: &Ident,
- meta: &Meta,
+ nested: ParseNestedMeta<'_>,
fields: &impl HasFieldMap,
allow_multiple: AllowMultipleAlternatives,
) -> TokenStream {
- let values = match meta {
- // `code = "foo"`
- Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => vec![s],
- // `code("foo", "bar")`
- Meta::List(MetaList { nested, .. }) => {
- if let AllowMultipleAlternatives::No = allow_multiple {
- span_err(
- meta.span().unwrap(),
- "expected exactly one string literal for `code = ...`",
- )
- .emit();
- vec![]
- } else if nested.is_empty() {
- span_err(
- meta.span().unwrap(),
- "expected at least one string literal for `code(...)`",
- )
- .emit();
- vec![]
- } else {
- nested
- .into_iter()
- .filter_map(|item| {
- if let NestedMeta::Lit(syn::Lit::Str(s)) = item {
- Some(s)
- } else {
- span_err(
- item.span().unwrap(),
- "`code(...)` must contain only string literals",
- )
- .emit();
- None
- }
- })
- .collect()
- }
- }
- _ => {
- span_err(
- meta.span().unwrap(),
- r#"`code = "..."`/`code(...)` must contain only string literals"#,
- )
- .emit();
- vec![]
- }
+ let values = match parse_suggestion_values(nested, allow_multiple) {
+ Ok(x) => x,
+ Err(e) => return e.into_compile_error(),
};
if let AllowMultipleAlternatives::Yes = allow_multiple {
@@ -591,11 +606,9 @@ impl SubdiagnosticKind {
let span = attr.span().unwrap();
- let name = attr.path.segments.last().unwrap().ident.to_string();
+ let name = attr.path().segments.last().unwrap().ident.to_string();
let name = name.as_str();
- let meta = attr.parse_meta()?;
-
let mut kind = match name {
"label" => SubdiagnosticKind::Label,
"note" => SubdiagnosticKind::Note,
@@ -608,7 +621,7 @@ impl SubdiagnosticKind {
name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix)
{
if suggestion_kind != SuggestionKind::Normal {
- invalid_attr(attr, &meta)
+ invalid_attr(attr)
.help(format!(
r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"#
))
@@ -625,7 +638,7 @@ impl SubdiagnosticKind {
name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix)
{
if suggestion_kind != SuggestionKind::Normal {
- invalid_attr(attr, &meta)
+ invalid_attr(attr)
.help(format!(
r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"#
))
@@ -637,16 +650,16 @@ impl SubdiagnosticKind {
applicability: None,
}
} else {
- throw_invalid_attr!(attr, &meta);
+ throw_invalid_attr!(attr);
}
}
};
- let nested = match meta {
- Meta::List(MetaList { ref nested, .. }) => {
+ let list = match &attr.meta {
+ Meta::List(list) => {
// An attribute with properties, such as `#[suggestion(code = "...")]` or
// `#[error(some::slug)]`
- nested
+ list
}
Meta::Path(_) => {
// An attribute without a slug or other properties, such as `#[note]` - return
@@ -668,69 +681,68 @@ impl SubdiagnosticKind {
}
}
_ => {
- throw_invalid_attr!(attr, &meta)
+ throw_invalid_attr!(attr)
}
};
let mut code = None;
let mut suggestion_kind = None;
- let mut nested_iter = nested.into_iter().peekable();
+ let mut first = true;
+ let mut slug = None;
- // Peek at the first nested attribute: if it's a slug path, consume it.
- let slug = if let Some(NestedMeta::Meta(Meta::Path(path))) = nested_iter.peek() {
- let path = path.clone();
- // Advance the iterator.
- nested_iter.next();
- Some(path)
- } else {
- None
- };
-
- for nested_attr in nested_iter {
- let meta = match nested_attr {
- NestedMeta::Meta(ref meta) => meta,
- NestedMeta::Lit(_) => {
- invalid_nested_attr(attr, nested_attr).emit();
- continue;
+ list.parse_nested_meta(|nested| {
+ if nested.input.is_empty() || nested.input.peek(Token![,]) {
+ if first {
+ slug = Some(nested.path);
+ } else {
+ span_err(nested.input.span().unwrap(), "a diagnostic slug must be the first argument to the attribute").emit();
}
- };
- let span = meta.span().unwrap();
- let nested_name = meta.path().segments.last().unwrap().ident.to_string();
+ first = false;
+ return Ok(());
+ }
+
+ first = false;
+
+ let nested_name = nested.path.segments.last().unwrap().ident.to_string();
let nested_name = nested_name.as_str();
- let string_value = match meta {
- Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => Some(value),
+ let path_span = nested.path.span().unwrap();
+ let val_span = nested.input.span().unwrap();
- Meta::Path(_) => throw_invalid_nested_attr!(attr, nested_attr, |diag| {
- diag.help("a diagnostic slug must be the first argument to the attribute")
- }),
- _ => None,
- };
+ macro_rules! get_string {
+ () => {{
+ let Ok(value) = nested.value().and_then(|x| x.parse::<LitStr>()) else {
+ span_err(val_span, "expected `= \"xxx\"`").emit();
+ return Ok(());
+ };
+ value
+ }};
+ }
+
+ let mut has_errors = false;
+ let input = nested.input;
match (nested_name, &mut kind) {
("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
let code_init = build_suggestion_code(
code_field,
- meta,
+ nested,
fields,
AllowMultipleAlternatives::Yes,
);
- code.set_once(code_init, span);
+ code.set_once(code_init, path_span);
}
(
"applicability",
SubdiagnosticKind::Suggestion { ref mut applicability, .. }
| SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. },
) => {
- let Some(value) = string_value else {
- invalid_nested_attr(attr, nested_attr).emit();
- continue;
- };
-
+ let value = get_string!();
let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| {
- span_err(span, "invalid applicability").emit();
+ span_err(value.span().unwrap(), "invalid applicability").emit();
+ has_errors = true;
Applicability::Unspecified
});
applicability.set_once(value, span);
@@ -740,15 +752,13 @@ impl SubdiagnosticKind {
SubdiagnosticKind::Suggestion { .. }
| SubdiagnosticKind::MultipartSuggestion { .. },
) => {
- let Some(value) = string_value else {
- invalid_nested_attr(attr, nested_attr).emit();
- continue;
- };
+ let value = get_string!();
let value = value.value().parse().unwrap_or_else(|()| {
span_err(value.span().unwrap(), "invalid suggestion style")
.help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`")
.emit();
+ has_errors = true;
SuggestionKind::Normal
});
@@ -757,22 +767,32 @@ impl SubdiagnosticKind {
// Invalid nested attribute
(_, SubdiagnosticKind::Suggestion { .. }) => {
- invalid_nested_attr(attr, nested_attr)
+ span_err(path_span, "invalid nested attribute")
.help(
"only `style`, `code` and `applicability` are valid nested attributes",
)
.emit();
+ has_errors = true;
}
(_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
- invalid_nested_attr(attr, nested_attr)
+ span_err(path_span, "invalid nested attribute")
.help("only `style` and `applicability` are valid nested attributes")
- .emit()
+ .emit();
+ has_errors = true;
}
_ => {
- invalid_nested_attr(attr, nested_attr).emit();
+ span_err(path_span, "invalid nested attribute").emit();
+ has_errors = true;
}
}
- }
+
+ if has_errors {
+ // Consume the rest of the input to avoid spamming errors
+ let _ = input.parse::<TokenStream>();
+ }
+
+ Ok(())
+ })?;
match kind {
SubdiagnosticKind::Suggestion {
@@ -835,5 +855,5 @@ pub(super) fn should_generate_set_arg(field: &Field) -> bool {
}
pub(super) fn is_doc_comment(attr: &Attribute) -> bool {
- attr.path.segments.last().unwrap().ident == "doc"
+ attr.path().segments.last().unwrap().ident == "doc"
}