• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: Apache-2.0 OR MIT
2 
3 use proc_macro2::{Span, TokenStream};
4 use quote::quote;
5 use syn::{
6     parse::{Parse, ParseStream},
7     spanned::Spanned,
8     Attribute, Error, Ident, Result, Token,
9 };
10 
11 use super::PIN;
12 use crate::utils::{ParseBufferExt, SliceExt};
13 
parse_args(attrs: &[Attribute]) -> Result<Args>14 pub(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 
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                 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)]
216 pub(super) enum UnpinImpl {
217     Default,
218     /// `UnsafeUnpin`.
219     Unsafe(Span),
220     /// `!Unpin`.
221     Negative(Span),
222 }
223 
224 /// `project_replace [= <ident>]` argument.
225 pub(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 
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