impl_trait_for_tuples/lib.rs
1/*!
2[](https://docs.rs/impl-trait-for-tuples/) [](https://crates.io/crates/impl-trait-for-tuples) [](https://crates.io/crates/impl-trait-for-tuples)
3
4Attribute macro to implement a trait for tuples
5
6* [Introduction](#introduction)
7* [Syntax](#syntax)
8* [Limitations](#limitations)
9* [Example](#example)
10* [License](#license)
11
12## Introduction
13
14When wanting to implement a trait for combinations of tuples, Rust requires the trait to be implemented
15for each combination manually. With this crate you just need to place `#[impl_for_tuples(5)]` above
16your trait declaration (in full-automatic mode) to implement the trait for the tuple combinations
17`(), (T0), (T0, T1), (T0, T1, T2), (T0, T1, T2, T3), (T0, T1, T2, T3, T4, T5)`. The number of tuples is the
18parameter given to the attribute and can be chosen freely.
19
20This crate provides two modes full-automatic and semi-automatic. The full-automatic mode just requires
21the trait definition to implement the trait for the tuple combinations. While being much easier to
22use, it also comes with some restrictions like no associated types, no return values or no associated
23consts. To support these, the semi-automatic mode is provided. This mode requires a dummy implementation
24block of the trait that is expanded to all the tuple combinations implementations. To express the
25tuple access in this dummy implementation a special syntax is required `for_tuples!( #( Tuple::function(); )* )`.
26This would expand to `Tuple::function();` for each tuple while `Tuple` is chosen by the user and will be
27replaced by the corresponding tuple identifier per iteration.
28
29## Syntax
30
31The attribute macro can be called with one `#[impl_for_tuples(5)]` or with two `#[impl_for_tuples(2, 5)]`
32parameters. The former instructs the macro to generate up to a tuple of five elements and the later instructs it
33to generate from a tuple with two element up to five elements.
34
35### Semi-automatic syntax
36
37```
38# use impl_trait_for_tuples::impl_for_tuples;
39trait Trait {
40 type Ret;
41 type Arg;
42 type FixedType;
43 const VALUE: u32;
44
45 fn test(arg: Self::Arg) -> Self::Ret;
46
47 fn test_with_self(&self) -> Result<(), ()>;
48}
49
50#[impl_for_tuples(1, 5)]
51impl Trait for Tuple {
52 // Here we expand the `Ret` and `Arg` associated types.
53 for_tuples!( type Ret = ( #( Tuple::Ret ),* ); );
54 for_tuples!( type Arg = ( #( Tuple::Arg ),* ); );
55 for_tuples!( const VALUE: u32 = #( Tuple::VALUE )+*; );
56
57 // Here we set the `FixedType` to `u32` and add a custom where bound that forces the same
58 // `FixedType` for all tuple types.
59 type FixedType = u32;
60 for_tuples!( where #( Tuple: Trait<FixedType=u32> )* );
61
62 fn test(arg: Self::Arg) -> Self::Ret {
63 for_tuples!( ( #( Tuple::test(arg.Tuple) ),* ) )
64 }
65
66 fn test_with_self(&self) -> Result<(), ()> {
67 for_tuples!( #( Tuple.test_with_self()?; )* );
68 Ok(())
69 }
70}
71
72# fn main() {}
73```
74
75The given example shows all supported combinations of `for_tuples!`. When accessing a method from the
76`self` tuple instance, `self.Tuple` is the required syntax and is replaced by `self.0`, `self.1`, etc.
77The placeholder tuple identifer is taken from the self type given to the implementation block. So, it
78is up to the user to chose any valid identifier.
79
80The separator given to `#( Tuple::something() )SEPARATOR*` can be chosen from `,`, `+`, `-`,
81`*`, `/`, `|`, `&` or nothing for no separator.
82
83By adding the `#[tuple_types_no_default_trait_bound]` above the impl block, the macro will not add the
84automatic bound to the implemented trait for each tuple type.
85
86The trait bound can be customized using `#[tuple_types_custom_trait_bound(NewBound)]`.
87The new bound will be used instead of the impleted trait for each tuple type.
88
89## Limitations
90
91The macro does not supports `for_tuples!` calls in a different macro, so stuff like
92`vec![ for_tuples!( bla ) ]` will generate invalid code.
93
94## Example
95
96### Full-automatic
97
98```
99# use impl_trait_for_tuples::impl_for_tuples;
100#[impl_for_tuples(5)]
101trait Notify {
102 fn notify(&self);
103}
104
105# fn main() {}
106```
107
108### Semi-automatic
109
110```
111# use impl_trait_for_tuples::impl_for_tuples;
112trait Notify {
113 fn notify(&self) -> Result<(), ()>;
114}
115
116#[impl_for_tuples(5)]
117impl Notify for TupleIdentifier {
118 fn notify(&self) -> Result<(), ()> {
119 for_tuples!( #( TupleIdentifier.notify()?; )* );
120 Ok(())
121 }
122}
123
124# fn main() {}
125```
126
127## License
128Licensed under either of
129 * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
130 * [MIT license](http://opensource.org/licenses/MIT)
131at your option.
132*/
133
134extern crate proc_macro;
135
136use proc_macro2::{Span, TokenStream};
137
138use syn::{
139 parse::{Parse, ParseStream},
140 parse_macro_input,
141 punctuated::Punctuated,
142 token, Attribute, Error, Ident, ItemImpl, ItemTrait, LitInt, Result,
143};
144
145mod full_automatic;
146mod semi_automatic;
147mod utils;
148
149/// Enum to parse the input and to distinguish between full/semi-automatic mode.
150enum FullOrSemiAutomatic {
151 /// Full-automatic trait implementation for tuples uses the trait definition.
152 Full(ItemTrait),
153 /// Sem-automatic trait implementation for tuples uses a trait implementation.
154 Semi(ItemImpl),
155}
156
157impl Parse for FullOrSemiAutomatic {
158 fn parse(input: ParseStream) -> Result<Self> {
159 // We need to parse any attributes first, before we know what we actually can parse.
160 let fork = input.fork();
161 fork.call(Attribute::parse_outer)?;
162
163 // If there is a `unsafe` in front of, just skip it.
164 if fork.peek(token::Unsafe) {
165 fork.parse::<token::Unsafe>()?;
166 }
167
168 let lookahead1 = fork.lookahead1();
169
170 if lookahead1.peek(token::Impl) {
171 Ok(Self::Semi(input.parse()?))
172 } else if lookahead1.peek(token::Trait) || lookahead1.peek(token::Pub) {
173 Ok(Self::Full(input.parse()?))
174 } else {
175 Err(lookahead1.error())
176 }
177 }
178}
179
180/// The minimum and maximum given as two `LitInt`'s to the macro as arguments.
181struct MinMax {
182 min: Option<usize>,
183 max: usize,
184}
185
186impl Parse for MinMax {
187 fn parse(input: ParseStream) -> Result<Self> {
188 let args = Punctuated::<LitInt, token::Comma>::parse_terminated(input)?;
189
190 if args.is_empty() {
191 Err(Error::new(
192 Span::call_site(),
193 "Expected at least one argument to the macro!",
194 ))
195 } else if args.len() == 1 {
196 Ok(Self {
197 max: args[0].base10_parse()?,
198 min: None,
199 })
200 } else if args.len() == 2 {
201 let min = args[0].base10_parse()?;
202 let max = args[1].base10_parse()?;
203
204 if min >= max {
205 Err(Error::new(
206 Span::call_site(),
207 "It is expected that `min` comes before `max` and that `max > min` is true!",
208 ))
209 } else {
210 Ok(Self {
211 min: Some(min),
212 max,
213 })
214 }
215 } else {
216 Err(Error::new(
217 Span::call_site(),
218 "Too many arguments given to the macro!",
219 ))
220 }
221 }
222}
223
224/// See [crate](index.html) documentation.
225#[proc_macro_attribute]
226pub fn impl_for_tuples(
227 args: proc_macro::TokenStream,
228 input: proc_macro::TokenStream,
229) -> proc_macro::TokenStream {
230 let input = parse_macro_input!(input as FullOrSemiAutomatic);
231 let min_max = parse_macro_input!(args as MinMax);
232
233 impl_for_tuples_impl(input, min_max)
234 .unwrap_or_else(|e| e.to_compile_error())
235 .into()
236}
237
238fn impl_for_tuples_impl(input: FullOrSemiAutomatic, min_max: MinMax) -> Result<TokenStream> {
239 let tuple_elements = (0usize..min_max.max)
240 .map(|i| generate_tuple_element_ident(i))
241 .collect::<Vec<_>>();
242
243 match input {
244 FullOrSemiAutomatic::Full(definition) => {
245 full_automatic::full_automatic_impl(definition, tuple_elements, min_max.min)
246 }
247 FullOrSemiAutomatic::Semi(trait_impl) => {
248 semi_automatic::semi_automatic_impl(trait_impl, tuple_elements, min_max.min)
249 }
250 }
251}
252
253fn generate_tuple_element_ident(num: usize) -> Ident {
254 Ident::new(&format!("TupleElement{}", num), Span::call_site())
255}