Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/span/src/lib.rs')
-rw-r--r--crates/span/src/lib.rs158
1 files changed, 129 insertions, 29 deletions
diff --git a/crates/span/src/lib.rs b/crates/span/src/lib.rs
index bbaf1b2a6d..b4e21d64f8 100644
--- a/crates/span/src/lib.rs
+++ b/crates/span/src/lib.rs
@@ -17,18 +17,6 @@ pub use syntax::Edition;
pub use text_size::{TextRange, TextSize};
pub use vfs::FileId;
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub struct FilePosition {
- pub file_id: FileId,
- pub offset: TextSize,
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
-pub struct FileRange {
- pub file_id: FileId,
- pub range: TextRange,
-}
-
// The first index is always the root node's AstId
/// The root ast id always points to the encompassing file, using this in spans is discouraged as
/// any range relative to it will be effectively absolute, ruining the entire point of anchored
@@ -45,6 +33,16 @@ pub const FIXUP_ERASED_FILE_AST_ID_MARKER: ErasedFileAstId =
pub type Span = SpanData<SyntaxContextId>;
+impl Span {
+ pub fn cover(self, other: Span) -> Span {
+ if self.anchor != other.anchor {
+ return self;
+ }
+ let range = self.range.cover(other.range);
+ Span { range, ..self }
+ }
+}
+
/// Spans represent a region of code, used by the IDE to be able link macro inputs and outputs
/// together. Positions in spans are relative to some [`SpanAnchor`] to make them more incremental
/// friendly.
@@ -63,7 +61,7 @@ pub struct SpanData<Ctx> {
impl<Ctx: fmt::Debug> fmt::Debug for SpanData<Ctx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
- fmt::Debug::fmt(&self.anchor.file_id.index(), f)?;
+ fmt::Debug::fmt(&self.anchor.file_id.file_id().index(), f)?;
f.write_char(':')?;
fmt::Debug::fmt(&self.anchor.ast_id.into_raw(), f)?;
f.write_char('@')?;
@@ -88,7 +86,7 @@ impl<Ctx: Copy> SpanData<Ctx> {
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- fmt::Debug::fmt(&self.anchor.file_id.index(), f)?;
+ fmt::Debug::fmt(&self.anchor.file_id.file_id().index(), f)?;
f.write_char(':')?;
fmt::Debug::fmt(&self.anchor.ast_id.into_raw(), f)?;
f.write_char('@')?;
@@ -100,7 +98,7 @@ impl fmt::Display for Span {
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct SpanAnchor {
- pub file_id: FileId,
+ pub file_id: EditionedFileId,
pub ast_id: ErasedFileAstId,
}
@@ -110,6 +108,81 @@ impl fmt::Debug for SpanAnchor {
}
}
+/// A [`FileId`] and [`Edition`] bundled up together.
+/// The MSB is reserved for `HirFileId` encoding, more upper bits are used to then encode the edition.
+#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct EditionedFileId(u32);
+
+impl fmt::Debug for EditionedFileId {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("EditionedFileId").field(&self.file_id()).field(&self.edition()).finish()
+ }
+}
+
+impl From<EditionedFileId> for FileId {
+ fn from(value: EditionedFileId) -> Self {
+ value.file_id()
+ }
+}
+
+const _: () = assert!(
+ EditionedFileId::RESERVED_HIGH_BITS
+ + EditionedFileId::EDITION_BITS
+ + EditionedFileId::FILE_ID_BITS
+ == u32::BITS
+);
+const _: () = assert!(
+ EditionedFileId::RESERVED_MASK ^ EditionedFileId::EDITION_MASK ^ EditionedFileId::FILE_ID_MASK
+ == 0xFFFF_FFFF
+);
+
+impl EditionedFileId {
+ pub const RESERVED_MASK: u32 = 0x8000_0000;
+ pub const EDITION_MASK: u32 = 0x7F80_0000;
+ pub const FILE_ID_MASK: u32 = 0x007F_FFFF;
+
+ pub const MAX_FILE_ID: u32 = Self::FILE_ID_MASK;
+
+ pub const RESERVED_HIGH_BITS: u32 = Self::RESERVED_MASK.count_ones();
+ pub const FILE_ID_BITS: u32 = Self::FILE_ID_MASK.count_ones();
+ pub const EDITION_BITS: u32 = Self::EDITION_MASK.count_ones();
+
+ pub const fn current_edition(file_id: FileId) -> Self {
+ Self::new(file_id, Edition::CURRENT)
+ }
+
+ pub const fn new(file_id: FileId, edition: Edition) -> Self {
+ let file_id = file_id.index();
+ let edition = edition as u32;
+ assert!(file_id <= Self::MAX_FILE_ID);
+ Self(file_id | (edition << Self::FILE_ID_BITS))
+ }
+
+ pub fn from_raw(u32: u32) -> Self {
+ assert!(u32 & Self::RESERVED_MASK == 0);
+ assert!((u32 & Self::EDITION_MASK) >> Self::FILE_ID_BITS <= Edition::LATEST as u32);
+ Self(u32)
+ }
+
+ pub const fn as_u32(self) -> u32 {
+ self.0
+ }
+
+ pub const fn file_id(self) -> FileId {
+ FileId::from_raw(self.0 & Self::FILE_ID_MASK)
+ }
+
+ pub const fn unpack(self) -> (FileId, Edition) {
+ (self.file_id(), self.edition())
+ }
+
+ pub const fn edition(self) -> Edition {
+ let edition = (self.0 & Self::EDITION_MASK) >> Self::FILE_ID_BITS;
+ debug_assert!(edition <= Edition::LATEST as u32);
+ unsafe { std::mem::transmute(edition as u8) }
+ }
+}
+
/// Input to the analyzer is a set of files, where each file is identified by
/// `FileId` and contains source code. However, another source of source code in
/// Rust are macros: each macro can be thought of as producing a "temporary
@@ -149,6 +222,38 @@ impl fmt::Debug for HirFileId {
}
}
+impl PartialEq<FileId> for HirFileId {
+ fn eq(&self, &other: &FileId) -> bool {
+ self.file_id().map(EditionedFileId::file_id) == Some(other)
+ }
+}
+impl PartialEq<HirFileId> for FileId {
+ fn eq(&self, other: &HirFileId) -> bool {
+ other.file_id().map(EditionedFileId::file_id) == Some(*self)
+ }
+}
+
+impl PartialEq<EditionedFileId> for HirFileId {
+ fn eq(&self, &other: &EditionedFileId) -> bool {
+ *self == HirFileId::from(other)
+ }
+}
+impl PartialEq<HirFileId> for EditionedFileId {
+ fn eq(&self, &other: &HirFileId) -> bool {
+ other == HirFileId::from(*self)
+ }
+}
+impl PartialEq<EditionedFileId> for FileId {
+ fn eq(&self, &other: &EditionedFileId) -> bool {
+ *self == FileId::from(other)
+ }
+}
+impl PartialEq<FileId> for EditionedFileId {
+ fn eq(&self, &other: &FileId) -> bool {
+ other == FileId::from(*self)
+ }
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MacroFileId {
pub macro_call_id: MacroCallId,
@@ -182,14 +287,14 @@ impl MacroCallId {
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum HirFileIdRepr {
- FileId(FileId),
+ FileId(EditionedFileId),
MacroFile(MacroFileId),
}
impl fmt::Debug for HirFileIdRepr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
- Self::FileId(arg0) => f.debug_tuple("FileId").field(&arg0.index()).finish(),
+ Self::FileId(arg0) => arg0.fmt(f),
Self::MacroFile(arg0) => {
f.debug_tuple("MacroFile").field(&arg0.macro_call_id.0).finish()
}
@@ -197,19 +302,17 @@ impl fmt::Debug for HirFileIdRepr {
}
}
-impl From<FileId> for HirFileId {
+impl From<EditionedFileId> for HirFileId {
#[allow(clippy::let_unit_value)]
- fn from(id: FileId) -> Self {
- _ = Self::ASSERT_MAX_FILE_ID_IS_SAME;
- assert!(id.index() <= Self::MAX_HIR_FILE_ID, "FileId index {} is too large", id.index());
- HirFileId(id.index())
+ fn from(id: EditionedFileId) -> Self {
+ assert!(id.as_u32() <= Self::MAX_HIR_FILE_ID, "FileId index {} is too large", id.as_u32());
+ HirFileId(id.as_u32())
}
}
impl From<MacroFileId> for HirFileId {
#[allow(clippy::let_unit_value)]
fn from(MacroFileId { macro_call_id: MacroCallId(id) }: MacroFileId) -> Self {
- _ = Self::ASSERT_MAX_FILE_ID_IS_SAME;
let id = id.as_u32();
assert!(id <= Self::MAX_HIR_FILE_ID, "MacroCallId index {id} is too large");
HirFileId(id | Self::MACRO_FILE_TAG_MASK)
@@ -217,9 +320,6 @@ impl From<MacroFileId> for HirFileId {
}
impl HirFileId {
- const ASSERT_MAX_FILE_ID_IS_SAME: () =
- [()][(Self::MAX_HIR_FILE_ID != FileId::MAX_FILE_ID) as usize];
-
const MAX_HIR_FILE_ID: u32 = u32::MAX ^ Self::MACRO_FILE_TAG_MASK;
const MACRO_FILE_TAG_MASK: u32 = 1 << 31;
@@ -239,9 +339,9 @@ impl HirFileId {
}
#[inline]
- pub fn file_id(self) -> Option<FileId> {
+ pub fn file_id(self) -> Option<EditionedFileId> {
match self.0 & Self::MACRO_FILE_TAG_MASK {
- 0 => Some(FileId::from_raw(self.0)),
+ 0 => Some(EditionedFileId(self.0)),
_ => None,
}
}
@@ -249,7 +349,7 @@ impl HirFileId {
#[inline]
pub fn repr(self) -> HirFileIdRepr {
match self.0 & Self::MACRO_FILE_TAG_MASK {
- 0 => HirFileIdRepr::FileId(FileId::from_raw(self.0)),
+ 0 => HirFileIdRepr::FileId(EditionedFileId(self.0)),
_ => HirFileIdRepr::MacroFile(MacroFileId {
macro_call_id: MacroCallId(InternId::from(self.0 ^ Self::MACRO_FILE_TAG_MASK)),
}),