use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use parking_lot::{Mutex, RwLock};
use rayon::prelude::*;
use crate::db::MirDb;
use crate::php_version::PhpVersion;
pub(crate) struct MirDbRw(MirDb);
unsafe impl Sync for MirDbRw {}
impl std::ops::Deref for MirDbRw {
type Target = MirDb;
fn deref(&self) -> &MirDb {
&self.0
}
}
impl std::ops::DerefMut for MirDbRw {
fn deref_mut(&mut self) -> &mut MirDb {
&mut self.0
}
}
pub struct SharedDb {
pub salsa: RwLock<MirDbRw>,
pub loaded_stubs: Mutex<HashSet<&'static str>>,
pub user_stubs_loaded: std::sync::atomic::AtomicBool,
}
impl SharedDb {
pub fn new() -> Self {
Self {
salsa: RwLock::new(MirDbRw(MirDb::default())),
loaded_stubs: Mutex::new(HashSet::new()),
user_stubs_loaded: std::sync::atomic::AtomicBool::new(false),
}
}
pub fn snapshot_db(&self) -> MirDb {
let guard = self.salsa.read();
(**guard).clone()
}
pub fn ingest_stub_paths(&self, paths: &[&'static str], php_version: PhpVersion) {
let needed: Vec<&'static str> = {
let loaded = self.loaded_stubs.lock();
paths
.iter()
.copied()
.filter(|p| !loaded.contains(p))
.collect()
};
if needed.is_empty() {
return;
}
let slices: Vec<(&'static str, mir_codebase::storage::StubSlice)> = needed
.par_iter()
.filter_map(|&path| {
crate::stubs::stub_content_for_path(path).map(|content| {
let slice =
crate::stubs::stub_slice_from_source(path, content, Some(php_version));
(path, slice)
})
})
.collect();
let mut guard = self.salsa.write();
let mut loaded = self.loaded_stubs.lock();
let to_ingest: Vec<&mir_codebase::storage::StubSlice> = slices
.iter()
.filter_map(|(path, slice)| {
if loaded.insert(*path) {
Some(slice)
} else {
None
}
})
.collect();
guard.ingest_stub_slices(to_ingest.iter().copied());
}
pub fn ingest_user_stubs(&self, files: &[PathBuf], dirs: &[PathBuf]) {
if files.is_empty() && dirs.is_empty() {
return;
}
let was_loaded = self
.user_stubs_loaded
.load(std::sync::atomic::Ordering::Relaxed);
if was_loaded {
return;
}
let slices = crate::stubs::user_stub_slices(files, dirs);
let mut guard = self.salsa.write();
guard.ingest_stub_slices(slices.iter());
self.user_stubs_loaded
.store(true, std::sync::atomic::Ordering::Relaxed);
}
pub fn collect_and_ingest_file(
&self,
file: Arc<str>,
source: &str,
) -> crate::db::FileDefinitions {
use mir_issues::Issue;
let arena = crate::arena::create_parse_arena(source.len());
let parsed = php_rs_parser::parse(&arena, source);
let mut all_issues: Vec<Issue> = parsed
.errors
.iter()
.map(|err| {
Issue::new(
mir_issues::IssueKind::ParseError {
message: err.to_string(),
},
mir_issues::Location {
file: file.clone(),
line: 1,
line_end: 1,
col_start: 0,
col_end: 0,
},
)
})
.collect();
let collector = crate::collector::DefinitionCollector::new_for_slice(
file.clone(),
source,
&parsed.source_map,
);
let (mut slice, collector_issues) = collector.collect_slice(&parsed.program);
all_issues.extend(collector_issues);
mir_codebase::storage::deduplicate_params_in_slice(&mut slice);
let file_defs = crate::db::FileDefinitions {
slice: Arc::new(slice),
issues: Arc::new(all_issues),
};
{
let mut guard = self.salsa.write();
guard.upsert_source_file(file.clone(), Arc::from(source));
guard.ingest_stub_slice(&file_defs.slice);
}
file_defs
}
}
impl Default for SharedDb {
fn default() -> Self {
Self::new()
}
}