1use std::sync::Arc;
14
15use salsa::Database;
16
17use crate::db::input::SourceFile;
18use crate::document::ast::ParsedDoc;
19
20#[derive(Clone)]
27pub struct ParsedArc(pub Arc<ParsedDoc>);
28
29impl ParsedArc {
30 pub fn get(&self) -> &ParsedDoc {
31 &self.0
32 }
33}
34
35crate::impl_arc_update!(ParsedArc);
39
40#[salsa::tracked(no_eq, lru = 2048)]
51pub fn parsed_doc(db: &dyn Database, file: SourceFile<'_>) -> ParsedArc {
52 let doc = ParsedDoc::parse(file.text_input(db).text(db));
54 ParsedArc(Arc::new(doc))
55}
56
57#[salsa::tracked(lru = 2048)]
60pub fn parse_error_count(db: &dyn Database, file: SourceFile<'_>) -> usize {
61 parsed_doc(db, file).get().errors.len()
62}
63
64#[cfg(test)]
65mod tests {
66 use std::sync::Arc;
67 use std::sync::atomic::{AtomicUsize, Ordering};
68
69 use super::*;
70 use crate::db::analysis::AnalysisHost;
71 use crate::db::input::{FileText, Workspace, workspace_files};
72 use salsa::Setter;
73
74 static CALLS: AtomicUsize = AtomicUsize::new(0);
75
76 #[salsa::tracked]
77 fn counted_parse(db: &dyn Database, file: SourceFile<'_>) -> usize {
78 CALLS.fetch_add(1, Ordering::SeqCst);
79 parsed_doc(db, file).get().errors.len()
80 }
81
82 fn make_ws(host: &AnalysisHost, uri: &str, ft: FileText) -> Workspace {
83 Workspace::new(
84 host.db(),
85 std::sync::Arc::from([(Arc::<str>::from(uri), ft)]),
86 mir_analyzer::PhpVersion::LATEST,
87 )
88 }
89
90 #[test]
91 fn parsed_doc_returns_ast() {
92 let host = AnalysisHost::new();
93 let ft = FileText::new(
94 host.db(),
95 Arc::<str>::from("<?php\nfunction greet() {}"),
96 None,
97 );
98 let ws = make_ws(&host, "file:///t.php", ft);
99 let files = workspace_files(host.db(), ws);
100 let arc = parsed_doc(host.db(), files[0]);
101 assert!(arc.get().errors.is_empty());
102 assert!(!arc.get().program().stmts.is_empty());
103 }
104
105 #[test]
106 fn parsed_doc_memoizes_and_invalidates() {
107 CALLS.store(0, Ordering::SeqCst);
108 let mut host = AnalysisHost::new();
109 let ft = FileText::new(host.db(), Arc::<str>::from("<?php\nfunction a() {}"), None);
110 let ws = make_ws(&host, "file:///t.php", ft);
111 {
112 let files = workspace_files(host.db(), ws);
113 let _ = counted_parse(host.db(), files[0]);
114 let _ = counted_parse(host.db(), files[0]);
115 assert_eq!(
116 CALLS.load(Ordering::SeqCst),
117 1,
118 "salsa should memoize the second call with unchanged input"
119 );
120 }
121
122 ft.set_text(host.db_mut())
123 .to(Arc::<str>::from("<?php\nclass {"));
124 {
125 let files = workspace_files(host.db(), ws);
126 let _ = counted_parse(host.db(), files[0]);
127 assert_eq!(
128 CALLS.load(Ordering::SeqCst),
129 2,
130 "downstream query should re-run after input text changes"
131 );
132 }
133 }
134
135 #[test]
136 fn parse_error_count_reflects_diagnostics() {
137 let host = AnalysisHost::new();
138 let ft = FileText::new(host.db(), Arc::<str>::from("<?php\nclass {"), None);
139 let ws = make_ws(&host, "file:///t.php", ft);
140 let files = workspace_files(host.db(), ws);
141 assert!(parse_error_count(host.db(), files[0]) > 0);
142 }
143}