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