1#![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
11macro_rules! unwrap_or_return {
13 ($e:expr) => {
14 match $e {
15 Ok(value) => value,
16 Err(err) => return err,
17 }
18 };
19}
20
21fn with_span(mut tree: TokenTree, span: Span) -> TokenTree {
23 tree.set_span(span);
24 tree
25}
26
27#[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#[derive(Debug)]
54struct MinMaxSpan(Span, Span);
55
56#[derive(Debug)]
58struct Type<const OPTIONAL: bool>(MinMax, MinMaxSpan);
59
60impl<const OPTIONAL: bool> Type<OPTIONAL> {
61 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 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
180fn 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
206fn 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)] #[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)] #[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}