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