autosized_num/
lib.rs

1//! # AutoSized Int Macros
2//!
3//! This crate provides a set of procedural macros that automatically select
4//! the smallest integer type capable of representing a given literal.
5//!
6//! - `auto_sized_unsigned!` / `auto_sized_unsigned_val!`  
7//!   → Choose among unsigned integers (`u8`, `u16`, `u32`, `u64`, `u128`).
8//!
9//! - `auto_sized_signed!` / `auto_sized_signed_val!`  
10//!   → Choose among signed integers (`i8`, `i16`, `i32`, `i64`, `i128`).
11//!
12//! - `auto_sized_int!` / `auto_sized_int_val!`  
13//!   → If the literal is negative, a signed type is chosen.  
14//!   If the literal is non‑negative, an unsigned type is chosen.  
15//!   **The accepted input range is the full `i128` domain (not `u128`).**
16//!
17//! ## Type vs. Value Macros
18//! - `*_unsigned!`, `*_signed!`, `*_int!` → expand to a **type**.
19//! - `*_val` variants → expand to a **value** (with an explicit `as` cast).
20//!
21//! ## Examples
22//! ```rust
23//! use autosized_num::*;
24//!
25//! // Type macros
26//! type T1 = auto_sized_unsigned!(300); // expands to u16
27//! type T2 = auto_sized_signed!(-200);  // expands to i16
28//! type T3 = auto_sized_int!(10);       // expands to u8
29//! type T4 = auto_sized_int!(-10);      // expands to i8
30//!
31//! // Value macros
32//! let a = auto_sized_unsigned_val!(300); // 300u16
33//! let b = auto_sized_signed_val!(-200);  // -200i16
34//! let c = auto_sized_int_val!(10);       // 10u8
35//! let d = auto_sized_int_val!(-10);      // -10i8
36//! ```
37//!
38//! ## Intended Use Cases
39//! - Binary parsing or serialization where minimal integer widths matter.
40//! - Defining constants or generic parameters with the smallest fitting type.
41//! - Compile‑time tests ensuring literals map to the expected integer type.
42//!
43//! ## Notes
44//! - `auto_sized_int!` and `auto_sized_int_val!` accept the full `i128` range.
45//! - Non‑integer inputs will trigger a `compile_error!`.
46
47use proc_macro::TokenStream;
48use quote::quote;
49use syn::{LitInt, parse_macro_input};
50
51/// Returns the smallest unsigned integer type (`u8`, `u16`, `u32`, `u64`, or `u128`)
52/// that can represent the given literal.
53///
54/// # Examples
55/// ```
56/// use autosized_num::auto_sized_unsigned;
57///
58/// type T = auto_sized_unsigned!(300);
59/// // expands to: type T = u16;
60/// ```
61#[proc_macro]
62pub fn auto_sized_unsigned(input: TokenStream) -> TokenStream {
63    let lit = parse_macro_input!(input as LitInt);
64    let value = match lit.base10_parse::<u128>() {
65        Ok(v) => v,
66        Err(_) => {
67            return quote! {
68                compile_error!("auto_sized_unsign! only accepts integer literals");
69            }
70            .into();
71        }
72    };
73
74    pick_unsigned_type(value).into()
75}
76
77/// Returns the given literal as a value, cast to the smallest unsigned integer type
78/// that can represent it.
79///
80/// # Examples
81/// ```
82/// use autosized_num::auto_sized_unsigned_val;
83///
84/// let x = auto_sized_unsigned_val!(300);
85/// // expands to: 300 as u16
86/// ```
87#[proc_macro]
88pub fn auto_sized_unsigned_val(input: TokenStream) -> TokenStream {
89    let lit = parse_macro_input!(input as LitInt);
90    let value = match lit.base10_parse::<u128>() {
91        Ok(v) => v,
92        Err(_) => {
93            return quote! {
94                compile_error!("auto_sized_unsign_val! only accepts integer literals");
95            }
96            .into();
97        }
98    };
99
100    let ty = pick_unsigned_type(value);
101
102    quote! { #value as #ty }.into()
103}
104
105/// Returns the smallest signed integer type (`i8`, `i16`, `i32`, `i64`, or `i128`)
106/// that can represent the given literal.
107///
108/// # Examples
109/// ```
110/// use autosized_num::auto_sized_signed;
111///
112/// type T = auto_sized_signed!(-200);
113/// // expands to: type T = i16;
114/// ```
115#[proc_macro]
116pub fn auto_sized_signed(input: TokenStream) -> TokenStream {
117    let lit = parse_macro_input!(input as LitInt);
118    let value = match lit.base10_parse::<i128>() {
119        Ok(v) => v,
120        Err(_) => {
121            return quote! {
122                compile_error!("auto_sized_sign! only accepts integer literals");
123            }
124            .into();
125        }
126    };
127
128    pick_signed_type(value).into()
129}
130
131/// Returns the given literal as a value, cast to the smallest signed integer type
132/// that can represent it.
133///
134/// # Examples
135/// ```
136/// use autosized_num::auto_sized_signed_val;
137///
138/// let y = auto_sized_signed_val!(-200);
139/// // expands to: -200 as i16
140/// ```
141#[proc_macro]
142pub fn auto_sized_signed_val(input: TokenStream) -> TokenStream {
143    let lit = parse_macro_input!(input as LitInt);
144    let value = match lit.base10_parse::<i128>() {
145        Ok(v) => v,
146        Err(_) => {
147            return quote! {
148                compile_error!("auto_sized_sign_val! only accepts integer literals");
149            }
150            .into();
151        }
152    };
153
154    let ty = pick_signed_type(value);
155
156    quote! { #value as #ty }.into()
157}
158
159/// Returns the smallest integer type (signed or unsigned) that can represent the given literal.
160/// - If the literal is negative, a signed type is chosen.
161/// - If the literal is non-negative, an unsigned type is chosen.
162/// - The accepted range of input is the full `i128` range (not `u128`).
163///
164/// # Examples
165/// ```
166/// use autosized_num::auto_sized_int;
167///
168/// type T1 = auto_sized_int!(10);   // expands to u8
169/// type T2 = auto_sized_int!(-10);  // expands to i8
170/// type T3 = auto_sized_int!(12345678901234567890); // expands to u64/u128 depending on value
171/// ```
172#[proc_macro]
173pub fn auto_sized_int(input: TokenStream) -> TokenStream {
174    let lit = parse_macro_input!(input as LitInt);
175    let value = match lit.base10_parse::<i128>() {
176        Ok(v) => v,
177        Err(_) => {
178            return quote! {
179                compile_error!("auto_sized_int! only accepts integer literals");
180            }
181            .into();
182        }
183    };
184
185    if value < 0 {
186        pick_signed_type(value)
187    } else {
188        pick_unsigned_type(value as u128)
189    }
190    .into()
191}
192
193/// Returns the given literal as a value, cast to the smallest integer type
194/// (signed or unsigned) that can represent it.
195/// - If the literal is negative, a signed type is chosen.
196/// - If the literal is non-negative, an unsigned type is chosen.
197/// - The accepted range of input is the full `i128` range (not `u128`).
198///
199/// # Examples
200/// ```
201/// use autosized_num::auto_sized_int_val;
202///
203/// let a = auto_sized_int_val!(10);   // expands to 10 as u8
204/// let b = auto_sized_int_val!(-10);  // expands to -10 as i8
205/// let c = auto_sized_int_val!(12345678901234567890); // expands to value as u64/u128
206/// ```
207#[proc_macro]
208pub fn auto_sized_int_val(input: TokenStream) -> TokenStream {
209    let lit = parse_macro_input!(input as LitInt);
210    let value = match lit.base10_parse::<i128>() {
211        Ok(v) => v,
212        Err(_) => {
213            return quote! {
214                compile_error!("auto_sized_int_val! only accepts integer literals");
215            }
216            .into();
217        }
218    };
219
220    let ty = if value < 0 {
221        pick_signed_type(value)
222    } else {
223        pick_unsigned_type(value as u128)
224    };
225
226    quote! { #value as #ty }.into()
227}
228
229fn pick_unsigned_type(value: u128) -> proc_macro2::TokenStream {
230    if value <= u8::MAX as u128 {
231        quote! { u8 }
232    } else if value <= u16::MAX as u128 {
233        quote! { u16 }
234    } else if value <= u32::MAX as u128 {
235        quote! { u32 }
236    } else if value <= u64::MAX as u128 {
237        quote! { u64 }
238    } else {
239        quote! { u128 }
240    }
241}
242
243fn pick_signed_type(value: i128) -> proc_macro2::TokenStream {
244    if value >= i8::MIN as i128 && value <= i8::MAX as i128 {
245        quote! { i8 }
246    } else if value >= i16::MIN as i128 && value <= i16::MAX as i128 {
247        quote! { i16 }
248    } else if value >= i32::MIN as i128 && value <= i32::MAX as i128 {
249        quote! { i32 }
250    } else if value >= i64::MIN as i128 && value <= i64::MAX as i128 {
251        quote! { i64 }
252    } else {
253        quote! { i128 }
254    }
255}