#![doc(test(
no_crate_inject,
attr(
deny(warnings, rust_2018_idioms, single_use_lifetimes),
allow(dead_code, unused_variables)
)
))]
#![forbid(unsafe_code)]
#[macro_use]
mod error;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::{
parse_quote, token, Error, Generics, ItemImpl, Lifetime, LifetimeParam, Path, Result, Token,
Type,
};
#[proc_macro_attribute]
pub fn negative_impl(args: TokenStream, input: TokenStream) -> TokenStream {
attribute(&args.into(), syn::parse_macro_input!(input))
.unwrap_or_else(Error::into_compile_error)
.into()
}
fn attribute(args: &TokenStream2, mut impl_: ItemImpl) -> Result<TokenStream2> {
parse_as_empty(args)?;
let (not_token, trait_path, for_token) = match impl_.trait_.take() {
Some((Some(not_token), path, for_token)) => (not_token, path, for_token),
Some((_, path, _)) => bail!(path, "may only be used on negative trait impls"),
None => bail!(impl_, "may only be used on negative trait impls"),
};
impl_.attrs.push(parse_quote!(#[doc(hidden)]));
if impl_.unsafety.is_some() {
bail!(quote!(#not_token #trait_path), "negative impls cannot be unsafe");
}
if let Some(item) = impl_.items.first() {
bail!(item, "negative impls cannot have any items");
}
let TraitInfo { trivial_bounds, unsafety, maybe_unsized, full_path } =
TraitInfo::new(&trait_path)?;
let wrapper_lifetime = Lifetime::new("'__wrapper", Span::call_site());
let wrapper_ident = format_ident!("__Wrapper");
let trivial_bounds = parse_quote!(
#wrapper_ident<#wrapper_lifetime, #trivial_bounds>: #full_path
);
impl_.generics.make_where_clause().predicates.push(trivial_bounds);
insert_lifetime(&mut impl_.generics, wrapper_lifetime);
let unsafety = if unsafety { Some(<Token![unsafe]>::default()) } else { None };
let sized = if maybe_unsized { Some(quote!(: ?Sized)) } else { None };
let wrapper = quote! {
pub struct #wrapper_ident<'a, T #sized>(::core::marker::PhantomData<&'a ()>, T);
#unsafety impl<T #sized> #full_path for #wrapper_ident<'_, T>
where T: #full_path {}
};
impl_.trait_ = Some((None, full_path, for_token));
impl_.unsafety = unsafety;
Ok(quote! {
const _: () = {
#wrapper
#[allow(clippy::non_send_fields_in_send_ty)]
#impl_
};
})
}
struct TraitInfo {
trivial_bounds: Type,
unsafety: bool,
maybe_unsized: bool,
full_path: Path,
}
impl TraitInfo {
fn new(path: &Path) -> Result<Self> {
match &*path.segments.last().unwrap().ident.to_string() {
"Send" => Ok(Self {
trivial_bounds: parse_quote!(*const ()),
unsafety: true,
maybe_unsized: true,
full_path: parse_quote!(::core::marker::Send),
}),
"Sync" => Ok(Self {
trivial_bounds: parse_quote!(*const ()),
unsafety: true,
maybe_unsized: true,
full_path: parse_quote!(::core::marker::Sync),
}),
"Unpin" => Ok(Self {
trivial_bounds: parse_quote!(::core::marker::PhantomPinned),
unsafety: false,
maybe_unsized: true,
full_path: parse_quote!(::core::marker::Unpin),
}),
"UnwindSafe" => Ok(Self {
trivial_bounds: parse_quote!(&'static mut ()),
unsafety: false,
maybe_unsized: true,
full_path: parse_quote!(::core::panic::UnwindSafe),
}),
"RefUnwindSafe" => Ok(Self {
trivial_bounds: parse_quote!(::core::cell::UnsafeCell<()>),
unsafety: false,
maybe_unsized: true,
full_path: parse_quote!(::core::panic::RefUnwindSafe),
}),
_ => bail!(path, "non auto traits are not supported"),
}
}
}
fn insert_lifetime(generics: &mut Generics, lifetime: Lifetime) {
generics.lt_token.get_or_insert_with(token::Lt::default);
generics.gt_token.get_or_insert_with(token::Gt::default);
generics.params.insert(0, LifetimeParam::new(lifetime).into());
}
fn parse_as_empty(tokens: &TokenStream2) -> Result<()> {
if tokens.is_empty() {
Ok(())
} else {
bail!(tokens, "unexpected token: `{}`", tokens)
}
}