amplify_derive/
util.rs

1// Rust language amplification derive library providing multiple generic trait
2// implementations, type wrappers, derive macros and other language enhancements
3//
4// Written in 2019-2020 by
5//     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
6//
7// To the extent possible under law, the author(s) have dedicated all
8// copyright and related and neighboring rights to this software to
9// the public domain worldwide. This software is distributed without
10// any warranty.
11//
12// You should have received a copy of the MIT License
13// along with this software.
14// If not, see <https://opensource.org/licenses/MIT>.
15
16#![allow(dead_code)]
17
18use syn::punctuated::IntoIter;
19use syn::spanned::Spanned;
20use syn::{Attribute, DeriveInput, Ident, Lit, Meta, MetaNameValue, NestedMeta, Path, Result};
21
22/// Macro producing `Result::Err` with [`syn::Error`] containing span
23/// information from `$attr` (first) argument and formatted string describing
24/// concrete error (description is taken from `$msg` second macro argument) and
25/// providing an example `$example` (third macro argument) of how the macro
26/// should be used.
27macro_rules! attr_err {
28    ($attr:expr, $msg:tt) => {
29        attr_err!($attr.span(), NAME, $msg, EXAMPLE)
30    };
31    ($name:expr, $msg:tt, $example:tt) => {
32        attr_err!(::proc_macro2::Span::call_site(), $name, $msg, $example)
33    };
34    ($attr:expr, $name:expr, $msg:tt, $example:tt) => {
35        ::syn::Error::new(
36            $attr.span(),
37            format!("Attribute `#[{}]`: {}\nExample use: {}", $name, $msg, $example),
38        )
39    };
40}
41
42pub(crate) fn get_amplify_crate(input: &DeriveInput) -> Path {
43    let name = "amplify_crate";
44    let example = "#[amplify_crate(amplify_crate_path)]";
45    let default = Path::from(Ident::new("amplify", input.span()));
46
47    let list = match attr_list(&input.attrs, name, example).ok().unwrap_or(None) {
48        Some(x) => x,
49        None => return default,
50    };
51    nested_one_path(&list, name, example)
52        .ok()
53        .unwrap_or(None)
54        .unwrap_or(default)
55}
56
57pub fn attr_list<'a>(
58    attrs: impl IntoIterator<Item = &'a Attribute>,
59    ident: &str,
60    example: &str,
61) -> Result<Option<IntoIter<NestedMeta>>> {
62    for attr in attrs {
63        if attr.path.is_ident(ident) {
64            match attr.parse_meta() {
65                Ok(meta) => match meta {
66                    Meta::Path(_) => {
67                        return Err(attr_err!(ident, "unexpected path argument", example));
68                    }
69                    Meta::List(list) => return Ok(Some(list.nested.into_iter())),
70                    Meta::NameValue(_) => {
71                        return Err(attr_err!(
72                            ident,
73                            "unexpected `name=\"value\"` argument",
74                            example
75                        ));
76                    }
77                },
78                Err(_) => return Err(attr_err!(ident, "wrong format", example)),
79            }
80        }
81    }
82
83    Ok(None)
84}
85
86pub fn attr_named_value<'a>(
87    attrs: impl IntoIterator<Item = &'a Attribute>,
88    ident: &str,
89    example: &str,
90) -> Result<Option<Lit>> {
91    for attr in attrs {
92        if attr.path.is_ident(ident) {
93            match attr.parse_meta() {
94                Ok(meta) => match meta {
95                    Meta::Path(_) => {
96                        return Err(attr_err!(ident, "unexpected path argument", example));
97                    }
98                    Meta::List(_) => {
99                        return Err(attr_err!(
100                            ident,
101                            "must have form `name=\"value\"`, not `name(value)`",
102                            example
103                        ));
104                    }
105                    Meta::NameValue(name_val) => return Ok(Some(name_val.lit)),
106                },
107                Err(_) => return Err(attr_err!(ident, "wrong format", example)),
108            }
109        }
110    }
111
112    Ok(None)
113}
114
115pub fn nested_one_meta(
116    list: &IntoIter<NestedMeta>,
117    attr_name: &str,
118    example: &str,
119) -> Result<Option<Meta>> {
120    match list.len() {
121        0 => Err(attr_err!(attr_name, "unexpected absence of argument", example)),
122        1 => match list
123            .clone()
124            .peekable()
125            .peek()
126            .expect("Core library iterator is broken")
127        {
128            NestedMeta::Meta(meta) => Ok(Some(meta.clone())),
129            NestedMeta::Lit(_) => {
130                Err(attr_err!(attr_name, "unexpected literal for type identifier is met", example))
131            }
132        },
133        _ => Err(attr_err!(attr_name, "unexpected multiple type identifiers", example)),
134    }
135}
136
137pub fn nested_one_path(
138    list: &IntoIter<NestedMeta>,
139    attr_name: &str,
140    example: &str,
141) -> Result<Option<Path>> {
142    let meta = nested_one_meta(list, attr_name, example)?;
143    meta.map(|meta| match meta {
144        Meta::Path(path) => Ok(path),
145        _ => Err(attr_err!(attr_name, "unexpected attribute type", example)),
146    })
147    .transpose()
148}
149
150pub fn nested_one_named_value(
151    list: &IntoIter<NestedMeta>,
152    attr_name: &str,
153    example: &str,
154) -> Result<Option<MetaNameValue>> {
155    let meta = nested_one_meta(list, attr_name, example)?;
156    meta.map(|meta| match meta {
157        Meta::NameValue(nested_meta) => Ok(nested_meta),
158        _ => Err(attr_err!(attr_name, "unexpected attribute type", example)),
159    })
160    .transpose()
161}