diff options
Diffstat (limited to 'third_party/rust/pin-project-internal/src/pin_project/args.rs')
-rw-r--r-- | third_party/rust/pin-project-internal/src/pin_project/args.rs | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/third_party/rust/pin-project-internal/src/pin_project/args.rs b/third_party/rust/pin-project-internal/src/pin_project/args.rs new file mode 100644 index 0000000000..d0d4f362f5 --- /dev/null +++ b/third_party/rust/pin-project-internal/src/pin_project/args.rs @@ -0,0 +1,254 @@ +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + spanned::Spanned, + Attribute, Error, Ident, Result, Token, +}; + +use super::PIN; +use crate::utils::{ParseBufferExt, SliceExt}; + +pub(super) fn parse_args(attrs: &[Attribute]) -> Result<Args> { + // `(__private(<args>))` -> `<args>` + struct Input(Option<TokenStream>); + + impl Parse for Input { + fn parse(input: ParseStream<'_>) -> Result<Self> { + Ok(Self((|| { + let content = input.parenthesized().ok()?; + let private = content.parse::<Ident>().ok()?; + if private == "__private" { + content.parenthesized().ok()?.parse::<TokenStream>().ok() + } else { + None + } + })())) + } + } + + if let Some(attr) = attrs.find("pin_project") { + bail!(attr, "duplicate #[pin_project] attribute"); + } + + let mut attrs = attrs.iter().filter(|attr| attr.path.is_ident(PIN)); + + let prev = if let Some(attr) = attrs.next() { + (attr, syn::parse2::<Input>(attr.tokens.clone()).unwrap().0) + } else { + // This only fails if another macro removes `#[pin]`. + bail!(TokenStream::new(), "#[pin_project] attribute has been removed"); + }; + + if let Some(attr) = attrs.next() { + let (prev_attr, prev_res) = &prev; + // As the `#[pin]` attribute generated by `#[pin_project]` + // has the same span as `#[pin_project]`, it is possible + // that a useless error message will be generated. + // So, use the span of `prev_attr` if it is not a valid attribute. + let res = syn::parse2::<Input>(attr.tokens.clone()).unwrap().0; + let span = match (prev_res, res) { + (Some(_), _) => attr, + (None, _) => prev_attr, + }; + bail!(span, "duplicate #[pin] attribute"); + } + // This `unwrap` only fails if another macro removes `#[pin]` and inserts own `#[pin]`. + syn::parse2(prev.1.unwrap()) +} + +pub(super) struct Args { + /// `PinnedDrop` argument. + pub(super) pinned_drop: Option<Span>, + /// `UnsafeUnpin` or `!Unpin` argument. + pub(super) unpin_impl: UnpinImpl, + /// `project = <ident>` argument. + pub(super) project: Option<Ident>, + /// `project_ref = <ident>` argument. + pub(super) project_ref: Option<Ident>, + /// `project_replace [= <ident>]` argument. + pub(super) project_replace: ProjReplace, +} + +impl Parse for Args { + fn parse(input: ParseStream<'_>) -> Result<Self> { + mod kw { + syn::custom_keyword!(Unpin); + } + + /// Parses `= <value>` in `<name> = <value>` and returns value and span of name-value pair. + fn parse_value( + input: ParseStream<'_>, + name: &Ident, + has_prev: bool, + ) -> Result<(Ident, TokenStream)> { + if input.is_empty() { + bail!(name, "expected `{0} = <identifier>`, found `{0}`", name); + } + let eq_token: Token![=] = input.parse()?; + if input.is_empty() { + let span = quote!(#name #eq_token); + bail!(span, "expected `{0} = <identifier>`, found `{0} =`", name); + } + let value: Ident = input.parse()?; + let span = quote!(#name #value); + if has_prev { + bail!(span, "duplicate `{}` argument", name); + } + Ok((value, span)) + } + + let mut pinned_drop = None; + let mut unsafe_unpin = None; + let mut not_unpin = None; + let mut project = None; + let mut project_ref = None; + let mut project_replace_value = None; + let mut project_replace_span = None; + + while !input.is_empty() { + if input.peek(Token![!]) { + let bang: Token![!] = input.parse()?; + if input.is_empty() { + bail!(bang, "expected `!Unpin`, found `!`"); + } + let unpin: kw::Unpin = input.parse()?; + let span = quote!(#bang #unpin); + if not_unpin.replace(span.span()).is_some() { + bail!(span, "duplicate `!Unpin` argument"); + } + } else { + let token = input.parse::<Ident>()?; + match &*token.to_string() { + "PinnedDrop" => { + if pinned_drop.replace(token.span()).is_some() { + bail!(token, "duplicate `PinnedDrop` argument"); + } + } + "UnsafeUnpin" => { + if unsafe_unpin.replace(token.span()).is_some() { + bail!(token, "duplicate `UnsafeUnpin` argument"); + } + } + "project" => { + project = Some(parse_value(input, &token, project.is_some())?.0); + } + "project_ref" => { + project_ref = Some(parse_value(input, &token, project_ref.is_some())?.0); + } + "project_replace" => { + if input.peek(Token![=]) { + let (value, span) = + parse_value(input, &token, project_replace_span.is_some())?; + project_replace_value = Some(value); + project_replace_span = Some(span.span()); + } else if project_replace_span.is_some() { + bail!(token, "duplicate `project_replace` argument"); + } else { + project_replace_span = Some(token.span()); + } + } + "Replace" => { + bail!( + token, + "`Replace` argument was removed, use `project_replace` argument instead" + ); + } + _ => bail!(token, "unexpected argument: {}", token), + } + } + + if input.is_empty() { + break; + } + let _: Token![,] = input.parse()?; + } + + if project.is_some() || project_ref.is_some() { + if project == project_ref { + bail!( + project_ref, + "name `{}` is already specified by `project` argument", + project_ref.as_ref().unwrap() + ); + } + if let Some(ident) = &project_replace_value { + if project == project_replace_value { + bail!(ident, "name `{}` is already specified by `project` argument", ident); + } else if project_ref == project_replace_value { + bail!(ident, "name `{}` is already specified by `project_ref` argument", ident); + } + } + } + + if let Some(span) = pinned_drop { + if project_replace_span.is_some() { + return Err(Error::new( + span, + "arguments `PinnedDrop` and `project_replace` are mutually exclusive", + )); + } + } + let project_replace = match (project_replace_span, project_replace_value) { + (None, _) => ProjReplace::None, + (Some(span), Some(ident)) => ProjReplace::Named { ident, span }, + (Some(span), None) => ProjReplace::Unnamed { span }, + }; + let unpin_impl = match (unsafe_unpin, not_unpin) { + (None, None) => UnpinImpl::Default, + (Some(span), None) => UnpinImpl::Unsafe(span), + (None, Some(span)) => UnpinImpl::Negative(span), + (Some(span), Some(_)) => { + return Err(Error::new( + span, + "arguments `UnsafeUnpin` and `!Unpin` are mutually exclusive", + )); + } + }; + + Ok(Self { pinned_drop, unpin_impl, project, project_ref, project_replace }) + } +} + +/// `UnsafeUnpin` or `!Unpin` argument. +#[derive(Clone, Copy)] +pub(super) enum UnpinImpl { + Default, + /// `UnsafeUnpin`. + Unsafe(Span), + /// `!Unpin`. + Negative(Span), +} + +/// `project_replace [= <ident>]` argument. +pub(super) enum ProjReplace { + None, + /// `project_replace`. + Unnamed { + span: Span, + }, + /// `project_replace = <ident>`. + #[allow(dead_code)] // false positive that fixed in Rust 1.38 + Named { + span: Span, + ident: Ident, + }, +} + +impl ProjReplace { + /// Return the span of this argument. + pub(super) fn span(&self) -> Option<Span> { + match self { + Self::None => None, + Self::Named { span, .. } | Self::Unnamed { span, .. } => Some(*span), + } + } + + pub(super) fn ident(&self) -> Option<&Ident> { + if let Self::Named { ident, .. } = self { + Some(ident) + } else { + None + } + } +} |