typenum_mappings/
lib.rs

1//! A proc-macro to generate mappings from typenum's `UInt` types to your own type.
2//!
3//! This is useful when emulating `const_generic_exprs` on stable, as the third step in the process of:
4//! 1. Converting const generics to typenum types
5//! 2. Performing the expression via typenum types
6//! 3. **Converting those typenum types back to the resulting type**
7//!
8//! # Example - `concat_array`
9//!
10//! ```rust
11//! use std::ops::Add;
12//!
13//! use typenum::{U, ToUInt, Const};
14//!
15//! trait ArrayLike {
16//!     fn new() -> Self;
17//! }
18//!
19//! impl<T: Default, const N: usize> ArrayLike for [T; N] {
20//!     fn new() -> Self {
21//!         std::array::from_fn(|_| T::default())
22//!     }
23//! }
24//!
25//! trait TypenumToArray<T> {
26//!     type Array: ArrayLike;
27//! }
28//!
29//! typenum_mappings::impl_typenum_mapping!(
30//!     impl<const N: usize = 0..=1000, T: Default> TypenumToArray<T> for #TypeNumName {
31//!         type Array: = [T; N];
32//!     }
33//! );
34//!
35//!
36//! type RunAdd<T1, T2> = <T1 as Add<T2>>::Output;
37//! type RunTypeToArray<T, N> = <N as TypenumToArray<T>>::Array;
38//!
39//! fn concat_array<const N1: usize, const N2: usize, T>(a1: [T; N1], a2: [T; N2]) -> RunTypeToArray<T, RunAdd<U<N1>, U<N2>>>
40//! where
41//!     Const<N1>: ToUInt,
42//!     Const<N2>: ToUInt,
43//!
44//!     U<N1>: Add<U<N2>>,
45//!     <U<N1> as Add<U<N2>>>::Output: TypenumToArray<T>
46//! {
47//! # if false {
48//!     todo!()
49//! # }
50//! #   ArrayLike::new()
51//! }
52//!
53//! let out = concat_array([1, 2, 3], [4, 5, 6]);
54//! assert_eq!(out.len(), 6);
55//! ```
56//!
57//! ## Minimum Supported Rust Version
58//!
59//! This is currently `1.63`, and it is considered a breaking change to increase.
60
61#![warn(clippy::pedantic, rust_2018_idioms)]
62
63use std::ops::Range;
64
65use proc_macro2::Span;
66use quote::quote;
67use syn::{punctuated::Punctuated, Token};
68use to_arraystring::ToArrayString;
69
70/// Parser of an integer range, as [`syn::RangeLimits`] parses `0..1> Other` as `0..(1 > Other)` instead of `(0..1) > Other`.
71struct ParsedRange(Range<u32>);
72
73impl syn::parse::Parse for ParsedRange {
74    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
75        let start = input.parse::<syn::LitInt>()?;
76        let needs_increment = if input.peek(Token![..=]) {
77            input.parse::<Token![..=]>()?;
78            true
79        } else if input.peek(Token![..]) {
80            input.parse::<Token![..]>()?;
81            false
82        } else {
83            return Err(input.error("Could not parse range syntax, expected `..` or `..=`"));
84        };
85
86        let end = input.parse::<syn::LitInt>()?;
87
88        let start = start.base10_parse()?;
89        let mut end = end.base10_parse()?;
90
91        if needs_increment {
92            end += 1;
93        }
94
95        Ok(Self(start..end))
96    }
97}
98
99/// Parser for the rest of an `impl` item's generics, half way through the list.
100struct RestGenerics(Punctuated<syn::GenericParam, Token![,]>);
101
102impl syn::parse::Parse for RestGenerics {
103    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
104        if input.parse::<Option<Token![,]>>()?.is_none() {
105            return Ok(Self(Punctuated::new()));
106        }
107
108        let mut out = Punctuated::new();
109        while !input.peek(Token![>]) {
110            out.push_value(input.parse()?);
111            if let Some(comma) = input.parse::<Option<Token![,]>>()? {
112                out.push_punct(comma);
113            } else {
114                break;
115            }
116        }
117
118        Ok(Self(out))
119    }
120}
121
122#[derive(Debug)]
123struct Arguments {
124    index: syn::Ident,
125    index_ty: syn::Type,
126    range: Range<u32>,
127    generics: Punctuated<syn::GenericParam, Token![,]>,
128    trait_name: syn::Ident,
129    trait_generics: syn::Generics,
130    where_clause: Option<syn::WhereClause>,
131    rest: proc_macro2::TokenStream,
132}
133
134impl syn::parse::Parse for Arguments {
135    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
136        input.parse::<Token![impl]>()?;
137
138        input.parse::<Token![<]>()?;
139        input.parse::<Token![const]>()?;
140        let index = input.parse()?;
141        input.parse::<Token![:]>()?;
142        let index_ty = input.parse::<syn::Type>()?;
143        input.parse::<Token![=]>()?;
144        let range = input.parse::<ParsedRange>()?.0;
145        let generics = input.parse::<RestGenerics>()?.0;
146        input.parse::<Token![>]>()?;
147
148        let trait_name = input.parse()?;
149        let trait_generics = input.parse()?;
150        input.parse::<Token![for]>()?;
151
152        if input.parse::<Token![#]>().is_err() || input.parse::<syn::Ident>()? != "TypeNumName" {
153            return Err(syn::Error::new(
154                Span::call_site(),
155                "Expected implementation for literal `#TypeNumName`",
156            ));
157        }
158
159        let where_clause = input.parse::<Option<syn::WhereClause>>()?;
160
161        let inside_parens;
162        syn::braced!(inside_parens in input);
163
164        let rest = inside_parens.parse::<proc_macro2::TokenStream>()?;
165
166        Ok(Self {
167            index,
168            index_ty,
169            range,
170            generics,
171            trait_name,
172            trait_generics,
173            where_clause,
174            rest,
175        })
176    }
177}
178
179#[derive(Debug)]
180struct GenerateTypenum(u32);
181
182impl quote::ToTokens for GenerateTypenum {
183    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
184        if self.0 == 0 {
185            return quote!(::typenum::UTerm).to_tokens(tokens);
186        }
187
188        let num_bits_set = u32::BITS - self.0.leading_zeros();
189        for _ in 0..num_bits_set {
190            quote!(::typenum::UInt<).to_tokens(tokens);
191        }
192
193        quote!(::typenum::UTerm,).to_tokens(tokens);
194
195        for n in (0..num_bits_set).rev() {
196            if self.0 & (1 << n) == 0 {
197                quote!(::typenum::B0>).to_tokens(tokens);
198            } else {
199                quote!(::typenum::B1>).to_tokens(tokens);
200            }
201
202            if n != 0 {
203                quote!(,).to_tokens(tokens);
204            }
205        }
206    }
207}
208
209#[proc_macro]
210pub fn impl_typenum_mapping(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
211    let Arguments {
212        index,
213        index_ty,
214        range,
215        generics,
216        trait_name,
217        trait_generics,
218        where_clause,
219        rest,
220    } = match syn::parse(tokens) {
221        Ok(args) => args,
222        Err(err) => return err.into_compile_error().into(),
223    };
224
225    let typenum_iter = range.clone().map(GenerateTypenum);
226    let range = range.map(|i| syn::LitInt::new(&i.to_arraystring(), Span::call_site()));
227
228    quote!(
229        #(const _: () = {
230            const #index: #index_ty = #range;
231            impl<#generics> #trait_name #trait_generics for #typenum_iter #where_clause {
232                #rest
233            }
234        };)*
235    )
236    .into()
237}