Skip to content

Commit

Permalink
Add new parsing util functions for Exprs
Browse files Browse the repository at this point in the history
  • Loading branch information
TedDriggs committed May 24, 2023
1 parent 9bdda12 commit 67330c0
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 0 deletions.
3 changes: 3 additions & 0 deletions core/src/from_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ impl<T: syn::parse::Parse, P: syn::parse::Parse> FromMeta for syn::punctuated::P
/// 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.
///
/// See [`util::parse_expr`](crate::util::parse_expr) for functions to provide
/// alternate parsing modes for this type.
impl FromMeta for syn::Expr {
fn from_expr(expr: &Expr) -> Result<Self> {
if let syn::Expr::Lit(expr_lit) = expr {
Expand Down
1 change: 1 addition & 0 deletions core/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod ident_string;
mod ignored;
mod over_ride;
mod parse_attribute;
pub mod parse_expr;
mod path_list;
mod path_to_string;
mod shape;
Expand Down
86 changes: 86 additions & 0 deletions core/src/util/parse_expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//! Functions to use with `#[darling(with = "...")]` that control how quoted values
//! in [`Meta`] instances are parsed into [`Expr`] fields.
//!
//! Version 1 of syn did not permit expressions on the right-hand side of the `=` in a
//! [`MetaNameValue`](syn::MetaNameValue), so darling accepted string literals and then
//! parsed their contents as expressions.
//! Passing a string literal in this version would have required the use of a raw string
//! to add quotation marks inside the literal.
//!
//! Version 2 of syn removes the requirement that the right-hand side be a literal.
//! For most types, such as [`Path`](syn::Path), the [`FromMeta`] impl can accept the
//! version without quotation marks without causing ambiguity; a path cannot start and
//! end with quotation marks, so removal is automatic.
//!
//! [`Expr`] is the one type where this ambiguity is new and unavoidable. To address this,
//! this module provides different functions for different expected behaviors.

use syn::{Expr, Meta};

use crate::{Error, FromMeta};

/// Parse a [`Meta`] to an [`Expr`]; if the value is a string literal, the emitted
/// expression will be a string literal.
pub fn preserve_str_literal(meta: &Meta) -> crate::Result<Expr> {
match meta {
Meta::Path(_) => Err(Error::unsupported_format("path").with_span(meta)),
Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)),
Meta::NameValue(nv) => Ok(nv.value.clone()),
}
}

/// Parse a [`Meta`] to an [`Expr`]; if the value is a string literal, the string's
/// contents will be parsed as an expression and emitted.
pub fn parse_str_literal(meta: &Meta) -> crate::Result<Expr> {
match meta {
Meta::Path(_) => Err(Error::unsupported_format("path").with_span(meta)),
Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)),
Meta::NameValue(nv) => {
if let Expr::Lit(expr_lit) = &nv.value {
Expr::from_value(&expr_lit.lit)
} else {
Ok(nv.value.clone())
}
}
}
}

#[cfg(test)]
mod tests {
use syn::parse_quote;

use super::*;

macro_rules! meta {
($body:expr) => {
{
let attr: ::syn::Attribute = ::syn::parse_quote!(#[ignore = $body]);
attr.meta
}
};
}

#[test]
fn preserve_str() {
assert_eq!(
preserve_str_literal(&meta!("World")).unwrap(),
parse_quote!("World")
);
}

#[test]
fn preserve_binary_exp() {
assert_eq!(
preserve_str_literal(&meta!("World" + 5)).unwrap(),
parse_quote!("World" + 5)
)
}

#[test]
fn parse_ident() {
assert_eq!(
parse_str_literal(&meta!("world")).unwrap(),
parse_quote!(world)
)
}
}
19 changes: 19 additions & 0 deletions examples/expr_with.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use darling::{util::parse_expr, FromDeriveInput};
use syn::{parse_quote, Expr};

#[derive(FromDeriveInput)]
#[darling(attributes(demo))]
pub struct Receiver {
#[darling(with = parse_expr::preserve_str_literal, map = Some)]
example1: Option<Expr>,
}

fn main() {
let input = Receiver::from_derive_input(&parse_quote! {
#[demo(example1 = test::path)]
struct Example;
})
.unwrap();

assert_eq!(input.example1, Some(parse_quote!(test::path)));
}

0 comments on commit 67330c0

Please sign in to comment.