From 9bdda12f704978aff19de2b2f7011679c9019e0d Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Tue, 23 May 2023 06:25:31 -0700 Subject: [PATCH] Accept paths and idents without quotation marks in value positions --- CHANGELOG.md | 4 ++ core/src/from_meta.rs | 82 +++++++++++++++++++++++++++++++-- examples/fallible_read.rs | 4 +- tests/unsupported_attributes.rs | 30 +++--------- 4 files changed, 91 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d11b438..d297430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Allow darling users to omit quotation marks for paths and idents + ## v0.20.1 (May 2, 2023) - Add `Clone` impl for `NestedMeta` [#230](https://github.com/TedDriggs/darling/pull/230) diff --git a/core/src/from_meta.rs b/core/src/from_meta.rs index 4c1ce24..5413d04 100644 --- a/core/src/from_meta.rs +++ b/core/src/from_meta.rs @@ -304,6 +304,57 @@ impl FromMeta for syn::Expr { } } +/// Parser for paths that supports both quote-wrapped and bare values. +impl FromMeta for syn::Path { + 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)) + } + } + + fn from_expr(expr: &Expr) -> Result { + match expr { + Expr::Lit(lit) => Self::from_value(&lit.lit), + Expr::Path(path) => Ok(path.path.clone()), + _ => Err(Error::unexpected_expr_type(expr)), + } + } +} + +impl FromMeta for syn::Ident { + 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)) + } + } + + fn from_expr(expr: &Expr) -> Result { + match expr { + Expr::Lit(lit) => Self::from_value(&lit.lit), + // All idents are paths, but not all paths are idents - + // the get_ident() method does additional validation to + // make sure the path is actually an ident. + Expr::Path(path) => match path.path.get_ident() { + Some(ident) => Ok(ident.clone()), + None => Err(Error::unexpected_expr_type(expr)), + }, + _ => Err(Error::unexpected_expr_type(expr)), + } + } +} + /// Adapter for various expression types. /// /// Prior to syn 2.0, darling supported arbitrary expressions as long as they @@ -342,7 +393,8 @@ macro_rules! from_syn_expr_type { from_syn_expr_type!(syn::ExprArray, Array); from_syn_expr_type!(syn::ExprPath, Path); -/// Adapter from `syn::parse::Parse` to `FromMeta`. +/// Adapter from `syn::parse::Parse` to `FromMeta` for items that cannot +/// be expressed in a [`syn::MetaNameValue`]. /// /// This cannot be a blanket impl, due to the `syn::Lit` family's need to handle non-string values. /// Therefore, we use a macro and a lot of impls. @@ -365,8 +417,6 @@ macro_rules! from_syn_parse { }; } -from_syn_parse!(syn::Ident); -from_syn_parse!(syn::Path); from_syn_parse!(syn::Type); from_syn_parse!(syn::TypeArray); from_syn_parse!(syn::TypeBareFn); @@ -901,6 +951,18 @@ mod tests { fm::(quote!(ignore = "{ a_statement(); in_a_block }")); } + #[test] + fn test_expr_without_quotes() { + fm::(quote!(ignore = x + y)); + fm::(quote!(ignore = an_object.method_call())); + fm::(quote!( + ignore = { + a_statement(); + in_a_block + } + )); + } + #[test] fn test_expr_path() { fm::(quote!(ignore = "std::mem::replace")); @@ -908,6 +970,20 @@ mod tests { fm::(quote!(ignore = "example::")); } + #[test] + fn test_expr_path_without_quotes() { + fm::(quote!(ignore = std::mem::replace)); + fm::(quote!(ignore = x)); + fm::(quote!(ignore = example::)); + } + + #[test] + fn test_path_without_quotes() { + fm::(quote!(ignore = std::mem::replace)); + fm::(quote!(ignore = x)); + fm::(quote!(ignore = example::)); + } + #[test] fn test_number_array() { assert_eq!(fm::>(quote!(ignore = [16, 0xff])), vec![0x10, 0xff]); diff --git a/examples/fallible_read.rs b/examples/fallible_read.rs index 32cc10c..848c3d2 100644 --- a/examples/fallible_read.rs +++ b/examples/fallible_read.rs @@ -10,10 +10,10 @@ use darling::{FromDeriveInput, FromMeta}; use syn::parse_str; #[derive(Debug, FromDeriveInput)] -#[darling(attributes(my_trait), and_then = "MyInputReceiver::autocorrect")] +#[darling(attributes(my_trait), and_then = MyInputReceiver::autocorrect)] pub struct MyInputReceiver { /// This field must be present and a string or else parsing will panic. - #[darling(map = "MyInputReceiver::make_string_shouty")] + #[darling(map = MyInputReceiver::make_string_shouty)] name: String, /// If this field fails to parse, the struct can still be built; the field diff --git a/tests/unsupported_attributes.rs b/tests/unsupported_attributes.rs index 98b190d..c0143ce 100644 --- a/tests/unsupported_attributes.rs +++ b/tests/unsupported_attributes.rs @@ -11,39 +11,21 @@ pub struct Bar { /// Per [#96](https://github.com/TedDriggs/darling/issues/96), make sure that an /// attribute which isn't a valid meta gets an error. -#[test] -fn non_meta_attribute_gets_own_error() { - let di = parse_quote! { - #[derive(Bar)] - #[bar(file = "motors/example_6.csv", st = RocketEngine)] - pub struct EstesC6; - }; - - let errors: darling::Error = Bar::from_derive_input(&di).unwrap_err().flatten(); - // 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() - .any(|e| e.to_string().contains("expected lit"))); -} - /// Properties can be split across multiple attributes; this test ensures that one /// non-meta attribute does not interfere with the parsing of other, well-formed attributes. #[test] fn non_meta_attribute_does_not_block_others() { let di = parse_quote! { #[derive(Bar)] - #[bar(st = RocketEngine)] + #[bar(st = RocketEngine: Debug)] #[bar(file = "motors/example_6.csv")] pub struct EstesC6; }; let errors: darling::Error = Bar::from_derive_input(&di).unwrap_err().flatten(); - // 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() - .any(|e| e.to_string().contains("expected lit"))); + // The number of errors here is 2: + // - The parsing error caused by a where-clause body where it doesn't belong + // - The missing `st` value because the parsing failure blocked that attribute from + // being read. + assert_eq!(2, errors.len()); }