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