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}