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