base_db/
lib.rs

1//! base_db defines basic database traits. The concrete DB is defined by ide.
2
3#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
4
5#[cfg(feature = "in-rust-tree")]
6extern crate rustc_driver as _;
7
8pub use salsa;
9pub use salsa_macros;
10
11// FIXME: Rename this crate, base db is non descriptive
12mod change;
13mod editioned_file_id;
14mod input;
15pub mod target;
16
17use std::{
18    cell::RefCell,
19    hash::BuildHasherDefault,
20    panic,
21    sync::{Once, atomic::AtomicUsize},
22};
23
24pub use crate::{
25    change::FileChange,
26    editioned_file_id::EditionedFileId,
27    input::{
28        BuiltCrateData, BuiltDependency, Crate, CrateBuilder, CrateBuilderId, CrateDataBuilder,
29        CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CratesIdMap, CratesMap,
30        DependencyBuilder, Env, ExtraCrateData, LangCrateOrigin, ProcMacroLoadingError,
31        ProcMacroPaths, ReleaseChannel, SourceRoot, SourceRootId, UniqueCrateData,
32    },
33};
34use dashmap::{DashMap, mapref::entry::Entry};
35pub use query_group::{self};
36use rustc_hash::FxHasher;
37use salsa::{Durability, Setter};
38pub use semver::{BuildMetadata, Prerelease, Version, VersionReq};
39use syntax::{Parse, SyntaxError, ast};
40use triomphe::Arc;
41pub use vfs::{AnchoredPath, AnchoredPathBuf, FileId, VfsPath, file_set::FileSet};
42
43pub type FxIndexSet<T> = indexmap::IndexSet<T, rustc_hash::FxBuildHasher>;
44pub type FxIndexMap<K, V> =
45    indexmap::IndexMap<K, V, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
46
47#[macro_export]
48macro_rules! impl_intern_key {
49    ($id:ident, $loc:ident) => {
50        #[salsa_macros::interned(no_lifetime, revisions = usize::MAX)]
51        #[derive(PartialOrd, Ord)]
52        pub struct $id {
53            pub loc: $loc,
54        }
55
56        // If we derive this salsa prints the values recursively, and this causes us to blow.
57        impl ::std::fmt::Debug for $id {
58            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
59                f.debug_tuple(stringify!($id))
60                    .field(&format_args!("{:04x}", self.0.index()))
61                    .finish()
62            }
63        }
64    };
65}
66
67/// # SAFETY
68///
69/// `old_pointer` must be valid for unique writes
70pub unsafe fn unsafe_update_eq<T>(old_pointer: *mut T, new_value: T) -> bool
71where
72    T: PartialEq,
73{
74    // SAFETY: Caller obligation
75    let old_ref: &mut T = unsafe { &mut *old_pointer };
76
77    if *old_ref != new_value {
78        *old_ref = new_value;
79        true
80    } else {
81        // Subtle but important: Eq impls can be buggy or define equality
82        // in surprising ways. If it says that the value has not changed,
83        // we do not modify the existing value, and thus do not have to
84        // update the revision, as downstream code will not see the new value.
85        false
86    }
87}
88
89pub const DEFAULT_FILE_TEXT_LRU_CAP: u16 = 16;
90pub const DEFAULT_PARSE_LRU_CAP: u16 = 128;
91pub const DEFAULT_BORROWCK_LRU_CAP: u16 = 2024;
92
93#[derive(Debug, Default)]
94pub struct Files {
95    files: Arc<DashMap<vfs::FileId, FileText, BuildHasherDefault<FxHasher>>>,
96    source_roots: Arc<DashMap<SourceRootId, SourceRootInput, BuildHasherDefault<FxHasher>>>,
97    file_source_roots: Arc<DashMap<vfs::FileId, FileSourceRootInput, BuildHasherDefault<FxHasher>>>,
98}
99
100impl Files {
101    pub fn file_text(&self, file_id: vfs::FileId) -> FileText {
102        match self.files.get(&file_id) {
103            Some(text) => *text,
104            None => {
105                panic!("Unable to fetch file text for `vfs::FileId`: {file_id:?}; this is a bug")
106            }
107        }
108    }
109
110    pub fn set_file_text(&self, db: &mut dyn SourceDatabase, file_id: vfs::FileId, text: &str) {
111        match self.files.entry(file_id) {
112            Entry::Occupied(mut occupied) => {
113                occupied.get_mut().set_text(db).to(Arc::from(text));
114            }
115            Entry::Vacant(vacant) => {
116                let text = FileText::new(db, Arc::from(text), file_id);
117                vacant.insert(text);
118            }
119        };
120    }
121
122    pub fn set_file_text_with_durability(
123        &self,
124        db: &mut dyn SourceDatabase,
125        file_id: vfs::FileId,
126        text: &str,
127        durability: Durability,
128    ) {
129        match self.files.entry(file_id) {
130            Entry::Occupied(mut occupied) => {
131                occupied.get_mut().set_text(db).with_durability(durability).to(Arc::from(text));
132            }
133            Entry::Vacant(vacant) => {
134                let text =
135                    FileText::builder(Arc::from(text), file_id).durability(durability).new(db);
136                vacant.insert(text);
137            }
138        };
139    }
140
141    /// Source root of the file.
142    pub fn source_root(&self, source_root_id: SourceRootId) -> SourceRootInput {
143        let source_root = match self.source_roots.get(&source_root_id) {
144            Some(source_root) => source_root,
145            None => panic!(
146                "Unable to fetch `SourceRootInput` with `SourceRootId` ({source_root_id:?}); this is a bug"
147            ),
148        };
149
150        *source_root
151    }
152
153    pub fn set_source_root_with_durability(
154        &self,
155        db: &mut dyn SourceDatabase,
156        source_root_id: SourceRootId,
157        source_root: Arc<SourceRoot>,
158        durability: Durability,
159    ) {
160        match self.source_roots.entry(source_root_id) {
161            Entry::Occupied(mut occupied) => {
162                occupied.get_mut().set_source_root(db).with_durability(durability).to(source_root);
163            }
164            Entry::Vacant(vacant) => {
165                let source_root =
166                    SourceRootInput::builder(source_root).durability(durability).new(db);
167                vacant.insert(source_root);
168            }
169        };
170    }
171
172    pub fn file_source_root(&self, id: vfs::FileId) -> FileSourceRootInput {
173        let file_source_root = match self.file_source_roots.get(&id) {
174            Some(file_source_root) => file_source_root,
175            None => panic!(
176                "Unable to get `FileSourceRootInput` with `vfs::FileId` ({id:?}); this is a bug",
177            ),
178        };
179        *file_source_root
180    }
181
182    pub fn set_file_source_root_with_durability(
183        &self,
184        db: &mut dyn SourceDatabase,
185        id: vfs::FileId,
186        source_root_id: SourceRootId,
187        durability: Durability,
188    ) {
189        match self.file_source_roots.entry(id) {
190            Entry::Occupied(mut occupied) => {
191                occupied
192                    .get_mut()
193                    .set_source_root_id(db)
194                    .with_durability(durability)
195                    .to(source_root_id);
196            }
197            Entry::Vacant(vacant) => {
198                let file_source_root =
199                    FileSourceRootInput::builder(source_root_id).durability(durability).new(db);
200                vacant.insert(file_source_root);
201            }
202        };
203    }
204}
205
206#[salsa_macros::input(debug)]
207pub struct FileText {
208    #[returns(ref)]
209    pub text: Arc<str>,
210    pub file_id: vfs::FileId,
211}
212
213#[salsa_macros::input(debug)]
214pub struct FileSourceRootInput {
215    pub source_root_id: SourceRootId,
216}
217
218#[salsa_macros::input(debug)]
219pub struct SourceRootInput {
220    pub source_root: Arc<SourceRoot>,
221}
222
223/// Database which stores all significant input facts: source code and project
224/// model. Everything else in rust-analyzer is derived from these queries.
225#[query_group::query_group]
226pub trait RootQueryDb: SourceDatabase + salsa::Database {
227    /// Parses the file into the syntax tree.
228    #[salsa::invoke(parse)]
229    #[salsa::lru(128)]
230    fn parse(&self, file_id: EditionedFileId) -> Parse<ast::SourceFile>;
231
232    /// Returns the set of errors obtained from parsing the file including validation errors.
233    #[salsa::transparent]
234    fn parse_errors(&self, file_id: EditionedFileId) -> Option<&[SyntaxError]>;
235
236    #[salsa::transparent]
237    fn toolchain_channel(&self, krate: Crate) -> Option<ReleaseChannel>;
238
239    /// Crates whose root file is in `id`.
240    #[salsa::invoke_interned(source_root_crates)]
241    fn source_root_crates(&self, id: SourceRootId) -> Arc<[Crate]>;
242
243    #[salsa::transparent]
244    fn relevant_crates(&self, file_id: FileId) -> Arc<[Crate]>;
245
246    /// Returns the crates in topological order.
247    ///
248    /// **Warning**: do not use this query in `hir-*` crates! It kills incrementality across crate metadata modifications.
249    #[salsa::input]
250    fn all_crates(&self) -> Arc<Box<[Crate]>>;
251}
252
253#[salsa_macros::db]
254pub trait SourceDatabase: salsa::Database {
255    /// Text of the file.
256    fn file_text(&self, file_id: vfs::FileId) -> FileText;
257
258    fn set_file_text(&mut self, file_id: vfs::FileId, text: &str);
259
260    fn set_file_text_with_durability(
261        &mut self,
262        file_id: vfs::FileId,
263        text: &str,
264        durability: Durability,
265    );
266
267    /// Contents of the source root.
268    fn source_root(&self, id: SourceRootId) -> SourceRootInput;
269
270    fn file_source_root(&self, id: vfs::FileId) -> FileSourceRootInput;
271
272    fn set_file_source_root_with_durability(
273        &mut self,
274        id: vfs::FileId,
275        source_root_id: SourceRootId,
276        durability: Durability,
277    );
278
279    /// Source root of the file.
280    fn set_source_root_with_durability(
281        &mut self,
282        source_root_id: SourceRootId,
283        source_root: Arc<SourceRoot>,
284        durability: Durability,
285    );
286
287    fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
288        // FIXME: this *somehow* should be platform agnostic...
289        let source_root = self.file_source_root(path.anchor);
290        let source_root = self.source_root(source_root.source_root_id(self));
291        source_root.source_root(self).resolve_path(path)
292    }
293
294    #[doc(hidden)]
295    fn crates_map(&self) -> Arc<CratesMap>;
296
297    fn nonce_and_revision(&self) -> (Nonce, salsa::Revision);
298}
299
300static NEXT_NONCE: AtomicUsize = AtomicUsize::new(0);
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
303pub struct Nonce(usize);
304
305impl Default for Nonce {
306    #[inline]
307    fn default() -> Self {
308        Nonce::new()
309    }
310}
311
312impl Nonce {
313    #[inline]
314    pub fn new() -> Nonce {
315        Nonce(NEXT_NONCE.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
316    }
317}
318
319/// Crate related data shared by the whole workspace.
320#[derive(Debug, PartialEq, Eq, Hash, Clone)]
321pub struct CrateWorkspaceData {
322    pub target: Result<target::TargetData, target::TargetLoadError>,
323    /// Toolchain version used to compile the crate.
324    pub toolchain: Option<Version>,
325}
326
327impl CrateWorkspaceData {
328    pub fn is_atleast_187(&self) -> bool {
329        const VERSION_187: Version = Version {
330            major: 1,
331            minor: 87,
332            patch: 0,
333            pre: Prerelease::EMPTY,
334            build: BuildMetadata::EMPTY,
335        };
336        self.toolchain.as_ref().map_or(false, |v| *v >= VERSION_187)
337    }
338}
339
340fn toolchain_channel(db: &dyn RootQueryDb, krate: Crate) -> Option<ReleaseChannel> {
341    krate.workspace_data(db).toolchain.as_ref().and_then(|v| ReleaseChannel::from_str(&v.pre))
342}
343
344fn parse(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Parse<ast::SourceFile> {
345    let _p = tracing::info_span!("parse", ?file_id).entered();
346    let (file_id, edition) = file_id.unpack(db.as_dyn_database());
347    let text = db.file_text(file_id).text(db);
348    ast::SourceFile::parse(text, edition)
349}
350
351fn parse_errors(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Option<&[SyntaxError]> {
352    #[salsa_macros::tracked(returns(ref))]
353    fn parse_errors(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Option<Box<[SyntaxError]>> {
354        let errors = db.parse(file_id).errors();
355        match &*errors {
356            [] => None,
357            [..] => Some(errors.into()),
358        }
359    }
360    parse_errors(db, file_id).as_ref().map(|it| &**it)
361}
362
363fn source_root_crates(db: &dyn RootQueryDb, id: SourceRootId) -> Arc<[Crate]> {
364    let crates = db.all_crates();
365    crates
366        .iter()
367        .copied()
368        .filter(|&krate| {
369            let root_file = krate.data(db).root_file_id;
370            db.file_source_root(root_file).source_root_id(db) == id
371        })
372        .collect()
373}
374
375fn relevant_crates(db: &dyn RootQueryDb, file_id: FileId) -> Arc<[Crate]> {
376    let _p = tracing::info_span!("relevant_crates").entered();
377
378    let source_root = db.file_source_root(file_id);
379    db.source_root_crates(source_root.source_root_id(db))
380}
381
382#[must_use]
383#[non_exhaustive]
384pub struct DbPanicContext;
385
386impl Drop for DbPanicContext {
387    fn drop(&mut self) {
388        Self::with_ctx(|ctx| assert!(ctx.pop().is_some()));
389    }
390}
391
392impl DbPanicContext {
393    pub fn enter(frame: String) -> DbPanicContext {
394        #[expect(clippy::print_stderr, reason = "already panicking anyway")]
395        fn set_hook() {
396            let default_hook = panic::take_hook();
397            panic::set_hook(Box::new(move |panic_info| {
398                default_hook(panic_info);
399                if let Some(backtrace) = salsa::Backtrace::capture() {
400                    eprintln!("{backtrace:#}");
401                }
402                DbPanicContext::with_ctx(|ctx| {
403                    if !ctx.is_empty() {
404                        eprintln!("additional context:");
405                        for (idx, frame) in ctx.iter().enumerate() {
406                            eprintln!("{idx:>4}: {frame}\n");
407                        }
408                    }
409                });
410            }));
411        }
412
413        static SET_HOOK: Once = Once::new();
414        SET_HOOK.call_once(set_hook);
415
416        Self::with_ctx(|ctx| ctx.push(frame));
417        DbPanicContext
418    }
419
420    fn with_ctx(f: impl FnOnce(&mut Vec<String>)) {
421        thread_local! {
422            static CTX: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
423        }
424        CTX.with(|ctx| f(&mut ctx.borrow_mut()));
425    }
426}