From b232b580a0117bc42a21f7cf4610500abfe3eec4 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Thu, 28 Sep 2023 13:05:29 +0200 Subject: [PATCH] update to syn 2 This mostly affected attribute parsing (due to the syn::Meta changes). Also creating `DelimSpan`s for custom-built `syn::Attribute`s is a bit... ugly. Upshot: turns out we can drop some helpers in util.rs with the new `syn::Meta` changes. Signed-off-by: Wolfgang Bumiller --- Cargo.toml | 2 +- proxmox-api-macro/src/api/attributes.rs | 59 +++--- proxmox-api-macro/src/api/enums.rs | 2 +- proxmox-api-macro/src/api/mod.rs | 2 +- proxmox-api-macro/src/api/structs.rs | 2 +- proxmox-api-macro/src/serde.rs | 73 ++++--- proxmox-api-macro/src/util.rs | 241 ++++++++++-------------- 7 files changed, 171 insertions(+), 210 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 698032e8..3158e5f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ regex = "1.5" serde = "1.0" serde_json = "1.0" serde_plain = "1.0" -syn = { version = "1.0", features = [ "full", "visit-mut" ] } +syn = { version = "2", features = [ "full", "visit-mut" ] } tar = "0.4" tokio = "1.6" tokio-openssl = "0.6.1" diff --git a/proxmox-api-macro/src/api/attributes.rs b/proxmox-api-macro/src/api/attributes.rs index 7d088109..b4ba13c5 100644 --- a/proxmox-api-macro/src/api/attributes.rs +++ b/proxmox-api-macro/src/api/attributes.rs @@ -1,8 +1,7 @@ use proc_macro2::TokenStream; -use quote::ToTokens; -use syn::{Meta, NestedMeta}; +use syn::meta::ParseNestedMeta; -use crate::util::{self, default_false, parse_str_value_to_option, set_bool}; +use crate::util; #[derive(Default)] pub struct UpdaterFieldAttributes { @@ -20,46 +19,42 @@ impl UpdaterFieldAttributes { pub fn from_attributes(input: &mut Vec) -> Self { let mut this = Self::default(); - util::extract_attributes(input, "updater", |attr, meta| this.parse(attr, meta)); + for attr in std::mem::take(input) { + if attr.style != syn::AttrStyle::Outer || !attr.path().is_ident("updater") { + input.push(attr); + continue; + } + match attr.parse_nested_meta(|meta| this.parse(meta)) { + Ok(()) => (), + Err(err) => crate::add_error(err), + } + } this } - fn parse(&mut self, attr: &syn::Attribute, input: NestedMeta) -> Result<(), syn::Error> { - match input { - NestedMeta::Lit(lit) => bail!(lit => "unexpected literal"), - NestedMeta::Meta(meta) => self.parse_meta(attr, meta), - } - } + fn parse(&mut self, meta: ParseNestedMeta<'_>) -> Result<(), syn::Error> { + let path = &meta.path; - fn parse_meta(&mut self, attr: &syn::Attribute, meta: Meta) -> Result<(), syn::Error> { - match meta { - Meta::Path(ref path) if path.is_ident("skip") => { - set_bool(&mut self.skip, path, true); + if path.is_ident("skip") { + if !meta.input.is_empty() { + return Err(meta.error("'skip' attribute does not take any data")); } - Meta::NameValue(ref nv) if nv.path.is_ident("type") => { - parse_str_value_to_option(&mut self.ty, nv) - } - Meta::NameValue(m) => bail!(&m => "invalid updater attribute: {:?}", m.path), - Meta::List(m) if m.path.is_ident("serde") => { - let mut tokens = TokenStream::new(); - m.paren_token - .surround(&mut tokens, |tokens| m.nested.to_tokens(tokens)); - self.serde.push(syn::Attribute { - path: m.path, - tokens, - ..attr.clone() - }); - } - Meta::List(m) => bail!(&m => "invalid updater attribute: {:?}", m.path), - Meta::Path(m) => bail!(&m => "invalid updater attribute: {:?}", m), + util::set_bool(&mut self.skip, path, true); + } else if path.is_ident("type") { + util::parse_str_value_to_option(&mut self.ty, path, meta.value()?); + } else if path.is_ident("serde") { + let content: TokenStream = meta.input.parse()?; + self.serde.push(syn::parse_quote! { # [ #path #content ] }); + } else { + return Err(meta.error(format!("invalid updater attribute: {path:?}"))); } Ok(()) } pub fn skip(&self) -> bool { - default_false(self.skip.as_ref()) + util::default_false(self.skip.as_ref()) } pub fn ty(&self) -> Option<&syn::TypePath> { @@ -68,7 +63,7 @@ impl UpdaterFieldAttributes { pub fn replace_serde_attributes(&self, attrs: &mut Vec) { if !self.serde.is_empty() { - attrs.retain(|attr| !attr.path.is_ident("serde")); + attrs.retain(|attr| !attr.path().is_ident("serde")); attrs.extend(self.serde.iter().cloned()) } } diff --git a/proxmox-api-macro/src/api/enums.rs b/proxmox-api-macro/src/api/enums.rs index 044126a5..879100bb 100644 --- a/proxmox-api-macro/src/api/enums.rs +++ b/proxmox-api-macro/src/api/enums.rs @@ -69,7 +69,7 @@ pub fn handle_enum( }; if derives_default { - if let Some(attr) = variant.attrs.iter().find(|a| a.path.is_ident("default")) { + if let Some(attr) = variant.attrs.iter().find(|a| a.path().is_ident("default")) { if let Some(default_value) = &default_value { error!(attr => "multiple default values defined"); error!(default_value => "default previously defined here"); diff --git a/proxmox-api-macro/src/api/mod.rs b/proxmox-api-macro/src/api/mod.rs index bf8eeac5..d55b7ca3 100644 --- a/proxmox-api-macro/src/api/mod.rs +++ b/proxmox-api-macro/src/api/mod.rs @@ -114,7 +114,7 @@ impl TryFrom for Schema { ); Ok(Self { - span: obj.brace_token.span, + span: obj.span(), description, item: SchemaItem::try_extract_from(&mut obj)?, properties: obj diff --git a/proxmox-api-macro/src/api/structs.rs b/proxmox-api-macro/src/api/structs.rs index 412c87a6..459d4759 100644 --- a/proxmox-api-macro/src/api/structs.rs +++ b/proxmox-api-macro/src/api/structs.rs @@ -32,7 +32,7 @@ pub fn handle_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result bail!( - fields.paren_token.span, + fields.paren_token.span.open(), "api macro does not support tuple structs" ), syn::Fields::Named(_) => handle_regular_struct(attribs, stru), diff --git a/proxmox-api-macro/src/serde.rs b/proxmox-api-macro/src/serde.rs index b759e91f..c3148a08 100644 --- a/proxmox-api-macro/src/serde.rs +++ b/proxmox-api-macro/src/serde.rs @@ -5,7 +5,8 @@ use std::convert::TryFrom; -use crate::util::AttrArgs; +use syn::punctuated::Punctuated; +use syn::Token; /// Serde name types. #[allow(clippy::enum_variant_names)] @@ -134,19 +135,28 @@ impl TryFrom<&[syn::Attribute]> for ContainerAttrib { let mut this: Self = Default::default(); for attrib in attributes { - if !attrib.path.is_ident("serde") { - continue; - } + let list = match &attrib.meta { + syn::Meta::List(list) if list.path.is_ident("serde") => list, + _ => continue, + }; - let args: AttrArgs = syn::parse2(attrib.tokens.clone())?; - for arg in args.args { - if let syn::NestedMeta::Meta(syn::Meta::NameValue(var)) = arg { - if var.path.is_ident("rename_all") { - let rename_all = RenameAll::try_from(&var.lit)?; - if this.rename_all.is_some() && this.rename_all != Some(rename_all) { - error!(var.lit => "multiple conflicting 'rename_all' attributes"); + let args = + list.parse_args_with(Punctuated::::parse_terminated)?; + + for arg in args { + if let syn::Meta::NameValue(var) = arg { + if !var.path.is_ident("rename_all") { + continue; + } + match &var.value { + syn::Expr::Lit(lit) => { + let rename_all = RenameAll::try_from(&lit.lit)?; + if this.rename_all.is_some() && this.rename_all != Some(rename_all) { + error!(var.value => "multiple conflicting 'rename_all' attributes"); + } + this.rename_all = Some(rename_all); } - this.rename_all = Some(rename_all); + _ => error!(var.value => "invalid 'rename_all' value type"), } } } @@ -165,30 +175,31 @@ pub struct SerdeAttrib { impl SerdeAttrib { pub fn parse_attribute(&mut self, attrib: &syn::Attribute) -> Result<(), syn::Error> { - use syn::{Meta, NestedMeta}; + let list = match &attrib.meta { + syn::Meta::List(list) if list.path.is_ident("serde") => list, + _ => return Ok(()), + }; - if !attrib.path.is_ident("serde") { - return Ok(()); - } + let args = list.parse_args_with(Punctuated::::parse_terminated)?; - let args: AttrArgs = syn::parse2(attrib.tokens.clone())?; - for arg in args.args { - match arg { - NestedMeta::Meta(Meta::NameValue(var)) if var.path.is_ident("rename") => { - match var.lit { - syn::Lit::Str(rename) => { - if self.rename.is_some() && self.rename.as_ref() != Some(&rename) { - error!(&rename => "multiple conflicting 'rename' attributes"); - } - self.rename = Some(rename); + for arg in args { + let path = arg.path(); + if path.is_ident("rename") { + match &arg.require_name_value()?.value { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(rename), + .. + }) => { + if self.rename.is_some() && self.rename.as_ref() != Some(rename) { + error!(&rename => "multiple conflicting 'rename' attributes"); } - _ => error!(var.lit => "'rename' value must be a string literal"), + self.rename = Some(rename.clone()); } + value => error!(value => "'rename' value must be a string literal"), } - NestedMeta::Meta(Meta::Path(path)) if path.is_ident("flatten") => { - self.flatten = true; - } - _ => continue, + } else if path.is_ident("flatten") { + arg.require_path_only()?; + self.flatten = true; } } diff --git a/proxmox-api-macro/src/util.rs b/proxmox-api-macro/src/util.rs index b90f303d..324c460d 100644 --- a/proxmox-api-macro/src/util.rs +++ b/proxmox-api-macro/src/util.rs @@ -183,7 +183,7 @@ impl JSONValue { pub fn span(&self) -> Span { match self { - JSONValue::Object(obj) => obj.brace_token.span, + JSONValue::Object(obj) => obj.span(), JSONValue::Expr(expr) => expr.span(), } } @@ -194,7 +194,7 @@ impl TryFrom for syn::Expr { type Error = syn::Error; fn try_from(value: JSONValue) -> Result { match value { - JSONValue::Object(s) => bail!(s.brace_token.span, "unexpected object"), + JSONValue::Object(s) => bail!(s.span(), "unexpected object"), JSONValue::Expr(e) => Ok(e), } } @@ -298,7 +298,7 @@ impl Parse for JSONValue { /// The "core" of our schema is a json object. pub struct JSONObject { - pub brace_token: syn::token::Brace, + pub brace_token: Option, pub elements: HashMap, } @@ -308,8 +308,7 @@ impl JSONObject { } fn parse_elements(input: ParseStream) -> syn::Result> { - let map_elems: Punctuated = - input.parse_terminated(JSONMapEntry::parse)?; + let map_elems = input.parse_terminated(JSONMapEntry::parse, Token![,])?; let mut elems = HashMap::with_capacity(map_elems.len()); for c in map_elems { if elems.insert(c.key.clone(), c.value).is_some() { @@ -321,9 +320,7 @@ impl JSONObject { pub fn parse_inner(input: ParseStream) -> syn::Result { Ok(Self { - brace_token: syn::token::Brace { - span: Span::call_site(), - }, + brace_token: None, elements: Self::parse_elements(input)?, }) } @@ -333,7 +330,7 @@ impl Parse for JSONObject { fn parse(input: ParseStream) -> syn::Result { let content; Ok(Self { - brace_token: syn::braced!(content in input), + brace_token: Some(syn::braced!(content in input)), elements: Self::parse_elements(&content)?, }) } @@ -355,7 +352,10 @@ impl std::ops::DerefMut for JSONObject { impl JSONObject { pub fn span(&self) -> Span { - self.brace_token.span + match &self.brace_token { + Some(brace) => brace.span.join(), + None => Span::call_site(), + } } pub fn remove_required_element(&mut self, name: &str) -> Result { @@ -415,12 +415,20 @@ pub fn get_doc_comments(attributes: &[syn::Attribute]) -> Result<(String, Span), continue; } - if attr.path.is_ident("doc") { - let doc: BareAssignment = syn::parse2(attr.tokens.clone())?; + let nv = match &attr.meta { + syn::Meta::NameValue(nv) if nv.path.is_ident("doc") => &nv.value, + _ => continue, + }; + + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(doc), + .. + }) = nv + { if !doc_comment.is_empty() { doc_comment.push('\n'); } - doc_comment.push_str(doc.content.value().trim()); + doc_comment.push_str(doc.value().trim()); } } @@ -554,7 +562,7 @@ pub fn make_ident_path(ident: Ident) -> syn::Path { pub fn make_path(span: Span, leading_colon: bool, path: &[&str]) -> syn::Path { syn::Path { leading_colon: if leading_colon { - Some(syn::token::Colon2 { + Some(syn::token::PathSep { spans: [span, span], }) } else { @@ -570,30 +578,6 @@ pub fn make_path(span: Span, leading_colon: bool, path: &[&str]) -> syn::Path { } } -/// Helper to do basically what `parse_macro_input!` does, except the macro expects a -/// `TokenStream_1`, and we always have a `TokenStream` from `proc_macro2`. -pub struct AttrArgs { - pub paren_token: syn::token::Paren, - pub args: Punctuated, -} - -impl Parse for AttrArgs { - fn parse(input: ParseStream) -> syn::Result { - let content; - Ok(Self { - paren_token: syn::parenthesized!(content in input), - args: Punctuated::parse_terminated(&content)?, - }) - } -} - -impl ToTokens for AttrArgs { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.paren_token - .surround(tokens, |inner| self.args.to_tokens(inner)); - } -} - /// Join an iterator over `Display` values. pub fn join(separator: &str, iter: impl Iterator) -> String where @@ -701,7 +685,7 @@ pub fn derives_trait(attributes: &[syn::Attribute], ident: &str) -> bool { /// Iterator over the types found in `#[derive(...)]` attributes. pub struct DerivedItems<'a> { - current: Option< as IntoIterator>::IntoIter>, + current: Option< as IntoIterator>::IntoIter>, attributes: std::slice::Iter<'a, syn::Attribute>, } @@ -713,7 +697,7 @@ impl<'a> Iterator for DerivedItems<'a> { if let Some(current) = &mut self.current { loop { match current.next() { - Some(syn::NestedMeta::Meta(syn::Meta::Path(path))) => return Some(path), + Some(syn::Meta::Path(path)) => return Some(path), Some(_) => continue, None => { self.current = None; @@ -728,15 +712,16 @@ impl<'a> Iterator for DerivedItems<'a> { continue; } - match attr.parse_meta() { - Ok(syn::Meta::List(list)) if list.path.is_ident("derive") => { - self.current = Some(list.nested.into_iter()); + match &attr.meta { + syn::Meta::List(list) if list.path.is_ident("derive") => { + if let Ok(items) = + list.parse_args_with(Punctuated::::parse_terminated) + { + self.current = Some(items.into_iter()); + } continue; } - // ignore anything that isn't a `derive(...)` attribute - Ok(_) => continue, - // ignore parse errors - Err(_) => continue, + _ => continue, } } } @@ -756,102 +741,71 @@ where continue; } - if !attr.path.is_ident("derive") { - attributes.push(attr); - continue; - } - - let mut args: AttrArgs = match syn::parse2(attr.tokens.clone()) { - Ok(args) => args, - Err(_) => { - // if we can't parse it, we don't care - attributes.push(attr); - continue; - } - }; - - for arg in std::mem::take(&mut args.args).into_pairs() { - match arg { - Pair::Punctuated(item, punct) => { - if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = &item { - if !func(path) { - continue; - } - } - args.args.push_value(item); - args.args.push_punct(punct); - } - Pair::End(item) => { - if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = &item { - if !func(path) { - continue; - } - } - args.args.push_value(item); - } - } - } - - if !args.args.is_empty() { - attr.tokens = args.into_token_stream(); - attributes.push(attr); - } - } -} - -pub fn make_attribute(span: Span, path: syn::Path, tokens: TokenStream) -> syn::Attribute { - syn::Attribute { - pound_token: syn::token::Pound { spans: [span] }, - style: syn::AttrStyle::Outer, - bracket_token: syn::token::Bracket { span }, - path, - tokens, - } -} - -pub fn make_derive_attribute(span: Span, content: TokenStream) -> syn::Attribute { - make_attribute( - span, - make_ident_path(Ident::new("derive", span)), - quote::quote! { (#content) }, - ) -} - -/// Extract (remove) an attribute from a list and run a callback on its parameters. -pub fn extract_attributes( - attributes: &mut Vec, - attr_name: &str, - mut func_matching: impl FnMut(&syn::Attribute, syn::NestedMeta) -> Result<(), syn::Error>, -) { - for attr in std::mem::take(attributes) { - if attr.style != syn::AttrStyle::Outer { - attributes.push(attr); - continue; - } - - let meta = match attr.parse_meta() { - Ok(meta) => meta, - Err(err) => { - crate::add_error(err); - attributes.push(attr); - continue; - } - }; - - let list = match meta { - syn::Meta::List(list) if list.path.is_ident(attr_name) => list, + let list = match &mut attr.meta { + syn::Meta::List(list) if list.path.is_ident("derive") => list, _ => { attributes.push(attr); continue; } }; - for entry in list.nested { - match func_matching(&attr, entry) { - Ok(()) => (), - Err(err) => crate::add_error(err), + let mut args = + match list.parse_args_with(Punctuated::::parse_terminated) { + Ok(args) => args, + Err(_) => { + // if we can't parse it, we don't care + attributes.push(attr); + continue; + } + }; + + for arg in std::mem::take(&mut args).into_pairs() { + match arg { + Pair::Punctuated(item, punct) => { + if let syn::Meta::Path(path) = &item { + if !func(path) { + continue; + } + } + args.push_value(item); + args.push_punct(punct); + } + Pair::End(item) => { + if let syn::Meta::Path(path) = &item { + if !func(path) { + continue; + } + } + args.push_value(item); + } } } + + if !args.is_empty() { + list.tokens = args.into_token_stream(); + attributes.push(attr); + } + } +} + +pub fn make_derive_attribute(span: Span, content: TokenStream) -> syn::Attribute { + // FIXME: syn2 wtf come on... + let bracket_span = + proc_macro2::Group::new(proc_macro2::Delimiter::Bracket, TokenStream::new()).delim_span(); + let paren_span = + proc_macro2::Group::new(proc_macro2::Delimiter::Parenthesis, TokenStream::new()) + .delim_span(); + + syn::Attribute { + pound_token: syn::token::Pound { spans: [span] }, + style: syn::AttrStyle::Outer, + bracket_token: syn::token::Bracket { span: bracket_span }, + //meta: syn::Meta::List(syn::parse_quote_spanned!(span => derive ( #content )).unwrap()), + meta: syn::Meta::List(syn::MetaList { + path: make_ident_path(Ident::new("derive", span)), + delimiter: syn::MacroDelimiter::Paren(syn::token::Paren { span: paren_span }), + tokens: content, + }), } } @@ -906,14 +860,15 @@ pub fn respan(mut token: TokenTree, span: Span) -> TokenTree { /// Parse a string attribute into a value, producing a duplication error if it has already been /// set. -pub fn parse_str_value_to_option(target: &mut Option, nv: &syn::MetaNameValue) { - duplicate(&*target, &nv.path); - match &nv.lit { - syn::Lit::Str(s) => match parse_lit_str(s) { - Ok(value) => *target = Some(value), - Err(err) => crate::add_error(err), - }, - other => error!(other => "bad value for '{:?}' attribute", nv.path), +pub fn parse_str_value_to_option( + target: &mut Option, + path: &syn::Path, + nv: syn::parse::ParseStream<'_>, +) { + duplicate(&*target, &path); + match nv.parse().and_then(|lit| parse_lit_str(&lit)) { + Ok(value) => *target = Some(value), + Err(err) => crate::add_error(err), } }