marker_trait/
lib.rs

1//! Implement a blanket implementation for a marker trait.
2//!
3//! [![MASTER CI status](https://github.com/Alorel/marker_trait-rs/actions/workflows/test.yml/badge.svg)](https://github.com/Alorel/marker_trait-rs/actions/workflows/test.yml?query=branch%3Amaster)
4//! [![crates.io badge](https://img.shields.io/crates/v/marker_trait)](https://crates.io/crates/marker_trait)
5//! [![Coverage Status](https://coveralls.io/repos/github/Alorel/marker_trait-rs/badge.svg)](https://coveralls.io/github/Alorel/marker_trait-rs)
6//! [![dependencies badge](https://img.shields.io/librariesio/release/cargo/marker_trait)](https://libraries.io/cargo/marker_trait)
7//!
8
9//! # Examples
10//!
11//! <details><summary>Basic example</summary>
12//!
13//! ```
14//! #[marker_trait::marker_trait]
15//! trait Cloneable: Clone + PartialEq {}
16//!
17//! #[derive(Clone, Eq, PartialEq, Debug)]
18//! struct Wrapper<T>(T);
19//!
20//! fn acceptor<T: Cloneable>(value: T) -> T { value }
21//!
22//! assert_eq!(acceptor(Wrapper(1)), Wrapper(1)); // Compiles fine
23//! ```
24//!
25//! Generated output:
26#![cfg_attr(doctest, doc = " ````no_test")]
27//! ```
28//! trait Cloneable: Clone + PartialEq {}
29//! impl<T: Clone + PartialEq> Cloneable for T {}
30//! ````
31//!
32//! </details>
33
34//! <details><summary>Generic example</summary>
35//!
36//! ```
37//! trait MySuper<A, B>: AsRef<A> {
38//!     type C;
39//!
40//!     fn foo(self) -> Result<B, Self::C>;
41//! }
42//!
43//! #[marker_trait::marker_trait]
44//! trait MySub<B, C>: MySuper<Self, B, C = C> + Sized {
45//! }
46//!
47//! struct MyStruct;
48//! impl AsRef<MyStruct> for MyStruct {
49//!   fn as_ref(&self) -> &Self { self }
50//! }
51//! impl MySuper<MyStruct, i8> for MyStruct {
52//!   type C = u8;
53//!   fn foo(self) -> Result<i8, Self::C> { Err(u8::MAX) }
54//! }
55//!
56//! fn acceptor<T: MySub<i8, u8>>(input: T) -> u8 { input.foo().unwrap_err() }
57//!
58//! assert_eq!(acceptor(MyStruct), u8::MAX);
59//! ```
60//!
61//! Generated output:
62//!
63#![cfg_attr(doctest, doc = " ````no_test")]
64//! ```
65//! impl<B, C, __MarkerTrait__: MySuper<Self, B, C = C> + Sized> MySub<B, C> for __MarkerTrait__ {}
66//! ````
67//!
68//! </details>
69
70//! <details><summary>Failing examples</summary>
71//!
72//! ```compile_fail
73//! #[marker_trait::marker_trait]
74//! trait Cloneable: Clone {}
75//!
76//! struct NonClone;
77//!
78//! fn acceptor<T: Cloneable>(value: T) -> T { value }
79//!
80//! let _ = acceptor(NonClone); // Doesn't implement clone and therefore cloneable
81//! ```
82//!
83//! ```compile_fail
84//! #[marker_trait::marker_trait]
85//! # #[allow(dead_code)]
86//! trait MyTrait: AsRef<Self::Foo> { // Empty trait body expected
87//!   type Foo;
88//! }
89//! ```
90//!
91//! ```compile_fail
92//! #[marker_trait::marker_trait]
93//! # #[allow(dead_code)]
94//! trait Foo {} // Expected at least one supertrait
95//! ```
96//!
97//! </details>
98
99#![deny(clippy::correctness, clippy::suspicious)]
100#![warn(clippy::complexity, clippy::perf, clippy::style, clippy::pedantic)]
101#![warn(missing_docs)]
102
103use proc_macro::TokenStream as TokenStream1;
104
105use proc_macro2::{Ident, Span, TokenStream};
106use quote::{quote, ToTokens};
107use syn::parse::{Parse, ParseStream};
108use syn::punctuated::Punctuated;
109use syn::{parse_macro_input, parse_quote, Error, GenericParam, ItemTrait, Token, TypeParamBound};
110
111/// See [crate-level docs](crate) for an example.
112#[proc_macro_attribute]
113pub fn marker_trait(_: TokenStream1, input: TokenStream1) -> TokenStream1 {
114    parse_macro_input!(input as MarkerTrait)
115        .into_tokens()
116        .into()
117}
118
119struct MarkerTrait(ItemTrait);
120
121impl MarkerTrait {
122    pub fn into_tokens(mut self) -> TokenStream {
123        let appendage = self.produce_appended_output();
124        let mut tokens = self.0.into_token_stream();
125        tokens.extend(appendage);
126
127        tokens
128    }
129
130    fn produce_appended_output(&mut self) -> TokenStream {
131        let ItemTrait {
132            ref unsafety,
133            ref ident,
134            ref generics,
135            ref mut supertraits,
136            ..
137        } = self.0;
138
139        let g2 = generics.split_for_impl().1;
140        let mut generics = generics.clone();
141
142        let out_ident = Ident::new("__MarkerTrait__", Span::call_site());
143
144        generics
145            .params
146            .push(make_generic_param(&out_ident, supertraits));
147
148        let (g1, _, g3) = generics.split_for_impl();
149
150        quote! {
151            #[automatically_derived]
152            #[allow(clippy::all)]
153            #unsafety impl #g1 #ident #g2 for #out_ident #g3 {}
154        }
155    }
156}
157
158impl Parse for MarkerTrait {
159    fn parse(input: ParseStream) -> syn::Result<Self> {
160        const MSG_AUTO: &str = "auto trait is not allowed";
161        const MSG_SUPER: &str = "Expected at least one supertrait";
162        const MSG_EMPTY: &str = "Expected empty trait";
163
164        let trait_def = input.parse::<ItemTrait>()?;
165
166        if trait_def.auto_token.is_some() {
167            return Err(Error::new(Span::call_site(), MSG_AUTO));
168        }
169
170        if trait_def.supertraits.is_empty() {
171            return Err(Error::new(Span::call_site(), MSG_SUPER));
172        }
173
174        if !trait_def.items.is_empty() {
175            return Err(Error::new(Span::call_site(), MSG_EMPTY));
176        }
177
178        Ok(Self(trait_def))
179    }
180}
181
182fn make_generic_param(
183    ident: &Ident,
184    bounds: &Punctuated<TypeParamBound, Token![+]>,
185) -> GenericParam {
186    parse_quote!(#ident: #bounds)
187}