negative_impl/
lib.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3/*!
4Negative trait implementations on stable Rust.
5
6This crate emulates the [unstable `negative_impls` feature](https://doc.rust-lang.org/nightly/unstable-book/language-features/negative-impls.html)
7by [generating a trait implementation with a condition that will never be true](https://github.com/taiki-e/negative-impl/issues/6#issuecomment-1669714453).
8
9## Usage
10
11Add this to your `Cargo.toml`:
12
13```toml
14[dependencies]
15negative-impl = "0.1"
16```
17
18## Examples
19
20```rust
21use negative_impl::negative_impl;
22
23pub struct Type {}
24
25#[negative_impl]
26impl !Send for Type {}
27#[negative_impl]
28impl !Sync for Type {}
29```
30
31## Supported traits
32
33Currently this crate only supports [auto traits](https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits).
34
35- [`Send`](https://doc.rust-lang.org/std/marker/trait.Send.html)
36- [`Sync`](https://doc.rust-lang.org/std/marker/trait.Sync.html)
37- [`Unpin`](https://doc.rust-lang.org/std/marker/trait.Unpin.html)
38- [`UnwindSafe`](https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html)
39- [`RefUnwindSafe`](https://doc.rust-lang.org/std/panic/trait.RefUnwindSafe.html)
40
41## Limitations
42
43### Conflicting implementations
44
45The following code cannot compile due to `impl<T: Send> Trait for T` and
46`impl Trait for Type` conflict.
47
48```rust,compile_fail,E0119
49use negative_impl::negative_impl;
50
51pub struct Type {}
52
53#[negative_impl]
54impl !Send for Type {}
55
56trait Trait {}
57
58impl<T: Send> Trait for T {}
59impl Trait for Type {}
60```
61
62```text
63error[E0119]: conflicting implementations of trait `Trait` for type `Type`:
64  --> src/lib.rs:60:1
65   |
6614 | impl<T: Send> Trait for T {}
67   | ------------------------- first implementation here
6815 | impl Trait for Type {}
69   | ^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Type`
70```
71
72The above code can be compiled using the unstable `negative_impls` feature.
73
74```rust
75#![feature(negative_impls)]
76
77pub struct Type {}
78
79impl !Send for Type {}
80
81trait Trait {}
82
83impl<T: Send> Trait for T {}
84impl Trait for Type {}
85```
86*/
87
88#![doc(test(
89    no_crate_inject,
90    attr(
91        deny(warnings, rust_2018_idioms, single_use_lifetimes),
92        allow(dead_code, unused_variables)
93    )
94))]
95#![forbid(unsafe_code)]
96
97#[macro_use]
98mod error;
99
100use proc_macro::TokenStream;
101use proc_macro2::{Span, TokenStream as TokenStream2};
102use quote::{format_ident, quote};
103use syn::{
104    parse_quote, token, Error, Generics, ItemImpl, Lifetime, LifetimeParam, Path, Result, Token,
105    Type,
106};
107
108#[proc_macro_attribute]
109pub fn negative_impl(args: TokenStream, input: TokenStream) -> TokenStream {
110    attribute(&args.into(), syn::parse_macro_input!(input))
111        .unwrap_or_else(Error::into_compile_error)
112        .into()
113}
114
115fn attribute(args: &TokenStream2, mut impl_: ItemImpl) -> Result<TokenStream2> {
116    parse_as_empty(args)?;
117
118    let (not_token, trait_path, for_token) = match impl_.trait_.take() {
119        Some((Some(not_token), path, for_token)) => (not_token, path, for_token),
120        Some((_, path, _)) => bail!(path, "may only be used on negative trait impls"),
121        None => bail!(impl_, "may only be used on negative trait impls"),
122    };
123    // https://github.com/rust-lang/rust/issues/80481
124    impl_.attrs.push(parse_quote!(#[doc(hidden)]));
125
126    if impl_.unsafety.is_some() {
127        bail!(quote!(#not_token #trait_path), "negative impls cannot be unsafe");
128    }
129    if let Some(item) = impl_.items.first() {
130        bail!(item, "negative impls cannot have any items");
131    }
132
133    let TraitInfo { trivial_bounds, unsafety, maybe_unsized, full_path } =
134        TraitInfo::new(&trait_path)?;
135
136    let wrapper_lifetime = Lifetime::new("'__wrapper", Span::call_site());
137    let wrapper_ident = format_ident!("__Wrapper");
138
139    let trivial_bounds = parse_quote!(
140        #wrapper_ident<#wrapper_lifetime, #trivial_bounds>: #full_path
141    );
142    impl_.generics.make_where_clause().predicates.push(trivial_bounds);
143
144    insert_lifetime(&mut impl_.generics, wrapper_lifetime);
145
146    let unsafety = if unsafety { Some(<Token![unsafe]>::default()) } else { None };
147
148    let sized = if maybe_unsized { Some(quote!(: ?Sized)) } else { None };
149    let wrapper = quote! {
150        pub struct #wrapper_ident<'a, T #sized>(::core::marker::PhantomData<&'a ()>, T);
151        #unsafety impl<T #sized> #full_path for #wrapper_ident<'_, T>
152            where T: #full_path {}
153    };
154
155    impl_.trait_ = Some((None, full_path, for_token));
156    impl_.unsafety = unsafety;
157    Ok(quote! {
158        const _: () = {
159            #wrapper
160            // This is false positive as we generate a trait implementation with a condition that will never be true.
161            #[allow(clippy::non_send_fields_in_send_ty)]
162            #impl_
163        };
164    })
165}
166
167struct TraitInfo {
168    trivial_bounds: Type,
169    unsafety: bool,
170    maybe_unsized: bool,
171    full_path: Path,
172}
173
174impl TraitInfo {
175    fn new(path: &Path) -> Result<Self> {
176        match &*path.segments.last().unwrap().ident.to_string() {
177            "Send" => Ok(Self {
178                // https://github.com/rust-lang/rust/blob/1.37.0/src/libcore/marker.rs#L41
179                // https://github.com/rust-lang/rust/blob/1.70.0/library/core/src/marker.rs#L43
180                trivial_bounds: parse_quote!(*const ()),
181                unsafety: true,
182                maybe_unsized: true,
183                full_path: parse_quote!(::core::marker::Send),
184            }),
185            "Sync" => Ok(Self {
186                // https://github.com/rust-lang/rust/blob/1.37.0/src/libcore/marker.rs#L380
187                // https://github.com/rust-lang/rust/blob/1.70.0/library/core/src/marker.rs#L547
188                trivial_bounds: parse_quote!(*const ()),
189                unsafety: true,
190                maybe_unsized: true,
191                full_path: parse_quote!(::core::marker::Sync),
192            }),
193            "Unpin" => Ok(Self {
194                // https://github.com/rust-lang/rust/blob/1.37.0/src/libcore/marker.rs#L650
195                // https://github.com/rust-lang/rust/blob/1.70.0/library/core/src/marker.rs#L840
196                trivial_bounds: parse_quote!(::core::marker::PhantomPinned),
197                unsafety: false,
198                maybe_unsized: true,
199                full_path: parse_quote!(::core::marker::Unpin),
200            }),
201            "UnwindSafe" => Ok(Self {
202                // https://github.com/rust-lang/rust/blob/1.37.0/src/libstd/panic.rs#L203
203                // https://github.com/rust-lang/rust/blob/1.70.0/library/core/src/panic/unwind_safe.rs#L181
204                trivial_bounds: parse_quote!(&'static mut ()),
205                unsafety: false,
206                maybe_unsized: true,
207                full_path: parse_quote!(::core::panic::UnwindSafe),
208            }),
209            "RefUnwindSafe" => Ok(Self {
210                // https://github.com/rust-lang/rust/blob/1.37.0/src/libstd/panic.rs#L234
211                // https://github.com/rust-lang/rust/blob/1.70.0/library/core/src/panic/unwind_safe.rs#L200
212                trivial_bounds: parse_quote!(::core::cell::UnsafeCell<()>),
213                unsafety: false,
214                maybe_unsized: true,
215                full_path: parse_quote!(::core::panic::RefUnwindSafe),
216            }),
217            _ => bail!(path, "non auto traits are not supported"),
218        }
219    }
220}
221
222/// Inserts a `lifetime` at position `0` of `generics.params`.
223fn insert_lifetime(generics: &mut Generics, lifetime: Lifetime) {
224    generics.lt_token.get_or_insert_with(token::Lt::default);
225    generics.gt_token.get_or_insert_with(token::Gt::default);
226    generics.params.insert(0, LifetimeParam::new(lifetime).into());
227}
228
229/// Checks if `tokens` is an empty `TokenStream`.
230///
231/// This is almost equivalent to `syn::parse2::<Nothing>()`, but produces
232/// a better error message and does not require ownership of `tokens`.
233fn parse_as_empty(tokens: &TokenStream2) -> Result<()> {
234    if tokens.is_empty() {
235        Ok(())
236    } else {
237        bail!(tokens, "unexpected token: `{}`", tokens)
238    }
239}