sway_core/query_engine/
mod.rs

1use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
2use std::{
3    collections::HashMap,
4    ops::{Deref, DerefMut},
5    path::PathBuf,
6    sync::Arc,
7    time::SystemTime,
8};
9use sway_error::{
10    error::CompileError,
11    warning::{CompileInfo, CompileWarning},
12};
13use sway_types::{IdentUnique, ProgramId, SourceId, Spanned};
14
15use crate::{
16    decl_engine::{DeclId, DeclRef},
17    language::ty::{TyFunctionDecl, TyFunctionSig, TyModule},
18    namespace, Engines, Programs,
19};
20
21#[derive(Debug, Clone, Hash, PartialEq, Eq)]
22pub struct ModuleCacheKey {
23    pub path: Arc<PathBuf>,
24    pub include_tests: bool,
25}
26
27impl ModuleCacheKey {
28    pub fn new(path: Arc<PathBuf>, include_tests: bool) -> Self {
29        Self {
30            path,
31            include_tests,
32        }
33    }
34}
35
36#[derive(Clone, Debug)]
37pub struct ModuleCommonInfo {
38    pub path: Arc<PathBuf>,
39    pub hash: u64,
40    pub include_tests: bool,
41    pub dependencies: Vec<Arc<PathBuf>>,
42}
43
44#[derive(Clone, Debug)]
45pub struct ParsedModuleInfo {
46    pub modified_time: Option<SystemTime>,
47    pub version: Option<u64>,
48}
49
50#[derive(Clone, Debug)]
51pub struct TypedModuleInfo {
52    pub module: Arc<TyModule>,
53    pub namespace_module: Arc<namespace::Module>,
54    pub version: Option<u64>,
55}
56
57#[derive(Clone, Debug)]
58pub struct ModuleCacheEntry {
59    pub common: ModuleCommonInfo,
60    pub parsed: ParsedModuleInfo,
61    pub typed: Option<TypedModuleInfo>,
62}
63
64impl ModuleCacheEntry {
65    pub fn new(common: ModuleCommonInfo, parsed: ParsedModuleInfo) -> Self {
66        Self {
67            common,
68            parsed,
69            typed: None,
70        }
71    }
72
73    pub fn is_typed(&self) -> bool {
74        self.typed.is_some()
75    }
76
77    pub fn set_typed(&mut self, typed: TypedModuleInfo) {
78        self.typed = Some(typed);
79    }
80
81    pub fn update_common(&mut self, new_common: ModuleCommonInfo) {
82        self.common = new_common;
83    }
84
85    pub fn update_parsed(&mut self, new_parsed: ParsedModuleInfo) {
86        self.parsed = new_parsed;
87    }
88
89    pub fn update_parsed_and_common(
90        &mut self,
91        new_common: ModuleCommonInfo,
92        new_parsed: ParsedModuleInfo,
93    ) {
94        self.common = new_common;
95        self.parsed = new_parsed;
96    }
97}
98
99#[derive(Debug, Default, Clone)]
100pub struct ModuleCacheMap(HashMap<ModuleCacheKey, ModuleCacheEntry>);
101
102impl Deref for ModuleCacheMap {
103    type Target = HashMap<ModuleCacheKey, ModuleCacheEntry>;
104    fn deref(&self) -> &Self::Target {
105        &self.0
106    }
107}
108
109impl DerefMut for ModuleCacheMap {
110    fn deref_mut(&mut self) -> &mut Self::Target {
111        &mut self.0
112    }
113}
114
115impl ModuleCacheMap {
116    pub fn update_entry(
117        &mut self,
118        key: &ModuleCacheKey,
119        new_common: ModuleCommonInfo,
120        new_parsed: ParsedModuleInfo,
121    ) {
122        if let Some(entry) = self.get_mut(key) {
123            entry.update_parsed_and_common(new_common, new_parsed);
124        } else {
125            self.insert(key.clone(), ModuleCacheEntry::new(new_common, new_parsed));
126        }
127    }
128}
129
130pub type ProgramsCacheMap = HashMap<Arc<PathBuf>, ProgramsCacheEntry>;
131pub type FunctionsCacheMap = HashMap<(IdentUnique, String), FunctionCacheEntry>;
132
133#[derive(Clone, Debug)]
134pub struct ProgramsCacheEntry {
135    pub path: Arc<PathBuf>,
136    pub programs: Programs,
137    pub handler_data: (Vec<CompileError>, Vec<CompileWarning>, Vec<CompileInfo>),
138}
139
140#[derive(Clone, Debug)]
141pub struct FunctionCacheEntry {
142    pub fn_decl: DeclRef<DeclId<TyFunctionDecl>>,
143}
144
145#[derive(Debug, Default)]
146pub struct QueryEngine {
147    // We want the below types wrapped in Arcs to optimize cloning from LSP.
148    programs_cache: CowCache<ProgramsCacheMap>,
149    pub module_cache: CowCache<ModuleCacheMap>,
150    // NOTE: Any further AstNodes that are cached need to have garbage collection applied, see clear_module()
151    function_cache: CowCache<FunctionsCacheMap>,
152}
153
154impl Clone for QueryEngine {
155    fn clone(&self) -> Self {
156        Self {
157            programs_cache: CowCache::new(self.programs_cache.read().clone()),
158            module_cache: CowCache::new(self.module_cache.read().clone()),
159            function_cache: CowCache::new(self.function_cache.read().clone()),
160        }
161    }
162}
163
164impl QueryEngine {
165    pub fn update_or_insert_parsed_module_cache_entry(&self, entry: ModuleCacheEntry) {
166        let path = entry.common.path.clone();
167        let include_tests = entry.common.include_tests;
168        let key = ModuleCacheKey::new(path, include_tests);
169        let mut cache = self.module_cache.write();
170        cache.update_entry(&key, entry.common, entry.parsed);
171    }
172
173    pub fn update_typed_module_cache_entry(&self, key: &ModuleCacheKey, entry: TypedModuleInfo) {
174        let mut cache = self.module_cache.write();
175        cache.get_mut(key).unwrap().set_typed(entry);
176    }
177
178    pub fn get_programs_cache_entry(&self, path: &Arc<PathBuf>) -> Option<ProgramsCacheEntry> {
179        let cache = self.programs_cache.read();
180        cache.get(path).cloned()
181    }
182
183    pub fn insert_programs_cache_entry(&self, entry: ProgramsCacheEntry) {
184        let mut cache = self.programs_cache.write();
185        cache.insert(entry.path.clone(), entry);
186    }
187
188    pub fn get_function(
189        &self,
190        engines: &Engines,
191        ident: &IdentUnique,
192        sig: TyFunctionSig,
193    ) -> Option<DeclRef<DeclId<TyFunctionDecl>>> {
194        let cache = self.function_cache.read();
195        cache
196            .get(&(ident.clone(), sig.get_type_str(engines)))
197            .map(|s| s.fn_decl.clone())
198    }
199
200    pub fn insert_function(
201        &self,
202        engines: &Engines,
203        ident: IdentUnique,
204        sig: TyFunctionSig,
205        fn_decl: DeclRef<DeclId<TyFunctionDecl>>,
206    ) {
207        let mut cache = self.function_cache.write();
208        cache.insert(
209            (ident, sig.get_type_str(engines)),
210            FunctionCacheEntry { fn_decl },
211        );
212    }
213
214    /// Removes all data associated with the `source_id` from the function cache.
215    pub fn clear_module(&mut self, source_id: &SourceId) {
216        self.function_cache
217            .write()
218            .retain(|(ident, _), _| (ident.span().source_id() != Some(source_id)));
219    }
220
221    /// Removes all data associated with the `program_id` from the function cache.
222    pub fn clear_program(&mut self, program_id: &ProgramId) {
223        self.function_cache.write().retain(|(ident, _), _| {
224            ident
225                .span()
226                .source_id()
227                .is_none_or(|id| id.program_id() != *program_id)
228        });
229    }
230
231    ///  Commits all changes to their respective caches.
232    pub fn commit(&self) {
233        self.programs_cache.commit();
234        self.module_cache.commit();
235        self.function_cache.commit();
236    }
237}
238
239/// Thread-safe, copy-on-write cache optimized for LSP operations.
240///
241/// Addresses key LSP challenges:
242/// 1. Concurrent read access to shared data
243/// 2. Local modifications for cancellable operations (e.g., compilation)
244/// 3. Prevents incomplete results from affecting shared state
245/// 4. Maintains consistency via explicit commit step
246///
247/// Uses `Arc<RwLock<T>>` for shared state and `RwLock<Option<T>>` for local changes.
248/// Suitable for interactive sessions with frequent file changes.
249#[derive(Debug, Default)]
250pub struct CowCache<T: Clone> {
251    inner: Arc<RwLock<T>>,
252    local: RwLock<Option<T>>,
253}
254
255impl<T: Clone> CowCache<T> {
256    /// Creates a new `CowCache` with the given initial value.
257    ///
258    /// The value is wrapped in an `Arc<RwLock<T>>` to allow shared access across threads.
259    pub fn new(value: T) -> Self {
260        Self {
261            inner: Arc::new(RwLock::new(value)),
262            local: RwLock::new(None),
263        }
264    }
265
266    /// Provides read access to the cached value.
267    ///
268    /// If a local modification exists, it returns a reference to the local copy.
269    /// Otherwise, it returns a reference to the shared state.
270    ///
271    /// This method is optimized for concurrent read access in LSP operations.
272    pub fn read(&self) -> impl Deref<Target = T> + '_ {
273        if self.local.read().is_some() {
274            ReadGuard::Local(self.local.read())
275        } else {
276            ReadGuard::Shared(self.inner.read())
277        }
278    }
279
280    /// Provides write access to a local copy of the cached value.
281    ///
282    /// In LSP, this is used for operations like compilation tasks that may be cancelled.
283    /// It allows modifications without affecting the shared state until explicitly committed.
284    pub fn write(&self) -> impl DerefMut<Target = T> + '_ {
285        let mut local = self.local.write();
286        if local.is_none() {
287            *local = Some(self.inner.read().clone());
288        }
289        WriteGuard(local)
290    }
291
292    /// Commits local modifications to the shared state.
293    ///
294    /// Called after successful completion of a compilation task.
295    /// If a task is cancelled, not calling this method effectively discards local changes.
296    pub fn commit(&self) {
297        if let Some(local) = self.local.write().take() {
298            *self.inner.write() = local;
299        }
300    }
301}
302
303/// A guard type that provides read access to either the local or shared state.
304enum ReadGuard<'a, T: Clone> {
305    Local(RwLockReadGuard<'a, Option<T>>),
306    Shared(RwLockReadGuard<'a, T>),
307}
308
309impl<T: Clone> Deref for ReadGuard<'_, T> {
310    type Target = T;
311
312    fn deref(&self) -> &Self::Target {
313        match self {
314            ReadGuard::Local(r) => r.as_ref().unwrap(),
315            ReadGuard::Shared(guard) => guard.deref(),
316        }
317    }
318}
319
320/// A guard type that provides write access to the local state.
321struct WriteGuard<'a, T: Clone>(RwLockWriteGuard<'a, Option<T>>);
322
323impl<T: Clone> Deref for WriteGuard<'_, T> {
324    type Target = T;
325
326    fn deref(&self) -> &Self::Target {
327        self.0.as_ref().unwrap()
328    }
329}
330
331impl<T: Clone> DerefMut for WriteGuard<'_, T> {
332    fn deref_mut(&mut self) -> &mut Self::Target {
333        self.0.as_mut().unwrap()
334    }
335}