1use darling::FromMeta;
2use proc_macro::TokenStream;
3use quote::{format_ident, quote, ToTokens};
4use std::{i16, i32, i64, i8, u16, u32, u64, u8, usize};
5use syn::{
6 parse_macro_input, spanned::Spanned, AttributeArgs, Error, Expr, ItemConst, Type,
7 Type::Reference,
8};
9
10type TokenStream2 = proc_macro2::TokenStream;
11
12#[derive(Debug, FromMeta)]
14struct Metadata<T>
15where
16 T: FromMeta,
17{
18 #[darling(default)]
19 min: Option<T>,
20 #[darling(default)]
21 max: Option<T>,
22 #[darling(default)]
23 step: Option<T>,
24}
25
26impl<T: FromMeta> Metadata<T> {
27 pub fn from_attributes(args: AttributeArgs) -> Result<Self, TokenStream> {
28 match Metadata::from_list(&args) {
29 Ok(v) => Ok(v),
30 Err(e) => Err(TokenStream::from(e.write_errors())),
31 }
32 }
33}
34
35fn field_init<T>(
37 field_type: &str,
38 ty: &Type,
39 metadata: Metadata<T>,
40 default_value: Expr,
41 default_min: T,
42 default_max: T,
43 default_step: T,
44) -> Result<TokenStream2, TokenStream>
45where
46 T: FromMeta + ToTokens,
47{
48 let min = metadata.min.unwrap_or(default_min);
49 let max = metadata.max.unwrap_or(default_max);
50 let step = metadata.step.unwrap_or(default_step);
51
52 Ok(match field_type {
53 "f32" => quote! {
54 const_tweaker::Field::F32 {
55 value: #default_value as f32,
56 min: #min,
57 max: #max,
58 step: #step,
59
60 module: module_path!().to_string(),
61 file: file!().to_string(),
62 line: line!(),
63 }
64 },
65 "f64" => quote! {
66 const_tweaker::Field::F64 {
67 value: #default_value,
68 min: #min,
69 max: #max,
70 step: #step,
71
72 module: module_path!().to_string(),
73 file: file!().to_string(),
74 line: line!(),
75 }
76 },
77 "i8" => quote! {
78 const_tweaker::Field::I8 {
79 value: #default_value,
80 min: #min,
81 max: #max,
82 step: #step,
83
84 module: module_path!().to_string(),
85 file: file!().to_string(),
86 line: line!(),
87 }
88 },
89 "u8" => quote! {
90 const_tweaker::Field::U8 {
91 value: #default_value,
92 min: #min,
93 max: #max,
94 step: #step,
95
96 module: module_path!().to_string(),
97 file: file!().to_string(),
98 line: line!(),
99 }
100 },
101 "i16" => quote! {
102 const_tweaker::Field::I16 {
103 value: #default_value,
104 min: #min,
105 max: #max,
106 step: #step,
107
108 module: module_path!().to_string(),
109 file: file!().to_string(),
110 line: line!(),
111 }
112 },
113 "u16" => quote! {
114 const_tweaker::Field::U16 {
115 value: #default_value,
116 min: #min,
117 max: #max,
118 step: #step,
119
120 module: module_path!().to_string(),
121 file: file!().to_string(),
122 line: line!(),
123 }
124 },
125 "i32" => quote! {
126 const_tweaker::Field::I32 {
127 value: #default_value,
128 min: #min,
129 max: #max,
130 step: #step,
131
132 module: module_path!().to_string(),
133 file: file!().to_string(),
134 line: line!(),
135 }
136 },
137 "u32" => quote! {
138 const_tweaker::Field::U32 {
139 value: #default_value,
140 min: #min,
141 max: #max,
142 step: #step,
143
144 module: module_path!().to_string(),
145 file: file!().to_string(),
146 line: line!(),
147 }
148 },
149 "i64" => quote! {
150 const_tweaker::Field::I64 {
151 value: #default_value,
152 min: #min,
153 max: #max,
154 step: #step,
155
156 module: module_path!().to_string(),
157 file: file!().to_string(),
158 line: line!(),
159 }
160 },
161 "u64" => quote! {
162 const_tweaker::Field::U64 {
163 value: #default_value,
164 min: #min,
165 max: #max,
166 step: #step,
167
168 module: module_path!().to_string(),
169 file: file!().to_string(),
170 line: line!(),
171 }
172 },
173 "usize" => quote! {
174 const_tweaker::Field::Usize {
175 value: #default_value,
176 min: #min,
177 max: #max,
178 step: #step,
179
180 module: module_path!().to_string(),
181 file: file!().to_string(),
182 line: line!(),
183 }
184 },
185 "bool" => quote! {
186 const_tweaker::Field::Bool {
187 value: #default_value,
188
189 module: module_path!().to_string(),
190 file: file!().to_string(),
191 line: line!(),
192 }
193 },
194 "str" => quote! {
195 const_tweaker::Field::String {
196 value: #default_value.to_string(),
197
198 module: module_path!().to_string(),
199 file: file!().to_string(),
200 line: line!(),
201 }
202 },
203 _ => {
204 return mismatching_type_error(&ty);
205 }
206 })
207}
208
209fn field_name(field_type: &str, ty: &Type) -> Result<TokenStream2, TokenStream> {
211 match field_type {
212 "f32" => Ok(quote! { const_tweaker::Field::F32 }),
213 "f64" => Ok(quote! { const_tweaker::Field::F64 }),
214 "i8" => Ok(quote! { const_tweaker::Field::I8 }),
215 "u8" => Ok(quote! { const_tweaker::Field::U8 }),
216 "i16" => Ok(quote! { const_tweaker::Field::I16 }),
217 "u16" => Ok(quote! { const_tweaker::Field::U16 }),
218 "i32" => Ok(quote! { const_tweaker::Field::I32 }),
219 "u32" => Ok(quote! { const_tweaker::Field::U32 }),
220 "i64" => Ok(quote! { const_tweaker::Field::I64 }),
221 "u64" => Ok(quote! { const_tweaker::Field::U64 }),
222 "usize" => Ok(quote! { const_tweaker::Field::Usize }),
223 "bool" => Ok(quote! { const_tweaker::Field::Bool }),
224 "str" => Ok(quote! { const_tweaker::Field::String }),
225 _ => mismatching_type_error(&ty),
226 }
227}
228
229fn field_type(ty: &Type) -> Result<String, TokenStream> {
231 if let Type::Path(type_path) = &*ty {
232 match type_path.path.get_ident() {
233 Some(type_ident) => Ok(type_ident.to_string()),
234 None => mismatching_type_error(&ty),
235 }
236 } else {
237 mismatching_type_error(&ty)
238 }
239}
240
241fn mismatching_type_error<T>(ty: &Type) -> Result<T, TokenStream> {
243 Err(TokenStream::from(
244 Error::new(
245 ty.span(),
246 "expected bool, &str, f32, f64, i8, u8, i16, u16, i32, u32, i64, u64, i128, u128 or usize, other types are not supported in const_tweaker (yet)",
247 )
248 .to_compile_error(),
249 ))
250}
251
252fn tweak_impl(args: AttributeArgs, input: ItemConst) -> Result<TokenStream, TokenStream> {
254 let name = input.ident;
255 let init_name = format_ident!("{}_init", name);
256 let ty = if let Reference(type_ref) = *input.ty {
257 type_ref.elem
258 } else {
259 input.ty
260 };
261 let field_type = field_type(&*ty)?;
262 let field_name = field_name(&field_type, &*ty)?;
263 let field_init = match &*field_type {
264 "f32" => field_init::<f32>(
265 &field_type,
266 &*ty,
267 Metadata::from_attributes(args)?,
268 *input.expr,
269 0.0,
270 1.0,
271 0.001,
272 )?,
273 "f64" => field_init::<f64>(
274 &field_type,
275 &*ty,
276 Metadata::from_attributes(args)?,
277 *input.expr,
278 0.0,
279 1.0,
280 0.001,
281 )?,
282 "i8" => field_init::<i8>(
283 &field_type,
284 &*ty,
285 Metadata::from_attributes(args)?,
286 *input.expr,
287 i8::MIN,
288 i8::MAX,
289 1,
290 )?,
291 "u8" => field_init::<u8>(
292 &field_type,
293 &*ty,
294 Metadata::from_attributes(args)?,
295 *input.expr,
296 u8::MIN,
297 u8::MAX,
298 1,
299 )?,
300 "i16" => field_init::<i16>(
301 &field_type,
302 &*ty,
303 Metadata::from_attributes(args)?,
304 *input.expr,
305 i16::MIN,
306 i16::MAX,
307 1,
308 )?,
309 "u16" => field_init::<u16>(
310 &field_type,
311 &*ty,
312 Metadata::from_attributes(args)?,
313 *input.expr,
314 u16::MIN,
315 u16::MAX,
316 1,
317 )?,
318 "i32" => field_init::<i32>(
319 &field_type,
320 &*ty,
321 Metadata::from_attributes(args)?,
322 *input.expr,
323 i32::MIN,
324 i32::MAX,
325 1,
326 )?,
327 "u32" => field_init::<u32>(
328 &field_type,
329 &*ty,
330 Metadata::from_attributes(args)?,
331 *input.expr,
332 u32::MIN,
333 u32::MAX,
334 1,
335 )?,
336 "i64" => field_init::<i64>(
337 &field_type,
338 &*ty,
339 Metadata::from_attributes(args)?,
340 *input.expr,
341 i64::MIN,
342 i64::MAX,
343 1,
344 )?,
345 "u64" => field_init::<u64>(
346 &field_type,
347 &*ty,
348 Metadata::from_attributes(args)?,
349 *input.expr,
350 u64::MIN,
351 u64::MAX,
352 1,
353 )?,
354 "usize" => field_init::<usize>(
355 &field_type,
356 &*ty,
357 Metadata::from_attributes(args)?,
358 *input.expr,
359 usize::MIN,
360 usize::MAX,
361 1,
362 )?,
363 "bool" => field_init(
364 &field_type,
365 &*ty,
366 Metadata::from_attributes(args)?,
367 *input.expr,
368 0,
369 0,
370 0,
371 )?,
372 "str" => field_init(
373 &field_type,
374 &*ty,
375 Metadata::from_attributes(args)?,
376 *input.expr,
377 0,
378 0,
379 0,
380 )?,
381 _ => {
382 return mismatching_type_error(&ty);
383 }
384 };
385
386 let type_impls = if field_type == "str" {
387 quote! {
388 impl std::convert::From<#name> for &#ty {
389 fn from(original: #name) -> &'static #ty {
390 original.get()
391 }
392 }
393 }
394 } else {
395 quote! {
396 impl std::convert::From<#name> for #ty {
397 fn from(original: #name) -> #ty {
398 *original.get()
399 }
400 }
401 }
402 };
403
404 let result = quote! {
405 #[allow(non_camel_case_types)]
406 #[doc(hidden)]
407 #[derive(Copy, Clone)]
408 pub struct #name {
409 __private_field: ()
410 }
411
412 impl #name {
413 pub fn get(&self) -> &'static #ty {
414 match const_tweaker::DATA.get(concat!(module_path!(), "::", stringify!(#name))).expect("Value should have been added already").value() {
416 #field_name { ref value, .. } => unsafe {
417 std::mem::transmute::<&#ty, &'static #ty>(value as &#ty)
420 },
421 _ => panic!("Type mismatch, this probably means there's a duplicate value in the map, please report an issue")
422 }
423 }
424 }
425
426 impl std::ops::Deref for #name {
428 type Target = #ty;
429
430 fn deref(&self) -> &'static #ty {
431 self.get()
432 }
433 }
434
435 impl std::fmt::Debug for #name {
436 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
437 write!(f, "{:?}", self.get())
438 }
439 }
440
441 impl std::fmt::Display for #name {
442 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
443 write!(f, "{:?}", self.get())
444 }
445 }
446
447 #type_impls
448
449 static #name: #name = #name { __private_field: () };
451
452 #[allow(non_snake_case)]
453 #[const_tweaker::ctor]
454 fn #init_name() {
455 const_tweaker::DATA.insert(concat!(module_path!(), "::", stringify!(#name)), #field_init);
457 }
458 };
459
460 Ok(result.into())
461}
462
463#[proc_macro_attribute]
465pub fn tweak(args: TokenStream, input: TokenStream) -> TokenStream {
466 let args = parse_macro_input!(args as AttributeArgs);
467 let input = parse_macro_input!(input as ItemConst);
468
469 match tweak_impl(args, input) {
470 Ok(result) => result,
471 Err(err) => err,
472 }
473}