ra_ap_base_db/
lib.rs

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