| //! base_db defines basic database traits. The concrete DB is defined by ide. |
| |
| #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] |
| |
| #[cfg(feature = "in-rust-tree")] |
| extern crate rustc_driver as _; |
| |
| pub use salsa; |
| pub use salsa_macros; |
| |
| // FIXME: Rename this crate, base db is non descriptive |
| mod change; |
| mod editioned_file_id; |
| mod input; |
| pub mod target; |
| |
| use std::{ |
| cell::RefCell, |
| hash::BuildHasherDefault, |
| panic, |
| sync::{Once, atomic::AtomicUsize}, |
| }; |
| |
| pub use crate::{ |
| change::FileChange, |
| editioned_file_id::EditionedFileId, |
| input::{ |
| BuiltCrateData, BuiltDependency, Crate, CrateBuilder, CrateBuilderId, CrateDataBuilder, |
| CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CratesIdMap, CratesMap, |
| DependencyBuilder, Env, ExtraCrateData, LangCrateOrigin, ProcMacroLoadingError, |
| ProcMacroPaths, ReleaseChannel, SourceRoot, SourceRootId, UniqueCrateData, |
| }, |
| }; |
| use dashmap::{DashMap, mapref::entry::Entry}; |
| pub use query_group; |
| use rustc_hash::{FxHashSet, FxHasher}; |
| use salsa::{Durability, Setter}; |
| pub use semver::{BuildMetadata, Prerelease, Version, VersionReq}; |
| use triomphe::Arc; |
| pub use vfs::{AbsPathBuf, AnchoredPath, AnchoredPathBuf, FileId, VfsPath, file_set::FileSet}; |
| |
| pub type FxIndexSet<T> = indexmap::IndexSet<T, rustc_hash::FxBuildHasher>; |
| pub type FxIndexMap<K, V> = |
| indexmap::IndexMap<K, V, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>; |
| |
| #[macro_export] |
| macro_rules! impl_intern_key { |
| ($id:ident, $loc:ident) => { |
| #[salsa_macros::interned(no_lifetime, revisions = usize::MAX)] |
| #[derive(PartialOrd, Ord)] |
| pub struct $id { |
| pub loc: $loc, |
| } |
| |
| // If we derive this salsa prints the values recursively, and this causes us to blow. |
| impl ::std::fmt::Debug for $id { |
| fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { |
| f.debug_tuple(stringify!($id)) |
| .field(&format_args!("{:04x}", self.0.index())) |
| .finish() |
| } |
| } |
| }; |
| } |
| |
| /// # SAFETY |
| /// |
| /// `old_pointer` must be valid for unique writes |
| pub unsafe fn unsafe_update_eq<T>(old_pointer: *mut T, new_value: T) -> bool |
| where |
| T: PartialEq, |
| { |
| // SAFETY: Caller obligation |
| let old_ref: &mut T = unsafe { &mut *old_pointer }; |
| |
| if *old_ref != new_value { |
| *old_ref = new_value; |
| true |
| } else { |
| // Subtle but important: Eq impls can be buggy or define equality |
| // in surprising ways. If it says that the value has not changed, |
| // we do not modify the existing value, and thus do not have to |
| // update the revision, as downstream code will not see the new value. |
| false |
| } |
| } |
| |
| pub const DEFAULT_FILE_TEXT_LRU_CAP: u16 = 16; |
| pub const DEFAULT_PARSE_LRU_CAP: u16 = 128; |
| pub const DEFAULT_BORROWCK_LRU_CAP: u16 = 2024; |
| |
| #[derive(Debug, Default)] |
| pub struct Files { |
| files: Arc<DashMap<vfs::FileId, FileText, BuildHasherDefault<FxHasher>>>, |
| source_roots: Arc<DashMap<SourceRootId, SourceRootInput, BuildHasherDefault<FxHasher>>>, |
| file_source_roots: Arc<DashMap<vfs::FileId, FileSourceRootInput, BuildHasherDefault<FxHasher>>>, |
| } |
| |
| impl Files { |
| pub fn file_text(&self, file_id: vfs::FileId) -> FileText { |
| match self.files.get(&file_id) { |
| Some(text) => *text, |
| None => { |
| panic!("Unable to fetch file text for `vfs::FileId`: {file_id:?}; this is a bug") |
| } |
| } |
| } |
| |
| pub fn set_file_text(&self, db: &mut dyn SourceDatabase, file_id: vfs::FileId, text: &str) { |
| match self.files.entry(file_id) { |
| Entry::Occupied(mut occupied) => { |
| occupied.get_mut().set_text(db).to(Arc::from(text)); |
| } |
| Entry::Vacant(vacant) => { |
| let text = FileText::new(db, Arc::from(text), file_id); |
| vacant.insert(text); |
| } |
| }; |
| } |
| |
| pub fn set_file_text_with_durability( |
| &self, |
| db: &mut dyn SourceDatabase, |
| file_id: vfs::FileId, |
| text: &str, |
| durability: Durability, |
| ) { |
| match self.files.entry(file_id) { |
| Entry::Occupied(mut occupied) => { |
| occupied.get_mut().set_text(db).with_durability(durability).to(Arc::from(text)); |
| } |
| Entry::Vacant(vacant) => { |
| let text = |
| FileText::builder(Arc::from(text), file_id).durability(durability).new(db); |
| vacant.insert(text); |
| } |
| }; |
| } |
| |
| /// Source root of the file. |
| pub fn source_root(&self, source_root_id: SourceRootId) -> SourceRootInput { |
| let source_root = match self.source_roots.get(&source_root_id) { |
| Some(source_root) => source_root, |
| None => panic!( |
| "Unable to fetch `SourceRootInput` with `SourceRootId` ({source_root_id:?}); this is a bug" |
| ), |
| }; |
| |
| *source_root |
| } |
| |
| pub fn set_source_root_with_durability( |
| &self, |
| db: &mut dyn SourceDatabase, |
| source_root_id: SourceRootId, |
| source_root: Arc<SourceRoot>, |
| durability: Durability, |
| ) { |
| match self.source_roots.entry(source_root_id) { |
| Entry::Occupied(mut occupied) => { |
| occupied.get_mut().set_source_root(db).with_durability(durability).to(source_root); |
| } |
| Entry::Vacant(vacant) => { |
| let source_root = |
| SourceRootInput::builder(source_root).durability(durability).new(db); |
| vacant.insert(source_root); |
| } |
| }; |
| } |
| |
| pub fn file_source_root(&self, id: vfs::FileId) -> FileSourceRootInput { |
| let file_source_root = match self.file_source_roots.get(&id) { |
| Some(file_source_root) => file_source_root, |
| None => panic!( |
| "Unable to get `FileSourceRootInput` with `vfs::FileId` ({id:?}); this is a bug", |
| ), |
| }; |
| *file_source_root |
| } |
| |
| pub fn set_file_source_root_with_durability( |
| &self, |
| db: &mut dyn SourceDatabase, |
| id: vfs::FileId, |
| source_root_id: SourceRootId, |
| durability: Durability, |
| ) { |
| match self.file_source_roots.entry(id) { |
| Entry::Occupied(mut occupied) => { |
| occupied |
| .get_mut() |
| .set_source_root_id(db) |
| .with_durability(durability) |
| .to(source_root_id); |
| } |
| Entry::Vacant(vacant) => { |
| let file_source_root = |
| FileSourceRootInput::builder(source_root_id).durability(durability).new(db); |
| vacant.insert(file_source_root); |
| } |
| }; |
| } |
| } |
| |
| /// The set of roots for crates.io libraries. |
| /// Files in libraries are assumed to never change. |
| #[salsa::input(singleton, debug)] |
| pub struct LibraryRoots { |
| #[returns(ref)] |
| pub roots: FxHashSet<SourceRootId>, |
| } |
| |
| /// The set of "local" (that is, from the current workspace) roots. |
| /// Files in local roots are assumed to change frequently. |
| #[salsa::input(singleton, debug)] |
| pub struct LocalRoots { |
| #[returns(ref)] |
| pub roots: FxHashSet<SourceRootId>, |
| } |
| |
| #[salsa_macros::input(debug)] |
| pub struct FileText { |
| #[returns(ref)] |
| pub text: Arc<str>, |
| pub file_id: vfs::FileId, |
| } |
| |
| #[salsa_macros::input(debug)] |
| pub struct FileSourceRootInput { |
| pub source_root_id: SourceRootId, |
| } |
| |
| #[salsa_macros::input(debug)] |
| pub struct SourceRootInput { |
| pub source_root: Arc<SourceRoot>, |
| } |
| |
| #[salsa_macros::db] |
| pub trait SourceDatabase: salsa::Database { |
| /// Text of the file. |
| fn file_text(&self, file_id: vfs::FileId) -> FileText; |
| |
| fn set_file_text(&mut self, file_id: vfs::FileId, text: &str); |
| |
| fn set_file_text_with_durability( |
| &mut self, |
| file_id: vfs::FileId, |
| text: &str, |
| durability: Durability, |
| ); |
| |
| /// Contents of the source root. |
| fn source_root(&self, id: SourceRootId) -> SourceRootInput; |
| |
| fn file_source_root(&self, id: vfs::FileId) -> FileSourceRootInput; |
| |
| fn set_file_source_root_with_durability( |
| &mut self, |
| id: vfs::FileId, |
| source_root_id: SourceRootId, |
| durability: Durability, |
| ); |
| |
| /// Source root of the file. |
| fn set_source_root_with_durability( |
| &mut self, |
| source_root_id: SourceRootId, |
| source_root: Arc<SourceRoot>, |
| durability: Durability, |
| ); |
| |
| fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> { |
| // FIXME: this *somehow* should be platform agnostic... |
| let source_root = self.file_source_root(path.anchor); |
| let source_root = self.source_root(source_root.source_root_id(self)); |
| source_root.source_root(self).resolve_path(path) |
| } |
| |
| #[doc(hidden)] |
| fn crates_map(&self) -> Arc<CratesMap>; |
| |
| fn nonce_and_revision(&self) -> (Nonce, salsa::Revision); |
| } |
| |
| static NEXT_NONCE: AtomicUsize = AtomicUsize::new(0); |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub struct Nonce(usize); |
| |
| impl Default for Nonce { |
| #[inline] |
| fn default() -> Self { |
| Nonce::new() |
| } |
| } |
| |
| impl Nonce { |
| #[inline] |
| pub fn new() -> Nonce { |
| Nonce(NEXT_NONCE.fetch_add(1, std::sync::atomic::Ordering::SeqCst)) |
| } |
| } |
| |
| /// Crate related data shared by the whole workspace. |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub struct CrateWorkspaceData { |
| pub target: Result<target::TargetData, target::TargetLoadError>, |
| /// Toolchain version used to compile the crate. |
| pub toolchain: Option<Version>, |
| } |
| |
| impl CrateWorkspaceData { |
| pub fn is_atleast_187(&self) -> bool { |
| const VERSION_187: Version = Version { |
| major: 1, |
| minor: 87, |
| patch: 0, |
| pre: Prerelease::EMPTY, |
| build: BuildMetadata::EMPTY, |
| }; |
| self.toolchain.as_ref().map_or(false, |v| *v >= VERSION_187) |
| } |
| } |
| |
| pub fn toolchain_channel(db: &dyn salsa::Database, krate: Crate) -> Option<ReleaseChannel> { |
| krate.workspace_data(db).toolchain.as_ref().and_then(|v| ReleaseChannel::from_str(&v.pre)) |
| } |
| |
| #[salsa::input(singleton, debug)] |
| struct AllCrates { |
| crates: std::sync::Arc<[Crate]>, |
| } |
| |
| pub fn set_all_crates_with_durability( |
| db: &mut dyn salsa::Database, |
| crates: impl IntoIterator<Item = Crate>, |
| durability: Durability, |
| ) { |
| AllCrates::try_get(db) |
| .unwrap_or_else(|| AllCrates::new(db, std::sync::Arc::default())) |
| .set_crates(db) |
| .with_durability(durability) |
| .to(crates.into_iter().collect()); |
| } |
| |
| /// Returns the crates in topological order. |
| /// |
| /// **Warning**: do not use this query in `hir-*` crates! It kills incrementality across crate metadata modifications. |
| pub fn all_crates(db: &dyn salsa::Database) -> std::sync::Arc<[Crate]> { |
| AllCrates::try_get(db).map_or(std::sync::Arc::default(), |all_crates| all_crates.crates(db)) |
| } |
| |
| // FIXME: VFS rewrite should allow us to get rid of this wrapper |
| #[doc(hidden)] |
| #[salsa::interned] |
| pub struct InternedSourceRootId { |
| pub id: SourceRootId, |
| } |
| |
| /// Crates whose root file is in `id`. |
| pub fn source_root_crates(db: &dyn SourceDatabase, id: SourceRootId) -> &[Crate] { |
| #[salsa::tracked(returns(deref))] |
| pub fn source_root_crates<'db>( |
| db: &'db dyn SourceDatabase, |
| id: InternedSourceRootId<'db>, |
| ) -> Box<[Crate]> { |
| let crates = AllCrates::get(db).crates(db); |
| let id = id.id(db); |
| crates |
| .iter() |
| .copied() |
| .filter(|&krate| { |
| let root_file = krate.data(db).root_file_id; |
| db.file_source_root(root_file).source_root_id(db) == id |
| }) |
| .collect() |
| } |
| source_root_crates(db, InternedSourceRootId::new(db, id)) |
| } |
| |
| pub fn relevant_crates(db: &dyn SourceDatabase, file_id: FileId) -> &[Crate] { |
| let _p = tracing::info_span!("relevant_crates").entered(); |
| |
| let source_root = db.file_source_root(file_id); |
| source_root_crates(db, source_root.source_root_id(db)) |
| } |
| |
| #[must_use] |
| #[non_exhaustive] |
| pub struct DbPanicContext; |
| |
| impl Drop for DbPanicContext { |
| fn drop(&mut self) { |
| Self::with_ctx(|ctx| assert!(ctx.pop().is_some())); |
| } |
| } |
| |
| impl DbPanicContext { |
| pub fn enter(frame: String) -> DbPanicContext { |
| #[expect(clippy::print_stderr, reason = "already panicking anyway")] |
| fn set_hook() { |
| let default_hook = panic::take_hook(); |
| panic::set_hook(Box::new(move |panic_info| { |
| default_hook(panic_info); |
| if let Some(backtrace) = salsa::Backtrace::capture() { |
| eprintln!("{backtrace:#}"); |
| } |
| DbPanicContext::with_ctx(|ctx| { |
| if !ctx.is_empty() { |
| eprintln!("additional context:"); |
| for (idx, frame) in ctx.iter().enumerate() { |
| eprintln!("{idx:>4}: {frame}\n"); |
| } |
| } |
| }); |
| })); |
| } |
| |
| static SET_HOOK: Once = Once::new(); |
| SET_HOOK.call_once(set_hook); |
| |
| Self::with_ctx(|ctx| ctx.push(frame)); |
| DbPanicContext |
| } |
| |
| fn with_ctx(f: impl FnOnce(&mut Vec<String>)) { |
| thread_local! { |
| static CTX: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) }; |
| } |
| CTX.with(|ctx| f(&mut ctx.borrow_mut())); |
| } |
| } |