newnum_proc_macros/
lib.rs

1use derive_syn_parse::Parse;
2use proc_macro2::{Span, TokenStream};
3use quote::{format_ident, quote};
4use syn::{parse_macro_input, DeriveInput, Lit, Token};
5
6macro_rules! empty_derive_macros {
7    ($($derive_trait:ident($derive_macro_ident:ident) -> $($impl_trait:ident), * $(,)?); * $(;)?) => {
8        $(
9            #[proc_macro_derive($derive_trait)]
10            pub fn $derive_macro_ident(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
11                let mut input = parse_macro_input!(input as DeriveInput);
12
13                let type_ident = &mut input.ident;
14                type_ident.set_span(Span::call_site());
15
16                let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
17
18                quote! {
19                    $(impl #impl_generics ::newnum::$impl_trait for #type_ident #ty_generics #where_clause {})*
20                }
21                .into()
22            }
23        )*
24    };
25}
26empty_derive_macros!(
27    Num(derive_num_macro) -> Num;
28    Prim(derive_prim_macro) -> Num, Prim;
29    Float(derive_float_macro) -> Num, Prim, Float;
30    Int(derive_int_macro) -> Num, Prim, Int;
31    SInt(derive_sint_macro) -> Num, Prim, Int, SignedPrim, SInt;
32    UInt(derive_uint_macro) -> Num, Prim, Int, UnsignedPrim, UInt;
33    SignedPrim(derive_signed_prim_macro) -> Num, Prim, SignedPrim;
34    UnsignedPrim(derive_unsigned_prim) -> Num, Prim, UnsignedPrim;
35);
36
37/// Converts a numeric literal into a `Num` type, generating a compile-time error if the literal is out of range for the type.
38///
39/// * the type doesn't have to implement `Num`, but it must implement `FromIntLiteral` and `FromFloatLiteral` for float literal support.
40///
41/// ### Syntax
42///
43/// `num!(<literal>)` or `num!(<literal>: <type>)`.
44///
45/// ### Example
46///
47/// ```
48/// use newnum::num;
49///
50/// fn inc(value: &mut impl Num) {
51///    *value += num!(1)
52/// }
53/// ```
54///
55/// ### Compile-Time Error
56///
57/// Because of rust const-fn limitations,
58/// the error shown when the literal is out of range is cryptic and is shown at the macro call-site.
59///
60/// Example:
61///
62///```
63/// use newnum::num;
64///
65/// fn add_alot(value: &mut impl Num) {
66///     //        |<- ERROR: evaluation of `add_alot::num_macro_fn::<u8>::{constant#0}` failed
67///     //        |
68///     *value += num!(1000)
69/// }
70///
71/// fn main() {
72///     let mut i = 5u8;
73///     add_alot(&mut i);
74/// }
75/// ```
76#[proc_macro]
77pub fn num(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
78    num_macro_helper(
79        "from_int_literal",
80        "from_float_literal",
81        quote! { ::newnum },
82        input,
83    )
84}
85
86/// Converts a numeric literal into a `Num` type, generating a compile-time error if the literal is out of range for the type.
87/// In comparison to `num!`, this macro also accepts literals that can only be approximately represented by the type.
88///
89/// * the type doesn't have to implement `Num`, but it must implement `FromIntLiteral` and `FromFloatLiteral` for float literal support.
90///
91/// ### Syntax
92///
93/// `num_approx!(<literal>)` or `num_approx!(<literal>: <type>)`.
94///
95/// ### Example
96///
97/// ```
98/// use newnum::num;
99///
100/// fn example<T: Float>() -> T {
101///     num_approx!(16_777_217)
102/// }
103/// ```
104///
105/// ### Compile-Time Error
106///
107/// Because of rust const-fn limitations,
108/// the error shown when the literal is out of range is cryptic and is shown at the macro call-site.
109///
110/// Example:
111///
112///```
113/// use newnum::num;
114///
115/// fn add_alot(value: &mut impl Num) {
116///     //        |<- ERROR: evaluation of `add_alot::num_macro_fn::<u8>::{constant#0}` failed
117///     //        |
118///     *value += num_approx!(1000)
119/// }
120///
121/// fn main() {
122///     let mut i = 5u8;
123///     add_alot(&mut i);
124/// }
125/// ```
126#[proc_macro]
127pub fn num_approx(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
128    num_macro_helper(
129        "approx_from_int_literal",
130        "approx_from_float_literal",
131        quote! { ::newnum },
132        input,
133    )
134}
135
136/// Is `num!` but uses `crate` as the crate path. This is useful for macros that are used in the `newnum` crate itself.
137///
138/// Converts a numeric literal into a `Num` type, generating a compile-time error if the literal is out of range for the type.
139///
140/// * the type doesn't have to implement `Num`, but it must implement `FromIntLiteral` and `FromFloatLiteral` for float literal support.
141///
142/// ### Syntax
143///
144/// `num!(<literal>)` or `num!(<literal>: <type>)`.
145///
146/// ### Example
147///
148/// ```
149/// use newnum::num;
150///
151/// fn inc(value: &mut impl Num) {
152///    *value += num!(1)
153/// }
154/// ```
155///
156/// ### Compile-Time Error
157///
158/// Because of rust const-fn limitations,
159/// the error shown when the literal is out of range is cryptic and is shown at the macro call-site.
160///
161/// Example:
162///
163///```
164/// use newnum::num;
165///
166/// fn add_alot(value: &mut impl Num) {
167///     //        |<- ERROR: evaluation of `add_alot::num_macro_fn::<u8>::{constant#0}` failed
168///     //        |
169///     *value += num!(1000)
170/// }
171///
172/// fn main() {
173///     let mut i = 5u8;
174///     add_alot(&mut i);
175/// }
176/// ```
177#[proc_macro]
178pub fn internal_num(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
179    num_macro_helper(
180        "from_int_literal",
181        "from_float_literal",
182        quote! { crate },
183        input,
184    )
185}
186
187/// Is `num_approx!` but uses `crate` as the crate path. This is useful for macros that are used in the `newnum` crate itself.
188///
189/// Converts a numeric literal into a `Num` type, generating a compile-time error if the literal is out of range for the type.
190/// In comparison to `num!`, this macro also accepts literals that can only be approximately represented by the type.
191///
192/// * the type doesn't have to implement `Num`, but it must implement `FromIntLiteral` and `FromFloatLiteral` for float literal support.
193///
194/// ### Syntax
195///
196/// `num_approx!(<literal>)` or `num_approx!(<literal>: <type>)`.
197///
198/// ### Example
199///
200/// ```
201/// use newnum::num;
202///
203/// fn example<T: Float>() -> T {
204///     num_approx!(16_777_217)
205/// }
206/// ```
207///
208/// ### Compile-Time Error
209///
210/// Because of rust const-fn limitations,
211/// the error shown when the literal is out of range is cryptic and is shown at the macro call-site.
212///
213/// Example:
214///
215///```
216/// use newnum::num;
217///
218/// fn add_alot(value: &mut impl Num) {
219///     //        |<- ERROR: evaluation of `add_alot::num_macro_fn::<u8>::{constant#0}` failed
220///     //        |
221///     *value += num_approx!(1000)
222/// }
223///
224/// fn main() {
225///     let mut i = 5u8;
226///     add_alot(&mut i);
227/// }
228/// ```
229#[proc_macro]
230pub fn internal_num_approx(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
231    num_macro_helper(
232        "approx_from_int_literal",
233        "approx_from_float_literal",
234        quote! { crate },
235        input,
236    )
237}
238
239fn num_macro_helper(
240    int_fn_ident: &str,
241    float_fn_ident: &str,
242    crate_path: TokenStream,
243    input: proc_macro::TokenStream,
244) -> proc_macro::TokenStream {
245    #[derive(Parse)]
246    struct Input {
247        literal: Lit,
248        #[prefix(Option<Token![:]> as punct)]
249        #[parse_if(punct.is_some())]
250        ty: Option<TokenStream>,
251    }
252
253    let Input { literal, ty } = parse_macro_input!(input as Input);
254
255    let (literal_ty, from_trait, from_fn) = if let Lit::Float(_) = literal {
256        (quote! { f64 }, quote! { FromFloatLiteral }, float_fn_ident)
257    } else {
258        (quote! { u128 }, quote! { FromIntLiteral }, int_fn_ident)
259    };
260
261    let from_fn = format_ident!("{from_fn}");
262    let ty = ty.unwrap_or_else(|| quote! { _});
263
264    quote! {
265        {
266            const MACRO_INPUT: #literal_ty = #literal;
267
268            {
269                fn num_macro_fn<NumMacroType: #crate_path::#from_trait>() -> NumMacroType {
270                    if const {
271                        if MACRO_INPUT < <NumMacroType as #crate_path::FromIntLiteral>::MIN_LITERAL as #literal_ty {
272                            panic!("literal out of range")
273                        }
274
275                        if MACRO_INPUT > <NumMacroType as #crate_path::FromIntLiteral>::MAX_LITERAL as #literal_ty {
276                            panic!("literal out of range")
277                        }
278
279                        true
280                    } {
281                        unsafe { <NumMacroType as #crate_path::#from_trait>::#from_fn(MACRO_INPUT) }
282                    } else {
283                        unreachable!()
284                    }
285                }
286
287                num_macro_fn::<#ty>()
288            }
289        }
290    }
291    .into()
292}