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}