| 1 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
| 2 | |
| 3 | use proc_macro2::{Span, TokenStream}; |
| 4 | use quote::quote; |
| 5 | use syn::{ |
| 6 | Attribute, Error, Ident, Result, Token, |
| 7 | parse::{Parse, ParseStream}, |
| 8 | spanned::Spanned as _, |
| 9 | }; |
| 10 | |
| 11 | use super::PIN; |
| 12 | use crate::utils::{ParseBufferExt as _, SliceExt as _}; |
| 13 | |
| 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 { |
| 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. |
| 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 { Some(ident) } else { None } |
| 249 | } |
| 250 | } |
| 251 | |