• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         return Err(error!(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         return Err(error!(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         Err(error!(span, "duplicate #[pin] attribute"))
55     } else {
56         // This `unwrap` only fails if another macro removes `#[pin]` and inserts own `#[pin]`.
57         syn::parse2(prev.1.unwrap())
58     }
59 }
60 
61 pub(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 
74 impl Parse for Args {
parse(input: ParseStream<'_>) -> Result<Self>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                 return Err(error!(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                 return Err(error!(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                 Err(error!(span, "duplicate `{}` argument", name))
98             } else {
99                 Ok((value, span))
100             }
101         }
102 
103         let mut pinned_drop = None;
104         let mut unsafe_unpin = None;
105         let mut not_unpin = None;
106         let mut project = None;
107         let mut project_ref = None;
108         let mut project_replace_value = None;
109         let mut project_replace_span = None;
110 
111         while !input.is_empty() {
112             if input.peek(Token![!]) {
113                 let bang: Token![!] = input.parse()?;
114                 if input.is_empty() {
115                     return Err(error!(bang, "expected `!Unpin`, found `!`"));
116                 }
117                 let unpin: kw::Unpin = input.parse()?;
118                 let span = quote!(#bang #unpin);
119                 if not_unpin.replace(span.span()).is_some() {
120                     return Err(error!(span, "duplicate `!Unpin` argument"));
121                 }
122             } else {
123                 let token = input.parse::<Ident>()?;
124                 match &*token.to_string() {
125                     "PinnedDrop" => {
126                         if pinned_drop.replace(token.span()).is_some() {
127                             return Err(error!(token, "duplicate `PinnedDrop` argument"));
128                         }
129                     }
130                     "UnsafeUnpin" => {
131                         if unsafe_unpin.replace(token.span()).is_some() {
132                             return Err(error!(token, "duplicate `UnsafeUnpin` argument"));
133                         }
134                     }
135                     "project" => {
136                         project = Some(parse_value(input, &token, project.is_some())?.0);
137                     }
138                     "project_ref" => {
139                         project_ref = Some(parse_value(input, &token, project_ref.is_some())?.0);
140                     }
141                     "project_replace" => {
142                         if input.peek(Token![=]) {
143                             let (value, span) =
144                                 parse_value(input, &token, project_replace_span.is_some())?;
145                             project_replace_value = Some(value);
146                             project_replace_span = Some(span.span());
147                         } else if project_replace_span.is_some() {
148                             return Err(error!(token, "duplicate `project_replace` argument"));
149                         } else {
150                             project_replace_span = Some(token.span());
151                         }
152                     }
153                     "Replace" => {
154                         return Err(error!(
155                             token,
156                             "`Replace` argument was removed, use `project_replace` argument instead"
157                         ));
158                     }
159                     _ => return Err(error!(token, "unexpected argument: {}", token)),
160                 }
161             }
162 
163             if input.is_empty() {
164                 break;
165             }
166             let _: Token![,] = input.parse()?;
167         }
168 
169         if project.is_some() || project_ref.is_some() {
170             if project == project_ref {
171                 return Err(error!(
172                     project_ref,
173                     "name `{}` is already specified by `project` argument",
174                     project_ref.as_ref().unwrap()
175                 ));
176             }
177             if let Some(ident) = &project_replace_value {
178                 if project == project_replace_value {
179                     return Err(error!(
180                         ident,
181                         "name `{}` is already specified by `project` argument", ident
182                     ));
183                 } else if project_ref == project_replace_value {
184                     return Err(error!(
185                         ident,
186                         "name `{}` is already specified by `project_ref` argument", ident
187                     ));
188                 }
189             }
190         }
191 
192         if let Some(span) = pinned_drop {
193             if project_replace_span.is_some() {
194                 return Err(Error::new(
195                     span,
196                     "arguments `PinnedDrop` and `project_replace` are mutually exclusive",
197                 ));
198             }
199         }
200         let project_replace = match (project_replace_span, project_replace_value) {
201             (None, _) => ProjReplace::None,
202             (Some(span), Some(ident)) => ProjReplace::Named { ident, span },
203             (Some(span), None) => ProjReplace::Unnamed { span },
204         };
205         let unpin_impl = match (unsafe_unpin, not_unpin) {
206             (None, None) => UnpinImpl::Default,
207             (Some(span), None) => UnpinImpl::Unsafe(span),
208             (None, Some(span)) => UnpinImpl::Negative(span),
209             (Some(span), Some(_)) => {
210                 return Err(Error::new(
211                     span,
212                     "arguments `UnsafeUnpin` and `!Unpin` are mutually exclusive",
213                 ));
214             }
215         };
216 
217         Ok(Self { pinned_drop, unpin_impl, project, project_ref, project_replace })
218     }
219 }
220 
221 /// `UnsafeUnpin` or `!Unpin` argument.
222 #[derive(Clone, Copy)]
223 pub(super) enum UnpinImpl {
224     Default,
225     /// `UnsafeUnpin`.
226     Unsafe(Span),
227     /// `!Unpin`.
228     Negative(Span),
229 }
230 
231 /// `project_replace [= <ident>]` argument.
232 pub(super) enum ProjReplace {
233     None,
234     /// `project_replace`.
235     Unnamed {
236         span: Span,
237     },
238     /// `project_replace = <ident>`.
239     #[allow(dead_code)] // false positive that fixed in Rust 1.38
240     Named {
241         span: Span,
242         ident: Ident,
243     },
244 }
245 
246 impl ProjReplace {
247     /// Return the span of this argument.
span(&self) -> Option<Span>248     pub(super) fn span(&self) -> Option<Span> {
249         match self {
250             Self::None => None,
251             Self::Named { span, .. } | Self::Unnamed { span, .. } => Some(*span),
252         }
253     }
254 
ident(&self) -> Option<&Ident>255     pub(super) fn ident(&self) -> Option<&Ident> {
256         if let Self::Named { ident, .. } = self { Some(ident) } else { None }
257     }
258 }
259