Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/base-db/src/editioned_file_id.rs')
| -rw-r--r-- | crates/base-db/src/editioned_file_id.rs | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/crates/base-db/src/editioned_file_id.rs b/crates/base-db/src/editioned_file_id.rs new file mode 100644 index 0000000000..e2791ffe6f --- /dev/null +++ b/crates/base-db/src/editioned_file_id.rs @@ -0,0 +1,302 @@ +//! Defines [`EditionedFileId`], an interned wrapper around [`span::EditionedFileId`] that +//! is interned (so queries can take it) and remembers its crate. + +use core::fmt; +use std::hash::{Hash, Hasher}; + +use span::Edition; +use vfs::FileId; + +use crate::{Crate, RootQueryDb}; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct EditionedFileId( + salsa::Id, + std::marker::PhantomData<&'static salsa::plumbing::interned::Value<EditionedFileId>>, +); + +const _: () = { + use salsa::plumbing as zalsa_; + use zalsa_::interned as zalsa_struct_; + type Configuration_ = EditionedFileId; + + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct EditionedFileIdData { + editioned_file_id: span::EditionedFileId, + krate: Crate, + } + + /// We like to include the origin crate in an `EditionedFileId` (for use in the item tree), + /// but this poses us a problem. + /// + /// Spans contain `EditionedFileId`s, and we don't want to make them store the crate too + /// because that will increase their size, which will increase memory usage significantly. + /// Furthermore, things using spans do not generally need the crate: they are using the + /// file id for queries like `ast_id_map` or `parse`, which do not care about the crate. + /// + /// To solve this, we hash **only the `span::EditionedFileId`**, but on still compare + /// the crate in equality check. This preserves the invariant of `Hash` and `Eq` - + /// although same hashes can be used for different items, same file ids used for multiple + /// crates is a rare thing, and different items always have different hashes. Then, + /// when we only have a `span::EditionedFileId`, we use the `intern()` method to + /// reuse existing file ids, and create new one only if needed. See [`from_span_guess_origin`]. + /// + /// See this for more info: https://rust-lang.zulipchat.com/#narrow/channel/185405-t-compiler.2Frust-analyzer/topic/Letting.20EditionedFileId.20know.20its.20crate/near/530189401 + /// + /// [`from_span_guess_origin`]: EditionedFileId::from_span_guess_origin + #[derive(Hash, PartialEq, Eq)] + struct WithoutCrate { + editioned_file_id: span::EditionedFileId, + } + + impl Hash for EditionedFileIdData { + #[inline] + fn hash<H: Hasher>(&self, state: &mut H) { + let EditionedFileIdData { editioned_file_id, krate: _ } = *self; + editioned_file_id.hash(state); + } + } + + impl zalsa_struct_::HashEqLike<WithoutCrate> for EditionedFileIdData { + #[inline] + fn hash<H: Hasher>(&self, state: &mut H) { + Hash::hash(self, state); + } + + #[inline] + fn eq(&self, data: &WithoutCrate) -> bool { + let EditionedFileIdData { editioned_file_id, krate: _ } = *self; + editioned_file_id == data.editioned_file_id + } + } + + impl zalsa_::HasJar for EditionedFileId { + type Jar = zalsa_struct_::JarImpl<EditionedFileId>; + const KIND: zalsa_::JarKind = zalsa_::JarKind::Struct; + } + + zalsa_::register_jar! { + zalsa_::ErasedJar::erase::<EditionedFileId>() + } + + impl zalsa_struct_::Configuration for EditionedFileId { + const LOCATION: salsa::plumbing::Location = + salsa::plumbing::Location { file: file!(), line: line!() }; + const DEBUG_NAME: &'static str = "EditionedFileId"; + const REVISIONS: std::num::NonZeroUsize = std::num::NonZeroUsize::MAX; + const PERSIST: bool = false; + + type Fields<'a> = EditionedFileIdData; + type Struct<'db> = EditionedFileId; + + fn serialize<S>(_: &Self::Fields<'_>, _: S) -> Result<S::Ok, S::Error> + where + S: zalsa_::serde::Serializer, + { + unimplemented!("attempted to serialize value that set `PERSIST` to false") + } + + fn deserialize<'de, D>(_: D) -> Result<Self::Fields<'static>, D::Error> + where + D: zalsa_::serde::Deserializer<'de>, + { + unimplemented!("attempted to deserialize value that cannot set `PERSIST` to false"); + } + } + + impl Configuration_ { + pub fn ingredient(zalsa: &zalsa_::Zalsa) -> &zalsa_struct_::IngredientImpl<Self> { + static CACHE: zalsa_::IngredientCache<zalsa_struct_::IngredientImpl<EditionedFileId>> = + zalsa_::IngredientCache::new(); + + // SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the only + // ingredient created by our jar is the struct ingredient. + unsafe { + CACHE.get_or_create(zalsa, || { + zalsa.lookup_jar_by_type::<zalsa_struct_::JarImpl<EditionedFileId>>() + }) + } + } + } + + impl zalsa_::AsId for EditionedFileId { + fn as_id(&self) -> salsa::Id { + self.0.as_id() + } + } + impl zalsa_::FromId for EditionedFileId { + fn from_id(id: salsa::Id) -> Self { + Self(<salsa::Id>::from_id(id), std::marker::PhantomData) + } + } + + unsafe impl Send for EditionedFileId {} + unsafe impl Sync for EditionedFileId {} + + impl std::fmt::Debug for EditionedFileId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Self::default_debug_fmt(*self, f) + } + } + + impl zalsa_::SalsaStructInDb for EditionedFileId { + type MemoIngredientMap = salsa::plumbing::MemoIngredientSingletonIndex; + + fn lookup_ingredient_index(aux: &zalsa_::Zalsa) -> salsa::plumbing::IngredientIndices { + aux.lookup_jar_by_type::<zalsa_struct_::JarImpl<EditionedFileId>>().into() + } + + fn entries(zalsa: &zalsa_::Zalsa) -> impl Iterator<Item = zalsa_::DatabaseKeyIndex> + '_ { + let _ingredient_index = + zalsa.lookup_jar_by_type::<zalsa_struct_::JarImpl<EditionedFileId>>(); + <EditionedFileId>::ingredient(zalsa).entries(zalsa).map(|entry| entry.key()) + } + + #[inline] + fn cast(id: salsa::Id, type_id: std::any::TypeId) -> Option<Self> { + if type_id == std::any::TypeId::of::<EditionedFileId>() { + Some(<Self as salsa::plumbing::FromId>::from_id(id)) + } else { + None + } + } + + #[inline] + unsafe fn memo_table( + zalsa: &zalsa_::Zalsa, + id: zalsa_::Id, + current_revision: zalsa_::Revision, + ) -> zalsa_::MemoTableWithTypes<'_> { + // SAFETY: Guaranteed by caller. + unsafe { + zalsa.table().memos::<zalsa_struct_::Value<EditionedFileId>>(id, current_revision) + } + } + } + + unsafe impl zalsa_::Update for EditionedFileId { + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { + if unsafe { *old_pointer } != new_value { + unsafe { *old_pointer = new_value }; + true + } else { + false + } + } + } + + impl EditionedFileId { + pub fn from_span( + db: &(impl salsa::Database + ?Sized), + editioned_file_id: span::EditionedFileId, + krate: Crate, + ) -> Self { + let (zalsa, zalsa_local) = db.zalsas(); + Configuration_::ingredient(zalsa).intern( + zalsa, + zalsa_local, + EditionedFileIdData { editioned_file_id, krate }, + |_, data| data, + ) + } + + /// Guesses the crate for the file. + /// + /// Only use this if you cannot precisely determine the origin. This can happen in one of two cases: + /// + /// 1. The file is not in the module tree. + /// 2. You are latency sensitive and cannot afford calling the def map to precisely compute the origin + /// (e.g. on enter feature, folding, etc.). + pub fn from_span_guess_origin( + db: &dyn RootQueryDb, + editioned_file_id: span::EditionedFileId, + ) -> Self { + let (zalsa, zalsa_local) = db.zalsas(); + Configuration_::ingredient(zalsa).intern( + zalsa, + zalsa_local, + WithoutCrate { editioned_file_id }, + |_, _| { + // FileId not in the database. + let krate = db + .relevant_crates(editioned_file_id.file_id()) + .first() + .copied() + .or_else(|| db.all_crates().first().copied()) + .unwrap_or_else(|| { + // What we're doing here is a bit fishy. We rely on the fact that we only need + // the crate in the item tree, and we should not create an `EditionedFileId` + // without a crate except in cases where it does not matter. The chances that + // `all_crates()` will be empty are also very slim, but it can occur during startup. + // In the very unlikely case that there is a bug and we'll use this crate, Salsa + // will panic. + + // SAFETY: 0 is less than `Id::MAX_U32`. + salsa::plumbing::FromId::from_id(unsafe { salsa::Id::from_index(0) }) + }); + EditionedFileIdData { editioned_file_id, krate } + }, + ) + } + + pub fn editioned_file_id(self, db: &dyn salsa::Database) -> span::EditionedFileId { + let zalsa = db.zalsa(); + let fields = Configuration_::ingredient(zalsa).fields(zalsa, self); + fields.editioned_file_id + } + + pub fn krate(self, db: &dyn salsa::Database) -> Crate { + let zalsa = db.zalsa(); + let fields = Configuration_::ingredient(zalsa).fields(zalsa, self); + fields.krate + } + + /// Default debug formatting for this struct (may be useful if you define your own `Debug` impl) + pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + zalsa_::with_attached_database(|db| { + let zalsa = db.zalsa(); + let fields = Configuration_::ingredient(zalsa).fields(zalsa, this); + fmt::Debug::fmt(fields, f) + }) + .unwrap_or_else(|| { + f.debug_tuple("EditionedFileId").field(&zalsa_::AsId::as_id(&this)).finish() + }) + } + } +}; + +impl EditionedFileId { + #[inline] + pub fn new(db: &dyn salsa::Database, file_id: FileId, edition: Edition, krate: Crate) -> Self { + EditionedFileId::from_span(db, span::EditionedFileId::new(file_id, edition), krate) + } + + /// Attaches the current edition and guesses the crate for the file. + /// + /// Only use this if you cannot precisely determine the origin. This can happen in one of two cases: + /// + /// 1. The file is not in the module tree. + /// 2. You are latency sensitive and cannot afford calling the def map to precisely compute the origin + /// (e.g. on enter feature, folding, etc.). + #[inline] + pub fn current_edition_guess_origin(db: &dyn RootQueryDb, file_id: FileId) -> Self { + Self::from_span_guess_origin(db, span::EditionedFileId::current_edition(file_id)) + } + + #[inline] + pub fn file_id(self, db: &dyn salsa::Database) -> vfs::FileId { + let id = self.editioned_file_id(db); + id.file_id() + } + + #[inline] + pub fn unpack(self, db: &dyn salsa::Database) -> (vfs::FileId, span::Edition) { + let id = self.editioned_file_id(db); + (id.file_id(), id.edition()) + } + + #[inline] + pub fn edition(self, db: &dyn salsa::Database) -> Edition { + self.editioned_file_id(db).edition() + } +} |