251 lines
8.8 KiB
Rust
251 lines
8.8 KiB
Rust
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
|
|
use proc_macro2::{Span, TokenStream};
|
|
use quote::quote;
|
|
use syn::{
|
|
Attribute, Error, Ident, Result, Token,
|
|
parse::{Parse, ParseStream},
|
|
spanned::Spanned as _,
|
|
};
|
|
|
|
use super::PIN;
|
|
use crate::utils::{ParseBufferExt as _, SliceExt as _};
|
|
|
|
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 private = input.parse::<Ident>().ok()?;
|
|
if private == "__private" {
|
|
input.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.meta.require_list()?.tokens.clone())?.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.meta.require_list()?.tokens.clone())?.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>`.
|
|
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 }
|
|
}
|
|
}
|