1use std::sync::Arc;
13
14use mir_codebase::Codebase;
15use salsa::{Database, Update};
16
17use crate::db::definitions::file_definitions;
18use crate::db::input::Workspace;
19
20#[derive(Clone)]
24pub struct CodebaseArc(pub Arc<Codebase>);
25
26impl CodebaseArc {
27 pub fn get(&self) -> &Codebase {
28 &self.0
29 }
30}
31
32unsafe impl Update for CodebaseArc {
34 unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
35 let old_ref = unsafe { &mut *old_pointer };
36 if Arc::ptr_eq(&old_ref.0, &new_value.0) {
37 false
38 } else {
39 *old_ref = new_value;
40 true
41 }
42 }
43}
44
45#[salsa::tracked(no_eq)]
54pub fn codebase(db: &dyn Database, ws: Workspace) -> CodebaseArc {
55 let mut builder = mir_codebase::CodebaseBuilder::new();
56 mir_analyzer::stubs::load_stubs(builder.codebase());
57 let files = ws.files(db);
58 for sf in files.iter() {
59 builder.add((*file_definitions(db, *sf).0).clone());
60 }
61 CodebaseArc(Arc::new(builder.finalize()))
64}
65
66#[cfg(test)]
67mod tests {
68 use std::sync::Arc;
69
70 use super::*;
71 use crate::db::analysis::AnalysisHost;
72 use crate::db::input::{FileId, SourceFile};
73 use salsa::Setter;
74
75 #[test]
76 fn codebase_aggregates_classes_across_files() {
77 let host = AnalysisHost::new();
78 let f1 = SourceFile::new(
79 host.db(),
80 FileId(0),
81 Arc::<str>::from("file:///a.php"),
82 Arc::<str>::from("<?php\nnamespace A;\nclass Foo {}"),
83 None,
84 );
85 let f2 = SourceFile::new(
86 host.db(),
87 FileId(1),
88 Arc::<str>::from("file:///b.php"),
89 Arc::<str>::from("<?php\nnamespace B;\nclass Bar {}"),
90 None,
91 );
92 let ws = Workspace::new(
93 host.db(),
94 Arc::from([f1, f2]),
95 mir_analyzer::PhpVersion::LATEST,
96 );
97
98 let cb = codebase(host.db(), ws);
99 assert!(cb.get().type_exists("A\\Foo"));
100 assert!(cb.get().type_exists("B\\Bar"));
101 }
102
103 #[test]
104 fn codebase_reruns_after_file_edit() {
105 let mut host = AnalysisHost::new();
106 let f1 = SourceFile::new(
107 host.db(),
108 FileId(0),
109 Arc::<str>::from("file:///t.php"),
110 Arc::<str>::from("<?php\nclass Before {}"),
111 None,
112 );
113 let ws = Workspace::new(host.db(), Arc::from([f1]), mir_analyzer::PhpVersion::LATEST);
114
115 let a1 = codebase(host.db(), ws);
116 assert!(a1.get().type_exists("Before"));
117 let first_ptr = Arc::as_ptr(&a1.0);
118
119 f1.set_text(host.db_mut())
120 .to(Arc::<str>::from("<?php\nclass After {}"));
121 let a2 = codebase(host.db(), ws);
122 assert_ne!(first_ptr, Arc::as_ptr(&a2.0), "edit should invalidate");
123 assert!(a2.get().type_exists("After"));
124 assert!(!a2.get().type_exists("Before"));
125 }
126
127 #[test]
128 fn codebase_memoizes_when_nothing_changes() {
129 let host = AnalysisHost::new();
130 let f1 = SourceFile::new(
131 host.db(),
132 FileId(0),
133 Arc::<str>::from("file:///t.php"),
134 Arc::<str>::from("<?php\nclass X {}"),
135 None,
136 );
137 let ws = Workspace::new(host.db(), Arc::from([f1]), mir_analyzer::PhpVersion::LATEST);
138
139 let a1 = codebase(host.db(), ws);
140 let a2 = codebase(host.db(), ws);
141 assert!(
142 Arc::ptr_eq(&a1.0, &a2.0),
143 "no input change — second call should return the memoized Arc"
144 );
145 }
146}