From 67330c0041164872050a7e6a4865ad2c13211d12 Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Mon, 22 May 2023 09:28:27 -0700 Subject: [PATCH] Add new parsing util functions for Exprs --- core/src/from_meta.rs | 3 ++ core/src/util/mod.rs | 1 + core/src/util/parse_expr.rs | 86 +++++++++++++++++++++++++++++++++++++ examples/expr_with.rs | 19 ++++++++ 4 files changed, 109 insertions(+) create mode 100644 core/src/util/parse_expr.rs create mode 100644 examples/expr_with.rs diff --git a/core/src/from_meta.rs b/core/src/from_meta.rs index 5413d04..1878b06 100644 --- a/core/src/from_meta.rs +++ b/core/src/from_meta.rs @@ -279,6 +279,9 @@ impl 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 { if let syn::Expr::Lit(expr_lit) = expr { diff --git a/core/src/util/mod.rs b/core/src/util/mod.rs index ddc240b..cf761ed 100644 --- a/core/src/util/mod.rs +++ b/core/src/util/mod.rs @@ -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; diff --git a/core/src/util/parse_expr.rs b/core/src/util/parse_expr.rs new file mode 100644 index 0000000..fcc96a0 --- /dev/null +++ b/core/src/util/parse_expr.rs @@ -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 { + 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 { + 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) + ) + } +} diff --git a/examples/expr_with.rs b/examples/expr_with.rs new file mode 100644 index 0000000..037ce25 --- /dev/null +++ b/examples/expr_with.rs @@ -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, +} + +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))); +}