deranged_macros/
lib.rs

1//! Macros for the `deranged` crate.
2
3#![doc(test(attr(deny(warnings))))]
4
5mod integer;
6
7use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
8
9use crate::integer::Integer;
10
11/// Unwrap a `Result` or return the error directly.
12macro_rules! unwrap_or_return {
13    ($e:expr) => {
14        match $e {
15            Ok(value) => value,
16            Err(err) => return err,
17        }
18    };
19}
20
21/// Attach a [`Span`] to a [`TokenTree`].
22fn with_span(mut tree: TokenTree, span: Span) -> TokenTree {
23    tree.set_span(span);
24    tree
25}
26
27/// The minimum and maximum values for a primitive integer.
28#[derive(Debug)]
29enum MinMax {
30    #[allow(clippy::missing_docs_in_private_items)]
31    U8(u8, u8),
32    #[allow(clippy::missing_docs_in_private_items)]
33    U16(u16, u16),
34    #[allow(clippy::missing_docs_in_private_items)]
35    U32(u32, u32),
36    #[allow(clippy::missing_docs_in_private_items)]
37    U64(u64, u64),
38    #[allow(clippy::missing_docs_in_private_items)]
39    U128(u128, u128),
40    #[allow(clippy::missing_docs_in_private_items)]
41    I8(i8, i8),
42    #[allow(clippy::missing_docs_in_private_items)]
43    I16(i16, i16),
44    #[allow(clippy::missing_docs_in_private_items)]
45    I32(i32, i32),
46    #[allow(clippy::missing_docs_in_private_items)]
47    I64(i64, i64),
48    #[allow(clippy::missing_docs_in_private_items)]
49    I128(i128, i128),
50}
51
52/// The spans for the minimum and maximum values in [`MinMax`].
53#[derive(Debug)]
54struct MinMaxSpan(Span, Span);
55
56/// A min-max spanned pair of primitive integers.
57#[derive(Debug)]
58struct Type<const OPTIONAL: bool>(MinMax, MinMaxSpan);
59
60impl<const OPTIONAL: bool> Type<OPTIONAL> {
61    /// Obtain the primitive integer type from a minimum-maximum pair.
62    fn from_min_max(min: &Integer, max: &Integer) -> Result<Self, TokenStream> {
63        let spans = MinMaxSpan(min.span, max.span);
64
65        if let (Some(min_value), Some(max_value)) = (min.to_unsigned(), max.to_unsigned()) {
66            Ok(Self(MinMax::U8(min_value, max_value), spans))
67        } else if let (Some(min_value), Some(max_value)) = (min.to_unsigned(), max.to_unsigned()) {
68            Ok(Self(MinMax::U16(min_value, max_value), spans))
69        } else if let (Some(min_value), Some(max_value)) = (min.to_unsigned(), max.to_unsigned()) {
70            Ok(Self(MinMax::U32(min_value, max_value), spans))
71        } else if let (Some(min_value), Some(max_value)) = (min.to_unsigned(), max.to_unsigned()) {
72            Ok(Self(MinMax::U64(min_value, max_value), spans))
73        } else if let (Some(min_value), Some(max_value)) = (min.to_unsigned(), max.to_unsigned()) {
74            Ok(Self(MinMax::U128(min_value, max_value), spans))
75        } else if let (Some(min_value), Some(max_value)) = (min.to_signed(), max.to_signed()) {
76            Ok(Self(MinMax::I8(min_value, max_value), spans))
77        } else if let (Some(min_value), Some(max_value)) = (min.to_signed(), max.to_signed()) {
78            Ok(Self(MinMax::I16(min_value, max_value), spans))
79        } else if let (Some(min_value), Some(max_value)) = (min.to_signed(), max.to_signed()) {
80            Ok(Self(MinMax::I32(min_value, max_value), spans))
81        } else if let (Some(min_value), Some(max_value)) = (min.to_signed(), max.to_signed()) {
82            Ok(Self(MinMax::I64(min_value, max_value), spans))
83        } else if let (Some(min_value), Some(max_value)) = (min.to_signed(), max.to_signed()) {
84            Ok(Self(MinMax::I128(min_value, max_value), spans))
85        } else {
86            Err(compile_error(
87                "minimum and maximum values cannot be represented by any one primitive integer",
88                None,
89            ))
90        }
91    }
92
93    /// Convert a type into a `TokenStream`.
94    fn into_tokens(self) -> TokenStream {
95        #[allow(clippy::missing_docs_in_private_items)]
96        macro_rules! maybe_optional {
97            ($name:literal) => {
98                if OPTIONAL {
99                    concat!("Option", $name)
100                } else {
101                    $name
102                }
103            };
104        }
105
106        let Self(min_max, span) = self;
107        let MinMaxSpan(min_span, max_span) = span;
108
109        let (type_name, min_token, max_token) = match min_max {
110            MinMax::U8(min, max) => (
111                maybe_optional!("RangedU8"),
112                Literal::u8_unsuffixed(min),
113                Literal::u8_unsuffixed(max),
114            ),
115            MinMax::U16(min, max) => (
116                maybe_optional!("RangedU16"),
117                Literal::u16_unsuffixed(min),
118                Literal::u16_unsuffixed(max),
119            ),
120            MinMax::U32(min, max) => (
121                maybe_optional!("RangedU32"),
122                Literal::u32_unsuffixed(min),
123                Literal::u32_unsuffixed(max),
124            ),
125            MinMax::U64(min, max) => (
126                maybe_optional!("RangedU64"),
127                Literal::u64_unsuffixed(min),
128                Literal::u64_unsuffixed(max),
129            ),
130            MinMax::U128(min, max) => (
131                maybe_optional!("RangedU128"),
132                Literal::u128_unsuffixed(min),
133                Literal::u128_unsuffixed(max),
134            ),
135            MinMax::I8(min, max) => (
136                maybe_optional!("RangedI8"),
137                Literal::i8_unsuffixed(min),
138                Literal::i8_unsuffixed(max),
139            ),
140            MinMax::I16(min, max) => (
141                maybe_optional!("RangedI16"),
142                Literal::i16_unsuffixed(min),
143                Literal::i16_unsuffixed(max),
144            ),
145            MinMax::I32(min, max) => (
146                maybe_optional!("RangedI32"),
147                Literal::i32_unsuffixed(min),
148                Literal::i32_unsuffixed(max),
149            ),
150            MinMax::I64(min, max) => (
151                maybe_optional!("RangedI64"),
152                Literal::i64_unsuffixed(min),
153                Literal::i64_unsuffixed(max),
154            ),
155            MinMax::I128(min, max) => (
156                maybe_optional!("RangedI128"),
157                Literal::i128_unsuffixed(min),
158                Literal::i128_unsuffixed(max),
159            ),
160        };
161
162        TokenStream::from_iter([
163            TokenTree::Punct(Punct::new(':', Spacing::Joint)),
164            TokenTree::Punct(Punct::new(':', Spacing::Alone)),
165            TokenTree::Ident(Ident::new("deranged", Span::mixed_site())),
166            TokenTree::Punct(Punct::new(':', Spacing::Joint)),
167            TokenTree::Punct(Punct::new(':', Spacing::Alone)),
168            TokenTree::Ident(Ident::new(type_name, Span::call_site())),
169            TokenTree::Punct(Punct::new(':', Spacing::Joint)),
170            TokenTree::Punct(Punct::new(':', Spacing::Alone)),
171            TokenTree::Punct(Punct::new('<', Spacing::Alone)),
172            with_span(TokenTree::Literal(min_token), min_span),
173            TokenTree::Punct(Punct::new(',', Spacing::Alone)),
174            with_span(TokenTree::Literal(max_token), max_span),
175            TokenTree::Punct(Punct::new('>', Spacing::Alone)),
176        ])
177    }
178}
179
180/// Construct a compilation error with the provided message.
181fn compile_error(message: &str, span: Option<(Span, Span)>) -> TokenStream {
182    let span_start = span.map_or_else(Span::call_site, |span| span.0);
183    let span_end = span.map_or(span_start, |span| span.1);
184
185    TokenStream::from_iter([
186        with_span(TokenTree::from(Punct::new(':', Spacing::Joint)), span_start),
187        with_span(TokenTree::from(Punct::new(':', Spacing::Alone)), span_start),
188        TokenTree::from(Ident::new("core", span_start)),
189        with_span(TokenTree::from(Punct::new(':', Spacing::Joint)), span_start),
190        with_span(TokenTree::from(Punct::new(':', Spacing::Alone)), span_start),
191        with_span(
192            TokenTree::from(Ident::new("compile_error", Span::mixed_site())),
193            span_start,
194        ),
195        with_span(TokenTree::from(Punct::new('!', Spacing::Alone)), span_start),
196        with_span(
197            TokenTree::from(Group::new(
198                Delimiter::Parenthesis,
199                TokenStream::from(TokenTree::Literal(Literal::string(message))),
200            )),
201            span_end,
202        ),
203    ])
204}
205
206/// Consume a comma, returning a `TokenStream` describing the error upon failure.
207fn parse_comma(iter: &mut impl Iterator<Item = TokenTree>) -> Result<(), TokenStream> {
208    match iter.next() {
209        Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => Ok(()),
210        Some(TokenTree::Punct(punct)) => {
211            let first_span = punct.span();
212            let last_span = iter
213                .take_while(|token| matches!(token, TokenTree::Punct(_)))
214                .last()
215                .map_or(first_span, |token| token.span());
216            Err(compile_error(
217                "minimum and maximum value must be separated by a comma",
218                Some((first_span, last_span)),
219            ))
220        }
221        Some(token) => Err(compile_error(
222            "minimum and maximum value must be separated by a comma",
223            Some((token.span(), token.span())),
224        )),
225        None => Err(compile_error("expected maximum value", None)),
226    }
227}
228
229#[allow(missing_docs)] // documented in re-export in `deranged`
230#[proc_macro]
231pub fn int(input: TokenStream) -> TokenStream {
232    let mut iter = input.into_iter();
233
234    let min = unwrap_or_return!(Integer::try_from_tokens(&mut iter, "minimum value"));
235    unwrap_or_return!(parse_comma(&mut iter));
236    let max = unwrap_or_return!(Integer::try_from_tokens(&mut iter, "maximum value"));
237    unwrap_or_return!(Type::<false>::from_min_max(&min, &max)).into_tokens()
238}
239
240#[allow(missing_docs)] // documented in re-export in `deranged`
241#[proc_macro]
242pub fn opt_int(input: TokenStream) -> TokenStream {
243    let mut iter = input.into_iter();
244
245    let min = unwrap_or_return!(Integer::try_from_tokens(&mut iter, "minimum value"));
246    unwrap_or_return!(parse_comma(&mut iter));
247    let max = unwrap_or_return!(Integer::try_from_tokens(&mut iter, "maximum value"));
248    unwrap_or_return!(Type::<true>::from_min_max(&min, &max)).into_tokens()
249}