diff options
| author | David Barsky <me@davidbarsky.com> | 2024-11-05 12:24:41 -0500 |
|---|---|---|
| committer | David Barsky <me@davidbarsky.com> | 2025-03-10 13:30:51 -0400 |
| commit | a5c071633be35085edff186cef7fcfb2d17c22af (patch) | |
| tree | e077d825c4e8460b8a69b7d87af43a6f0d7125c9 /src/tools/rust-analyzer/crates/base-db | |
| parent | 83c1c4c2bb7c5744fe7e39869c941f52d54d80b7 (diff) | |
internal: port rust-analyzer to new Salsa
Diffstat (limited to 'src/tools/rust-analyzer/crates/base-db')
| -rw-r--r-- | src/tools/rust-analyzer/crates/base-db/Cargo.toml | 4 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/crates/base-db/src/change.rs | 15 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/crates/base-db/src/lib.rs | 338 |
3 files changed, 234 insertions, 123 deletions
diff --git a/src/tools/rust-analyzer/crates/base-db/Cargo.toml b/src/tools/rust-analyzer/crates/base-db/Cargo.toml index 042dd36488a..4780d5191be 100644 --- a/src/tools/rust-analyzer/crates/base-db/Cargo.toml +++ b/src/tools/rust-analyzer/crates/base-db/Cargo.toml @@ -15,7 +15,9 @@ rust-version.workspace = true lz4_flex = { version = "0.11", default-features = false } la-arena.workspace = true -ra-salsa.workspace = true +dashmap.workspace = true +salsa.workspace = true +query-group.workspace = true rustc-hash.workspace = true triomphe.workspace = true semver.workspace = true diff --git a/src/tools/rust-analyzer/crates/base-db/src/change.rs b/src/tools/rust-analyzer/crates/base-db/src/change.rs index 7e40f5408f1..90413a573ae 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/change.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/change.rs @@ -3,15 +3,12 @@ use std::fmt; -use ra_salsa::Durability; use rustc_hash::FxHashMap; +use salsa::Durability; use triomphe::Arc; use vfs::FileId; -use crate::{ - CrateGraph, CrateId, CrateWorkspaceData, SourceDatabaseFileInputExt, SourceRoot, - SourceRootDatabase, SourceRootId, -}; +use crate::{CrateGraph, CrateId, CrateWorkspaceData, RootQueryDb, SourceRoot, SourceRootId}; /// Encapsulate a bunch of raw `.set` calls on the database. #[derive(Default)] @@ -59,7 +56,7 @@ impl FileChange { self.ws_data = Some(data); } - pub fn apply(self, db: &mut dyn SourceRootDatabase) { + pub fn apply(self, db: &mut dyn RootQueryDb) { let _p = tracing::info_span!("FileChange::apply").entered(); if let Some(roots) = self.roots { for (idx, root) in roots.into_iter().enumerate() { @@ -68,14 +65,16 @@ impl FileChange { for file_id in root.iter() { db.set_file_source_root_with_durability(file_id, root_id, durability); } + db.set_source_root_with_durability(root_id, Arc::new(root), durability); } } for (file_id, text) in self.files_changed { let source_root_id = db.file_source_root(file_id); - let source_root = db.source_root(source_root_id); - let durability = durability(&source_root); + let source_root = db.source_root(source_root_id.source_root_id(db)); + + let durability = durability(&source_root.source_root(db)); // XXX: can't actually remove the file, just reset the text let text = text.unwrap_or_default(); db.set_file_text_with_durability(file_id, &text, durability) diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index eed8c886839..9ec9100968d 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -3,14 +3,7 @@ mod change; mod input; -use std::panic; - -use ra_salsa::Durability; -use rustc_hash::FxHashMap; -use span::EditionedFileId; -use syntax::{ast, Parse, SourceFile, SyntaxError}; -use triomphe::Arc; -use vfs::FileId; +use std::hash::BuildHasherDefault; pub use crate::{ change::FileChange, @@ -20,20 +13,30 @@ pub use crate::{ TargetLayoutLoadResult, }, }; -pub use ra_salsa::{self, Cancelled}; -pub use vfs::{file_set::FileSet, AnchoredPath, AnchoredPathBuf, VfsPath}; - +use dashmap::{mapref::entry::Entry, DashMap}; +pub use query_group::{self}; +use rustc_hash::{FxHashMap, FxHasher}; +pub use salsa::{self}; +use salsa::{Durability, Setter}; pub use semver::{BuildMetadata, Prerelease, Version, VersionReq}; +use syntax::{ast, Parse, SyntaxError}; +use triomphe::Arc; +pub use vfs::{file_set::FileSet, AnchoredPath, AnchoredPathBuf, FileId, VfsPath}; #[macro_export] macro_rules! impl_intern_key { - ($name:ident) => { - impl $crate::ra_salsa::InternKey for $name { - fn from_intern_id(v: $crate::ra_salsa::InternId) -> Self { - $name(v) - } - fn as_intern_id(&self) -> $crate::ra_salsa::InternId { - self.0 + ($id:ident, $loc:ident) => { + #[salsa::interned(no_debug, no_lifetime)] + 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.as_u32())) + .finish() } } }; @@ -47,39 +50,213 @@ 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; -pub trait FileLoader { - fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId>; - /// Crates whose root's source root is the same as the source root of `file_id` - fn relevant_crates(&self, file_id: FileId) -> Arc<[CrateId]>; +#[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>>>, } -/// Database which stores all significant input facts: source code and project -/// model. Everything else in rust-analyzer is derived from these queries. -#[ra_salsa::query_group(SourceDatabaseStorage)] -pub trait SourceDatabase: FileLoader + std::fmt::Debug { - #[ra_salsa::input] - fn compressed_file_text(&self, file_id: FileId) -> Arc<[u8]>; +impl Files { + pub fn file_text(&self, file_id: vfs::FileId) -> FileText { + *self.files.get(&file_id).expect("Unable to fetch file; this is a bug") + } - /// Text of the file. - #[ra_salsa::lru] - fn file_text(&self, file_id: FileId) -> Arc<str>; + pub fn set_file_text(&self, db: &mut dyn SourceDatabase, file_id: vfs::FileId, text: &str) { + let files = Arc::clone(&self.files); + match 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, + ) { + let files = Arc::clone(&self.files); + match files.entry(file_id) { + Entry::Occupied(mut occupied) => { + occupied.get_mut().set_text(db).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 = self + .source_roots + .get(&source_root_id) + .expect("Unable to fetch 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, + ) { + let source_roots = Arc::clone(&self.source_roots); + match source_roots.entry(source_root_id) { + Entry::Occupied(mut occupied) => { + occupied.get_mut().set_source_root(db).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 = self + .file_source_roots + .get(&id) + .expect("Unable to fetch FileSourceRootInput; 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, + ) { + let file_source_roots = Arc::clone(&self.file_source_roots); + // let db = self; + match file_source_roots.entry(id) { + Entry::Occupied(mut occupied) => { + occupied.get_mut().set_source_root_id(db).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); + } + }; + } +} + +#[salsa::interned(no_lifetime)] +pub struct EditionedFileId { + pub editioned_file_id: span::EditionedFileId, +} + +impl EditionedFileId { + pub fn file_id(&self, db: &dyn salsa::Database) -> vfs::FileId { + let id = self.editioned_file_id(db); + id.file_id() + } + + fn unpack(&self, db: &dyn salsa::Database) -> (vfs::FileId, span::Edition) { + let id = self.editioned_file_id(db); + (id.file_id(), id.edition()) + } +} + +#[salsa::input] +pub struct FileText { + pub text: Arc<str>, + pub file_id: vfs::FileId, +} + +#[salsa::input] +pub struct FileSourceRootInput { + pub source_root_id: SourceRootId, +} + +#[salsa::input] +pub struct SourceRootInput { + pub source_root: Arc<SourceRoot>, +} + +/// Database which stores all significant input facts: source code and project +/// model. Everything else in rust-analyzer is derived from these queries. +#[query_group::query_group] +pub trait RootQueryDb: SourceDatabase + salsa::Database { /// Parses the file into the syntax tree. - #[ra_salsa::lru] + #[salsa::invoke_actual(parse)] + #[salsa::lru(128)] fn parse(&self, file_id: EditionedFileId) -> Parse<ast::SourceFile>; /// Returns the set of errors obtained from parsing the file including validation errors. fn parse_errors(&self, file_id: EditionedFileId) -> Option<Arc<[SyntaxError]>>; /// The crate graph. - #[ra_salsa::input] + #[salsa::input] fn crate_graph(&self) -> Arc<CrateGraph>; - #[ra_salsa::input] + #[salsa::input] fn crate_workspace_data(&self) -> Arc<FxHashMap<CrateId, Arc<CrateWorkspaceData>>>; - #[ra_salsa::transparent] + #[salsa::transparent] fn toolchain_channel(&self, krate: CrateId) -> Option<ReleaseChannel>; + + /// Crates whose root file is in `id`. + fn source_root_crates(&self, id: SourceRootId) -> Arc<[CrateId]>; + + #[salsa::transparent] + fn relevant_crates(&self, file_id: FileId) -> Arc<[CrateId]>; +} + +#[salsa::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) + } } /// Crate related data shared by the whole workspace. @@ -91,7 +268,7 @@ pub struct CrateWorkspaceData { pub toolchain: Option<Version>, } -fn toolchain_channel(db: &dyn SourceDatabase, krate: CrateId) -> Option<ReleaseChannel> { +fn toolchain_channel(db: &dyn RootQueryDb, krate: CrateId) -> Option<ReleaseChannel> { db.crate_workspace_data() .get(&krate)? .toolchain @@ -99,14 +276,14 @@ fn toolchain_channel(db: &dyn SourceDatabase, krate: CrateId) -> Option<ReleaseC .and_then(|v| ReleaseChannel::from_str(&v.pre)) } -fn parse(db: &dyn SourceDatabase, file_id: EditionedFileId) -> Parse<ast::SourceFile> { +fn parse(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Parse<ast::SourceFile> { let _p = tracing::info_span!("parse", ?file_id).entered(); - let (file_id, edition) = file_id.unpack(); - let text = db.file_text(file_id); - SourceFile::parse(&text, edition) + let (file_id, edition) = file_id.unpack(db.as_dyn_database()); + let text = db.file_text(file_id).text(db); + ast::SourceFile::parse(&text, edition) } -fn parse_errors(db: &dyn SourceDatabase, file_id: EditionedFileId) -> Option<Arc<[SyntaxError]>> { +fn parse_errors(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Option<Arc<[SyntaxError]>> { let errors = db.parse(file_id).errors(); match &*errors { [] => None, @@ -114,67 +291,13 @@ fn parse_errors(db: &dyn SourceDatabase, file_id: EditionedFileId) -> Option<Arc } } -fn file_text(db: &dyn SourceDatabase, file_id: FileId) -> Arc<str> { - let bytes = db.compressed_file_text(file_id); - let bytes = - lz4_flex::decompress_size_prepended(&bytes).expect("lz4 decompression should not fail"); - let text = std::str::from_utf8(&bytes).expect("file contents should be valid UTF-8"); - Arc::from(text) -} - -/// We don't want to give HIR knowledge of source roots, hence we extract these -/// methods into a separate DB. -#[ra_salsa::query_group(SourceRootDatabaseStorage)] -pub trait SourceRootDatabase: SourceDatabase { - /// Path to a file, relative to the root of its source root. - /// Source root of the file. - #[ra_salsa::input] - fn file_source_root(&self, file_id: FileId) -> SourceRootId; - /// Contents of the source root. - #[ra_salsa::input] - fn source_root(&self, id: SourceRootId) -> Arc<SourceRoot>; - - /// Crates whose root file is in `id`. - fn source_root_crates(&self, id: SourceRootId) -> Arc<[CrateId]>; -} - -pub trait SourceDatabaseFileInputExt { - fn set_file_text(&mut self, file_id: FileId, text: &str) { - self.set_file_text_with_durability(file_id, text, Durability::LOW); - } - - fn set_file_text_with_durability( - &mut self, - file_id: FileId, - text: &str, - durability: Durability, - ); -} - -impl<Db: ?Sized + SourceRootDatabase> SourceDatabaseFileInputExt for Db { - fn set_file_text_with_durability( - &mut self, - file_id: FileId, - text: &str, - durability: Durability, - ) { - let bytes = text.as_bytes(); - let compressed = lz4_flex::compress_prepend_size(bytes); - self.set_compressed_file_text_with_durability( - file_id, - Arc::from(compressed.as_slice()), - durability, - ) - } -} - -fn source_root_crates(db: &dyn SourceRootDatabase, id: SourceRootId) -> Arc<[CrateId]> { +fn source_root_crates(db: &dyn RootQueryDb, id: SourceRootId) -> Arc<[CrateId]> { let graph = db.crate_graph(); let mut crates = graph .iter() .filter(|&krate| { let root_file = graph[krate].root_file_id; - db.file_source_root(root_file) == id + db.file_source_root(root_file).source_root_id(db) == id }) .collect::<Vec<_>>(); crates.sort(); @@ -182,22 +305,9 @@ fn source_root_crates(db: &dyn SourceRootDatabase, id: SourceRootId) -> Arc<[Cra crates.into_iter().collect() } -// FIXME: Would be nice to get rid of this somehow -/// Silly workaround for cyclic deps due to the SourceRootDatabase and SourceDatabase split -/// regarding FileLoader -pub struct FileLoaderDelegate<T>(pub T); +fn relevant_crates(db: &dyn RootQueryDb, file_id: FileId) -> Arc<[CrateId]> { + let _p = tracing::info_span!("relevant_crates").entered(); -impl<T: SourceRootDatabase> FileLoader for FileLoaderDelegate<&'_ T> { - fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> { - // FIXME: this *somehow* should be platform agnostic... - let source_root = self.0.file_source_root(path.anchor); - let source_root = self.0.source_root(source_root); - source_root.resolve_path(path) - } - - fn relevant_crates(&self, file_id: FileId) -> Arc<[CrateId]> { - let _p = tracing::info_span!("relevant_crates").entered(); - let source_root = self.0.file_source_root(file_id); - self.0.source_root_crates(source_root) - } + let source_root = db.file_source_root(file_id); + db.source_root_crates(source_root.source_root_id(db)) } |
