From dc6d8bcffdb4a2f69effd01e163fedb6392a8cff Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Sat, 18 Mar 2023 11:32:34 +0100 Subject: [PATCH 01/22] Do not implement Spanned manually The Spanned trait becomes sealed in v2 of syn. This means manual implementations of Spanned are no longer possible. If needed, the only way to implement Spanned is to implement `quote::ToTokens`, because there is a blanket implementation. --- core/src/util/flag.rs | 6 ------ core/src/util/parse_attribute.rs | 7 ++++--- core/src/util/spanned_value.rs | 6 ------ 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/core/src/util/flag.rs b/core/src/util/flag.rs index ade88988..66f2adc8 100644 --- a/core/src/util/flag.rs +++ b/core/src/util/flag.rs @@ -77,12 +77,6 @@ impl FromMeta for Flag { } } -impl Spanned for Flag { - fn span(&self) -> Span { - self.0.unwrap_or_else(Span::call_site) - } -} - impl From for bool { fn from(flag: Flag) -> Self { flag.is_present() diff --git a/core/src/util/parse_attribute.rs b/core/src/util/parse_attribute.rs index 8d8be5d7..4a088e10 100644 --- a/core/src/util/parse_attribute.rs +++ b/core/src/util/parse_attribute.rs @@ -1,4 +1,4 @@ -use crate::{util::SpannedValue, Error, Result}; +use crate::{Error, Result}; use std::fmt; use syn::{punctuated::Pair, spanned::Spanned, token, Attribute, Meta, MetaList, Path}; @@ -18,8 +18,9 @@ pub fn parse_attribute_to_meta_list(attr: &Attribute) -> Result { paren_token: token::Paren(attr.span()), nested: Default::default(), }), - Err(e) => Err(Error::custom(format!("Unable to parse attribute: {}", e)) - .with_span(&SpannedValue::new((), e.span()))), + Err(e) => { + Err(Error::custom(format!("Unable to parse attribute: {}", e)).with_span(&e.span())) + } } } diff --git a/core/src/util/spanned_value.rs b/core/src/util/spanned_value.rs index a97f9977..68d5423a 100644 --- a/core/src/util/spanned_value.rs +++ b/core/src/util/spanned_value.rs @@ -67,12 +67,6 @@ impl AsRef for SpannedValue { } } -impl Spanned for SpannedValue { - fn span(&self) -> Span { - self.span - } -} - macro_rules! spanned { ($trayt:ident, $method:ident, $syn:path) => { impl $trayt for SpannedValue { From 66e9c6bb8cf8c78f0e05d693f3a8e421dfe33cda Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Sat, 18 Mar 2023 14:50:09 +0100 Subject: [PATCH 02/22] Update implementation of UsesTypeParams --- core/src/usage/type_params.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/core/src/usage/type_params.rs b/core/src/usage/type_params.rs index 152d218e..5c95b978 100644 --- a/core/src/usage/type_params.rs +++ b/core/src/usage/type_params.rs @@ -87,8 +87,8 @@ impl UsesTypeParams for Punctuated { } uses_type_params!(syn::AngleBracketedGenericArguments, args); +uses_type_params!(syn::AssocType, ty); uses_type_params!(syn::BareFnArg, ty); -uses_type_params!(syn::Binding, ty); uses_type_params!(syn::Constraint, bounds); uses_type_params!(syn::DataEnum, variants); uses_type_params!(syn::DataStruct, fields); @@ -96,7 +96,6 @@ uses_type_params!(syn::DataUnion, fields); uses_type_params!(syn::Field, ty); uses_type_params!(syn::FieldsNamed, named); uses_type_params!(syn::ParenthesizedGenericArguments, inputs, output); -uses_type_params!(syn::PredicateEq, lhs_ty, rhs_ty); uses_type_params!(syn::PredicateType, bounded_ty, bounds); uses_type_params!(syn::QSelf, ty); uses_type_params!(syn::TraitBound, path); @@ -217,7 +216,8 @@ impl UsesTypeParams for syn::WherePredicate { match *self { syn::WherePredicate::Lifetime(_) => Default::default(), syn::WherePredicate::Type(ref v) => v.uses_type_params(options, type_set), - syn::WherePredicate::Eq(ref v) => v.uses_type_params(options, type_set), + // non-exhaustive enum + _ => Default::default(), } } } @@ -226,11 +226,13 @@ impl UsesTypeParams for syn::GenericArgument { fn uses_type_params<'a>(&self, options: &Options, type_set: &'a IdentSet) -> IdentRefSet<'a> { match *self { syn::GenericArgument::Type(ref v) => v.uses_type_params(options, type_set), - syn::GenericArgument::Binding(ref v) => v.uses_type_params(options, type_set), + syn::GenericArgument::AssocType(ref v) => v.uses_type_params(options, type_set), syn::GenericArgument::Constraint(ref v) => v.uses_type_params(options, type_set), - syn::GenericArgument::Const(_) | syn::GenericArgument::Lifetime(_) => { - Default::default() - } + syn::GenericArgument::AssocConst(_) + | syn::GenericArgument::Const(_) + | syn::GenericArgument::Lifetime(_) => Default::default(), + // non-exhaustive enum + _ => Default::default(), } } } @@ -240,6 +242,8 @@ impl UsesTypeParams for syn::TypeParamBound { match *self { syn::TypeParamBound::Trait(ref v) => v.uses_type_params(options, type_set), syn::TypeParamBound::Lifetime(_) => Default::default(), + // non-exhaustive enum + _ => Default::default(), } } } From cff5d50ac13a86d2312c3eb31f286843430ea4bb Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Sat, 18 Mar 2023 14:50:27 +0100 Subject: [PATCH 03/22] Update implementation of UsesLifetimes --- core/src/usage/lifetimes.rs | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/core/src/usage/lifetimes.rs b/core/src/usage/lifetimes.rs index b940173c..87a2d829 100644 --- a/core/src/usage/lifetimes.rs +++ b/core/src/usage/lifetimes.rs @@ -112,20 +112,20 @@ impl UsesLifetimes for Lifetime { } uses_lifetimes!(syn::AngleBracketedGenericArguments, args); +uses_lifetimes!(syn::AssocType, ty); uses_lifetimes!(syn::BareFnArg, ty); -uses_lifetimes!(syn::Binding, ty); uses_lifetimes!(syn::BoundLifetimes, lifetimes); +uses_lifetimes!(syn::ConstParam, ty); uses_lifetimes!(syn::Constraint, bounds); uses_lifetimes!(syn::DataEnum, variants); uses_lifetimes!(syn::DataStruct, fields); uses_lifetimes!(syn::DataUnion, fields); uses_lifetimes!(syn::Field, ty); uses_lifetimes!(syn::FieldsNamed, named); -uses_lifetimes!(syn::LifetimeDef, lifetime, bounds); +uses_lifetimes!(syn::LifetimeParam, lifetime, bounds); uses_lifetimes!(syn::ParenthesizedGenericArguments, inputs, output); uses_lifetimes!(syn::Path, segments); uses_lifetimes!(syn::PathSegment, arguments); -uses_lifetimes!(syn::PredicateEq, lhs_ty, rhs_ty); uses_lifetimes!(syn::PredicateLifetime, lifetime, bounds); uses_lifetimes!(syn::PredicateType, lifetimes, bounded_ty, bounds); uses_lifetimes!(syn::QSelf, ty); @@ -134,6 +134,7 @@ uses_lifetimes!(syn::TypeArray, elem); uses_lifetimes!(syn::TypeBareFn, inputs, output); uses_lifetimes!(syn::TypeGroup, elem); uses_lifetimes!(syn::TypeImplTrait, bounds); +uses_lifetimes!(syn::TypeParam, bounds); uses_lifetimes!(syn::TypeParen, elem); uses_lifetimes!(syn::TypePtr, elem); uses_lifetimes!(syn::TypeReference, lifetime, elem); @@ -245,7 +246,8 @@ impl UsesLifetimes for syn::WherePredicate { match *self { syn::WherePredicate::Type(ref v) => v.uses_lifetimes(options, lifetimes), syn::WherePredicate::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes), - syn::WherePredicate::Eq(ref v) => v.uses_lifetimes(options, lifetimes), + // non-exhaustive enum + _ => Default::default(), } } } @@ -258,10 +260,27 @@ impl UsesLifetimes for syn::GenericArgument { ) -> LifetimeRefSet<'a> { match *self { syn::GenericArgument::Type(ref v) => v.uses_lifetimes(options, lifetimes), - syn::GenericArgument::Binding(ref v) => v.uses_lifetimes(options, lifetimes), + syn::GenericArgument::AssocType(ref v) => v.uses_lifetimes(options, lifetimes), syn::GenericArgument::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes), syn::GenericArgument::Constraint(ref v) => v.uses_lifetimes(options, lifetimes), - syn::GenericArgument::Const(_) => Default::default(), + syn::GenericArgument::AssocConst(_) + | syn::GenericArgument::Const(_) => Default::default(), + // non-exhaustive enum + _ => Default::default(), + } + } +} + +impl UsesLifetimes for syn::GenericParam { + fn uses_lifetimes<'a>( + &self, + options: &Options, + lifetimes: &'a LifetimeSet, + ) -> LifetimeRefSet<'a> { + match *self { + syn::GenericParam::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes), + syn::GenericParam::Type(ref v) => v.uses_lifetimes(options, lifetimes), + syn::GenericParam::Const(ref v) => v.uses_lifetimes(options, lifetimes), } } } @@ -275,6 +294,7 @@ impl UsesLifetimes for syn::TypeParamBound { match *self { syn::TypeParamBound::Trait(ref v) => v.uses_lifetimes(options, lifetimes), syn::TypeParamBound::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes), + _ => Default::default(), } } } @@ -282,8 +302,7 @@ impl UsesLifetimes for syn::TypeParamBound { #[cfg(test)] mod tests { use proc_macro2::Span; - use syn::parse_quote; - use syn::DeriveInput; + use syn::{parse_quote, DeriveInput}; use super::UsesLifetimes; use crate::usage::GenericsExt; From 139b46886d8b2e1e921ea079d03d272ad5a86f89 Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Sat, 18 Mar 2023 14:56:44 +0100 Subject: [PATCH 04/22] Migrate to syn v2 darling heavily relies on `syn::NestedMeta`. This type has been removed in syn v2. Instead create a copy under `darling::ast::NestedMeta` and use that to replace all uses of `syn::NestedMeta`. --- Cargo.toml | 2 +- core/Cargo.toml | 2 +- core/src/ast/data.rs | 40 +++++++++++++++++++++++ core/src/ast/generics.rs | 14 ++++---- core/src/codegen/from_meta_impl.rs | 6 ++-- core/src/codegen/variant_data.rs | 4 +-- core/src/error/mod.rs | 52 ++++++++++++++++++++++++++++-- core/src/from_meta.rs | 42 ++++++++++++------------ core/src/options/forward_attrs.rs | 3 +- core/src/options/mod.rs | 16 ++++----- core/src/options/shape.rs | 5 +-- core/src/util/over_ride.rs | 3 +- core/src/util/parse_attribute.rs | 33 ++++++++++++------- core/src/util/path_list.rs | 5 +-- core/src/util/spanned_value.rs | 4 +-- macro/Cargo.toml | 2 +- src/lib.rs | 2 ++ 17 files changed, 167 insertions(+), 68 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6f9aa306..5326366b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ darling_macro = { version = "=0.14.4", path = "macro" } [dev-dependencies] proc-macro2 = "1.0.37" quote = "1.0.18" -syn = "1.0.91" +syn = "2.0.0" [target.'cfg(compiletests)'.dev-dependencies] rustversion = "1.0.9" diff --git a/core/Cargo.toml b/core/Cargo.toml index 677f9815..ce2d2a8d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,6 +18,6 @@ suggestions = ["strsim"] ident_case = "1.0.1" proc-macro2 = "1.0.37" quote = "1.0.18" -syn = { version = "1.0.91", features = ["full", "extra-traits"] } +syn = { version = "2.0.0", features = ["full", "extra-traits"] } fnv = "1.0.7" strsim = { version = "0.10.0", optional = true } diff --git a/core/src/ast/data.rs b/core/src/ast/data.rs index 958c7be9..38b0190e 100644 --- a/core/src/ast/data.rs +++ b/core/src/ast/data.rs @@ -2,7 +2,10 @@ use std::{slice, vec}; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; +use syn::ext::IdentExt; +use syn::parse::Parser; use syn::spanned::Spanned; +use syn::Token; use crate::usage::{ self, IdentRefSet, IdentSet, LifetimeRefSet, LifetimeSet, UsesLifetimes, UsesTypeParams, @@ -410,6 +413,43 @@ impl<'a> From<&'a syn::Fields> for Style { } } +#[derive(Debug)] +pub enum NestedMeta { + Meta(syn::Meta), + Lit(syn::Lit), +} + +impl NestedMeta { + pub fn parse_meta_list(tokens: TokenStream) -> syn::Result> { + syn::punctuated::Punctuated::::parse_terminated + .parse2(tokens) + .map(|punctuated| punctuated.into_iter().collect()) + } +} + +impl syn::parse::Parse for NestedMeta { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + if input.peek(syn::Lit) && !(input.peek(syn::LitBool) && input.peek2(Token![=])) { + input.parse().map(NestedMeta::Lit) + } else if input.peek(syn::Ident::peek_any) + || input.peek(Token![::]) && input.peek3(syn::Ident::peek_any) + { + input.parse().map(NestedMeta::Meta) + } else { + Err(input.error("expected identifier or literal")) + } + } +} + +impl ToTokens for NestedMeta { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + NestedMeta::Meta(meta) => meta.to_tokens(tokens), + NestedMeta::Lit(lit) => lit.to_tokens(tokens), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/src/ast/generics.rs b/core/src/ast/generics.rs index 3de3c579..d048e6a2 100644 --- a/core/src/ast/generics.rs +++ b/core/src/ast/generics.rs @@ -14,7 +14,7 @@ use crate::{FromGenericParam, FromGenerics, FromTypeParam, Result}; pub trait GenericParamExt { /// The type this GenericParam uses to represent type params and their bounds type TypeParam; - type LifetimeDef; + type LifetimeParam; type ConstParam; /// If this GenericParam is a type param, get the underlying value. @@ -23,7 +23,7 @@ pub trait GenericParamExt { } /// If this GenericParam is a lifetime, get the underlying value. - fn as_lifetime_def(&self) -> Option<&Self::LifetimeDef> { + fn as_lifetime_def(&self) -> Option<&Self::LifetimeParam> { None } @@ -35,7 +35,7 @@ pub trait GenericParamExt { impl GenericParamExt for syn::GenericParam { type TypeParam = syn::TypeParam; - type LifetimeDef = syn::LifetimeDef; + type LifetimeParam = syn::LifetimeParam; type ConstParam = syn::ConstParam; fn as_type_param(&self) -> Option<&Self::TypeParam> { @@ -46,7 +46,7 @@ impl GenericParamExt for syn::GenericParam { } } - fn as_lifetime_def(&self) -> Option<&Self::LifetimeDef> { + fn as_lifetime_def(&self) -> Option<&Self::LifetimeParam> { if let syn::GenericParam::Lifetime(ref val) = *self { Some(val) } else { @@ -65,7 +65,7 @@ impl GenericParamExt for syn::GenericParam { impl GenericParamExt for syn::TypeParam { type TypeParam = syn::TypeParam; - type LifetimeDef = (); + type LifetimeParam = (); type ConstParam = (); fn as_type_param(&self) -> Option<&Self::TypeParam> { @@ -75,7 +75,7 @@ impl GenericParamExt for syn::TypeParam { /// A mirror of `syn::GenericParam` which is generic over all its contents. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum GenericParam { +pub enum GenericParam { Type(T), Lifetime(L), Const(C), @@ -103,7 +103,7 @@ impl FromGenericParam for GenericParam { impl GenericParamExt for GenericParam { type TypeParam = T; - type LifetimeDef = L; + type LifetimeParam = L; type ConstParam = C; fn as_type_param(&self) -> Option<&T> { diff --git a/core/src/codegen/from_meta_impl.rs b/core/src/codegen/from_meta_impl.rs index af59f3b1..5a65c5d7 100644 --- a/core/src/codegen/from_meta_impl.rs +++ b/core/src/codegen/from_meta_impl.rs @@ -55,7 +55,7 @@ impl<'a> ToTokens for FromMetaImpl<'a> { let post_transform = base.post_transform_call(); quote!( - fn from_list(__items: &[::darling::export::syn::NestedMeta]) -> ::darling::Result { + fn from_list(__items: &[::darling::export::NestedMeta]) -> ::darling::Result { #decls @@ -91,13 +91,13 @@ impl<'a> ToTokens for FromMetaImpl<'a> { }; quote!( - fn from_list(__outer: &[::darling::export::syn::NestedMeta]) -> ::darling::Result { + fn from_list(__outer: &[::darling::export::NestedMeta]) -> ::darling::Result { // An enum must have exactly one value inside the parentheses if it's not a unit // match arm match __outer.len() { 0 => ::darling::export::Err(::darling::Error::too_few_items(1)), 1 => { - if let ::darling::export::syn::NestedMeta::Meta(ref __nested) = __outer[0] { + if let ::darling::export::NestedMeta::Meta(ref __nested) = __outer[0] { match ::darling::util::path_to_string(__nested.path()).as_ref() { #(#struct_arms)* __other => ::darling::export::Err(::darling::Error::#unknown_variant_err.with_span(__nested)) diff --git a/core/src/codegen/variant_data.rs b/core/src/codegen/variant_data.rs index d1c878e2..7c0063ea 100644 --- a/core/src/codegen/variant_data.rs +++ b/core/src/codegen/variant_data.rs @@ -60,14 +60,14 @@ impl<'a> FieldsGen<'a> { quote!( for __item in __items { match *__item { - ::darling::export::syn::NestedMeta::Meta(ref __inner) => { + ::darling::export::NestedMeta::Meta(ref __inner) => { let __name = ::darling::util::path_to_string(__inner.path()); match __name.as_str() { #(#arms)* __other => { #handle_unknown } } } - ::darling::export::syn::NestedMeta::Lit(ref __inner) => { + ::darling::export::NestedMeta::Lit(ref __inner) => { __errors.push(::darling::Error::unsupported_format("literal") .with_span(__inner)); } diff --git a/core/src/error/mod.rs b/core/src/error/mod.rs index 8d888645..f165ff76 100644 --- a/core/src/error/mod.rs +++ b/core/src/error/mod.rs @@ -13,7 +13,7 @@ use std::iter::{self, Iterator}; use std::string::ToString; use std::vec; use syn::spanned::Spanned; -use syn::{Lit, LitStr, Path}; +use syn::{Lit, LitStr, Path, Expr}; #[cfg(feature = "diagnostics")] mod child; @@ -149,6 +149,53 @@ impl Error { Error::new(ErrorKind::UnexpectedType(ty.into())) } + pub fn unexpected_expr_type(expr: &Expr) -> Self { + Error::unexpected_type(match *expr { + Expr::Array(_) => "array", + Expr::Assign(_) => "assign", + Expr::Async(_) => "async", + Expr::Await(_) => "await", + Expr::Binary(_) => "binary", + Expr::Block(_) => "block", + Expr::Break(_) => "break", + Expr::Call(_) => "call", + Expr::Cast(_) => "cast", + Expr::Closure(_) => "closure", + Expr::Const(_) => "const", + Expr::Continue(_) => "continue", + Expr::Field(_) => "field", + Expr::ForLoop(_) => "for_loop", + Expr::Group(_) => "group", + Expr::If(_) => "if", + Expr::Index(_) => "index", + Expr::Infer(_) => "infer", + Expr::Let(_) => "let", + Expr::Lit(_) => "lit", + Expr::Loop(_) => "loop", + Expr::Macro(_) => "macro", + Expr::Match(_) => "match", + Expr::MethodCall(_) => "method_call", + Expr::Paren(_) => "paren", + Expr::Path(_) => "path", + Expr::Range(_) => "range", + Expr::Reference(_) => "reference", + Expr::Repeat(_) => "repeat", + Expr::Return(_) => "return", + Expr::Struct(_) => "struct", + Expr::Try(_) => "try", + Expr::TryBlock(_) => "try_block", + Expr::Tuple(_) => "tuple", + Expr::Unary(_) => "unary", + Expr::Unsafe(_) => "unsafe", + Expr::Verbatim(_) => "verbatim", + Expr::While(_) => "while", + Expr::Yield(_) => "yield", + // non-exhaustive enum + _ => "unknown", + }) + .with_span(expr) + } + /// Creates a new error for a field which has an unexpected literal type. This will automatically /// extract the literal type name from the passed-in `Lit` and set the span to encompass only the /// literal value. @@ -188,6 +235,8 @@ impl Error { Lit::Float(_) => "float", Lit::Bool(_) => "bool", Lit::Verbatim(_) => "verbatim", + // non-exhaustive enum + _ => "unknown", }) .with_span(lit) } @@ -258,7 +307,6 @@ impl Error { /// overridden: /// /// * `FromMeta::from_meta` - /// * `FromMeta::from_nested_meta` /// * `FromMeta::from_value` pub fn with_span(mut self, node: &T) -> Self { if !self.has_span() { diff --git a/core/src/from_meta.rs b/core/src/from_meta.rs index ac4f06c3..9a4ef74d 100644 --- a/core/src/from_meta.rs +++ b/core/src/from_meta.rs @@ -7,9 +7,11 @@ use std::rc::Rc; use std::sync::atomic::AtomicBool; use std::sync::Arc; -use syn::{Expr, Lit, Meta, NestedMeta}; +use syn::{Expr, Lit, Meta}; -use crate::{util::path_to_string, Error, Result}; +use crate::ast::NestedMeta; +use crate::util::path_to_string; +use crate::{Error, Result}; /// Create an instance from an item in an attribute declaration. /// @@ -47,14 +49,6 @@ use crate::{util::path_to_string, Error, Result}; /// * Allows for fallible parsing; will populate the target field with the result of the /// parse attempt. pub trait FromMeta: Sized { - fn from_nested_meta(item: &NestedMeta) -> Result { - (match *item { - NestedMeta::Lit(ref lit) => Self::from_value(lit), - NestedMeta::Meta(ref mi) => Self::from_meta(mi), - }) - .map_err(|e| e.with_span(item)) - } - /// Create an instance from a `syn::Meta` by dispatching to the format-appropriate /// trait function. This generally should not be overridden by implementers. /// @@ -66,14 +60,10 @@ pub trait FromMeta: Sized { fn from_meta(item: &Meta) -> Result { (match *item { Meta::Path(_) => Self::from_word(), - Meta::List(ref value) => Self::from_list( - &value - .nested - .iter() - .cloned() - .collect::>()[..], - ), - Meta::NameValue(ref value) => Self::from_value(&value.lit), + Meta::List(ref value) => { + Self::from_list(&NestedMeta::parse_meta_list(value.tokens.clone())?[..]) + } + Meta::NameValue(ref value) => Self::from_expr(&value.value), }) .map_err(|e| e.with_span(item)) } @@ -121,6 +111,14 @@ pub trait FromMeta: Sized { .map_err(|e| e.with_span(value)) } + fn from_expr(expr: &Expr) -> Result { + match *expr { + Expr::Lit(ref lit) => Self::from_value(&lit.lit), + _ => Err(Error::unexpected_expr_type(expr)), + } + .map_err(|e| e.with_span(expr)) + } + /// Create an instance from a char literal in a value position. #[allow(unused_variables)] fn from_char(value: char) -> Result { @@ -519,7 +517,7 @@ impl KeyFromPath for syn::Ident { macro_rules! hash_map { ($key:ty) => { impl FromMeta for HashMap<$key, V, S> { - fn from_list(nested: &[syn::NestedMeta]) -> Result { + fn from_list(nested: &[NestedMeta]) -> Result { // Convert the nested meta items into a sequence of (path, value result) result tuples. // An outer Err means no (key, value) structured could be found, while an Err in the // second position of the tuple means that value was rejected by FromMeta. @@ -530,14 +528,14 @@ macro_rules! hash_map { .iter() .map(|item| -> Result<(&syn::Path, Result)> { match *item { - syn::NestedMeta::Meta(ref inner) => { + NestedMeta::Meta(ref inner) => { let path = inner.path(); Ok(( path, FromMeta::from_meta(inner).map_err(|e| e.at_path(&path)), )) } - syn::NestedMeta::Lit(_) => Err(Error::unsupported_format("literal")), + NestedMeta::Lit(_) => Err(Error::unsupported_format("expression")), } }); @@ -613,7 +611,7 @@ mod tests { /// parse a string as a syn::Meta instance. fn pm(tokens: TokenStream) -> ::std::result::Result { let attribute: syn::Attribute = parse_quote!(#[#tokens]); - attribute.parse_meta().map_err(|_| "Unable to parse".into()) + Ok(attribute.meta) } fn fm(tokens: TokenStream) -> T { diff --git a/core/src/options/forward_attrs.rs b/core/src/options/forward_attrs.rs index c72d009d..d9dfb375 100644 --- a/core/src/options/forward_attrs.rs +++ b/core/src/options/forward_attrs.rs @@ -1,7 +1,6 @@ -use syn::NestedMeta; - use crate::util::PathList; use crate::{FromMeta, Result}; +use crate::ast::NestedMeta; /// A rule about which attributes to forward to the generated struct. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/core/src/options/mod.rs b/core/src/options/mod.rs index d8a147d8..12b474c9 100644 --- a/core/src/options/mod.rs +++ b/core/src/options/mod.rs @@ -2,6 +2,7 @@ use proc_macro2::Span; use syn::{parse_quote, spanned::Spanned}; use crate::{Error, FromMeta, Result}; +use crate::ast::NestedMeta; mod core; mod forward_attrs; @@ -50,7 +51,7 @@ impl FromMeta for DefaultExpression { match item { syn::Meta::Path(_) => Ok(DefaultExpression::Trait { span: item.span() }), syn::Meta::List(nm) => Err(Error::unsupported_format("list").with_span(nm)), - syn::Meta::NameValue(nv) => Self::from_value(&nv.lit), + syn::Meta::NameValue(nv) => Self::from_expr(&nv.value), } } @@ -66,7 +67,7 @@ pub trait ParseAttribute: Sized { fn parse_attributes(mut self, attrs: &[syn::Attribute]) -> Result { let mut errors = Error::accumulator(); for attr in attrs { - if attr.path == parse_quote!(darling) { + if attr.meta.path() == &parse_quote!(darling) { errors.handle(parse_attr(attr, &mut self)); } } @@ -80,10 +81,10 @@ pub trait ParseAttribute: Sized { fn parse_attr(attr: &syn::Attribute, target: &mut T) -> Result<()> { let mut errors = Error::accumulator(); - match attr.parse_meta().ok() { - Some(syn::Meta::List(data)) => { - for item in data.nested { - if let syn::NestedMeta::Meta(ref mi) = item { + match &attr.meta { + syn::Meta::List(data) => { + for item in NestedMeta::parse_meta_list(data.tokens.clone())? { + if let NestedMeta::Meta(ref mi) = item { errors.handle(target.parse_nested(mi)); } else { panic!("Wasn't able to parse: `{:?}`", item); @@ -92,8 +93,7 @@ fn parse_attr(attr: &syn::Attribute, target: &mut T) -> Resul errors.finish() } - Some(ref item) => panic!("Wasn't able to parse: `{:?}`", item), - None => panic!("Unable to parse {:?}", attr), + item => panic!("Wasn't able to parse: `{:?}`", item), } } diff --git a/core/src/options/shape.rs b/core/src/options/shape.rs index 5245a00d..83cf8d0b 100644 --- a/core/src/options/shape.rs +++ b/core/src/options/shape.rs @@ -3,9 +3,10 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens, TokenStreamExt}; -use syn::{parse_quote, Meta, NestedMeta}; +use syn::{parse_quote, Meta}; use crate::{Error, FromMeta, Result}; +use crate::ast::NestedMeta; /// Receiver struct for shape validation. Shape validation allows a deriving type /// to declare that it only accepts - for example - named structs, or newtype enum @@ -225,7 +226,7 @@ mod tests { /// parse a string as a syn::Meta instance. fn pm(tokens: TokenStream) -> ::std::result::Result { let attribute: syn::Attribute = parse_quote!(#[#tokens]); - attribute.parse_meta().map_err(|_| "Unable to parse".into()) + Ok(attribute.meta) } fn fm(tokens: TokenStream) -> T { diff --git a/core/src/util/over_ride.rs b/core/src/util/over_ride.rs index 92ce25a3..c705d46e 100644 --- a/core/src/util/over_ride.rs +++ b/core/src/util/over_ride.rs @@ -1,8 +1,9 @@ use std::fmt; -use syn::{Lit, NestedMeta}; +use syn::{Lit, }; use crate::{FromMeta, Result}; +use crate::ast::NestedMeta; use self::Override::*; diff --git a/core/src/util/parse_attribute.rs b/core/src/util/parse_attribute.rs index 4a088e10..1ffe7690 100644 --- a/core/src/util/parse_attribute.rs +++ b/core/src/util/parse_attribute.rs @@ -1,26 +1,34 @@ use crate::{Error, Result}; use std::fmt; -use syn::{punctuated::Pair, spanned::Spanned, token, Attribute, Meta, MetaList, Path}; +use syn::punctuated::Pair; +use syn::spanned::Spanned; +use syn::{token, Attribute, Meta, MetaList, Path}; /// Try to parse an attribute into a meta list. Path-type meta values are accepted and returned /// as empty lists with their passed-in path. Name-value meta values and non-meta attributes /// will cause errors to be returned. pub fn parse_attribute_to_meta_list(attr: &Attribute) -> Result { - match attr.parse_meta() { - Ok(Meta::List(list)) => Ok(list), - Ok(Meta::NameValue(nv)) => Err(Error::custom(format!( + match &attr.meta { + Meta::List(list) => Ok(list.clone()), + Meta::NameValue(nv) => Err(Error::custom(format!( "Name-value arguments are not supported. Use #[{}(...)]", DisplayPath(&nv.path) )) .with_span(&nv)), - Ok(Meta::Path(path)) => Ok(MetaList { - path, - paren_token: token::Paren(attr.span()), - nested: Default::default(), + Meta::Path(path) => Ok(MetaList { + path: path.clone(), + delimiter: syn::MacroDelimiter::Paren(token::Paren { + span: { + let mut group = proc_macro2::Group::new( + proc_macro2::Delimiter::None, + proc_macro2::TokenStream::new(), + ); + group.set_span(attr.span()); + group.delim_span() + }, + }), + tokens: Default::default(), }), - Err(e) => { - Err(Error::custom(format!("Unable to parse attribute: {}", e)).with_span(&e.span())) - } } } @@ -46,7 +54,8 @@ impl fmt::Display for DisplayPath<'_> { #[cfg(test)] mod tests { use super::parse_attribute_to_meta_list; - use syn::{parse_quote, spanned::Spanned, Ident}; + use syn::spanned::Spanned; + use syn::{parse_quote, Ident}; #[test] fn parse_list() { diff --git a/core/src/util/path_list.rs b/core/src/util/path_list.rs index 18b0f630..1fdd3ed6 100644 --- a/core/src/util/path_list.rs +++ b/core/src/util/path_list.rs @@ -1,8 +1,9 @@ use std::ops::Deref; -use syn::{Meta, NestedMeta, Path}; +use syn::{Meta, Path}; use crate::{Error, FromMeta, Result}; +use crate::ast::NestedMeta; use super::path_to_string; @@ -72,7 +73,7 @@ mod tests { /// parse a string as a syn::Meta instance. fn pm(tokens: TokenStream) -> ::std::result::Result { let attribute: Attribute = parse_quote!(#[#tokens]); - attribute.parse_meta().map_err(|_| "Unable to parse".into()) + Ok(attribute.meta) } fn fm(tokens: TokenStream) -> T { diff --git a/core/src/util/spanned_value.rs b/core/src/util/spanned_value.rs index 68d5423a..68dcc380 100644 --- a/core/src/util/spanned_value.rs +++ b/core/src/util/spanned_value.rs @@ -89,10 +89,10 @@ impl FromMeta for SpannedValue { syn::Meta::Path(path) => path.span(), // Example: `#[darling(attributes(Value))]` as a SpannedValue> // should have the span pointing to the list contents. - syn::Meta::List(list) => list.nested.span(), + syn::Meta::List(list) => list.tokens.span(), // Example: `#[darling(skip = true)]` as SpannedValue // should have the span pointing to the word `true`. - syn::Meta::NameValue(nv) => nv.lit.span(), + syn::Meta::NameValue(nv) => nv.value.span(), }; Ok(Self::new(value, span)) diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 3f50ffa3..6a652cef 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" [dependencies] quote = "1.0.18" -syn = "1.0.91" +syn = "2.0.0" darling_core = { version = "=0.14.4", path = "../core" } [lib] diff --git a/src/lib.rs b/src/lib.rs index b2be36d0..657a6ffe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,8 @@ pub mod export { pub use darling_core::syn; pub use std::string::ToString; pub use std::vec::Vec; + + pub use crate::ast::NestedMeta; } #[macro_use] From 130dc3e1cee9b644a6eccb4dcb24957f8627f7fb Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Sat, 18 Mar 2023 15:06:57 +0100 Subject: [PATCH 05/22] Get the macro generated code to compile. Switch access to `.nested` to use `parse_meta_list` instead. --- core/src/codegen/attr_extractor.rs | 19 ++++++++++++------- core/src/codegen/variant.rs | 3 ++- tests/hash_map.rs | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/core/src/codegen/attr_extractor.rs b/core/src/codegen/attr_extractor.rs index 521d01c5..a5d911c3 100644 --- a/core/src/codegen/attr_extractor.rs +++ b/core/src/codegen/attr_extractor.rs @@ -55,13 +55,18 @@ pub trait ExtractAttribute { #(#attr_names)|* => { match ::darling::util::parse_attribute_to_meta_list(__attr) { ::darling::export::Ok(__data) => { - if __data.nested.is_empty() { - continue; + match ::darling::export::NestedMeta::parse_meta_list(__data.tokens.clone()) { + ::darling::export::Ok(ref __items) => { + if __items.is_empty() { + continue; + } + + #core_loop + } + ::darling::export::Err(__err) => { + __errors.push(__err.into()); + } } - - let __items = &__data.nested; - - #core_loop } // darling was asked to handle this attribute name, but the actual attribute // isn't one that darling can work with. This either indicates a typing error @@ -92,7 +97,7 @@ pub trait ExtractAttribute { for __attr in #attrs_accessor { // Filter attributes based on name - match ::darling::export::ToString::to_string(&__attr.path.clone().into_token_stream()).as_str() { + match ::darling::export::ToString::to_string(&__attr.path().clone().into_token_stream()).as_str() { #parse_handled #forward_unhandled } diff --git a/core/src/codegen/variant.rs b/core/src/codegen/variant.rs index 9f5902ba..ccbabad6 100644 --- a/core/src/codegen/variant.rs +++ b/core/src/codegen/variant.rs @@ -121,7 +121,8 @@ impl<'a> ToTokens for DataMatchArm<'a> { tokens.append_all(quote!( #name_in_attr => { if let ::darling::export::syn::Meta::List(ref __data) = *__nested { - let __items = &__data.nested; + let __items = ::darling::export::NestedMeta::parse_meta_list(__data.tokens.clone())?; + let __items = &__items; #declare_errors diff --git a/tests/hash_map.rs b/tests/hash_map.rs index 5d9a0114..881cd1cf 100644 --- a/tests/hash_map.rs +++ b/tests/hash_map.rs @@ -16,7 +16,7 @@ fn parse_map() { #[foo(first(name = "Hello", option), the::second(name = "Second"))] }; - let meta = attr.parse_meta().unwrap(); + let meta = attr.meta; let map: HashMap = FromMeta::from_meta(&meta).unwrap(); let comparison: HashMap = vec![ From a38aaa838539215315ea19b4b503209bd809d1c2 Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Sat, 18 Mar 2023 16:34:54 +0100 Subject: [PATCH 06/22] Fix tests --- tests/unsupported_attributes.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unsupported_attributes.rs b/tests/unsupported_attributes.rs index 14edf2c8..98b190d5 100644 --- a/tests/unsupported_attributes.rs +++ b/tests/unsupported_attributes.rs @@ -20,8 +20,8 @@ fn non_meta_attribute_gets_own_error() { }; let errors: darling::Error = Bar::from_derive_input(&di).unwrap_err().flatten(); - // The number of errors here is 1 for the bad attribute + 2 for the missing fields - assert_eq!(3, errors.len()); + // The number of errors here is 1 for the bad value of the `st` attribute + assert_eq!(1, errors.len()); // Make sure one of the errors propagates the syn error assert!(errors .into_iter() @@ -40,8 +40,8 @@ fn non_meta_attribute_does_not_block_others() { }; let errors: darling::Error = Bar::from_derive_input(&di).unwrap_err().flatten(); - // The number of errors here is 1 for the bad attribute + 1 for the missing "st" field - assert_eq!(2, errors.len()); + // The number of errors here is 1 for the bad value of the `st` attribute + assert_eq!(1, errors.len()); // Make sure one of the errors propagates the syn error assert!(errors .into_iter() From c9c22894f7b0ebec36daf043acee7547885be65b Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Mon, 20 Mar 2023 21:44:30 +0100 Subject: [PATCH 07/22] Fix two `.nested` which were not converted --- core/src/util/parse_attribute.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/util/parse_attribute.rs b/core/src/util/parse_attribute.rs index 1ffe7690..129e49c0 100644 --- a/core/src/util/parse_attribute.rs +++ b/core/src/util/parse_attribute.rs @@ -56,18 +56,21 @@ mod tests { use super::parse_attribute_to_meta_list; use syn::spanned::Spanned; use syn::{parse_quote, Ident}; + use crate::ast::NestedMeta; #[test] fn parse_list() { let meta = parse_attribute_to_meta_list(&parse_quote!(#[bar(baz = 4)])).unwrap(); - assert_eq!(meta.nested.len(), 1); + let nested_meta = NestedMeta::parse_meta_list(meta.tokens.clone()).unwrap(); + assert_eq!(nested_meta.len(), 1); } #[test] fn parse_path_returns_empty_list() { let meta = parse_attribute_to_meta_list(&parse_quote!(#[bar])).unwrap(); + let nested_meta = NestedMeta::parse_meta_list(meta.tokens.clone()).unwrap(); assert!(meta.path.is_ident(&Ident::new("bar", meta.path.span()))); - assert!(meta.nested.is_empty()); + assert!(nested_meta.is_empty()); } #[test] From cb6629a738e00b65aff38950df4fcf842d50fbad Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Mon, 20 Mar 2023 21:50:05 +0100 Subject: [PATCH 08/22] Fix some formatting issues --- core/src/error/mod.rs | 2 +- core/src/options/forward_attrs.rs | 2 +- core/src/options/mod.rs | 2 +- core/src/options/shape.rs | 2 +- core/src/usage/lifetimes.rs | 5 +++-- core/src/util/over_ride.rs | 4 ++-- core/src/util/path_list.rs | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/core/src/error/mod.rs b/core/src/error/mod.rs index f165ff76..17b58765 100644 --- a/core/src/error/mod.rs +++ b/core/src/error/mod.rs @@ -13,7 +13,7 @@ use std::iter::{self, Iterator}; use std::string::ToString; use std::vec; use syn::spanned::Spanned; -use syn::{Lit, LitStr, Path, Expr}; +use syn::{Expr, Lit, LitStr, Path}; #[cfg(feature = "diagnostics")] mod child; diff --git a/core/src/options/forward_attrs.rs b/core/src/options/forward_attrs.rs index d9dfb375..ac9f4e1a 100644 --- a/core/src/options/forward_attrs.rs +++ b/core/src/options/forward_attrs.rs @@ -1,6 +1,6 @@ +use crate::ast::NestedMeta; use crate::util::PathList; use crate::{FromMeta, Result}; -use crate::ast::NestedMeta; /// A rule about which attributes to forward to the generated struct. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/core/src/options/mod.rs b/core/src/options/mod.rs index 12b474c9..066aa1c1 100644 --- a/core/src/options/mod.rs +++ b/core/src/options/mod.rs @@ -1,8 +1,8 @@ use proc_macro2::Span; use syn::{parse_quote, spanned::Spanned}; -use crate::{Error, FromMeta, Result}; use crate::ast::NestedMeta; +use crate::{Error, FromMeta, Result}; mod core; mod forward_attrs; diff --git a/core/src/options/shape.rs b/core/src/options/shape.rs index 83cf8d0b..3e35a236 100644 --- a/core/src/options/shape.rs +++ b/core/src/options/shape.rs @@ -5,8 +5,8 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens, TokenStreamExt}; use syn::{parse_quote, Meta}; -use crate::{Error, FromMeta, Result}; use crate::ast::NestedMeta; +use crate::{Error, FromMeta, Result}; /// Receiver struct for shape validation. Shape validation allows a deriving type /// to declare that it only accepts - for example - named structs, or newtype enum diff --git a/core/src/usage/lifetimes.rs b/core/src/usage/lifetimes.rs index 87a2d829..db065e91 100644 --- a/core/src/usage/lifetimes.rs +++ b/core/src/usage/lifetimes.rs @@ -263,8 +263,9 @@ impl UsesLifetimes for syn::GenericArgument { syn::GenericArgument::AssocType(ref v) => v.uses_lifetimes(options, lifetimes), syn::GenericArgument::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes), syn::GenericArgument::Constraint(ref v) => v.uses_lifetimes(options, lifetimes), - syn::GenericArgument::AssocConst(_) - | syn::GenericArgument::Const(_) => Default::default(), + syn::GenericArgument::AssocConst(_) | syn::GenericArgument::Const(_) => { + Default::default() + } // non-exhaustive enum _ => Default::default(), } diff --git a/core/src/util/over_ride.rs b/core/src/util/over_ride.rs index c705d46e..3de6ed53 100644 --- a/core/src/util/over_ride.rs +++ b/core/src/util/over_ride.rs @@ -1,9 +1,9 @@ use std::fmt; -use syn::{Lit, }; +use syn::Lit; -use crate::{FromMeta, Result}; use crate::ast::NestedMeta; +use crate::{FromMeta, Result}; use self::Override::*; diff --git a/core/src/util/path_list.rs b/core/src/util/path_list.rs index 1fdd3ed6..bea25fd5 100644 --- a/core/src/util/path_list.rs +++ b/core/src/util/path_list.rs @@ -2,8 +2,8 @@ use std::ops::Deref; use syn::{Meta, Path}; -use crate::{Error, FromMeta, Result}; use crate::ast::NestedMeta; +use crate::{Error, FromMeta, Result}; use super::path_to_string; From 83df29a9819a8dc3245b62ef423b22f478994cfe Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Tue, 21 Mar 2023 13:49:12 -0700 Subject: [PATCH 09/22] Add `dyn` to fix unit tests It's no longer allowed to use bare trait types for Box type parameter. --- core/src/usage/type_params.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/usage/type_params.rs b/core/src/usage/type_params.rs index 5c95b978..74967ae7 100644 --- a/core/src/usage/type_params.rs +++ b/core/src/usage/type_params.rs @@ -327,7 +327,7 @@ mod tests { #[test] fn box_fn_output() { - let input: DeriveInput = parse_quote! { struct Foo(Box T>); }; + let input: DeriveInput = parse_quote! { struct Foo(Box T>); }; let generics = ident_set(vec!["T"]); let matches = input.data.uses_type_params(&BoundImpl.into(), &generics); assert_eq!(matches.len(), 1); @@ -336,7 +336,7 @@ mod tests { #[test] fn box_fn_input() { - let input: DeriveInput = parse_quote! { struct Foo(Box ()>); }; + let input: DeriveInput = parse_quote! { struct Foo(Box ()>); }; let generics = ident_set(vec!["T"]); let matches = input.data.uses_type_params(&BoundImpl.into(), &generics); assert_eq!(matches.len(), 1); From 7974d9e348bcff12f1a1d45ddcdf120b17ebf4f2 Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Tue, 21 Mar 2023 14:04:02 -0700 Subject: [PATCH 10/22] Bump MSRV to 1.56.0 This is syn v2's MSRV, and darling cannot go below that. --- .github/workflows/ci.yml | 132 +++++++++++++++++++-------------------- README.md | 69 +++++++++++--------- clippy.toml | 2 +- macro/src/lib.rs | 3 - 4 files changed, 105 insertions(+), 101 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae44483e..4e55e2c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,76 +1,76 @@ name: CI on: - push: - pull_request: - schedule: [cron: "40 1 * * *"] + push: + pull_request: + schedule: [cron: "40 1 * * *"] env: - RUST_BACKTRACE: 1 + RUST_BACKTRACE: 1 jobs: - test: - name: Test Rust ${{ matrix.rust }} on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - { rust: nightly, os: ubuntu-latest } - - { rust: nightly, os: macos-latest } - - { rust: nightly, os: windows-latest } - - { rust: stable, os: ubuntu-latest } - - { rust: stable, os: macos-latest } - - { rust: stable, os: windows-latest } - - { rust: 1.31.0, os: ubuntu-latest } - - { rust: 1.31.0, os: windows-latest } - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.rust }} - - uses: Swatinem/rust-cache@v2 - - name: Check Cargo availability - run: cargo --version - - run: cargo test --verbose --all - - run: cargo test --verbose --manifest-path core/Cargo.toml --no-default-features + test: + name: Test Rust ${{ matrix.rust }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - { rust: nightly, os: ubuntu-latest } + - { rust: nightly, os: macos-latest } + - { rust: nightly, os: windows-latest } + - { rust: stable, os: ubuntu-latest } + - { rust: stable, os: macos-latest } + - { rust: stable, os: windows-latest } + - { rust: 1.56.0, os: ubuntu-latest } + - { rust: 1.56.0, os: windows-latest } + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 + - name: Check Cargo availability + run: cargo --version + - run: cargo test --verbose --all + - run: cargo test --verbose --manifest-path core/Cargo.toml --no-default-features - # Diagnostics are remaining a nightly-only feature for the foreseeable future, but - # we don't want them to break without us realizing. - test_diagnostics: - name: Test nightly with diagnostics feature - runs-on: ubuntu-latest - continue-on-error: true - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - - uses: Swatinem/rust-cache@v2 - - name: Check Cargo availability - run: cargo --version - - run: cargo test --verbose --workspace --features diagnostics + # Diagnostics are remaining a nightly-only feature for the foreseeable future, but + # we don't want them to break without us realizing. + test_diagnostics: + name: Test nightly with diagnostics feature + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + - uses: Swatinem/rust-cache@v2 + - name: Check Cargo availability + run: cargo --version + - run: cargo test --verbose --workspace --features diagnostics - clippy: - name: Lint with clippy - runs-on: ubuntu-latest - env: - RUSTFLAGS: -Dwarnings - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - uses: Swatinem/rust-cache@v2 - - name: Run clippy --workspace --tests - run: cargo clippy --workspace --tests + clippy: + name: Lint with clippy + runs-on: ubuntu-latest + env: + RUSTFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: Run clippy --workspace --tests + run: cargo clippy --workspace --tests - rustfmt: - name: Verify code formatting - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - uses: Swatinem/rust-cache@v2 - - name: Run fmt --all -- --check - run: cargo fmt --all -- --check + rustfmt: + name: Verify code formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Run fmt --all -- --check + run: cargo fmt --all -- --check diff --git a/README.md b/README.md index 5f7f9d5b..88a99810 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ -Darling -======= +# Darling [![Build Status](https://github.com/TedDriggs/darling/workflows/CI/badge.svg)](https://github.com/TedDriggs/darling/actions) [![Latest Version](https://img.shields.io/crates/v/darling.svg)](https://crates.io/crates/darling) -[![Rustc Version 1.31+](https://img.shields.io/badge/rustc-1.31+-lightgray.svg)](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html) +[![Rustc Version 1.56+](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)] `darling` is a crate for proc macro authors, which enables parsing attributes into structs. It is heavily inspired by `serde` both in its internals and in its API. # Benefits -* Easy and declarative parsing of macro input - make your proc-macros highly controllable with minimal time investment. -* Great validation and errors, no work required. When users of your proc-macro make a mistake, `darling` makes sure they get error markers at the right place in their source, and provides "did you mean" suggestions for misspelled fields. + +- Easy and declarative parsing of macro input - make your proc-macros highly controllable with minimal time investment. +- Great validation and errors, no work required. When users of your proc-macro make a mistake, `darling` makes sure they get error markers at the right place in their source, and provides "did you mean" suggestions for misspelled fields. # Usage + `darling` provides a set of traits which can be derived or manually implemented. 1. `FromMeta` is used to extract values from a meta-item in an attribute. Implementations are likely reusable for many libraries, much like `FromStr` or `serde::Deserialize`. Trait implementations are provided for primitives, some std types, and some `syn` types. @@ -21,9 +22,10 @@ Darling 5. `FromAttributes` is a lower-level version of the more-specific `FromDeriveInput`, `FromField`, and `FromVariant` traits. Structs deriving this trait get a meta-item extractor and error collection which works for any syntax element, including traits, trait items, and functions. This is useful for non-derive proc macros. ## Additional Modules -* `darling::ast` provides generic types for representing the AST. -* `darling::usage` provides traits and functions for determining where type parameters and lifetimes are used in a struct or enum. -* `darling::util` provides helper types with special `FromMeta` implementations, such as `IdentList`. + +- `darling::ast` provides generic types for representing the AST. +- `darling::usage` provides traits and functions for determining where type parameters and lifetimes are used in a struct or enum. +- `darling::util` provides helper types with special `FromMeta` implementations, such as `IdentList`. # Example @@ -59,11 +61,13 @@ pub struct ConsumingType; ``` # Attribute Macros + Non-derive attribute macros are supported. To parse arguments for attribute macros, derive `FromMeta` on the argument receiver type, then pass `&syn::AttributeArgs` to the `from_list` method. This will produce a normal `darling::Result` that can be used the same as a result from parsing a `DeriveInput`. ## Macro Code + ```rust,ignore use darling::FromMeta; use syn::{AttributeArgs, ItemFn}; @@ -92,6 +96,7 @@ fn your_attr(args: TokenStream, input: TokenStream) -> TokenStream { ``` ## Consuming Code + ```rust,ignore use your_crate::your_attr; @@ -102,37 +107,39 @@ fn do_stuff() { ``` # Features + Darling's features are built to work well for real-world projects. -* **Defaults**: Supports struct- and field-level defaults, using the same path syntax as `serde`. - Additionally, `Option` and `darling::util::Flag` fields are innately optional; you don't need to declare `#[darling(default)]` for those. -* **Field Renaming**: Fields can have different names in usage vs. the backing code. -* **Auto-populated fields**: Structs deriving `FromDeriveInput` and `FromField` can declare properties named `ident`, `vis`, `ty`, `attrs`, and `generics` to automatically get copies of the matching values from the input AST. `FromDeriveInput` additionally exposes `data` to get access to the body of the deriving type, and `FromVariant` exposes `fields`. -* **Mapping function**: Use `#[darling(map="path")]` or `#[darling(and_then="path")]` to specify a function that runs on the result of parsing a meta-item field. This can change the return type, which enables you to parse to an intermediate form and convert that to the type you need in your struct. -* **Skip fields**: Use `#[darling(skip)]` to mark a field that shouldn't be read from attribute meta-items. -* **Multiple-occurrence fields**: Use `#[darling(multiple)]` on a `Vec` field to allow that field to appear multiple times in the meta-item. Each occurrence will be pushed into the `Vec`. -* **Span access**: Use `darling::util::SpannedValue` in a struct to get access to that meta item's source code span. This can be used to emit warnings that point at a specific field from your proc macro. In addition, you can use `darling::Error::write_errors` to automatically get precise error location details in most cases. -* **"Did you mean" suggestions**: Compile errors from derived darling trait impls include suggestions for misspelled fields. +- **Defaults**: Supports struct- and field-level defaults, using the same path syntax as `serde`. + Additionally, `Option` and `darling::util::Flag` fields are innately optional; you don't need to declare `#[darling(default)]` for those. +- **Field Renaming**: Fields can have different names in usage vs. the backing code. +- **Auto-populated fields**: Structs deriving `FromDeriveInput` and `FromField` can declare properties named `ident`, `vis`, `ty`, `attrs`, and `generics` to automatically get copies of the matching values from the input AST. `FromDeriveInput` additionally exposes `data` to get access to the body of the deriving type, and `FromVariant` exposes `fields`. +- **Mapping function**: Use `#[darling(map="path")]` or `#[darling(and_then="path")]` to specify a function that runs on the result of parsing a meta-item field. This can change the return type, which enables you to parse to an intermediate form and convert that to the type you need in your struct. +- **Skip fields**: Use `#[darling(skip)]` to mark a field that shouldn't be read from attribute meta-items. +- **Multiple-occurrence fields**: Use `#[darling(multiple)]` on a `Vec` field to allow that field to appear multiple times in the meta-item. Each occurrence will be pushed into the `Vec`. +- **Span access**: Use `darling::util::SpannedValue` in a struct to get access to that meta item's source code span. This can be used to emit warnings that point at a specific field from your proc macro. In addition, you can use `darling::Error::write_errors` to automatically get precise error location details in most cases. +- **"Did you mean" suggestions**: Compile errors from derived darling trait impls include suggestions for misspelled fields. ## Shape Validation + Some proc-macros only work on structs, while others need enums whose variants are either unit or newtype variants. Darling makes this sort of validation extremely simple. On the receiver that derives `FromDeriveInput`, add `#[darling(supports(...))]` and then list the shapes that your macro should accept. -|Name|Description| -|---|---| -|`any`|Accept anything| -|`struct_any`|Accept any struct| -|`struct_named`|Accept structs with named fields, e.g. `struct Example { field: String }`| -|`struct_newtype`|Accept newtype structs, e.g. `struct Example(String)`| -|`struct_tuple`|Accept tuple structs, e.g. `struct Example(String, String)`| -|`struct_unit`|Accept unit structs, e.g. `struct Example;`| -|`enum_any`|Accept any enum| -|`enum_named`|Accept enum variants with named fields| -|`enum_newtype`|Accept newtype enum variants| -|`enum_tuple`|Accept tuple enum variants| -|`enum_unit`|Accept unit enum variants| +| Name | Description | +| ---------------- | ------------------------------------------------------------------------- | +| `any` | Accept anything | +| `struct_any` | Accept any struct | +| `struct_named` | Accept structs with named fields, e.g. `struct Example { field: String }` | +| `struct_newtype` | Accept newtype structs, e.g. `struct Example(String)` | +| `struct_tuple` | Accept tuple structs, e.g. `struct Example(String, String)` | +| `struct_unit` | Accept unit structs, e.g. `struct Example;` | +| `enum_any` | Accept any enum | +| `enum_named` | Accept enum variants with named fields | +| `enum_newtype` | Accept newtype enum variants | +| `enum_tuple` | Accept tuple enum variants | +| `enum_unit` | Accept unit enum variants | Each one is additive, so listing `#[darling(supports(struct_any, enum_newtype))]` would accept all structs and any enum where every variant is a newtype variant. -This can also be used when deriving `FromVariant`, without the `enum_` prefix. \ No newline at end of file +This can also be used when deriving `FromVariant`, without the `enum_` prefix. diff --git a/clippy.toml b/clippy.toml index ce89b550..e221e795 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,2 @@ -msrv = "1.31.0" +msrv = "1.56.0" disallowed-names = [] # we want to be able to use placeholder names in tests \ No newline at end of file diff --git a/macro/src/lib.rs b/macro/src/lib.rs index de9709fb..8da0272e 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -1,6 +1,3 @@ -// This is needed for 1.31.0 to keep compiling -extern crate proc_macro; - use darling_core::{derive, Error}; use proc_macro::TokenStream; use syn::parse_macro_input; From 36dce38826aabdda97ae07360b8938c0ea99cbc3 Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Tue, 21 Mar 2023 15:18:20 -0700 Subject: [PATCH 11/22] Fix rustfmt violation --- core/src/util/parse_attribute.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/util/parse_attribute.rs b/core/src/util/parse_attribute.rs index 129e49c0..87b61d85 100644 --- a/core/src/util/parse_attribute.rs +++ b/core/src/util/parse_attribute.rs @@ -54,9 +54,9 @@ impl fmt::Display for DisplayPath<'_> { #[cfg(test)] mod tests { use super::parse_attribute_to_meta_list; + use crate::ast::NestedMeta; use syn::spanned::Spanned; use syn::{parse_quote, Ident}; - use crate::ast::NestedMeta; #[test] fn parse_list() { From f9ba1103f89d2aa4d07cc43a167937387c21e9db Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Wed, 22 Mar 2023 18:51:54 +0100 Subject: [PATCH 12/22] Fix new clippy warnings enabled by MSRV bump --- core/src/options/core.rs | 2 +- core/src/options/from_derive.rs | 8 +------- core/src/options/from_field.rs | 8 +------- core/src/options/from_type_param.rs | 8 +------- core/src/options/from_variant.rs | 8 +------- core/src/options/outer_from.rs | 8 +------- examples/fallible_read.rs | 2 +- 7 files changed, 7 insertions(+), 37 deletions(-) diff --git a/core/src/options/core.rs b/core/src/options/core.rs index c79f7d5d..cc05f218 100644 --- a/core/src/options/core.rs +++ b/core/src/options/core.rs @@ -166,7 +166,7 @@ impl<'a> From<&'a Core> for codegen::TraitImpl<'a> { .map_enum_variants(|variant| variant.as_codegen_variant(&v.ident)), default: v.as_codegen_default(), post_transform: v.post_transform.as_ref(), - bound: v.bound.as_ref().map(|i| i.as_slice()), + bound: v.bound.as_deref(), allow_unknown_fields: v.allow_unknown_fields.unwrap_or_default(), } } diff --git a/core/src/options/from_derive.rs b/core/src/options/from_derive.rs index 0f1e1c4d..71a3c3cb 100644 --- a/core/src/options/from_derive.rs +++ b/core/src/options/from_derive.rs @@ -52,13 +52,7 @@ impl ParseData for FdiOptions { } fn parse_field(&mut self, field: &syn::Field) -> Result<()> { - match field - .ident - .as_ref() - .map(|v| v.to_string()) - .as_ref() - .map(|v| v.as_str()) - { + match field.ident.as_ref().map(|v| v.to_string()).as_deref() { Some("vis") => { self.vis = field.ident.clone(); Ok(()) diff --git a/core/src/options/from_field.rs b/core/src/options/from_field.rs index fcfab755..75e5fed2 100644 --- a/core/src/options/from_field.rs +++ b/core/src/options/from_field.rs @@ -37,13 +37,7 @@ impl ParseData for FromFieldOptions { } fn parse_field(&mut self, field: &syn::Field) -> Result<()> { - match field - .ident - .as_ref() - .map(|v| v.to_string()) - .as_ref() - .map(|v| v.as_str()) - { + match field.ident.as_ref().map(|v| v.to_string()).as_deref() { Some("vis") => { self.vis = field.ident.clone(); Ok(()) diff --git a/core/src/options/from_type_param.rs b/core/src/options/from_type_param.rs index 5ab3241e..80178ff7 100644 --- a/core/src/options/from_type_param.rs +++ b/core/src/options/from_type_param.rs @@ -37,13 +37,7 @@ impl ParseData for FromTypeParamOptions { } fn parse_field(&mut self, field: &syn::Field) -> Result<()> { - match field - .ident - .as_ref() - .map(|v| v.to_string()) - .as_ref() - .map(|v| v.as_str()) - { + match field.ident.as_ref().map(|v| v.to_string()).as_deref() { Some("bounds") => { self.bounds = field.ident.clone(); Ok(()) diff --git a/core/src/options/from_variant.rs b/core/src/options/from_variant.rs index fabdae17..ad761ade 100644 --- a/core/src/options/from_variant.rs +++ b/core/src/options/from_variant.rs @@ -58,13 +58,7 @@ impl ParseAttribute for FromVariantOptions { impl ParseData for FromVariantOptions { fn parse_field(&mut self, field: &Field) -> Result<()> { - match field - .ident - .as_ref() - .map(|v| v.to_string()) - .as_ref() - .map(|v| v.as_str()) - { + match field.ident.as_ref().map(|v| v.to_string()).as_deref() { Some("discriminant") => { self.discriminant = field.ident.clone(); Ok(()) diff --git a/core/src/options/outer_from.rs b/core/src/options/outer_from.rs index 0a8088a0..492b0cd6 100644 --- a/core/src/options/outer_from.rs +++ b/core/src/options/outer_from.rs @@ -66,13 +66,7 @@ impl ParseAttribute for OuterFrom { impl ParseData for OuterFrom { fn parse_field(&mut self, field: &Field) -> Result<()> { - match field - .ident - .as_ref() - .map(|v| v.to_string()) - .as_ref() - .map(|v| v.as_str()) - { + match field.ident.as_ref().map(|v| v.to_string()).as_deref() { Some("ident") => { self.ident = field.ident.clone(); Ok(()) diff --git a/examples/fallible_read.rs b/examples/fallible_read.rs index 850465e8..32cc10c9 100644 --- a/examples/fallible_read.rs +++ b/examples/fallible_read.rs @@ -51,7 +51,7 @@ impl MyInputReceiver { // we'll go ahead and make it positive. let amplitude = match amplitude { Ok(amp) => amp, - Err(mi) => (i64::from_meta(&mi)?).abs() as u64, + Err(mi) => (i64::from_meta(&mi)?).unsigned_abs(), }; Ok(Self { From a7ad9dffef548853cbc9d9607a753dd8584ddd71 Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Wed, 22 Mar 2023 19:01:20 +0100 Subject: [PATCH 13/22] Add a missing comment about non-exhaustive enums --- core/src/usage/lifetimes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/usage/lifetimes.rs b/core/src/usage/lifetimes.rs index db065e91..921debe9 100644 --- a/core/src/usage/lifetimes.rs +++ b/core/src/usage/lifetimes.rs @@ -295,6 +295,7 @@ impl UsesLifetimes for syn::TypeParamBound { match *self { syn::TypeParamBound::Trait(ref v) => v.uses_lifetimes(options, lifetimes), syn::TypeParamBound::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes), + // non-exhaustive enum _ => Default::default(), } } From a84e0ec3e603e82b9b2a78bf8a423f710f6fd363 Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Wed, 22 Mar 2023 19:01:33 +0100 Subject: [PATCH 14/22] `from_nested_meta` can stay a part of `FromMeta` trait This was initially removed in an attempt to get rid of `NestedMeta`, but since the type stays, the function can too. --- core/src/error/mod.rs | 1 + core/src/from_meta.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/core/src/error/mod.rs b/core/src/error/mod.rs index 17b58765..6f2c0454 100644 --- a/core/src/error/mod.rs +++ b/core/src/error/mod.rs @@ -307,6 +307,7 @@ impl Error { /// overridden: /// /// * `FromMeta::from_meta` + /// * `FromMeta::from_nested_meta` /// * `FromMeta::from_value` pub fn with_span(mut self, node: &T) -> Self { if !self.has_span() { diff --git a/core/src/from_meta.rs b/core/src/from_meta.rs index 9a4ef74d..d858d5f9 100644 --- a/core/src/from_meta.rs +++ b/core/src/from_meta.rs @@ -49,6 +49,14 @@ use crate::{Error, Result}; /// * Allows for fallible parsing; will populate the target field with the result of the /// parse attempt. pub trait FromMeta: Sized { + fn from_nested_meta(item: &NestedMeta) -> Result { + (match *item { + NestedMeta::Lit(ref lit) => Self::from_value(lit), + NestedMeta::Meta(ref mi) => Self::from_meta(mi), + }) + .map_err(|e| e.with_span(item)) + } + /// Create an instance from a `syn::Meta` by dispatching to the format-appropriate /// trait function. This generally should not be overridden by implementers. /// From 85fcd4b62fdf2ab0b7505ae86b347fc79dce4cef Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Wed, 22 Mar 2023 19:05:59 +0100 Subject: [PATCH 15/22] Rename more occurences of lifetime "def" to "param" --- core/src/ast/generics.rs | 6 +++--- tests/from_generics.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/ast/generics.rs b/core/src/ast/generics.rs index d048e6a2..d4bf0c9b 100644 --- a/core/src/ast/generics.rs +++ b/core/src/ast/generics.rs @@ -23,7 +23,7 @@ pub trait GenericParamExt { } /// If this GenericParam is a lifetime, get the underlying value. - fn as_lifetime_def(&self) -> Option<&Self::LifetimeParam> { + fn as_lifetime_param(&self) -> Option<&Self::LifetimeParam> { None } @@ -46,7 +46,7 @@ impl GenericParamExt for syn::GenericParam { } } - fn as_lifetime_def(&self) -> Option<&Self::LifetimeParam> { + fn as_lifetime_param(&self) -> Option<&Self::LifetimeParam> { if let syn::GenericParam::Lifetime(ref val) = *self { Some(val) } else { @@ -114,7 +114,7 @@ impl GenericParamExt for GenericParam { } } - fn as_lifetime_def(&self) -> Option<&L> { + fn as_lifetime_param(&self) -> Option<&L> { if let GenericParam::Lifetime(ref val) = *self { Some(val) } else { diff --git a/tests/from_generics.rs b/tests/from_generics.rs index 5cdd697d..e2cc99ee 100644 --- a/tests/from_generics.rs +++ b/tests/from_generics.rs @@ -48,8 +48,8 @@ fn expand_some() { .expect("Input is well-formed"); assert!(rec.generics.where_clause.is_none()); - // Make sure we've preserved the lifetime def, though we don't do anything with it. - assert!(rec.generics.params[0].as_lifetime_def().is_some()); + // Make sure we've preserved the lifetime param, though we don't do anything with it. + assert!(rec.generics.params[0].as_lifetime_param().is_some()); let mut ty_param_iter = rec.generics.type_params(); From 0deab844b3804406ff837e7342fe4bca15ecd709 Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Wed, 22 Mar 2023 19:15:06 +0100 Subject: [PATCH 16/22] Remove unnecessary clones --- core/src/codegen/attr_extractor.rs | 2 +- core/src/util/parse_attribute.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/codegen/attr_extractor.rs b/core/src/codegen/attr_extractor.rs index a5d911c3..4afb174b 100644 --- a/core/src/codegen/attr_extractor.rs +++ b/core/src/codegen/attr_extractor.rs @@ -55,7 +55,7 @@ pub trait ExtractAttribute { #(#attr_names)|* => { match ::darling::util::parse_attribute_to_meta_list(__attr) { ::darling::export::Ok(__data) => { - match ::darling::export::NestedMeta::parse_meta_list(__data.tokens.clone()) { + match ::darling::export::NestedMeta::parse_meta_list(__data.tokens) { ::darling::export::Ok(ref __items) => { if __items.is_empty() { continue; diff --git a/core/src/util/parse_attribute.rs b/core/src/util/parse_attribute.rs index 87b61d85..747c30de 100644 --- a/core/src/util/parse_attribute.rs +++ b/core/src/util/parse_attribute.rs @@ -61,14 +61,14 @@ mod tests { #[test] fn parse_list() { let meta = parse_attribute_to_meta_list(&parse_quote!(#[bar(baz = 4)])).unwrap(); - let nested_meta = NestedMeta::parse_meta_list(meta.tokens.clone()).unwrap(); + let nested_meta = NestedMeta::parse_meta_list(meta.tokens).unwrap(); assert_eq!(nested_meta.len(), 1); } #[test] fn parse_path_returns_empty_list() { let meta = parse_attribute_to_meta_list(&parse_quote!(#[bar])).unwrap(); - let nested_meta = NestedMeta::parse_meta_list(meta.tokens.clone()).unwrap(); + let nested_meta = NestedMeta::parse_meta_list(meta.tokens).unwrap(); assert!(meta.path.is_ident(&Ident::new("bar", meta.path.span()))); assert!(nested_meta.is_empty()); } From 31d92c1787a5d3e0d72035338b445fad25fb4db5 Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Wed, 22 Mar 2023 19:28:56 +0100 Subject: [PATCH 17/22] Handle invisible groups in `from_expr` --- core/src/from_meta.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/from_meta.rs b/core/src/from_meta.rs index d858d5f9..8dd7271e 100644 --- a/core/src/from_meta.rs +++ b/core/src/from_meta.rs @@ -122,6 +122,7 @@ pub trait FromMeta: Sized { fn from_expr(expr: &Expr) -> Result { match *expr { Expr::Lit(ref lit) => Self::from_value(&lit.lit), + Expr::Group(ref group) => Self::from_expr(&group.expr), _ => Err(Error::unexpected_expr_type(expr)), } .map_err(|e| e.with_span(expr)) From 4f7c7947b0fa0c7f754d2b70c1d7114b3a8335a6 Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Sat, 1 Apr 2023 21:49:34 +0200 Subject: [PATCH 18/22] Update example in README which used the outdated `AttributeArgs` type --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 88a99810..cc945841 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,9 @@ This will produce a normal `darling::Result` that can be used the same as a r ## Macro Code ```rust,ignore -use darling::FromMeta; -use syn::{AttributeArgs, ItemFn}; +use darling::{Error, FromMeta}; +use darling::ast::NestedMeta; +use syn::ItemFn; use proc_macro::TokenStream; #[derive(Debug, FromMeta)] @@ -80,10 +81,13 @@ pub struct MacroArgs { path: String, } -#[proc_macro_attribute] +// #[proc_macro_attribute] fn your_attr(args: TokenStream, input: TokenStream) -> TokenStream { - let attr_args = parse_macro_input!(args as AttributeArgs); - let _input = parse_macro_input!(input as ItemFn); + let attr_args = match NestedMeta::parse_meta_list(args) { + Ok(v) => v, + Err(e) => { return TokenStream::from(Error::from(e).write_errors()); } + }; + let _input = syn::parse_macro_input!(input as ItemFn); let _args = match MacroArgs::from_list(&attr_args) { Ok(v) => v, From 7e6f4a3a397d8c2bce576051df9fba0633d58d02 Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Thu, 6 Apr 2023 17:40:55 -0700 Subject: [PATCH 19/22] Add support for unquoted expressions in value position --- core/src/from_meta.rs | 97 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/core/src/from_meta.rs b/core/src/from_meta.rs index 8dd7271e..4c1ce245 100644 --- a/core/src/from_meta.rs +++ b/core/src/from_meta.rs @@ -274,6 +274,74 @@ impl FromMeta for syn::punctuated::P } } +/// Support for arbitrary expressions as values in a meta item. +/// +/// For backwards-compatibility to versions of `darling` based on `syn` 1, +/// string literals will be "unwrapped" and their contents will be parsed +/// as an expression. +impl FromMeta for syn::Expr { + fn from_expr(expr: &Expr) -> Result { + if let syn::Expr::Lit(expr_lit) = expr { + if let syn::Lit::Str(_) = &expr_lit.lit { + return Self::from_value(&expr_lit.lit); + } + } + + Ok(expr.clone()) + } + + fn from_string(value: &str) -> Result { + syn::parse_str(value).map_err(|_| Error::unknown_value(value)) + } + + fn from_value(value: &::syn::Lit) -> Result { + if let ::syn::Lit::Str(ref v) = *value { + v.parse::() + .map_err(|_| Error::unknown_lit_str_value(v)) + } else { + Err(Error::unexpected_lit_type(value)) + } + } +} + +/// Adapter for various expression types. +/// +/// Prior to syn 2.0, darling supported arbitrary expressions as long as they +/// were wrapped in quotation marks. This was helpful for people writing +/// libraries that needed expressions, but it now creates an ambiguity when +/// parsing a meta item. +/// +/// To address this, the macro supports both formats; if it cannot parse the +/// item as an expression of the right type and the passed-in expression is +/// a string literal, it will fall back to parsing the string contents. +macro_rules! from_syn_expr_type { + ($ty:path, $variant:ident) => { + impl FromMeta for $ty { + fn from_expr(expr: &syn::Expr) -> Result { + if let syn::Expr::$variant(body) = expr { + Ok(body.clone()) + } else if let syn::Expr::Lit(expr_lit) = expr { + Self::from_value(&expr_lit.lit) + } else { + Err(Error::unexpected_expr_type(expr)) + } + } + + fn from_value(value: &::syn::Lit) -> Result { + if let syn::Lit::Str(body) = &value { + body.parse::<$ty>() + .map_err(|_| Error::unknown_lit_str_value(body)) + } else { + Err(Error::unexpected_lit_type(value)) + } + } + } + }; +} + +from_syn_expr_type!(syn::ExprArray, Array); +from_syn_expr_type!(syn::ExprPath, Path); + /// Adapter from `syn::parse::Parse` to `FromMeta`. /// /// This cannot be a blanket impl, due to the `syn::Lit` family's need to handle non-string values. @@ -298,9 +366,6 @@ macro_rules! from_syn_parse { } from_syn_parse!(syn::Ident); -from_syn_parse!(syn::Expr); -from_syn_parse!(syn::ExprArray); -from_syn_parse!(syn::ExprPath); from_syn_parse!(syn::Path); from_syn_parse!(syn::Type); from_syn_parse!(syn::TypeArray); @@ -325,11 +390,9 @@ macro_rules! from_numeric_array { ($ty:ident) => { /// Parsing an unsigned integer array, i.e. `example = "[1, 2, 3, 4]"`. impl FromMeta for Vec<$ty> { - fn from_value(value: &Lit) -> Result { - let expr_array = syn::ExprArray::from_value(value)?; - // To meet rust <1.36 borrow checker rules on expr_array.elems - let v = - expr_array + fn from_expr(expr: &syn::Expr) -> Result { + if let syn::Expr::Array(expr_array) = expr { + let v = expr_array .elems .iter() .map(|expr| match expr { @@ -338,7 +401,17 @@ macro_rules! from_numeric_array { .with_span(expr)), }) .collect::>>(); - v + v + } else if let syn::Expr::Lit(expr_lit) = expr { + Self::from_value(&expr_lit.lit) + } else { + Err(Error::unexpected_expr_type(expr)) + } + } + + fn from_value(value: &Lit) -> Result { + let expr_array = syn::ExprArray::from_value(value)?; + Self::from_expr(&syn::Expr::Array(expr_array)) } } }; @@ -623,6 +696,7 @@ mod tests { Ok(attribute.meta) } + #[track_caller] fn fm(tokens: TokenStream) -> T { FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input")) .expect("Tests should pass valid input") @@ -836,10 +910,7 @@ mod tests { #[test] fn test_number_array() { - assert_eq!( - fm::>(quote!(ignore = "[16, 0xff]")), - vec![0x10, 0xff] - ); + assert_eq!(fm::>(quote!(ignore = [16, 0xff])), vec![0x10, 0xff]); assert_eq!( fm::>(quote!(ignore = "[32, 0xffff]")), vec![0x20, 0xffff] From c8eb001889cea66b6d8e67a9128cb2387b5c8c63 Mon Sep 17 00:00:00 2001 From: Jonas Bushart Date: Fri, 7 Apr 2023 18:02:09 +0200 Subject: [PATCH 20/22] Replace `Default` returns with explicit panics for unknown enum variants --- core/src/usage/lifetimes.rs | 9 ++++++--- core/src/usage/type_params.rs | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/core/src/usage/lifetimes.rs b/core/src/usage/lifetimes.rs index 921debe9..b7124aa5 100644 --- a/core/src/usage/lifetimes.rs +++ b/core/src/usage/lifetimes.rs @@ -247,7 +247,8 @@ impl UsesLifetimes for syn::WherePredicate { syn::WherePredicate::Type(ref v) => v.uses_lifetimes(options, lifetimes), syn::WherePredicate::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes), // non-exhaustive enum - _ => Default::default(), + // TODO: replace panic with failible function + _ => panic!("Unknown syn::WherePredicate: {:?}", self), } } } @@ -267,7 +268,8 @@ impl UsesLifetimes for syn::GenericArgument { Default::default() } // non-exhaustive enum - _ => Default::default(), + // TODO: replace panic with failible function + _ => panic!("Unknown syn::GenericArgument: {:?}", self), } } } @@ -296,7 +298,8 @@ impl UsesLifetimes for syn::TypeParamBound { syn::TypeParamBound::Trait(ref v) => v.uses_lifetimes(options, lifetimes), syn::TypeParamBound::Lifetime(ref v) => v.uses_lifetimes(options, lifetimes), // non-exhaustive enum - _ => Default::default(), + // TODO: replace panic with failible function + _ => panic!("Unknown syn::TypeParamBound: {:?}", self), } } } diff --git a/core/src/usage/type_params.rs b/core/src/usage/type_params.rs index 74967ae7..d4e7e799 100644 --- a/core/src/usage/type_params.rs +++ b/core/src/usage/type_params.rs @@ -217,7 +217,8 @@ impl UsesTypeParams for syn::WherePredicate { syn::WherePredicate::Lifetime(_) => Default::default(), syn::WherePredicate::Type(ref v) => v.uses_type_params(options, type_set), // non-exhaustive enum - _ => Default::default(), + // TODO: replace panic with failible function + _ => panic!("Unknown syn::WherePredicate: {:?}", self), } } } @@ -232,7 +233,8 @@ impl UsesTypeParams for syn::GenericArgument { | syn::GenericArgument::Const(_) | syn::GenericArgument::Lifetime(_) => Default::default(), // non-exhaustive enum - _ => Default::default(), + // TODO: replace panic with failible function + _ => panic!("Unknown syn::GenericArgument: {:?}", self), } } } @@ -243,7 +245,8 @@ impl UsesTypeParams for syn::TypeParamBound { syn::TypeParamBound::Trait(ref v) => v.uses_type_params(options, type_set), syn::TypeParamBound::Lifetime(_) => Default::default(), // non-exhaustive enum - _ => Default::default(), + // TODO: replace panic with failible function + _ => panic!("Unknown syn::TypeParamBound: {:?}", self), } } } From 1d57646eb77f57785a53505f0e50cddbb4f9660b Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Thu, 20 Apr 2023 16:09:58 -0700 Subject: [PATCH 21/22] Bump syn to 2.0.15 --- Cargo.toml | 2 +- core/Cargo.toml | 2 +- macro/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5326366b..2049149d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ darling_macro = { version = "=0.14.4", path = "macro" } [dev-dependencies] proc-macro2 = "1.0.37" quote = "1.0.18" -syn = "2.0.0" +syn = "2.0.15" [target.'cfg(compiletests)'.dev-dependencies] rustversion = "1.0.9" diff --git a/core/Cargo.toml b/core/Cargo.toml index ce2d2a8d..b3c2150b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,6 +18,6 @@ suggestions = ["strsim"] ident_case = "1.0.1" proc-macro2 = "1.0.37" quote = "1.0.18" -syn = { version = "2.0.0", features = ["full", "extra-traits"] } +syn = { version = "2.0.15", features = ["full", "extra-traits"] } fnv = "1.0.7" strsim = { version = "0.10.0", optional = true } diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 6a652cef..f2ab2c88 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" [dependencies] quote = "1.0.18" -syn = "2.0.0" +syn = "2.0.15" darling_core = { version = "=0.14.4", path = "../core" } [lib] From 47368626ab23b1f0dc16852a4ed339a6044a44ba Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Thu, 27 Apr 2023 08:51:01 -0700 Subject: [PATCH 22/22] Bump version to 0.20.0 --- Cargo.toml | 8 ++++---- core/Cargo.toml | 2 +- macro/Cargo.toml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2049149d..56751cda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "darling" -version = "0.14.4" +version = "0.20.0" authors = ["Ted Driggs "] repository = "https://github.com/TedDriggs/darling" -documentation = "https://docs.rs/darling/0.14.4" +documentation = "https://docs.rs/darling/0.20.0" description = """ A proc-macro library for reading attributes into structs when implementing custom derives. @@ -17,8 +17,8 @@ exclude = ["/.travis.yml", "/publish.sh", "/.github/**"] maintenance = { status = "actively-developed" } [dependencies] -darling_core = { version = "=0.14.4", path = "core" } -darling_macro = { version = "=0.14.4", path = "macro" } +darling_core = { version = "=0.20.0", path = "core" } +darling_macro = { version = "=0.20.0", path = "macro" } [dev-dependencies] proc-macro2 = "1.0.37" diff --git a/core/Cargo.toml b/core/Cargo.toml index b3c2150b..655e555b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "darling_core" -version = "0.14.4" +version = "0.20.0" authors = ["Ted Driggs "] repository = "https://github.com/TedDriggs/darling" description = """ diff --git a/macro/Cargo.toml b/macro/Cargo.toml index f2ab2c88..59fc63f5 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "darling_macro" -version = "0.14.4" +version = "0.20.0" authors = ["Ted Driggs "] repository = "https://github.com/TedDriggs/darling" description = """ @@ -13,7 +13,7 @@ edition = "2018" [dependencies] quote = "1.0.18" syn = "2.0.15" -darling_core = { version = "=0.14.4", path = "../core" } +darling_core = { version = "=0.20.0", path = "../core" } [lib] proc-macro = true