1extern crate proc_macro;
16
17use std::{
18 env,
19 fs::{create_dir_all, File, OpenOptions},
20 io::{Read, Seek, SeekFrom, Write},
21 path::PathBuf,
22 time::{SystemTime, UNIX_EPOCH},
23};
24
25use fs2::FileExt;
26use proc_macro::TokenStream;
27
28struct LockGuard<'a> {
29 file: &'a mut File,
30}
31
32impl<'a> LockGuard<'a> {
33 fn new(file: &'a mut File) -> std::io::Result<Self> {
34 file.lock_exclusive()?;
35 Ok(Self { file })
36 }
37}
38
39impl<'a> Drop for LockGuard<'a> {
40 fn drop(&mut self) {
41 let _ = self.file.unlock();
42 }
43}
44
45fn get_unix_timestamp() -> u64 {
46 SystemTime::now()
47 .duration_since(UNIX_EPOCH)
48 .unwrap()
49 .as_secs()
50}
51
52fn read_and_update_counter(file: &mut File) -> std::io::Result<u64> {
53 let mut content = String::new();
54 file.read_to_string(&mut content)?;
55
56 let counter: u64 = content
57 .trim()
58 .parse()
59 .unwrap_or_else(|_| get_unix_timestamp());
60 let new_counter = counter + 1;
61
62 file.seek(SeekFrom::Start(0))?;
63 file.write_all(new_counter.to_string().as_bytes())?;
64 file.sync_all()?;
65
66 Ok(new_counter)
67}
68
69fn get_next_counter() -> u64 {
70 let lock_path = env!("VTID_PROC_MACRO_LOCK_FILE_PATH");
71
72 let path = PathBuf::from(&lock_path);
73 if let Some(parent) = path.parent() {
74 let _ = create_dir_all(parent);
75 }
76
77 let mut file = OpenOptions::new()
78 .read(true)
79 .write(true)
80 .create(true)
81 .open(&path)
82 .expect("Failed to open lock file");
83
84 let _guard = LockGuard::new(&mut file).expect("Failed to lock file");
85 read_and_update_counter(&mut *_guard.file).expect("Failed to read and update counter")
86}
87
88lazy_static::lazy_static! {
89 static ref BASE_ID: u64 = get_next_counter();
90}
91
92#[proc_macro_derive(HasVtid)]
93pub fn derive_answer_fn(item: TokenStream) -> TokenStream {
94 let mut input = syn::parse_macro_input!(item as syn::DeriveInput);
95
96 let ident = &input.ident;
97
98 let where_clause = input.generics.make_where_clause();
99 where_clause.predicates.push(syn::parse_quote!(Self: 'static));
100
101 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
102
103 let where_clause = if let Some(where_clause) = where_clause {
104 let mut where_clause = where_clause.clone();
105 where_clause.predicates.push(syn::parse_quote!(Self: 'static));
106 Some(where_clause)
107 } else {
108 let where_clause: syn::WhereClause = syn::parse_quote!(where Self: 'static);
109 Some(where_clause)
110 };
111
112 let base_id = *BASE_ID;
113
114 let tokens = quote::quote! {
115 impl #impl_generics ::vtid::HasVtid for #ident #ty_generics #where_clause {
116 fn vtid() -> ::vtid::Vtid {
117 ::vtid::private::vtid::<Self>(#base_id)
118 }
119 }
120 };
121
122 tokens.into()
123}