auto_impl/
proxy.rs

1use std::iter::Peekable;
2use syn::Error;
3
4use crate::proc_macro::{token_stream, TokenStream, TokenTree};
5
6/// Types for which a trait can automatically be implemented.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub(crate) enum ProxyType {
9    Ref,
10    RefMut,
11    Arc,
12    Rc,
13    Box,
14    Fn,
15    FnMut,
16    FnOnce,
17}
18
19impl ProxyType {
20    pub(crate) fn is_fn(&self) -> bool {
21        matches!(*self, ProxyType::Fn | ProxyType::FnMut | ProxyType::FnOnce)
22    }
23}
24
25/// Parses the attribute token stream into a list of proxy types.
26///
27/// The attribute token stream is the one in `#[auto_impl(...)]`. It is
28/// supposed to be a comma-separated list of possible proxy types. Legal values
29/// are `&`, `&mut`, `Box`, `Rc`, `Arc`, `Fn`, `FnMut` and `FnOnce`.
30///
31/// If the given TokenStream is not valid, errors are emitted as appropriate.
32/// Erroneous types will not be put into the Vec but rather simply skipped,
33/// the emitted errors will abort the compilation anyway.
34pub(crate) fn parse_types(args: TokenStream) -> Vec<ProxyType> {
35    let mut out = Vec::new();
36    let mut iter = args.into_iter().peekable();
37
38    // While there are still tokens left...
39    while iter.peek().is_some() {
40        // First, we expect one of the proxy types.
41        if let Ok(ty) = eat_type(&mut iter) {
42            out.push(ty);
43        }
44
45        // If the next token is a comma, we eat it (trailing commas are
46        // allowed). If not, nothing happens (in this case, it's probably the
47        // end of the stream, otherwise an error will occur later).
48        let comma_next =
49            matches!(iter.peek(), Some(TokenTree::Punct(punct)) if punct.as_char() == ',');
50
51        if comma_next {
52            let _ = iter.next();
53        }
54    }
55
56    out
57}
58
59/// Parses one `ProxyType` from the given token iterator. The iterator must not
60/// be empty!
61fn eat_type(iter: &mut Peekable<token_stream::IntoIter>) -> syn::Result<ProxyType> {
62    #[rustfmt::skip]
63    const NOTE_TEXT: &str = "\
64        attribute format should be `#[auto_impl(<types>)]` where `<types>` is \
65        a comma-separated list of types. Allowed values for types: `&`, \
66        `&mut`, `Box`, `Rc`, `Arc`, `Fn`, `FnMut` and `FnOnce`.\
67    ";
68    const EXPECTED_TEXT: &str = "expected '&' or ident.";
69
70    // We can unwrap because this function requires the iterator to be
71    // non-empty.
72    let ty = match iter.next().unwrap() {
73        TokenTree::Group(group) => {
74            return Err(Error::new(
75                group.span().into(),
76                format_args!("unexpected group, {}\n{}", EXPECTED_TEXT, NOTE_TEXT),
77            ));
78        }
79
80        TokenTree::Literal(lit) => {
81            return Err(Error::new(
82                lit.span().into(),
83                format_args!("unexpected literal, {}\n{}", EXPECTED_TEXT, NOTE_TEXT),
84            ));
85        }
86
87        TokenTree::Punct(punct) => {
88            // Only '&' are allowed. Everything else leads to an error.
89            if punct.as_char() != '&' {
90                return Err(Error::new(
91                    punct.span().into(),
92                    format_args!(
93                        "unexpected punctuation '{}', {}\n{}",
94                        punct, EXPECTED_TEXT, NOTE_TEXT
95                    ),
96                ));
97            }
98
99            // Check if the next token is `mut`. If not, we will ignore it.
100            let is_mut_next =
101                matches!(iter.peek(), Some(TokenTree::Ident(id)) if id.to_string() == "mut");
102
103            if is_mut_next {
104                // Eat `mut`
105                let _ = iter.next();
106                ProxyType::RefMut
107            } else {
108                ProxyType::Ref
109            }
110        }
111
112        TokenTree::Ident(ident) => match &*ident.to_string() {
113            "Box" => ProxyType::Box,
114            "Rc" => ProxyType::Rc,
115            "Arc" => ProxyType::Arc,
116            "Fn" => ProxyType::Fn,
117            "FnMut" => ProxyType::FnMut,
118            "FnOnce" => ProxyType::FnOnce,
119            _ => {
120                return Err(Error::new(
121                    ident.span().into(),
122                    format_args!("unexpected '{}', {}\n{}", ident, EXPECTED_TEXT, NOTE_TEXT),
123                ));
124            }
125        },
126    };
127
128    Ok(ty)
129}
130
131// Right now, we can't really write useful tests. Many functions from
132// `proc_macro` use a compiler internal session. This session is only valid
133// when we were actually called as a proc macro. We need to add tests once
134// this limitation of `proc_macro` is fixed.