nix_compat_derive/internal/
attrs.rs

1use quote::ToTokens;
2use syn::meta::ParseNestedMeta;
3use syn::parse::Parse;
4use syn::{parse_quote, Attribute, Expr, ExprLit, ExprPath, Lit, Token};
5
6use super::symbol::{
7    Symbol, CRATE, DEFAULT, DISPLAY, FROM, FROM_STR, INTO, NIX, TRY_FROM, TRY_INTO, VERSION,
8};
9use super::Context;
10
11#[derive(Debug, PartialEq, Eq)]
12pub enum Default {
13    None,
14    #[allow(clippy::enum_variant_names)]
15    Default(syn::Path),
16    Path(ExprPath),
17}
18
19impl Default {
20    pub fn is_none(&self) -> bool {
21        matches!(self, Default::None)
22    }
23}
24
25#[derive(Debug, PartialEq, Eq)]
26pub struct Field {
27    pub default: Default,
28    pub version: Option<syn::ExprRange>,
29}
30
31impl Field {
32    pub fn from_ast(ctx: &Context, attrs: &Vec<Attribute>) -> Field {
33        let mut version = None;
34        let mut version_path = None;
35        let mut default = Default::None;
36        for attr in attrs {
37            if attr.path() != NIX {
38                continue;
39            }
40            if let Err(err) = attr.parse_nested_meta(|meta| {
41                if meta.path == VERSION {
42                    version = parse_lit(ctx, &meta, VERSION)?;
43                    version_path = Some(meta.path);
44                } else if meta.path == DEFAULT {
45                    if meta.input.peek(Token![=]) {
46                        if let Some(path) = parse_lit(ctx, &meta, DEFAULT)? {
47                            default = Default::Path(path);
48                        }
49                    } else {
50                        default = Default::Default(meta.path);
51                    }
52                } else {
53                    let path = meta.path.to_token_stream().to_string();
54                    return Err(meta.error(format_args!("unknown nix field attribute '{}'", path)));
55                }
56                Ok(())
57            }) {
58                eprintln!("{:?}", err.span().source_text());
59                ctx.syn_error(err);
60            }
61        }
62        if version.is_some() && default.is_none() {
63            default = Default::Default(version_path.unwrap());
64        }
65
66        Field { default, version }
67    }
68}
69
70#[derive(Debug, PartialEq, Eq)]
71pub struct Variant {
72    pub version: syn::ExprRange,
73}
74
75impl Variant {
76    pub fn from_ast(ctx: &Context, attrs: &Vec<Attribute>) -> Variant {
77        let mut version = parse_quote!(..);
78        for attr in attrs {
79            if attr.path() != NIX {
80                continue;
81            }
82            if let Err(err) = attr.parse_nested_meta(|meta| {
83                if meta.path == VERSION {
84                    if let Some(v) = parse_lit(ctx, &meta, VERSION)? {
85                        version = v;
86                    }
87                } else {
88                    let path = meta.path.to_token_stream().to_string();
89                    return Err(
90                        meta.error(format_args!("unknown nix variant attribute '{}'", path))
91                    );
92                }
93                Ok(())
94            }) {
95                eprintln!("{:?}", err.span().source_text());
96                ctx.syn_error(err);
97            }
98        }
99
100        Variant { version }
101    }
102}
103
104#[derive(Debug, PartialEq, Eq)]
105pub struct Container {
106    pub from_str: Option<syn::Path>,
107    pub type_from: Option<syn::Type>,
108    pub type_try_from: Option<syn::Type>,
109    pub type_into: Option<syn::Type>,
110    pub type_try_into: Option<syn::Type>,
111    pub display: Default,
112    pub crate_path: Option<syn::Path>,
113}
114
115impl Container {
116    pub fn from_ast(ctx: &Context, attrs: &Vec<Attribute>) -> Container {
117        let mut type_from = None;
118        let mut type_try_from = None;
119        let mut crate_path = None;
120        let mut from_str = None;
121        let mut type_into = None;
122        let mut type_try_into = None;
123        let mut display = Default::None;
124
125        for attr in attrs {
126            if attr.path() != NIX {
127                continue;
128            }
129            if let Err(err) = attr.parse_nested_meta(|meta| {
130                if meta.path == FROM {
131                    type_from = parse_lit(ctx, &meta, FROM)?;
132                } else if meta.path == TRY_FROM {
133                    type_try_from = parse_lit(ctx, &meta, TRY_FROM)?;
134                } else if meta.path == FROM_STR {
135                    from_str = Some(meta.path);
136                } else if meta.path == INTO {
137                    type_into = parse_lit(ctx, &meta, INTO)?;
138                } else if meta.path == TRY_INTO {
139                    type_try_into = parse_lit(ctx, &meta, TRY_INTO)?;
140                } else if meta.path == DISPLAY {
141                    if meta.input.peek(Token![=]) {
142                        if let Some(path) = parse_lit(ctx, &meta, DISPLAY)? {
143                            display = Default::Path(path);
144                        }
145                    } else {
146                        display = Default::Default(meta.path);
147                    }
148                } else if meta.path == CRATE {
149                    crate_path = parse_lit(ctx, &meta, CRATE)?;
150                } else {
151                    let path = meta.path.to_token_stream().to_string();
152                    return Err(
153                        meta.error(format_args!("unknown nix variant attribute '{}'", path))
154                    );
155                }
156                Ok(())
157            }) {
158                eprintln!("{:?}", err.span().source_text());
159                ctx.syn_error(err);
160            }
161        }
162
163        Container {
164            from_str,
165            type_from,
166            type_try_from,
167            type_into,
168            type_try_into,
169            display,
170            crate_path,
171        }
172    }
173}
174
175pub fn get_lit_str(
176    ctx: &Context,
177    meta: &ParseNestedMeta,
178    attr: Symbol,
179) -> syn::Result<Option<syn::LitStr>> {
180    let expr: Expr = meta.value()?.parse()?;
181    let mut value = &expr;
182    while let Expr::Group(e) = value {
183        value = &e.expr;
184    }
185    if let Expr::Lit(ExprLit {
186        lit: Lit::Str(s), ..
187    }) = value
188    {
189        Ok(Some(s.clone()))
190    } else {
191        ctx.error_spanned(
192            expr,
193            format_args!("expected nix attribute {} to be string", attr),
194        );
195        Ok(None)
196    }
197}
198
199pub fn parse_lit<T: Parse>(
200    ctx: &Context,
201    meta: &ParseNestedMeta,
202    attr: Symbol,
203) -> syn::Result<Option<T>> {
204    match get_lit_str(ctx, meta, attr)? {
205        Some(lit) => Ok(Some(lit.parse()?)),
206        None => Ok(None),
207    }
208}
209
210#[cfg(test)]
211mod test {
212    use syn::{parse_quote, Attribute};
213
214    use crate::internal::Context;
215
216    use super::*;
217
218    #[test]
219    fn parse_field_version() {
220        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(version="..34")])];
221        let ctx = Context::new();
222        let field = Field::from_ast(&ctx, &attrs);
223        ctx.check().unwrap();
224        assert_eq!(
225            field,
226            Field {
227                default: Default::Default(parse_quote!(version)),
228                version: Some(parse_quote!(..34)),
229            }
230        );
231    }
232
233    #[test]
234    fn parse_field_default() {
235        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(default)])];
236        let ctx = Context::new();
237        let field = Field::from_ast(&ctx, &attrs);
238        ctx.check().unwrap();
239        assert_eq!(
240            field,
241            Field {
242                default: Default::Default(parse_quote!(default)),
243                version: None,
244            }
245        );
246    }
247
248    #[test]
249    fn parse_field_default_path() {
250        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(default="Default::default")])];
251        let ctx = Context::new();
252        let field = Field::from_ast(&ctx, &attrs);
253        ctx.check().unwrap();
254        assert_eq!(
255            field,
256            Field {
257                default: Default::Path(parse_quote!(Default::default)),
258                version: None,
259            }
260        );
261    }
262
263    #[test]
264    fn parse_field_both() {
265        let attrs: Vec<Attribute> =
266            vec![parse_quote!(#[nix(version="..", default="Default::default")])];
267        let ctx = Context::new();
268        let field = Field::from_ast(&ctx, &attrs);
269        ctx.check().unwrap();
270        assert_eq!(
271            field,
272            Field {
273                default: Default::Path(parse_quote!(Default::default)),
274                version: Some(parse_quote!(..)),
275            }
276        );
277    }
278
279    #[test]
280    fn parse_field_both_rev() {
281        let attrs: Vec<Attribute> =
282            vec![parse_quote!(#[nix(default="Default::default", version="..")])];
283        let ctx = Context::new();
284        let field = Field::from_ast(&ctx, &attrs);
285        ctx.check().unwrap();
286        assert_eq!(
287            field,
288            Field {
289                default: Default::Path(parse_quote!(Default::default)),
290                version: Some(parse_quote!(..)),
291            }
292        );
293    }
294
295    #[test]
296    fn parse_field_no_attr() {
297        let attrs: Vec<Attribute> = vec![];
298        let ctx = Context::new();
299        let field = Field::from_ast(&ctx, &attrs);
300        ctx.check().unwrap();
301        assert_eq!(
302            field,
303            Field {
304                default: Default::None,
305                version: None,
306            }
307        );
308    }
309
310    #[test]
311    fn parse_field_no_subattrs() {
312        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix()])];
313        let ctx = Context::new();
314        let field = Field::from_ast(&ctx, &attrs);
315        ctx.check().unwrap();
316        assert_eq!(
317            field,
318            Field {
319                default: Default::None,
320                version: None,
321            }
322        );
323    }
324
325    #[test]
326    fn parse_variant_version() {
327        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(version="..34")])];
328        let ctx = Context::new();
329        let variant = Variant::from_ast(&ctx, &attrs);
330        ctx.check().unwrap();
331        assert_eq!(
332            variant,
333            Variant {
334                version: parse_quote!(..34),
335            }
336        );
337    }
338
339    #[test]
340    fn parse_variant_no_attr() {
341        let attrs: Vec<Attribute> = vec![];
342        let ctx = Context::new();
343        let variant = Variant::from_ast(&ctx, &attrs);
344        ctx.check().unwrap();
345        assert_eq!(
346            variant,
347            Variant {
348                version: parse_quote!(..),
349            }
350        );
351    }
352
353    #[test]
354    fn parse_variant_no_subattrs() {
355        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix()])];
356        let ctx = Context::new();
357        let variant = Variant::from_ast(&ctx, &attrs);
358        ctx.check().unwrap();
359        assert_eq!(
360            variant,
361            Variant {
362                version: parse_quote!(..),
363            }
364        );
365    }
366
367    #[test]
368    fn parse_container_from_str() {
369        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(from_str)])];
370        let ctx = Context::new();
371        let container = Container::from_ast(&ctx, &attrs);
372        ctx.check().unwrap();
373        assert_eq!(
374            container,
375            Container {
376                from_str: Some(parse_quote!(from_str)),
377                type_from: None,
378                type_try_from: None,
379                type_into: None,
380                type_try_into: None,
381                display: Default::None,
382                crate_path: None,
383            }
384        );
385    }
386
387    #[test]
388    fn parse_container_from() {
389        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(from="u64")])];
390        let ctx = Context::new();
391        let container = Container::from_ast(&ctx, &attrs);
392        ctx.check().unwrap();
393        assert_eq!(
394            container,
395            Container {
396                from_str: None,
397                type_from: Some(parse_quote!(u64)),
398                type_try_from: None,
399                type_into: None,
400                type_try_into: None,
401                display: Default::None,
402                crate_path: None,
403            }
404        );
405    }
406
407    #[test]
408    fn parse_container_try_from() {
409        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(try_from="u64")])];
410        let ctx = Context::new();
411        let container = Container::from_ast(&ctx, &attrs);
412        ctx.check().unwrap();
413        assert_eq!(
414            container,
415            Container {
416                from_str: None,
417                type_from: None,
418                type_try_from: Some(parse_quote!(u64)),
419                type_into: None,
420                type_try_into: None,
421                display: Default::None,
422                crate_path: None,
423            }
424        );
425    }
426
427    #[test]
428    fn parse_container_into() {
429        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(into="u64")])];
430        let ctx = Context::new();
431        let container = Container::from_ast(&ctx, &attrs);
432        ctx.check().unwrap();
433        assert_eq!(
434            container,
435            Container {
436                from_str: None,
437                type_from: None,
438                type_try_from: None,
439                type_into: Some(parse_quote!(u64)),
440                type_try_into: None,
441                display: Default::None,
442                crate_path: None,
443            }
444        );
445    }
446
447    #[test]
448    fn parse_container_try_into() {
449        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(try_into="u64")])];
450        let ctx = Context::new();
451        let container = Container::from_ast(&ctx, &attrs);
452        ctx.check().unwrap();
453        assert_eq!(
454            container,
455            Container {
456                from_str: None,
457                type_from: None,
458                type_try_from: None,
459                type_into: None,
460                type_try_into: Some(parse_quote!(u64)),
461                display: Default::None,
462                crate_path: None,
463            }
464        );
465    }
466
467    #[test]
468    fn parse_container_display() {
469        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(display)])];
470        let ctx = Context::new();
471        let container = Container::from_ast(&ctx, &attrs);
472        ctx.check().unwrap();
473        assert_eq!(
474            container,
475            Container {
476                from_str: None,
477                type_from: None,
478                type_try_from: None,
479                type_into: None,
480                type_try_into: None,
481                display: Default::Default(parse_quote!(display)),
482                crate_path: None,
483            }
484        );
485    }
486
487    #[test]
488    fn parse_container_display_path() {
489        let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(display="Path::display")])];
490        let ctx = Context::new();
491        let container = Container::from_ast(&ctx, &attrs);
492        ctx.check().unwrap();
493        assert_eq!(
494            container,
495            Container {
496                from_str: None,
497                type_from: None,
498                type_try_from: None,
499                type_into: None,
500                type_try_into: None,
501                display: Default::Path(parse_quote!(Path::display)),
502                crate_path: None,
503            }
504        );
505    }
506}