use serde::Deserialize;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
static TYPE_INFO_CACHE: OnceLock<Option<TypeInfoCache>> = OnceLock::new();
#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
#[serde(default)]
pub struct VariableTypeInfo {
pub name: String,
pub ty: String,
pub is_copy: bool,
pub is_clone: bool,
pub is_send: bool,
pub is_sync: bool,
pub is_drop: bool,
pub is_sized: bool,
pub is_future: bool,
pub is_iterator: bool,
pub is_primitive: bool,
pub is_reference: bool,
pub is_mutable_reference: bool,
pub is_raw_ptr: bool,
pub is_slice: bool,
pub is_str: bool,
pub is_closure: bool,
pub is_fn_ptr: bool,
pub is_dyn_trait: bool,
pub is_union: bool,
pub is_rc: bool,
pub is_arc: bool,
pub is_box: bool,
pub is_weak: bool,
pub is_refcell: bool,
pub is_cell: bool,
pub is_mutex: bool,
pub is_rwlock: bool,
pub is_guard: bool,
pub is_vec: bool,
pub is_string: bool,
pub is_option: bool,
pub is_result: bool,
pub is_pin: bool,
pub is_cow: bool,
pub is_once_cell: bool,
pub is_maybe_uninit: bool,
pub is_channel: bool,
pub is_extern_type: bool,
pub is_static: bool,
pub is_const: bool,
pub is_tuple_binding: bool,
pub is_mut_binding: bool,
pub is_impl_trait: bool,
pub initializer_kind: Option<String>,
pub function_name: Option<String>,
pub decl_index: Option<u32>,
}
#[derive(Debug, Deserialize)]
struct ProjectTypeInfo {
#[allow(dead_code)]
version: String,
#[allow(dead_code)]
files: HashMap<String, Vec<VariableTypeInfo>>,
#[serde(default)]
by_name: HashMap<String, Vec<VariableTypeInfo>>,
#[serde(default)]
by_function: HashMap<String, HashMap<String, Vec<VariableTypeInfo>>>,
}
pub struct TypeInfoCache {
by_name: HashMap<String, Vec<VariableTypeInfo>>,
by_function: HashMap<String, HashMap<String, Vec<VariableTypeInfo>>>,
}
impl TypeInfoCache {
fn load(project_root: &Path) -> Option<Self> {
let json_path = project_root.join(".borrowscope/type-info.json");
if !json_path.exists() {
return None;
}
let content = std::fs::read_to_string(&json_path).ok()?;
let info: ProjectTypeInfo = serde_json::from_str(&content).ok()?;
Some(Self {
by_name: info.by_name,
by_function: info.by_function,
})
}
pub fn lookup_in_function(&self, fn_name: &str, var_name: &str, decl_index: Option<u32>) -> Option<&VariableTypeInfo> {
let fn_vars = self.by_function.get(fn_name)?;
let entries = fn_vars.get(var_name)?;
if entries.len() == 1 {
return Some(&entries[0]);
}
if let Some(idx) = decl_index {
if let Some(entry) = entries.iter().find(|e| e.decl_index == Some(idx)) {
return Some(entry);
}
}
let first = entries.first()?;
if entries.iter().skip(1).all(|e| Self::same_classification(first, e)) {
Some(first)
} else {
None
}
}
pub fn lookup(&self, var_name: &str) -> Option<&VariableTypeInfo> {
let entries = self.by_name.get(var_name)?;
if entries.is_empty() {
return None;
}
if entries.len() == 1 {
return Some(&entries[0]);
}
let first = &entries[0];
if entries.iter().skip(1).all(|e| Self::same_classification(first, e)) {
Some(first)
} else {
None }
}
fn same_classification(a: &VariableTypeInfo, b: &VariableTypeInfo) -> bool {
a.is_rc == b.is_rc && a.is_arc == b.is_arc && a.is_box == b.is_box
&& a.is_refcell == b.is_refcell && a.is_cell == b.is_cell
&& a.is_reference == b.is_reference && a.is_mutable_reference == b.is_mutable_reference
&& a.is_raw_ptr == b.is_raw_ptr && a.is_mutex == b.is_mutex
&& a.is_rwlock == b.is_rwlock && a.is_guard == b.is_guard
&& a.initializer_kind == b.initializer_kind
}
}
fn find_project_root() -> Option<PathBuf> {
if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
return Some(PathBuf::from(dir));
}
let mut current = std::env::current_dir().ok()?;
loop {
if current.join("Cargo.toml").exists() {
return Some(current);
}
if !current.pop() {
return None;
}
}
}
pub fn get_type_info() -> Option<&'static TypeInfoCache> {
TYPE_INFO_CACHE
.get_or_init(|| find_project_root().and_then(|root| TypeInfoCache::load(&root)))
.as_ref()
}
pub fn lookup_in_function(fn_name: &str, var_name: &str, decl_index: Option<u32>) -> Option<&'static VariableTypeInfo> {
get_type_info()?.lookup_in_function(fn_name, var_name, decl_index)
}
pub fn lookup_by_name(var_name: &str) -> Option<&'static VariableTypeInfo> {
get_type_info()?.lookup(var_name)
}