nix_compat_derive/internal/
attrs.rs

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