Skip to main content

darling_core/util/
extract_option.rs

1//! Functions to extract a type `T` from a type declaration `Option<T>`.
2//!
3//! These functions return `Result` rather than `Option` so they can be used with the `?` operator
4//!
5//! # Heuristics
6//!
7//! Since proc-macros don't have access to type information, we have no way of telling if some
8//! `Type` is really an `Option`. We can only guess.
9//!
10//! ```ignore
11//! use Vec as Option;
12//!
13//! #[derive(Serialize)]
14//! struct Evil {
15//!     // oh no
16//!     evil: Vec<String>
17//! }
18//! ```
19//!
20//! These functions will return `Ok` if the given [`Type`] is a [`Type::Path`], with a single generic
21//! argument, and the last path segment is `"Option"`
22
23use crate::{Error, Result};
24use syn::{spanned::Spanned, Type};
25
26/// Extracts `T` from `Option<T>`
27///
28/// # Errors
29///
30/// Returns an error if the given type is not an `Option<T>`. For more information, see the [module-level](self) documentation
31///
32/// # Example
33///
34/// ```
35/// # use darling_core as darling;
36/// use darling::util::extract_option;
37/// use quote::ToTokens;
38/// use syn::Type;
39///
40/// let ty: Type = syn::parse_str("::std::option::Option<String>")?;
41/// let result = extract_option::from_owned(ty)?;
42/// let result = result.into_token_stream().to_string();
43///
44/// assert_eq!(result, "String");
45/// # Ok::<(), darling::Error>(())
46/// ```
47pub fn from_owned(ty: Type) -> Result<Type> {
48    let span = ty.span();
49    let err = || Error::custom("expected an `Option`").with_span(&span);
50
51    let Type::Path(path) = ty else {
52        return Err(err());
53    };
54
55    if path.qself.is_some() {
56        return Err(err());
57    }
58
59    let Some(last_segment) = path.path.segments.last() else {
60        return Err(err());
61    };
62
63    if last_segment.ident != "Option" {
64        return Err(err());
65    }
66
67    let syn::PathArguments::AngleBracketed(ty) = last_segment.clone().arguments else {
68        return Err(err());
69    };
70
71    let args = ty.args.into_iter().collect::<Vec<_>>();
72
73    if args.len() != 1 {
74        return Err(err());
75    }
76
77    let arg = args
78        .into_iter()
79        .next()
80        .expect("just checked that `.len() == 1`");
81
82    let syn::GenericArgument::Type(ty) = arg else {
83        return Err(err());
84    };
85
86    Ok(ty)
87}
88
89/// Extracts a `&mut Type` `T` from an `Option<T>`
90///
91/// # Errors
92///
93/// Returns an error if the given type is not an `Option<T>`. For more information, see the [module-level](self) documentation
94///
95/// # Example
96///
97/// ```
98/// # use darling_core as darling;
99/// use darling::util::extract_option;
100/// use quote::ToTokens;
101/// use syn::Type;
102///
103/// let mut ty: Type = syn::parse_str("::std::option::Option<String>")?;
104/// let result = extract_option::from_mut(&mut ty)?;
105/// let result = result.into_token_stream().to_string();
106///
107/// assert_eq!(result, "String");
108/// # Ok::<(), darling::Error>(())
109/// ```
110pub fn from_mut(ty: &mut Type) -> Result<&mut Type> {
111    let span = ty.span();
112    let err = || Error::custom("expected an `Option`").with_span(&span);
113
114    let Type::Path(path) = ty else {
115        return Err(err());
116    };
117
118    if path.qself.is_some() {
119        return Err(err());
120    }
121
122    let Some(last_segment) = path.path.segments.last_mut() else {
123        return Err(err());
124    };
125
126    if last_segment.ident != "Option" {
127        return Err(err());
128    }
129
130    let syn::PathArguments::AngleBracketed(ty) = &mut last_segment.arguments else {
131        return Err(err());
132    };
133
134    let args = ty.args.iter_mut().collect::<Vec<_>>();
135
136    if args.len() != 1 {
137        return Err(err());
138    }
139
140    let arg = args
141        .into_iter()
142        .next()
143        .expect("just checked that `.len() == 1`");
144
145    let syn::GenericArgument::Type(ty) = arg else {
146        return Err(err());
147    };
148
149    Ok(ty)
150}
151
152/// Extracts a `&Type` `T` from an `Option<T>`
153///
154/// # Errors
155///
156/// Returns an error if the given type is not an `Option<T>`. For more information, see the [module-level](self) documentation
157///
158/// # Example
159///
160/// ```
161/// # use darling_core as darling;
162/// use darling::util::extract_option;
163/// use quote::ToTokens;
164/// use syn::Type;
165///
166/// let ty: Type = syn::parse_str("::std::option::Option<String>")?;
167/// let result = extract_option::from_ref(&ty)?;
168/// let result = result.into_token_stream().to_string();
169///
170/// assert_eq!(result, "String");
171/// # Ok::<(), darling::Error>(())
172/// ```
173pub fn from_ref(ty: &Type) -> Result<&Type> {
174    let span = ty.span();
175    let err = || Error::custom("expected an `Option`").with_span(&span);
176
177    let Type::Path(path) = ty else {
178        return Err(err());
179    };
180
181    if path.qself.is_some() {
182        return Err(err());
183    }
184
185    let Some(last_segment) = &path.path.segments.last() else {
186        return Err(err());
187    };
188
189    if &last_segment.ident != "Option" {
190        return Err(err());
191    }
192
193    let syn::PathArguments::AngleBracketed(ty) = &last_segment.arguments else {
194        return Err(err());
195    };
196
197    let args = ty.args.iter().collect::<Vec<_>>();
198
199    if args.len() != 1 {
200        return Err(err());
201    }
202
203    let arg = args
204        .into_iter()
205        .next()
206        .expect("just checked that `.len() == 1`");
207
208    let syn::GenericArgument::Type(ty) = arg else {
209        return Err(err());
210    };
211
212    Ok(ty)
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use syn::Type;
219
220    macro_rules! test_all {
221        ($test:literal, $method:ident) => {
222            from_owned(syn::parse_str::<Type>($test).unwrap()).$method();
223            from_ref(&syn::parse_str::<Type>($test).unwrap()).$method();
224            from_mut(&mut syn::parse_str::<Type>($test).unwrap()).$method();
225        };
226    }
227
228    // Success
229
230    #[test]
231    fn simple() {
232        test_all!("Option<String>", unwrap);
233    }
234
235    #[test]
236    fn fully_qualified() {
237        test_all!("std::option::Option<String>", unwrap);
238        test_all!("core::option::Option<String>", unwrap);
239    }
240
241    #[test]
242    fn absolute_path() {
243        test_all!("::std::option::Option<String>", unwrap);
244        test_all!("::core::option::Option<String>", unwrap);
245    }
246
247    #[test]
248    fn submodule() {
249        test_all!("option::Option<String>", unwrap);
250    }
251
252    // Fail
253
254    #[test]
255    fn wrong_arg_count() {
256        test_all!("Option<String, u8>", unwrap_err);
257    }
258
259    #[test]
260    fn rejects_qself() {
261        test_all!("<T as Option>::Option<u32>", unwrap_err);
262    }
263
264    #[test]
265    fn non_path() {
266        test_all!("&'a Option<u8>", unwrap_err);
267    }
268}