Skip to content

Commit

Permalink
Accept paths and idents without quotation marks in value positions
Browse files Browse the repository at this point in the history
  • Loading branch information
TedDriggs committed May 24, 2023
1 parent 6f15cc3 commit 9bdda12
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 29 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
82 changes: 79 additions & 3 deletions core/src/from_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
syn::parse_str(value).map_err(|_| Error::unknown_value(value))
}

fn from_value(value: &::syn::Lit) -> Result<Self> {
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<Self> {
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<Self> {
syn::parse_str(value).map_err(|_| Error::unknown_value(value))
}

fn from_value(value: &syn::Lit) -> Result<Self> {
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<Self> {
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
Expand Down Expand Up @@ -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.
Expand All @@ -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);
Expand Down Expand Up @@ -901,13 +951,39 @@ mod tests {
fm::<syn::Expr>(quote!(ignore = "{ a_statement(); in_a_block }"));
}

#[test]
fn test_expr_without_quotes() {
fm::<syn::Expr>(quote!(ignore = x + y));
fm::<syn::Expr>(quote!(ignore = an_object.method_call()));
fm::<syn::Expr>(quote!(
ignore = {
a_statement();
in_a_block
}
));
}

#[test]
fn test_expr_path() {
fm::<syn::ExprPath>(quote!(ignore = "std::mem::replace"));
fm::<syn::ExprPath>(quote!(ignore = "x"));
fm::<syn::ExprPath>(quote!(ignore = "example::<Test>"));
}

#[test]
fn test_expr_path_without_quotes() {
fm::<syn::ExprPath>(quote!(ignore = std::mem::replace));
fm::<syn::ExprPath>(quote!(ignore = x));
fm::<syn::ExprPath>(quote!(ignore = example::<Test>));
}

#[test]
fn test_path_without_quotes() {
fm::<syn::Path>(quote!(ignore = std::mem::replace));
fm::<syn::Path>(quote!(ignore = x));
fm::<syn::Path>(quote!(ignore = example::<Test>));
}

#[test]
fn test_number_array() {
assert_eq!(fm::<Vec<u8>>(quote!(ignore = [16, 0xff])), vec![0x10, 0xff]);
Expand Down
4 changes: 2 additions & 2 deletions examples/fallible_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 6 additions & 24 deletions tests/unsupported_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

0 comments on commit 9bdda12

Please sign in to comment.