1
Fork 0

migrate rustc_macros to syn 2.0

This commit is contained in:
Deadbeef 2023-03-27 13:44:06 +00:00
parent 33289132ec
commit af74ef8993
11 changed files with 275 additions and 344 deletions

View file

@ -632,7 +632,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.102", "syn 1.0.102",
"synstructure", "synstructure 0.12.6",
] ]
[[package]] [[package]]
@ -4994,8 +4994,8 @@ dependencies = [
"fluent-syntax", "fluent-syntax",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.102", "syn 2.0.8",
"synstructure", "synstructure 0.13.0",
"unic-langid", "unic-langid",
] ]
@ -6131,6 +6131,18 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "synstructure"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.8",
"unicode-xid",
]
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.38" version = "0.4.38"
@ -7154,7 +7166,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.102", "syn 1.0.102",
"synstructure", "synstructure 0.12.6",
] ]
[[package]] [[package]]
@ -7175,7 +7187,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.102", "syn 1.0.102",
"synstructure", "synstructure 0.12.6",
] ]
[[package]] [[package]]
@ -7204,5 +7216,5 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.102", "syn 1.0.102",
"synstructure", "synstructure 0.12.6",
] ]

View file

@ -10,8 +10,8 @@ proc-macro = true
annotate-snippets = "0.9" annotate-snippets = "0.9"
fluent-bundle = "0.15.2" fluent-bundle = "0.15.2"
fluent-syntax = "0.11" fluent-syntax = "0.11"
synstructure = "0.12.1" synstructure = "0.13.0"
syn = { version = "1", features = ["full"] } syn = { version = "2", features = ["full"] }
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
unic-langid = { version = "0.9.0", features = ["macros"] } unic-langid = { version = "0.9.0", features = ["macros"] }

View file

