blob: e2791ffe6f0a2aba315d3c4ef23b5e21e7d6c2b1 [file] [log] [blame]
//! 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()
}
}