pin_project_internal/pin_project/
args.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use proc_macro2::{Span, TokenStream};
4use quote::quote;
5use syn::{
6    parse::{Parse, ParseStream},
7    spanned::Spanned,
8    Attribute, Error, Ident, Result, Token,
9};
10
11use super::PIN;
12use crate::utils::{ParseBufferExt, SliceExt};
13
14pub(super) fn parse_args(attrs: &[Attribute]) -> Result<Args> {
15    // `(__private(<args>))` -> `<args>`
16    struct Input(Option<TokenStream>);
17
18    impl Parse for Input {
19        fn parse(input: ParseStream<'_>) -> Result<Self> {
20            Ok(Self((|| {
21                let private = input.parse::<Ident>().ok()?;
22                if private == "__private" {
23                    input.parenthesized().ok()?.parse::<TokenStream>().ok()
24                } else {
25                    None
26                }
27            })()))
28        }
29    }
30
31    if let Some(attr) = attrs.find("pin_project") {
32        bail!(attr, "duplicate #[pin_project] attribute");
33    }
34
35    let mut attrs = attrs.iter().filter(|attr| attr.path().is_ident(PIN));
36
37    let prev = if let Some(attr) = attrs.next() {
38        (attr, syn::parse2::<Input>(attr.meta.require_list()?.tokens.clone())?.0)
39    } else {
40        // This only fails if another macro removes `#[pin]`.
41        bail!(TokenStream::new(), "#[pin_project] attribute has been removed");
42    };
43
44    if let Some(attr) = attrs.next() {
45        let (prev_attr, prev_res) = &prev;
46        // As the `#[pin]` attribute generated by `#[pin_project]`
47        // has the same span as `#[pin_project]`, it is possible
48        // that a useless error message will be generated.
49        // So, use the span of `prev_attr` if it is not a valid attribute.
50        let res = syn::parse2::<Input>(attr.meta.require_list()?.tokens.clone())?.0;
51        let span = match (prev_res, res) {
52            (Some(_), _) => attr,
53            (None, _) => prev_attr,
54        };
55        bail!(span, "duplicate #[pin] attribute");
56    }
57    // This `unwrap` only fails if another macro removes `#[pin]` and inserts own `#[pin]`.
58    syn::parse2(prev.1.unwrap())
59}
60
61pub(super) struct Args {
62    /// `PinnedDrop` argument.
63    pub(super) pinned_drop: Option<Span>,
64    /// `UnsafeUnpin` or `!Unpin` argument.
65    pub(super) unpin_impl: UnpinImpl,
66    /// `project = <ident>` argument.
67    pub(super) project: Option<Ident>,
68    /// `project_ref = <ident>` argument.
69    pub(super) project_ref: Option<Ident>,
70    /// `project_replace [= <ident>]` argument.
71    pub(super) project_replace: ProjReplace,
72}
73
74impl Parse for Args {
75    fn parse(input: ParseStream<'_>) -> Result<Self> {
76        mod kw {
77            syn::custom_keyword!(Unpin);
78        }
79
80        /// Parses `= <value>` in `<name> = <value>` and returns value and span of name-value pair.
81        fn parse_value(
82            input: ParseStream<'_>,
83            name: &Ident,
84            has_prev: bool,
85        ) -> Result<(Ident, TokenStream)> {
86            if input.is_empty() {
87                bail!(name, "expected `{0} = <identifier>`, found `{0}`", name);
88            }
89            let eq_token: Token![=] = input.parse()?;
90            if input.is_empty() {
91                let span = quote!(#name #eq_token);
92                bail!(span, "expected `{0} = <identifier>`, found `{0} =`", name);
93            }
94            let value: Ident = input.parse()?;
95            let span = quote!(#name #value);
96            if has_prev {
97                bail!(span, "duplicate `{}` argument", name);
98            }
99            Ok((value, span))
100        }
101
102        let mut pinned_drop = None;
103        let mut unsafe_unpin = None;
104        let mut not_unpin = None;
105        let mut project = None;
106        let mut project_ref = None;
107        let mut project_replace_value = None;
108        let mut project_replace_span = None;
109
110        while !input.is_empty() {
111            if input.peek(Token![!]) {
112                let bang: Token![!] = input.parse()?;
113                if input.is_empty() {
114                    bail!(bang, "expected `!Unpin`, found `!`");
115                }
116                let unpin: kw::Unpin = input.parse()?;
117                let span = quote!(#bang #unpin);
118                if not_unpin.replace(span.span()).is_some() {
119                    bail!(span, "duplicate `!Unpin` argument");
120                }
121            } else {
122                let token = input.parse::<Ident>()?;
123                match &*token.to_string() {
124                    "PinnedDrop" => {
125                        if pinned_drop.replace(token.span()).is_some() {
126                            bail!(token, "duplicate `PinnedDrop` argument");
127                        }
128                    }
129                    "UnsafeUnpin" => {
130                        if unsafe_unpin.replace(token.span()).is_some() {
131                            bail!(token, "duplicate `UnsafeUnpin` argument");
132                        }
133                    }
134                    "project" => {
135                        project = Some(parse_value(input, &token, project.is_some())?.0);
136                    }
137                    "project_ref" => {
138                        project_ref = Some(parse_value(input, &token, project_ref.is_some())?.0);
139                    }
140                    "project_replace" => {
141                        if input.peek(Token![=]) {
142                            let (value, span) =
143                                parse_value(input, &token, project_replace_span.is_some())?;
144                            project_replace_value = Some(value);
145                            project_replace_span = Some(span.span());
146                        } else if project_replace_span.is_some() {
147                            bail!(token, "duplicate `project_replace` argument");
148                        } else {
149                            project_replace_span = Some(token.span());
150                        }
151                    }
152                    "Replace" => {
153                        bail!(
154                            token,
155                            "`Replace` argument was removed, use `project_replace` argument instead"
156                        );
157                    }
158                    _ => bail!(token, "unexpected argument: {}", token),
159                }
160            }
161
162            if input.is_empty() {
163                break;
164            }
165            let _: Token![,] = input.parse()?;
166        }
167
168        if project.is_some() || project_ref.is_some() {
169            if project == project_ref {
170                bail!(
171                    project_ref,
172                    "name `{}` is already specified by `project` argument",
173                    project_ref.as_ref().unwrap()
174                );
175            }
176            if let Some(ident) = &project_replace_value {
177                if project == project_replace_value {
178                    bail!(ident, "name `{}` is already specified by `project` argument", ident);
179                } else if project_ref == project_replace_value {
180                    bail!(ident, "name `{}` is already specified by `project_ref` argument", ident);
181                }
182            }
183        }
184
185        if let Some(span) = pinned_drop {
186            if project_replace_span.is_some() {
187                return Err(Error::new(
188                    span,
189                    "arguments `PinnedDrop` and `project_replace` are mutually exclusive",
190                ));
191            }
192        }
193        let project_replace = match (project_replace_span, project_replace_value) {
194            (None, _) => ProjReplace::None,
195            (Some(span), Some(ident)) => ProjReplace::Named { ident, span },
196            (Some(span), None) => ProjReplace::Unnamed { span },
197        };
198        let unpin_impl = match (unsafe_unpin, not_unpin) {
199            (None, None) => UnpinImpl::Default,
200            (Some(span), None) => UnpinImpl::Unsafe(span),
201            (None, Some(span)) => UnpinImpl::Negative(span),
202            (Some(span), Some(_)) => {
203                return Err(Error::new(
204                    span,
205                    "arguments `UnsafeUnpin` and `!Unpin` are mutually exclusive",
206                ));
207            }
208        };
209
210        Ok(Self { pinned_drop, unpin_impl, project, project_ref, project_replace })
211    }
212}
213
214/// `UnsafeUnpin` or `!Unpin` argument.
215#[derive(Clone, Copy)]
216pub(super) enum UnpinImpl {
217    Default,
218    /// `UnsafeUnpin`.
219    Unsafe(Span),
220    /// `!Unpin`.
221    Negative(Span),
222}
223
224/// `project_replace [= <ident>]` argument.
225pub(super) enum ProjReplace {
226    None,
227    /// `project_replace`.
228    Unnamed {
229        span: Span,
230    },
231    /// `project_replace = <ident>`.
232    Named {
233        span: Span,
234        ident: Ident,
235    },
236}
237
238impl ProjReplace {
239    /// Return the span of this argument.
240    pub(super) fn span(&self) -> Option<Span> {
241        match self {
242            Self::None => None,
243            Self::Named { span, .. } | Self::Unnamed { span, .. } => Some(*span),
244        }
245    }
246
247    pub(super) fn ident(&self) -> Option<&Ident> {
248        if let Self::Named { ident, .. } = self {
249            Some(ident)
250        } else {
251            None
252        }
253    }
254}