1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use proc_macro2::{Span, TokenStream};
4use quote::quote;
5use syn::{
6 parse::{Parse, ParseStream},
7 spanned::Spanned,
8 Attribute, Error, Ident, Result, Token,
9};
10
11use super::PIN;
12use crate::utils::{ParseBufferExt, SliceExt};
13
14pub(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
61pub(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
74impl Parse for Args {
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)]
216pub(super) enum UnpinImpl {
217 Default,
218 /// `UnsafeUnpin`.
219 Unsafe(Span),
220 /// `!Unpin`.
221 Negative(Span),
222}
223
224/// `project_replace [= <ident>]` argument.
225pub(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
238impl ProjReplace {
239 /// Return the span of this argument.
240 pub(super) fn span(&self) -> Option<Span> {
241 match self {
242 Self::None => None,
243 Self::Named { span: &Span, .. } | Self::Unnamed { span: &Span, .. } => Some(*span),
244 }
245 }
246
247 pub(super) fn ident(&self) -> Option<&Ident> {
248 if let Self::Named { ident: &Ident, .. } = self {
249 Some(ident)
250 } else {
251 None
252 }
253 }
254}
255