//! 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>, ); 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, } // FIXME: This poses an invalidation problem, if one constructs an `EditionedFileId` with a // different crate then whatever the input of a memo used, it will invalidate the memo causing // it to recompute even if the crate is not really used. /// 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(&self, state: &mut H) { let EditionedFileIdData { editioned_file_id, krate: _ } = *self; editioned_file_id.hash(state); } } impl zalsa_struct_::HashEqLike for EditionedFileIdData { #[inline] fn hash(&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; const KIND: zalsa_::JarKind = zalsa_::JarKind::Struct; } zalsa_::register_jar! { zalsa_::ErasedJar::erase::() } 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(_: &Self::Fields<'_>, _: S) -> Result where S: zalsa_::serde::Serializer, { unimplemented!("attempted to serialize value that set `PERSIST` to false") } fn deserialize<'de, D>(_: D) -> Result, 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 { static CACHE: zalsa_::IngredientCache> = 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::>() }) } } } 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(::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::>().into() } fn entries(zalsa: &zalsa_::Zalsa) -> impl Iterator + '_ { let _ingredient_index = zalsa.lookup_jar_by_type::>(); ::ingredient(zalsa).entries(zalsa).map(|entry| entry.key()) } #[inline] fn cast(id: salsa::Id, type_id: std::any::TypeId) -> Option { if type_id == std::any::TypeId::of::() { Some(::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::>(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() } }