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