attribute_derive/
utils.rs

1//! Utilities implementing useful patterns for fields inside an attribute.
2use from_partial::{FromPartial, Partial};
3use manyhow::SpanRanged;
4use parsing::{AttributeNamed, AttributeValue};
5use syn::token::Paren;
6use syn::Token;
7
8use self::parsing::parse_name;
9use crate::parsing::Named;
10use crate::{SpannedValue, *};
11
12/// [`FromAttr`] value that can be used both as a flag and with a value.
13///
14/// When parameter is specified both as flag and as value, the value will
15/// dominate.
16///
17/// ```
18/// # use attribute_derive::{FromAttr, utils::FlagOrValue};
19/// # use quote::quote;
20/// #[derive(FromAttr)]
21/// struct Test {
22///     param: FlagOrValue<String>,
23/// }
24///
25/// assert_eq!(
26///     Test::from_args(quote!(param)).unwrap().param,
27///     FlagOrValue::Flag
28/// );
29/// assert_eq!(
30///     Test::from_args(quote!(param = "value")).unwrap().param,
31///     FlagOrValue::Value("value".into())
32/// );
33/// assert_eq!(
34///     Test::from_args(quote!(param, param = "value", param))
35///         .unwrap()
36///         .param,
37///     FlagOrValue::Value("value".into())
38/// );
39/// assert_eq!(Test::from_args(quote!()).unwrap().param, FlagOrValue::None);
40/// ```
41#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
42pub enum FlagOrValue<T> {
43    /// Was not specified.
44    #[default]
45    None,
46    /// Was specified as a flag, i.e., without a value.
47    Flag,
48    /// Was specified with a value.
49    Value(T),
50}
51
52impl<T> FlagOrValue<T> {
53    /// Was not specified.
54    pub fn is_none(&self) -> bool {
55        matches!(self, Self::None)
56    }
57
58    /// Was specified as a flag, i.e., without a value.
59    pub fn is_flag(&self) -> bool {
60        matches!(self, Self::Flag,)
61    }
62
63    /// Was specified with a value.
64    pub fn is_value(&self) -> bool {
65        matches!(self, Self::Value(_),)
66    }
67
68    /// Returns value if set.
69    pub fn into_value(self) -> Option<T> {
70        match self {
71            FlagOrValue::Value(value) => Some(value),
72            _ => None,
73        }
74    }
75
76    /// Returns value if set.
77    pub fn as_value(&self) -> Option<&T> {
78        match self {
79            FlagOrValue::Value(value) => Some(value),
80            _ => None,
81        }
82    }
83
84    /// Maps the `value` if present.
85    pub fn map_value<I>(self, map: impl FnOnce(T) -> I) -> FlagOrValue<I> {
86        match self {
87            FlagOrValue::None => FlagOrValue::None,
88            FlagOrValue::Flag => FlagOrValue::Flag,
89            FlagOrValue::Value(value) => FlagOrValue::Value(map(value)),
90        }
91    }
92}
93
94/// Enables the `transpose` function on [`FlagOrValue`] containing or being
95/// contained in [`Option`] or [`Result`](std::result::Result).
96pub trait Transpose<T> {
97    /// Should behave equivalent to the built-in `transpose` functions available
98    /// on [`Result<Option>`](Result::transpose) and
99    /// [`Option<Result>`](Option::transpose).
100    fn transpose(self) -> T;
101}
102
103impl<T> Transpose<Option<FlagOrValue<T>>> for FlagOrValue<Option<T>> {
104    fn transpose(self) -> Option<FlagOrValue<T>> {
105        match self {
106            FlagOrValue::None => Some(FlagOrValue::None),
107            FlagOrValue::Flag => Some(FlagOrValue::Flag),
108            FlagOrValue::Value(option) => option.map(FlagOrValue::Value),
109        }
110    }
111}
112
113impl<T, E> Transpose<std::result::Result<FlagOrValue<T>, E>>
114    for FlagOrValue<std::result::Result<T, E>>
115{
116    fn transpose(self) -> std::result::Result<FlagOrValue<T>, E> {
117        match self {
118            FlagOrValue::None => Ok(FlagOrValue::None),
119            FlagOrValue::Flag => Ok(FlagOrValue::Flag),
120            FlagOrValue::Value(result) => result.map(FlagOrValue::Value),
121        }
122    }
123}
124
125impl<T: FromPartial<P>, P> FromPartial<Partial<FlagOrValue<P>>> for FlagOrValue<T> {
126    fn from(partial: Partial<FlagOrValue<P>>) -> syn::Result<Self> {
127        partial.0.map_value(T::from).transpose()
128    }
129
130    fn from_option(partial: Option<Partial<FlagOrValue<P>>>, _: &str) -> syn::Result<Self> {
131        // Pass in surrounding span
132        partial
133            .map(FromPartial::from)
134            .transpose()
135            .map(Option::unwrap_or_default)
136    }
137
138    fn join(
139        first: Option<SpannedValue<Partial<FlagOrValue<P>>>>,
140        second: SpannedValue<Partial<FlagOrValue<P>>>,
141        specified_twice_error: &str,
142    ) -> syn::Result<Option<SpannedValue<Partial<FlagOrValue<P>>>>> {
143        match (first, second) {
144            (None, this) => Ok(Some(this)),
145            (
146                Some(value),
147                SpannedValue {
148                    value: Partial(FlagOrValue::None | FlagOrValue::Flag),
149                    ..
150                },
151            )
152            | (
153                Some(SpannedValue {
154                    value: Partial(FlagOrValue::None | FlagOrValue::Flag),
155                    ..
156                }),
157                value,
158            ) => Ok(Some(value)),
159            (Some(first), second) => P::join(
160                Some(first.map_value(|v| {
161                    v.0.into_value()
162                        .expect("flag and none are checked in earlier branch")
163                })),
164                second.map_value(|Partial(v)| {
165                    v.into_value()
166                        .expect("flag and none are checked in earlier branch")
167                }),
168                specified_twice_error,
169            )
170            .map(|o| o.map(|a| a.map_value(FlagOrValue::Value).map_value(Partial))),
171        }
172    }
173}
174
175impl<T: AttributeBase> AttributeBase for FlagOrValue<T> {
176    type Partial = Partial<FlagOrValue<T::Partial>>;
177}
178
179impl<T: AttributeValue> AttributeNamed for FlagOrValue<T> {
180    fn parse_named(
181        name: &'static str,
182        input: syn::parse::ParseStream,
183    ) -> syn::Result<Option<Named<SpannedValue<Self::Partial>>>> {
184        let Some(ident) = parse_name(input, name) else {
185            return Ok(None);
186        };
187        let value = if input.peek(Token![=]) {
188            T::parse_value_eq(input)?.map_value(FlagOrValue::Value)
189        } else if input.peek(Paren) {
190            T::parse_value_meta(input)?.map_value(FlagOrValue::Value)
191        } else {
192            SpannedValue {
193                value: FlagOrValue::Flag,
194                span: ident.span().span_range(),
195            }
196        }
197        .map_value(Partial);
198        Ok(Some(Named { value, name: ident }))
199    }
200}
201
202impl<T: FromAttr> FromAttr for FlagOrValue<T> {
203    fn parse_partial(input: ParseStream) -> Result<Self::Partial> {
204        if input.is_empty() {
205            Ok(Partial(FlagOrValue::Flag))
206        } else {
207            T::parse_partial(input).map(FlagOrValue::Value).map(Partial)
208        }
209    }
210}