@ -1,8 +1,7 @@
#![deny(unused_must_use)] #![deny(unused_must_use)]
use crate::diagnostics::error::{ use crate::diagnostics::error::{
invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError,
DiagnosticDeriveError,
}; };
use crate::diagnostics::utils::{ use crate::diagnostics::utils::{
build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error, build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error,
@ -11,9 +10,8 @@ use crate::diagnostics::utils::{
}; };
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{ use syn::Token;
parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type, use syn::{parse_quote, spanned::Spanned, Attribute, Meta, Path, Type};
};
use synstructure::{BindingInfo, Structure, VariantInfo}; use synstructure::{BindingInfo, Structure, VariantInfo};
/// What kind of diagnostic is being derived - a fatal/error/warning or a lint? /// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
@ -77,7 +75,7 @@ impl DiagnosticDeriveBuilder {
match ast.data { match ast.data {
syn::Data::Struct(..) | syn::Data::Enum(..) => (), syn::Data::Struct(..) | syn::Data::Enum(..) => (),
syn::Data::Union(..) => { 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();
} }
} }
@ -160,8 +158,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
}; };
if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag { if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag {
let meta = attr.parse_meta()?; throw_invalid_attr!(attr, |diag| diag
throw_invalid_attr!(attr, &meta, |diag| diag
.help("consider creating a `Subdiagnostic` instead")); .help("consider creating a `Subdiagnostic` instead"));
} }
@ -191,71 +188,39 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
return Ok(quote! {}); 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 name = name.as_str();
let meta = attr.parse_meta()?;
let mut first = true;
if name == "diag" { if name == "diag" {
let Meta::List(MetaList { ref nested, .. }) = meta else {
throw_invalid_attr!(
attr,
&meta
);
};
let mut nested_iter = nested.into_iter().peekable();
match nested_iter.peek() {
Some(NestedMeta::Meta(Meta::Path(slug))) => {
self.slug.set_once(slug.clone(), slug.span().unwrap());
nested_iter.next();
}
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(); let mut tokens = TokenStream::new();
for nested_attr in nested_iter { attr.parse_nested_meta(|nested| {
let (value, path) = match nested_attr { let path = &nested.path;
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
lit: syn::Lit::Str(value), if first && (nested.input.is_empty() || nested.input.peek(Token![,])) {
path, self.slug.set_once(path.clone(), path.span().unwrap());
.. first = false;
})) => (value, path), return Ok(())
NestedMeta::Meta(Meta::Path(_)) => { }
invalid_nested_attr(attr, nested_attr)
.help("diagnostic slug must be the first argument") first = false;
.emit();
continue; let Ok(nested) = nested.value() else {
} span_err(nested.input.span().unwrap(), "diagnostic slug must be the first argument").emit();
_ => { return Ok(())
invalid_nested_attr(attr, nested_attr).emit();
continue;
}
}; };
let nested_name = path.segments.last().unwrap().ident.to_string(); if path.is_ident("code") {
// Struct attributes are only allowed to be applied once, and the diagnostic self.code.set_once((), path.span().unwrap());
// 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(); let code = nested.parse::<TokenStream>()?;
tokens.extend(quote! { tokens.extend(quote! {
#diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); #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(),
} }
} Ok(())
})?;
return Ok(tokens); return Ok(tokens);
} }
@ -270,7 +235,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
Ok(self.add_subdiagnostic(&fn_ident, slug)) Ok(self.add_subdiagnostic(&fn_ident, slug))
} }
SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => { 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")); .help("`#[label]` and `#[suggestion]` can only be applied to fields"));
} }
SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
@ -309,7 +274,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
return quote! {}; 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 = let needs_clone =
name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_)); name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_));
let (binding, needs_destructure) = if needs_clone { let (binding, needs_destructure) = if needs_clone {
@ -343,11 +308,10 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
binding: TokenStream, binding: TokenStream,
) -> Result<TokenStream, DiagnosticDeriveError> { ) -> Result<TokenStream, DiagnosticDeriveError> {
let diag = &self.parent.diag; 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(); 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 // Don't need to do anything - by virtue of the attribute existing, the
// `set_arg` call will not be generated. // `set_arg` call will not be generated.
(Meta::Path(_), "skip_arg") => return Ok(quote! {}), (Meta::Path(_), "skip_arg") => return Ok(quote! {}),
@ -361,7 +325,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
}); });
} }
DiagnosticDeriveKind::LintDiagnostic => { 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") diag.help("the `primary_span` field attribute is not valid for lint diagnostics")
}) })
} }
@ -378,26 +342,34 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
return Ok(quote! { #diag.subdiagnostic(#binding); }); return Ok(quote! { #diag.subdiagnostic(#binding); });
} }
} }
(Meta::List(MetaList { ref nested, .. }), "subdiagnostic") => { (Meta::List(meta_list), "subdiagnostic") => {
if nested.len() == 1 let err = || {
&& let Some(NestedMeta::Meta(Meta::Path(path))) = nested.first() span_err(
&& path.is_ident("eager") { meta_list.span().unwrap(),
let handler = match &self.parent.kind { "`eager` is the only supported nested attribute for `subdiagnostic`",
DiagnosticDeriveKind::Diagnostic { handler } => handler, )
DiagnosticDeriveKind::LintDiagnostic => { .emit();
throw_invalid_attr!(attr, &meta, |diag| { };
diag.help("eager subdiagnostics are not supported on lints")
}) let Ok(p): Result<Path, _> = meta_list.parse_args() else {
} err();
}; return Ok(quote! {});
return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); }); };
} else {
throw_invalid_attr!(attr, &meta, |diag| { if !p.is_ident("eager") {
diag.help( err();
"`eager` is the only supported nested attribute for `subdiagnostic`", 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); });
} }
_ => (), _ => (),
} }
@ -432,7 +404,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
code_init, code_init,
} => { } => {
if let FieldInnerTy::Vec(_) = info.ty { if let FieldInnerTy::Vec(_) = info.ty {
throw_invalid_attr!(attr, &meta, |diag| { throw_invalid_attr!(attr, |diag| {
diag diag
.note("`#[suggestion(...)]` applied to `Vec` field is ambiguous") .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 suggestion consisting of multiple parts, use a `Subdiagnostic` annotated with `#[multipart_suggestion(...)]`")

View file

@ -1,7 +1,7 @@
use proc_macro::{Diagnostic, Level, MultiSpan}; use proc_macro::{Diagnostic, Level, MultiSpan};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::{spanned::Spanned, Attribute, Error as SynError, Meta, NestedMeta}; use syn::{spanned::Spanned, Attribute, Error as SynError, Meta};
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum DiagnosticDeriveError { 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`. /// Returns an error diagnostic on span `span` with msg `msg`.
#[must_use]
pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic { pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic {
Diagnostic::spanned(span, Level::Error, msg) Diagnostic::spanned(span, Level::Error, msg)
} }
@ -72,10 +73,10 @@ macro_rules! throw_span_err {
pub(crate) use throw_span_err; pub(crate) use throw_span_err;
/// Returns an error diagnostic for an invalid attribute. /// 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 span = attr.span().unwrap();
let path = path_to_string(&attr.path); let path = path_to_string(attr.path());
match meta { match attr.meta {
Meta::Path(_) => span_err(span, &format!("`#[{path}]` is not a valid attribute")), Meta::Path(_) => span_err(span, &format!("`#[{path}]` is not a valid attribute")),
Meta::NameValue(_) => { Meta::NameValue(_) => {
span_err(span, &format!("`#[{path} = ...]` is not a valid attribute")) 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>`: /// For methods that return a `Result<_, DiagnosticDeriveError>`:
macro_rules! throw_invalid_attr { macro_rules! throw_invalid_attr {
($attr:expr, $meta:expr) => {{ throw_invalid_attr!($attr, $meta, |diag| diag) }}; ($attr:expr) => {{ throw_invalid_attr!($attr, |diag| diag) }};
($attr:expr, $meta:expr, $f:expr) => {{ ($attr:expr, $f:expr) => {{
let diag = crate::diagnostics::error::invalid_attr($attr, $meta); let diag = crate::diagnostics::error::invalid_attr($attr);
return Err(crate::diagnostics::error::_throw_err(diag, $f)); return Err(crate::diagnostics::error::_throw_err(diag, $f));
}}; }};
} }
pub(crate) use throw_invalid_attr; 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;

View file

@ -1,8 +1,7 @@
#![deny(unused_must_use)] #![deny(unused_must_use)]
use crate::diagnostics::error::{ use crate::diagnostics::error::{
invalid_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, invalid_attr, span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError,
DiagnosticDeriveError,
}; };
use crate::diagnostics::utils::{ use crate::diagnostics::utils::{
build_field_mapping, is_doc_comment, new_code_ident, build_field_mapping, is_doc_comment, new_code_ident,
@ -11,7 +10,7 @@ use crate::diagnostics::utils::{
}; };
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; 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 synstructure::{BindingInfo, Structure, VariantInfo};
use super::utils::{build_suggestion_code, AllowMultipleAlternatives}; use super::utils::{build_suggestion_code, AllowMultipleAlternatives};
@ -39,7 +38,8 @@ impl SubdiagnosticDeriveBuilder {
span_err( span_err(
span, span,
"`#[derive(Subdiagnostic)]` can only be used on structs and enums", "`#[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 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(); let name = name.as_str();
throw_span_err!( throw_span_err!(
@ -265,17 +265,18 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
info: FieldInfo<'_>, info: FieldInfo<'_>,
clone_suggestion_code: bool, clone_suggestion_code: bool,
) -> Result<TokenStream, DiagnosticDeriveError> { ) -> Result<TokenStream, DiagnosticDeriveError> {
let meta = attr.parse_meta()?; match &attr.meta {
match meta { Meta::Path(path) => {
Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path), self.generate_field_code_inner_path(kind_stats, attr, info, path.clone())
Meta::List(list @ MetaList { .. }) => self.generate_field_code_inner_list( }
Meta::List(list) => self.generate_field_code_inner_list(
kind_stats, kind_stats,
attr, attr,
info, info,
list, list,
clone_suggestion_code, 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! {}), "skip_arg" => Ok(quote! {}),
"primary_span" => { "primary_span" => {
if kind_stats.has_multipart_suggestion { if kind_stats.has_multipart_suggestion {
invalid_attr(attr, &Meta::Path(path)) invalid_attr(attr)
.help( .help(
"multipart suggestions use one or more `#[suggestion_part]`s rather \ "multipart suggestions use one or more `#[suggestion_part]`s rather \
than one `#[primary_span]`", 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 // FIXME(#100717): support `Option<Span>` on `primary_span` like in the
// diagnostic derive // diagnostic derive
if !matches!(info.ty, FieldInnerTy::Plain(_)) { 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"); let diag = diag.note("there must be exactly one primary span");
if kind_stats.has_normal_suggestion { if kind_stats.has_normal_suggestion {
@ -335,7 +336,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`") span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
.emit(); .emit();
} else { } else {
invalid_attr(attr, &Meta::Path(path)) invalid_attr(attr)
.help( .help(
"`#[suggestion_part(...)]` is only valid in multipart suggestions, \ "`#[suggestion_part(...)]` is only valid in multipart suggestions, \
use `#[primary_span]` instead", use `#[primary_span]` instead",
@ -375,7 +376,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
span_attrs.push("primary_span") span_attrs.push("primary_span")
} }
invalid_attr(attr, &Meta::Path(path)) invalid_attr(attr)
.help(format!( .help(format!(
"only `{}`, `applicability` and `skip_arg` are valid field attributes", "only `{}`, `applicability` and `skip_arg` are valid field attributes",
span_attrs.join(", ") span_attrs.join(", ")
@ -394,7 +395,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
kind_stats: KindsStatistics, kind_stats: KindsStatistics,
attr: &Attribute, attr: &Attribute,
info: FieldInfo<'_>, info: FieldInfo<'_>,
list: MetaList, list: &MetaList,
clone_suggestion_code: bool, clone_suggestion_code: bool,
) -> Result<TokenStream, DiagnosticDeriveError> { ) -> Result<TokenStream, DiagnosticDeriveError> {
let span = attr.span().unwrap(); let span = attr.span().unwrap();
@ -405,7 +406,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
match name { match name {
"suggestion_part" => { "suggestion_part" => {
if !kind_stats.has_multipart_suggestion { if !kind_stats.has_multipart_suggestion {
throw_invalid_attr!(attr, &Meta::List(list), |diag| { throw_invalid_attr!(attr, |diag| {
diag.help( diag.help(
"`#[suggestion_part(...)]` is only valid in multipart suggestions", "`#[suggestion_part(...)]` is only valid in multipart suggestions",
) )
@ -417,31 +418,26 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
report_error_if_not_applied_to_span(attr, &info)?; report_error_if_not_applied_to_span(attr, &info)?;
let mut code = None; 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(); list.parse_nested_meta(|nested| {
let nested_name = meta.path().segments.last().unwrap().ident.to_string(); if nested.path.is_ident("code") {
let nested_name = nested_name.as_str(); let code_field = new_code_ident();
let formatting_init = build_suggestion_code(
match nested_name { &code_field,
"code" => { nested,
let code_field = new_code_ident(); self,
let formatting_init = build_suggestion_code( AllowMultipleAlternatives::No,
&code_field, );
meta, code.set_once((code_field, formatting_init), span);
self, } else {
AllowMultipleAlternatives::No, span_err(
); nested.path.span().unwrap(),
code.set_once((code_field, formatting_init), span); "`code` is the only valid nested attribute",
} )
_ => throw_invalid_nested_attr!(attr, nested_attr, |diag| { .emit();
diag.help("`code` is the only valid nested attribute")
}),
} }
} Ok(())
})?;
let Some((code_field, formatting_init)) = code.value() else { let Some((code_field, formatting_init)) = code.value() else {
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`") span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
@ -458,7 +454,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
}; };
Ok(quote! { suggestions.push((#binding, #code_field)); }) 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![]; let mut span_attrs = vec![];
if kind_stats.has_multipart_suggestion { if kind_stats.has_multipart_suggestion {
span_attrs.push("suggestion_part"); span_attrs.push("suggestion_part");

View file

@ -1,5 +1,5 @@
use crate::diagnostics::error::{ 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_macro::Span;
use proc_macro2::{Ident, TokenStream}; use proc_macro2::{Ident, TokenStream};
@ -8,11 +8,13 @@ use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap}; use std::collections::{BTreeSet, HashMap};
use std::fmt; use std::fmt;
use std::str::FromStr; 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::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple};
use syn::{MetaList, MetaNameValue, NestedMeta, Path};
use synstructure::{BindingInfo, VariantInfo}; use synstructure::{BindingInfo, VariantInfo};
use super::error::{invalid_attr, invalid_nested_attr}; use super::error::invalid_attr;
thread_local! { thread_local! {
pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0); pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0);
@ -60,8 +62,8 @@ pub(crate) fn report_type_error(
attr: &Attribute, attr: &Attribute,
ty_name: &str, ty_name: &str,
) -> Result<!, DiagnosticDeriveError> { ) -> Result<!, DiagnosticDeriveError> {
let name = attr.path.segments.last().unwrap().ident.to_string(); let name = attr.path().segments.last().unwrap().ident.to_string();
let meta = attr.parse_meta()?; let meta = &attr.meta;
throw_span_err!( throw_span_err!(
attr.span().unwrap(), attr.span().unwrap(),
@ -422,55 +424,53 @@ pub(super) enum AllowMultipleAlternatives {
/// `#[suggestion*(code("foo", "bar"))]` attribute field /// `#[suggestion*(code("foo", "bar"))]` attribute field
pub(super) fn build_suggestion_code( pub(super) fn build_suggestion_code(
code_field: &Ident, code_field: &Ident,
meta: &Meta, nested: ParseNestedMeta<'_>,
fields: &impl HasFieldMap, fields: &impl HasFieldMap,
allow_multiple: AllowMultipleAlternatives, allow_multiple: AllowMultipleAlternatives,
) -> TokenStream { ) -> TokenStream {
let values = match meta { let values = match (|| {
// `code = "foo"` let values: Vec<LitStr> = if let Ok(val) = nested.value() {
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => vec![s], vec![val.parse()?]
// `code("foo", "bar")` } else {
Meta::List(MetaList { nested, .. }) => { let content;
parenthesized!(content in nested.input);
if let AllowMultipleAlternatives::No = allow_multiple { if let AllowMultipleAlternatives::No = allow_multiple {
span_err( span_err(
meta.span().unwrap(), nested.input.span().unwrap(),
"expected exactly one string literal for `code = ...`", "expected exactly one string literal for `code = ...`",
) )
.emit(); .emit();
vec![] vec![]
} else if nested.is_empty() {
span_err(
meta.span().unwrap(),
"expected at least one string literal for `code(...)`",
)
.emit();
vec![]
} else { } else {
nested let literals = Punctuated::<LitStr, Token![,]>::parse_terminated(&content);
.into_iter()
.filter_map(|item| { match literals {
if let NestedMeta::Lit(syn::Lit::Str(s)) = item { Ok(p) if p.is_empty() => {
Some(s) span_err(
} else { content.span().unwrap(),
span_err( "expected at least one string literal for `code(...)`",
item.span().unwrap(), )
"`code(...)` must contain only string literals", .emit();
) vec![]
.emit(); }
None Ok(p) => p.into_iter().collect(),
} Err(_) => {
}) span_err(
.collect() content.span().unwrap(),
"`code(...)` must contain only string literals",
)
.emit();
vec![]
}
}
} }
} };
_ => {
span_err( Ok(values)
meta.span().unwrap(), })() {
r#"`code = "..."`/`code(...)` must contain only string literals"#, Ok(x) => x,
) Err(e) => return e.into_compile_error(),
.emit();
vec![]
}
}; };
if let AllowMultipleAlternatives::Yes = allow_multiple { if let AllowMultipleAlternatives::Yes = allow_multiple {
@ -601,11 +601,9 @@ impl SubdiagnosticKind {
let span = attr.span().unwrap(); 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 name = name.as_str();
let meta = attr.parse_meta()?;
let mut kind = match name { let mut kind = match name {
"label" => SubdiagnosticKind::Label, "label" => SubdiagnosticKind::Label,
"note" => SubdiagnosticKind::Note, "note" => SubdiagnosticKind::Note,
@ -618,7 +616,7 @@ impl SubdiagnosticKind {
name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix) name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix)
{ {
if suggestion_kind != SuggestionKind::Normal { if suggestion_kind != SuggestionKind::Normal {
invalid_attr(attr, &meta) invalid_attr(attr)
.help(format!( .help(format!(
r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"# r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"#
)) ))
@ -635,7 +633,7 @@ impl SubdiagnosticKind {
name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix) name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix)
{ {
if suggestion_kind != SuggestionKind::Normal { if suggestion_kind != SuggestionKind::Normal {
invalid_attr(attr, &meta) invalid_attr(attr)
.help(format!( .help(format!(
r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"# r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"#
)) ))
@ -647,16 +645,16 @@ impl SubdiagnosticKind {
applicability: None, applicability: None,
} }
} else { } else {
throw_invalid_attr!(attr, &meta); throw_invalid_attr!(attr);
} }
} }
}; };
let nested = match meta { let list = match &attr.meta {
Meta::List(MetaList { ref nested, .. }) => { Meta::List(list) => {
// An attribute with properties, such as `#[suggestion(code = "...")]` or // An attribute with properties, such as `#[suggestion(code = "...")]` or
// `#[error(some::slug)]` // `#[error(some::slug)]`
nested list
} }
Meta::Path(_) => { Meta::Path(_) => {
// An attribute without a slug or other properties, such as `#[note]` - return // An attribute without a slug or other properties, such as `#[note]` - return
@ -678,52 +676,51 @@ impl SubdiagnosticKind {
} }
} }
_ => { _ => {
throw_invalid_attr!(attr, &meta) throw_invalid_attr!(attr)
} }
}; };
let mut code = None; let mut code = None;
let mut suggestion_kind = 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. list.parse_nested_meta(|nested| {
let slug = if let Some(NestedMeta::Meta(Meta::Path(path))) = nested_iter.peek() { if nested.input.is_empty() || nested.input.peek(Token![,]) {
let path = path.clone(); if first {
// Advance the iterator. slug = Some(nested.path);
nested_iter.next(); } else {
Some(path) span_err(nested.input.span().unwrap(), "a diagnostic slug must be the first argument to the attribute").emit();
} else {
None
};
for nested_attr in nested_iter {
let meta = match nested_attr {
NestedMeta::Meta(ref meta) => meta,
NestedMeta::Lit(_) => {
invalid_nested_attr(attr, nested_attr).emit();
continue;
} }
};
let span = meta.span().unwrap(); first = false;
let nested_name = meta.path().segments.last().unwrap().ident.to_string(); return Ok(());
}
first = false;
let nested_name = nested.path.segments.last().unwrap().ident.to_string();
let nested_name = nested_name.as_str(); let nested_name = nested_name.as_str();
let string_value = match meta { let path_span = nested.path.span().unwrap();
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => Some(value), let val_span = nested.input.span().unwrap();
Meta::Path(_) => throw_invalid_nested_attr!(attr, nested_attr, |diag| { macro get_string() {
diag.help("a diagnostic slug must be the first argument to the attribute") {
}), let Ok(value) = nested.value().and_then(|x| x.parse::<LitStr>()) else {
_ => None, span_err(val_span, "expected `= \"xxx\"`").emit();
}; return Ok(());
};
value
}
}
match (nested_name, &mut kind) { match (nested_name, &mut kind) {
("code", SubdiagnosticKind::Suggestion { code_field, .. }) => { ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
let code_init = build_suggestion_code( let code_init = build_suggestion_code(
code_field, code_field,
meta, nested,
fields, fields,
AllowMultipleAlternatives::Yes, AllowMultipleAlternatives::Yes,
); );
@ -734,13 +731,9 @@ impl SubdiagnosticKind {
SubdiagnosticKind::Suggestion { ref mut applicability, .. } SubdiagnosticKind::Suggestion { ref mut applicability, .. }
| SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. }, | SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. },
) => { ) => {
let Some(value) = string_value else { let value = get_string!();
invalid_nested_attr(attr, nested_attr).emit();
continue;
};
let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| { let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| {
span_err(span, "invalid applicability").emit(); span_err(value.span().unwrap(), "invalid applicability").emit();
Applicability::Unspecified Applicability::Unspecified
}); });
applicability.set_once(value, span); applicability.set_once(value, span);
@ -750,10 +743,7 @@ impl SubdiagnosticKind {
SubdiagnosticKind::Suggestion { .. } SubdiagnosticKind::Suggestion { .. }
| SubdiagnosticKind::MultipartSuggestion { .. }, | SubdiagnosticKind::MultipartSuggestion { .. },
) => { ) => {
let Some(value) = string_value else { let value = get_string!();
invalid_nested_attr(attr, nested_attr).emit();
continue;
};
let value = value.value().parse().unwrap_or_else(|()| { let value = value.value().parse().unwrap_or_else(|()| {
span_err(value.span().unwrap(), "invalid suggestion style") span_err(value.span().unwrap(), "invalid suggestion style")
@ -767,22 +757,24 @@ impl SubdiagnosticKind {
// Invalid nested attribute // Invalid nested attribute
(_, SubdiagnosticKind::Suggestion { .. }) => { (_, SubdiagnosticKind::Suggestion { .. }) => {
invalid_nested_attr(attr, nested_attr) span_err(path_span, "invalid nested attribute")
.help( .help(
"only `style`, `code` and `applicability` are valid nested attributes", "only `style`, `code` and `applicability` are valid nested attributes",
) )
.emit(); .emit();
} }
(_, SubdiagnosticKind::MultipartSuggestion { .. }) => { (_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
invalid_nested_attr(attr, nested_attr) span_err(path_span, "invalid nested attribute")
.help("only `style` and `applicability` are valid nested attributes") .help("only `style` and `applicability` are valid nested attributes")
.emit() .emit();
} }
_ => { _ => {
invalid_nested_attr(attr, nested_attr).emit(); span_err(path_span, "invalid nested attribute").emit();
} }
} }
}
Ok(())
})?;
match kind { match kind {
SubdiagnosticKind::Suggestion { SubdiagnosticKind::Suggestion {
@ -845,5 +837,5 @@ pub(super) fn should_generate_set_arg(field: &Field) -> bool {
} }
pub(super) fn is_doc_comment(attr: &Attribute) -> bool { pub(super) fn is_doc_comment(attr: &Attribute) -> bool {
attr.path.segments.last().unwrap().ident == "doc" attr.path().segments.last().unwrap().ident == "doc"
} }

View file

@ -1,6 +1,6 @@
use proc_macro2::{self, Ident}; use proc_macro2::{self, Ident};
use quote::quote; use quote::quote;
use syn::{self, parse_quote, Meta, NestedMeta}; use syn::{self, parse_quote};
struct Attributes { struct Attributes {
ignore: bool, ignore: bool,
@ -10,32 +10,29 @@ struct Attributes {
fn parse_attributes(field: &syn::Field) -> Attributes { fn parse_attributes(field: &syn::Field) -> Attributes {
let mut attrs = Attributes { ignore: false, project: None }; let mut attrs = Attributes { ignore: false, project: None };
for attr in &field.attrs { for attr in &field.attrs {
if let Ok(meta) = attr.parse_meta() { let meta = &attr.meta;
if !meta.path().is_ident("stable_hasher") { if !meta.path().is_ident("stable_hasher") {
continue; continue;
}
let mut any_attr = false;
let _ = attr.parse_nested_meta(|nested| {
if nested.path.is_ident("ignore") {
attrs.ignore = true;
any_attr = true;
} }
let mut any_attr = false; if nested.path.is_ident("project") {
if let Meta::List(list) = meta { let _ = nested.parse_nested_meta(|meta| {
for nested in list.nested.iter() { if attrs.project.is_none() {
if let NestedMeta::Meta(meta) = nested { attrs.project = meta.path.get_ident().cloned();
if meta.path().is_ident("ignore") {
attrs.ignore = true;
any_attr = true;
}
if meta.path().is_ident("project") {
if let Meta::List(list) = meta {
if let Some(NestedMeta::Meta(meta)) = list.nested.iter().next() {
attrs.project = meta.path().get_ident().cloned();
any_attr = true;
}
}
}
} }
} any_attr = true;
} Ok(())
if !any_attr { });
panic!("error parsing stable_hasher");
} }
Ok(())
});
if !any_attr {
panic!("error parsing stable_hasher");
} }
} }
attrs attrs

View file

@ -25,7 +25,7 @@ impl Parse for Newtype {
let mut encodable = true; let mut encodable = true;
let mut ord = true; let mut ord = true;
attrs.retain(|attr| match attr.path.get_ident() { attrs.retain(|attr| match attr.path().get_ident() {
Some(ident) => match &*ident.to_string() { Some(ident) => match &*ident.to_string() {
"custom_encodable" => { "custom_encodable" => {
encodable = false; encodable = false;
@ -36,22 +36,22 @@ impl Parse for Newtype {
false false
} }
"max" => { "max" => {
let Ok(Meta::NameValue(literal) )= attr.parse_meta() else { let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta else {
panic!("#[max = NUMBER] attribute requires max value"); panic!("#[max = NUMBER] attribute requires max value");
}; };
if let Some(old) = max.replace(literal.lit) { if let Some(old) = max.replace(lit.lit.clone()) {
panic!("Specified multiple max: {old:?}"); panic!("Specified multiple max: {old:?}");
} }
false false
} }
"debug_format" => { "debug_format" => {
let Ok(Meta::NameValue(literal) )= attr.parse_meta() else { let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. } ) = &attr.meta else {
panic!("#[debug_format = FMT] attribute requires a format"); panic!("#[debug_format = FMT] attribute requires a format");
}; };
if let Some(old) = debug_format.replace(literal.lit) { if let Some(old) = debug_format.replace(lit.lit.clone()) {
panic!("Specified multiple debug format options: {old:?}"); panic!("Specified multiple debug format options: {old:?}");
} }

View file

@ -15,7 +15,7 @@ mod kw {
/// Ensures only doc comment attributes are used /// Ensures only doc comment attributes are used
fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> { fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> {
let inner = |attr: Attribute| { let inner = |attr: Attribute| {
if !attr.path.is_ident("doc") { if !attr.path().is_ident("doc") {
Err(Error::new(attr.span(), "attributes not supported on queries")) Err(Error::new(attr.span(), "attributes not supported on queries"))
} else if attr.style != AttrStyle::Outer { } else if attr.style != AttrStyle::Outer {
Err(Error::new( Err(Error::new(
@ -48,7 +48,7 @@ impl Parse for Query {
let name: Ident = input.parse()?; let name: Ident = input.parse()?;
let arg_content; let arg_content;
parenthesized!(arg_content in input); parenthesized!(arg_content in input);
let key = arg_content.parse()?; let key = Pat::parse_single(&arg_content)?;
arg_content.parse::<Token![:]>()?; arg_content.parse::<Token![:]>()?;
let arg = arg_content.parse()?; let arg = arg_content.parse()?;
let result = input.parse()?; let result = input.parse()?;
@ -158,7 +158,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
} else { } else {
None None
}; };
let list = attr_content.parse_terminated(Expr::parse)?; let list = attr_content.parse_terminated(Expr::parse, Token![,])?;
try_insert!(desc = (tcx, list)); try_insert!(desc = (tcx, list));
} else if modifier == "cache_on_disk_if" { } else if modifier == "cache_on_disk_if" {
// Parse a cache modifier like: // Parse a cache modifier like:
@ -166,7 +166,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
let args = if input.peek(token::Paren) { let args = if input.peek(token::Paren) {
let args; let args;
parenthesized!(args in input); parenthesized!(args in input);
let tcx = args.parse()?; let tcx = Pat::parse_single(&args)?;
Some(tcx) Some(tcx)
} else { } else {
None None

View file

@ -1,5 +1,5 @@
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{parse_quote, Attribute, Meta, NestedMeta}; use syn::parse_quote;
pub fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { pub fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
if let syn::Data::Union(_) = s.ast().data { if let syn::Data::Union(_) = s.ast().data {
@ -17,21 +17,20 @@ pub fn type_foldable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::
vi.construct(|_, index| { vi.construct(|_, index| {
let bind = &bindings[index]; let bind = &bindings[index];
let mut fixed = false;
// retain value of fields with #[type_foldable(identity)] // retain value of fields with #[type_foldable(identity)]
let fixed = bind bind.ast().attrs.iter().for_each(|x| {
.ast() if !x.path().is_ident("type_foldable") {
.attrs return;
.iter() }
.map(Attribute::parse_meta) let _ = x.parse_nested_meta(|nested| {
.filter_map(Result::ok) if nested.path.is_ident("identity") {
.flat_map(|attr| match attr { fixed = true;
Meta::List(list) if list.path.is_ident("type_foldable") => list.nested, }
_ => Default::default(), Ok(())
})
.any(|nested| match nested {
NestedMeta::Meta(Meta::Path(path)) => path.is_ident("identity"),
_ => false,
}); });
});
if fixed { if fixed {
bind.to_token_stream() bind.to_token_stream()

View file

@ -1,5 +1,5 @@
use quote::quote; use quote::quote;
use syn::{parse_quote, Attribute, Meta, NestedMeta}; use syn::parse_quote;
pub fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { pub fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
if let syn::Data::Union(_) = s.ast().data { if let syn::Data::Union(_) = s.ast().data {
@ -8,19 +8,21 @@ pub fn type_visitable_derive(mut s: synstructure::Structure<'_>) -> proc_macro2:
// ignore fields with #[type_visitable(ignore)] // ignore fields with #[type_visitable(ignore)]
s.filter(|bi| { s.filter(|bi| {
!bi.ast() let mut ignored = false;
.attrs
.iter() bi.ast().attrs.iter().for_each(|attr| {
.map(Attribute::parse_meta) if !attr.path().is_ident("type_visitable") {
.filter_map(Result::ok) return;
.flat_map(|attr| match attr { }
Meta::List(list) if list.path.is_ident("type_visitable") => list.nested, let _ = attr.parse_nested_meta(|nested| {
_ => Default::default(), if nested.path.is_ident("ignore") {
}) ignored = true;
.any(|nested| match nested { }
NestedMeta::Meta(Meta::Path(path)) => path.is_ident("ignore"), Ok(())
_ => false, });
}) });
!ignored
}); });
if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") { if !s.ast().generics.lifetimes().any(|lt| lt.lifetime.ident == "tcx") {