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