use std::collections::HashMap;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::analyzer::{FunctionSignature as AnalyzerFunctionSignature, OwnershipMode};
use crate::parser::Type;
use super::function_metadata::{try_analyzer_signature_from_metadata, FunctionSignature};
use super::ModuleMetadata;
pub(in crate::metadata) fn find_project_root(start: &Path) -> Option<PathBuf> {
let mut dir = if start.is_file() {
start.parent()?
} else {
start
};
loop {
if dir.join("Cargo.toml").exists() || dir.join("wj.toml").exists() {
return Some(dir.to_path_buf());
}
dir = dir.parent()?;
}
}
pub fn meta_cache_path(source_file: &Path) -> PathBuf {
if let Some(project_root) = find_project_root(source_file) {
let src_dir = project_root.join("src");
if let Ok(relative) = source_file.strip_prefix(&src_dir) {
let mut cache_path = project_root.join(".wj-cache");
cache_path.push(relative);
let file_name = cache_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
cache_path.set_file_name(format!("{}.meta", file_name));
return cache_path;
}
if let Ok(relative) = source_file.strip_prefix(&project_root) {
let mut cache_path = project_root.join(".wj-cache");
cache_path.push(relative);
let file_name = cache_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
cache_path.set_file_name(format!("{}.meta", file_name));
return cache_path;
}
}
source_file.with_extension("wj.meta")
}
pub fn meta_cache_root(source_root: &Path) -> PathBuf {
if let Some(project_root) = find_project_root(source_root) {
project_root.join(".wj-cache")
} else {
source_root.join(".wj-cache")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrateMetadata {
pub structs: HashMap<String, HashMap<String, String>>,
pub functions: HashMap<String, FunctionSignature>,
pub version: String,
}
impl Default for CrateMetadata {
fn default() -> Self {
Self::new()
}
}
impl CrateMetadata {
pub fn new() -> Self {
CrateMetadata {
structs: HashMap::new(),
functions: HashMap::new(),
version: env!("CARGO_PKG_VERSION").to_string(),
}
}
pub fn merge_module(&mut self, module: &ModuleMetadata) {
for (struct_name, fields) in &module.structs {
self.structs
.entry(struct_name.clone())
.or_default()
.extend(fields.clone());
}
for (func_name, sig) in &module.functions {
self.functions.insert(func_name.clone(), sig.clone());
}
}
}
pub fn extract_struct_field_types(
crate_meta: &CrateMetadata,
) -> HashMap<String, HashMap<String, Type>> {
let mut result = HashMap::new();
for (struct_name, fields) in &crate_meta.structs {
let mut typed_fields = HashMap::new();
for (field_name, type_str) in fields {
if let Some(ty) = ModuleMetadata::deserialize_type(type_str) {
typed_fields.insert(field_name.clone(), ty);
}
}
if !typed_fields.is_empty() {
result.insert(struct_name.clone(), typed_fields);
}
}
result
}
pub fn load_struct_field_types_from_file(path: &Path) -> HashMap<String, HashMap<String, Type>> {
let Ok(text) = std::fs::read_to_string(path) else {
return HashMap::new();
};
let Ok(crate_meta) = serde_json::from_str::<CrateMetadata>(&text) else {
return HashMap::new();
};
extract_struct_field_types(&crate_meta)
}
pub fn load_merged_external_struct_fields(
external_paths: &HashMap<String, std::path::PathBuf>,
exclude_local: Option<&std::collections::HashSet<String>>,
) -> HashMap<String, HashMap<String, Type>> {
let mut merged: HashMap<String, HashMap<String, Type>> = HashMap::new();
for meta_path in external_paths.values() {
let fields = load_struct_field_types_from_file(meta_path);
for (struct_name, field_map) in fields {
if exclude_local.is_none_or(|locals| !locals.contains(&struct_name)) {
merged.entry(struct_name).or_default().extend(field_map);
}
}
}
merged
}
pub(in crate::metadata) fn merge_crate_metadata_file(
path: &Path,
registry: &mut crate::analyzer::SignatureRegistry,
_copy_structs: &mut Vec<String>,
_all_struct_fields: &mut HashMap<String, Vec<Vec<String>>>,
) {
let Ok(text) = std::fs::read_to_string(path) else {
return;
};
let Ok(crate_meta) = serde_json::from_str::<CrateMetadata>(&text) else {
return;
};
for (name, sig) in &crate_meta.functions {
if let Some(a_sig) = try_analyzer_signature_from_metadata(name, sig) {
registry.add_function(name.clone(), a_sig);
} else if sig.is_extern {
let param_types: Vec<Type> = sig
.params
.iter()
.filter_map(|p| ModuleMetadata::deserialize_type(p))
.collect();
let param_ownership = param_types
.iter()
.map(|_| crate::analyzer::OwnershipMode::Owned)
.collect();
let return_type = sig
.return_type
.as_ref()
.and_then(|s| ModuleMetadata::deserialize_type(s));
registry.add_function(
name.clone(),
AnalyzerFunctionSignature {
name: name.to_string(),
param_types,
param_ownership,
return_type,
return_ownership: OwnershipMode::Owned,
has_self_receiver: sig.has_self_receiver,
is_extern: true,
},
);
}
}
}