1use std::sync::Arc;
14
15use salsa::{Database, Update};
16
17use crate::ast::ParsedDoc;
18use crate::db::input::SourceFile;
19use crate::diagnostics::parse_document;
20
21#[derive(Clone)]
28pub struct ParsedArc(pub Arc<ParsedDoc>);
29
30impl ParsedArc {
31 pub fn get(&self) -> &ParsedDoc {
32 &self.0
33 }
34}
35
36unsafe impl Update for ParsedArc {
41 unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
42 let old_ref = unsafe { &mut *old_pointer };
43 if Arc::ptr_eq(&old_ref.0, &new_value.0) {
44 false
45 } else {
46 *old_ref = new_value;
47 true
48 }
49 }
50}
51
52#[salsa::tracked(no_eq, lru = 2048)]
63pub fn parsed_doc(db: &dyn Database, file: SourceFile) -> ParsedArc {
64 let text = file.text(db);
65 let (doc, _diags) = parse_document(&text);
66 ParsedArc(Arc::new(doc))
67}
68
69#[salsa::tracked]
72pub fn parse_error_count(db: &dyn Database, file: SourceFile) -> usize {
73 let text = file.text(db);
74 let (_doc, diags) = parse_document(&text);
75 diags.len()
76}
77
78#[cfg(test)]
79mod tests {
80 use std::sync::Arc;
81 use std::sync::atomic::{AtomicUsize, Ordering};
82
83 use super::*;
84 use crate::db::analysis::AnalysisHost;
85 use crate::db::input::{FileId, SourceFile};
86 use salsa::Setter;
87
88 static CALLS: AtomicUsize = AtomicUsize::new(0);
89
90 #[salsa::tracked]
91 fn counted_parse(db: &dyn Database, file: SourceFile) -> usize {
92 CALLS.fetch_add(1, Ordering::SeqCst);
93 parsed_doc(db, file).get().errors.len()
94 }
95
96 #[test]
97 fn parsed_doc_returns_ast() {
98 let host = AnalysisHost::new();
99 let file = SourceFile::new(
100 host.db(),
101 FileId(0),
102 Arc::<str>::from("file:///t.php"),
103 Arc::<str>::from("<?php\nfunction greet() {}"),
104 None,
105 );
106 let arc = parsed_doc(host.db(), file);
107 assert!(arc.get().errors.is_empty());
108 assert!(!arc.get().program().stmts.is_empty());
109 }
110
111 #[test]
112 fn parsed_doc_memoizes_and_invalidates() {
113 CALLS.store(0, Ordering::SeqCst);
114 let mut host = AnalysisHost::new();
115 let file = SourceFile::new(
116 host.db(),
117 FileId(1),
118 Arc::<str>::from("file:///t.php"),
119 Arc::<str>::from("<?php\nfunction a() {}"),
120 None,
121 );
122
123 let _ = counted_parse(host.db(), file);
124 let _ = counted_parse(host.db(), file);
125 assert_eq!(
126 CALLS.load(Ordering::SeqCst),
127 1,
128 "salsa should memoize the second call with unchanged input"
129 );
130
131 file.set_text(host.db_mut())
132 .to(Arc::<str>::from("<?php\nclass {"));
133 let _ = counted_parse(host.db(), file);
134 assert_eq!(
135 CALLS.load(Ordering::SeqCst),
136 2,
137 "downstream query should re-run after input text changes"
138 );
139 }
140
141 #[test]
142 fn parse_error_count_reflects_diagnostics() {
143 let host = AnalysisHost::new();
144 let file = SourceFile::new(
145 host.db(),
146 FileId(2),
147 Arc::<str>::from("file:///t.php"),
148 Arc::<str>::from("<?php\nclass {"),
149 None,
150 );
151 assert!(parse_error_count(host.db(), file) > 0);
152 }
153}