vtid_proc/
lib.rs

1//! Volatile Type ID (Vtid) generator.
2//!
3//! # How it works
4//! 1. Each time a crate is recompiled, a counter in a lock file is incremented to create a new version
5//! 2. This version becomes the base ID for all types in that compilation unit
6//! 3. When combined with type information, it creates unique IDs that:
7//!    - Are consistent for the same type within one compilation
8//!    - Automatically version bump on recompilation
9//!    - Allow tracking type changes across hot reloads
10//!    - Maintain thread-safety through file locking
11//!
12//! The versioning ensures that if a type's definition changes and the crate is recompiled,
13//! the old and new versions of the type will have different IDs.
14
15extern 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}