use std::sync::Arc;
use mir_codebase::Codebase;
use salsa::{Database, Update};
use crate::db::definitions::file_definitions;
use crate::db::input::Workspace;
#[derive(Clone)]
pub struct CodebaseArc(pub Arc<Codebase>);
impl CodebaseArc {
pub fn get(&self) -> &Codebase {
&self.0
}
}
unsafe impl Update for CodebaseArc {
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
let old_ref = unsafe { &mut *old_pointer };
if Arc::ptr_eq(&old_ref.0, &new_value.0) {
false
} else {
*old_ref = new_value;
true
}
}
}
#[salsa::tracked(no_eq)]
pub fn codebase(db: &dyn Database, ws: Workspace) -> CodebaseArc {
let mut builder = mir_codebase::CodebaseBuilder::new();
mir_analyzer::stubs::load_stubs(builder.codebase());
let files = ws.files(db);
for sf in files.iter() {
builder.add((*file_definitions(db, *sf).0).clone());
}
CodebaseArc(Arc::new(builder.finalize()))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
use crate::db::analysis::AnalysisHost;
use crate::db::input::{FileId, SourceFile};
use salsa::Setter;
#[test]
fn codebase_aggregates_classes_across_files() {
let host = AnalysisHost::new();
let f1 = SourceFile::new(
host.db(),
FileId(0),
Arc::<str>::from("file:///a.php"),
Arc::<str>::from("<?php\nnamespace A;\nclass Foo {}"),
None,
);
let f2 = SourceFile::new(
host.db(),
FileId(1),
Arc::<str>::from("file:///b.php"),
Arc::<str>::from("<?php\nnamespace B;\nclass Bar {}"),
None,
);
let ws = Workspace::new(
host.db(),
Arc::from([f1, f2]),
mir_analyzer::PhpVersion::LATEST,
);
let cb = codebase(host.db(), ws);
assert!(cb.get().type_exists("A\\Foo"));
assert!(cb.get().type_exists("B\\Bar"));
}
#[test]
fn codebase_reruns_after_file_edit() {
let mut host = AnalysisHost::new();
let f1 = SourceFile::new(
host.db(),
FileId(0),
Arc::<str>::from("file:///t.php"),
Arc::<str>::from("<?php\nclass Before {}"),
None,
);
let ws = Workspace::new(host.db(), Arc::from([f1]), mir_analyzer::PhpVersion::LATEST);
let a1 = codebase(host.db(), ws);
assert!(a1.get().type_exists("Before"));
let first_ptr = Arc::as_ptr(&a1.0);
f1.set_text(host.db_mut())
.to(Arc::<str>::from("<?php\nclass After {}"));
let a2 = codebase(host.db(), ws);
assert_ne!(first_ptr, Arc::as_ptr(&a2.0), "edit should invalidate");
assert!(a2.get().type_exists("After"));
assert!(!a2.get().type_exists("Before"));
}
#[test]
fn codebase_memoizes_when_nothing_changes() {
let host = AnalysisHost::new();
let f1 = SourceFile::new(
host.db(),
FileId(0),
Arc::<str>::from("file:///t.php"),
Arc::<str>::from("<?php\nclass X {}"),
None,
);
let ws = Workspace::new(host.db(), Arc::from([f1]), mir_analyzer::PhpVersion::LATEST);
let a1 = codebase(host.db(), ws);
let a2 = codebase(host.db(), ws);
assert!(
Arc::ptr_eq(&a1.0, &a2.0),
"no input change — second call should return the memoized Arc"
);
}
}