use std::cell::OnceCell;
use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
#[allow(clippy::type_complexity, clippy::incompatible_msrv)]
static CALL_COUNTERS: LazyLock<Mutex<HashMap<(&'static str, u32, u32), usize>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub fn reset_call_counters() {
CALL_COUNTERS.lock().unwrap().clear();
}
fn next_call_index(file: &'static str, line: u32, column: u32) -> usize {
let mut counters = CALL_COUNTERS.lock().unwrap();
let key = (file, line, column);
let index = counters.get(&key).copied().unwrap_or(0);
counters.insert(key, index + 1);
index
}
pub struct InstanceKey {
key: OnceCell<String>,
prefix: &'static str,
file: &'static str,
line: u32,
column: u32,
index: usize,
}
impl InstanceKey {
#[track_caller]
pub fn new(prefix: &'static str) -> Self {
let loc = std::panic::Location::caller();
let index = next_call_index(loc.file(), loc.line(), loc.column());
Self {
key: OnceCell::new(),
prefix,
file: loc.file(),
line: loc.line(),
column: loc.column(),
index,
}
}
pub fn explicit(key: impl Into<String>) -> Self {
let instance = Self {
key: OnceCell::new(),
prefix: "",
file: "",
line: 0,
column: 0,
index: 0,
};
let _ = instance.key.set(key.into());
instance
}
pub fn get(&self) -> &str {
self.key.get_or_init(|| {
format!(
"{}:{}:{}:{}:{}",
self.prefix, self.file, self.line, self.column, self.index
)
})
}
pub fn derive(&self, suffix: &str) -> String {
format!("{}_{}", self.get(), suffix)
}
pub fn location(&self) -> (&'static str, u32, u32) {
(self.file, self.line, self.column)
}
pub fn index(&self) -> usize {
self.index
}
}
impl std::fmt::Debug for InstanceKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "InstanceKey({})", self.get())
}
}
impl Clone for InstanceKey {
fn clone(&self) -> Self {
Self::explicit(self.get())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unique_keys_in_loop() {
reset_call_counters();
let mut keys = Vec::new();
for _ in 0..5 {
let key = InstanceKey::new("test");
keys.push(key.get().to_string());
}
let unique: std::collections::HashSet<_> = keys.iter().collect();
assert_eq!(unique.len(), 5);
assert!(keys[0].ends_with(":0"));
assert!(keys[1].ends_with(":1"));
assert!(keys[2].ends_with(":2"));
}
#[test]
fn test_keys_stable_across_rebuilds() {
fn create_keys() -> (String, String) {
let key1 = InstanceKey::new("test").get().to_string();
let key2 = InstanceKey::new("test").get().to_string();
(key1, key2)
}
reset_call_counters();
let (key1_frame1, key2_frame1) = create_keys();
reset_call_counters();
let (key1_frame2, key2_frame2) = create_keys();
assert_eq!(key1_frame1, key1_frame2);
assert_eq!(key2_frame1, key2_frame2);
assert_ne!(key1_frame1, key2_frame1);
}
#[test]
fn test_explicit_key() {
let key = InstanceKey::explicit("my-custom-key");
assert_eq!(key.get(), "my-custom-key");
}
#[test]
fn test_derive() {
let key = InstanceKey::explicit("base");
assert_eq!(key.derive("child"), "base_child");
assert_eq!(key.derive("other"), "base_other");
}
#[test]
fn test_key_stability() {
reset_call_counters();
let key = InstanceKey::new("test");
let first = key.get().to_string();
let second = key.get().to_string();
assert_eq!(first, second);
}
#[test]
fn test_clone_preserves_key() {
reset_call_counters();
let key = InstanceKey::new("test");
let original = key.get().to_string();
let cloned = key.clone();
assert_eq!(cloned.get(), original);
}
#[test]
fn test_different_source_locations_independent() {
reset_call_counters();
fn create_dropdown() -> InstanceKey {
InstanceKey::new("dropdown")
}
fn create_button() -> InstanceKey {
InstanceKey::new("button")
}
let dropdown1 = create_dropdown();
let button1 = create_button();
let dropdown2 = create_dropdown();
assert!(dropdown1.get().contains("dropdown") && dropdown1.get().ends_with(":0"));
assert!(button1.get().contains("button") && button1.get().ends_with(":0"));
assert!(dropdown2.get().contains("dropdown") && dropdown2.get().ends_with(":1"));
}
}