darling_core/util/
path_list.rs

1use std::ops::Deref;
2
3use syn::{Meta, Path};
4
5use crate::ast::NestedMeta;
6use crate::{Error, FromMeta, Result};
7
8use super::path_to_string;
9
10/// A list of `syn::Path` instances. This type is used to extract a list of paths from an
11/// attribute.
12///
13/// # Usage
14/// An `PathList` field on a struct implementing `FromMeta` will turn `#[builder(derive(serde::Debug, Clone))]` into:
15///
16/// ```rust,ignore
17/// StructOptions {
18///     derive: PathList(vec![syn::Path::new("serde::Debug"), syn::Path::new("Clone")])
19/// }
20/// ```
21#[derive(Debug, Default, Clone, PartialEq, Eq)]
22pub struct PathList(Vec<Path>);
23
24impl PathList {
25    /// Create a new list.
26    pub fn new<T: Into<Path>>(vals: Vec<T>) -> Self {
27        PathList(vals.into_iter().map(T::into).collect())
28    }
29
30    /// Create a new `Vec` containing the string representation of each path.
31    pub fn to_strings(&self) -> Vec<String> {
32        self.0.iter().map(path_to_string).collect()
33    }
34
35    /// Visits the values representing the intersection, i.e., the values that are both in `self` and `other`.
36    ///
37    /// Values will be returned in the order they appear in `self`.
38    ///
39    /// Values that are present multiple times in `self` and present at least once in `other` will be returned multiple times.
40    ///
41    /// # Performance
42    /// This function runs in `O(n * m)` time.
43    /// It is believed that path lists are usually short enough that this is better than allocating a set containing the values
44    /// of `other` or adding a conditional solution.
45    pub fn intersection<'a>(&'a self, other: &'a PathList) -> impl Iterator<Item = &'a Path> {
46        self.0.iter().filter(|path| other.0.contains(path))
47    }
48}
49
50impl Deref for PathList {
51    type Target = Vec<Path>;
52
53    fn deref(&self) -> &Self::Target {
54        &self.0
55    }
56}
57
58impl From<Vec<Path>> for PathList {
59    fn from(v: Vec<Path>) -> Self {
60        PathList(v)
61    }
62}
63
64impl FromMeta for PathList {
65    fn from_list(v: &[NestedMeta]) -> Result<Self> {
66        let mut paths = Vec::with_capacity(v.len());
67        for nmi in v {
68            if let NestedMeta::Meta(Meta::Path(ref path)) = *nmi {
69                paths.push(path.clone());
70            } else {
71                return Err(Error::unexpected_type("non-word").with_span(nmi));
72            }
73        }
74
75        Ok(PathList(paths))
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::PathList;
82    use crate::FromMeta;
83    use proc_macro2::TokenStream;
84    use quote::quote;
85    use syn::{parse_quote, Attribute, Meta};
86
87    /// parse a string as a syn::Meta instance.
88    fn pm(tokens: TokenStream) -> ::std::result::Result<Meta, String> {
89        let attribute: Attribute = parse_quote!(#[#tokens]);
90        Ok(attribute.meta)
91    }
92
93    fn fm<T: FromMeta>(tokens: TokenStream) -> T {
94        FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input"))
95            .expect("Tests should pass valid input")
96    }
97
98    #[test]
99    fn succeeds() {
100        let paths = fm::<PathList>(quote!(ignore(Debug, Clone, Eq)));
101        assert_eq!(
102            paths.to_strings(),
103            vec![
104                String::from("Debug"),
105                String::from("Clone"),
106                String::from("Eq")
107            ]
108        );
109    }
110
111    /// Check that the parser rejects non-word members of the list, and that the error
112    /// has an associated span.
113    #[test]
114    fn fails_non_word() {
115        let input = PathList::from_meta(&pm(quote!(ignore(Debug, Clone = false))).unwrap());
116        let err = input.unwrap_err();
117        assert!(err.has_span());
118    }
119
120    #[test]
121    fn intersection() {
122        let left = fm::<PathList>(quote!(ignore(Debug, Clone, Eq)));
123        let right = fm::<PathList>(quote!(ignore(Clone, Eq, Clone)));
124        assert_eq!(
125            left.intersection(&right).cloned().collect::<Vec<_>>(),
126            vec![parse_quote!(Clone), parse_quote!(Eq)],
127        );
128    }
129}