1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! Internal attributes of the form `#[auto_impl(name(...))]` that can be
//! attached to trait items.

use proc_macro2::{Delimiter, TokenTree};
use syn::{
    spanned::Spanned,
    visit_mut::{visit_item_trait_mut, VisitMut},
    Attribute, Error, Meta, TraitItem,
};

use crate::proxy::{parse_types, ProxyType};

/// Removes all `#[auto_impl]` attributes that are attached to methods of the
/// given trait.
pub(crate) fn remove_our_attrs(trait_def: &mut syn::ItemTrait) -> syn::Result<()> {
    struct AttrRemover(syn::Result<()>);
    impl VisitMut for AttrRemover {
        fn visit_trait_item_mut(&mut self, item: &mut TraitItem) {
            let item_span = item.span();
            let (attrs, is_method) = match item {
                TraitItem::Fn(m) => (&mut m.attrs, true),
                TraitItem::Const(c) => (&mut c.attrs, false),
                TraitItem::Type(t) => (&mut t.attrs, false),
                TraitItem::Macro(m) => (&mut m.attrs, false),
                _ => {
                    let err = syn::Error::new(
                        item.span(),
                        "encountered unexpected `TraitItem`, cannot handle that, sorry!",
                    );

                    if let Err(ref mut current_err) = self.0 {
                        current_err.combine(err);
                    } else {
                        self.0 = Err(err);
                    };

                    return;
                }
            };

            // Make sure non-methods do not have our attributes.
            if !is_method && attrs.iter().any(is_our_attr) {
                let err = syn::Error::new(
                    item_span,
                    "`#[auto_impl]` attributes are only allowed on methods",
                );

                if let Err(ref mut current_err) = self.0 {
                    current_err.combine(err);
                } else {
                    self.0 = Err(err);
                };

                return;
            }

            attrs.retain(|a| !is_our_attr(a));
        }
    }

    let mut visitor = AttrRemover(Ok(()));
    visit_item_trait_mut(&mut visitor, trait_def);

    visitor.0
}

/// Checks if the given attribute is "our" attribute. That means that it's path
/// is `auto_impl`.
pub(crate) fn is_our_attr(attr: &Attribute) -> bool {
    attr.path().is_ident("auto_impl")
}

/// Tries to parse the given attribute as one of our own `auto_impl`
/// attributes. If it's invalid, an error is emitted and `Err(())` is returned.
/// You have to make sure that `attr` is one of our attrs with `is_our_attr`
/// before calling this function!
pub(crate) fn parse_our_attr(attr: &Attribute) -> syn::Result<OurAttr> {
    assert!(is_our_attr(attr));

    // Get the body of the attribute (which has to be a ground, because we
    // required the syntax `auto_impl(...)` and forbid stuff like
    // `auto_impl = ...`).
    let body = match &attr.meta {
        Meta::List(list) => list.tokens.clone(),
        _ => {
            return Err(Error::new(
                attr.span(),
                "expected single group delimited by `()`",
            ));
        }
    };

    let mut it = body.clone().into_iter();

    // Try to extract the name (we require the body to be `name(...)`).
    let name = match it.next() {
        Some(TokenTree::Ident(x)) => x,
        Some(other) => {
            return Err(Error::new(
                other.span(),
                format_args!("expected ident, found '{}'", other),
            ));
        }
        None => {
            return Err(Error::new(attr.span(), "expected ident, found nothing"));
        }
    };

    // Extract the parameters (which again, have to be a group delimited by
    // `()`)
    let params = match it.next() {
        Some(TokenTree::Group(ref g)) if g.delimiter() == Delimiter::Parenthesis => g.stream(),
        Some(other) => {
            return Err(Error::new(
                other.span(),
                format_args!(
                    "expected arguments for '{}' in parenthesis `()`, found `{}`",
                    name, other
                ),
            ));
        }
        None => {
            return Err(Error::new(
                body.span(),
                format_args!(
                    "expected arguments for '{}' in parenthesis `()`, found nothing",
                    name,
                ),
            ));
        }
    };

    // Finally match over the name of the attribute.
    let out = if name == "keep_default_for" {
        let proxy_types = parse_types(params.into());
        OurAttr::KeepDefaultFor(proxy_types)
    } else {
        return Err(Error::new(
            name.span(),
            format_args!(
                "invalid attribute '{}'; only `keep_default_for` is supported",
                name
            ),
        ));
    };

    Ok(out)
}

/// Attributes of the form `#[auto_impl(...)]` that can be attached to items of
/// the trait.
#[derive(Clone, PartialEq, Debug)]
pub(crate) enum OurAttr {
    KeepDefaultFor(Vec<ProxyType>),
}