| //! File and span related types. |
| use std::fmt::{self, Write}; |
| |
| mod ast_id; |
| mod hygiene; |
| mod map; |
| |
| pub use self::{ |
| ast_id::{AstIdMap, AstIdNode, ErasedFileAstId, FileAstId}, |
| hygiene::{SyntaxContext, Transparency}, |
| map::{RealSpanMap, SpanMap}, |
| }; |
| |
| pub use syntax::Edition; |
| pub use text_size::{TextRange, TextSize}; |
| pub use vfs::FileId; |
| |
| // 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 |
| /// relative text ranges. |
| pub const ROOT_ERASED_FILE_AST_ID: ErasedFileAstId = ErasedFileAstId::from_raw(0); |
| |
| /// FileId used as the span for syntax node fixups. Any Span containing this file id is to be |
| /// considered fake. |
| pub const FIXUP_ERASED_FILE_AST_ID_MARKER: ErasedFileAstId = |
| // we pick the second to last for this in case we ever consider making this a NonMaxU32, this |
| // is required to be stable for the proc-macro-server |
| ErasedFileAstId::from_raw(!0 - 1); |
| |
| pub type Span = SpanData<SyntaxContext>; |
| |
| 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. |
| #[derive(Clone, Copy, PartialEq, Eq, Hash)] |
| pub struct SpanData<Ctx> { |
| /// The text range of this span, relative to the anchor. |
| /// We need the anchor for incrementality, as storing absolute ranges will require |
| /// recomputation on every change in a file at all times. |
| pub range: TextRange, |
| /// The anchor this span is relative to. |
| pub anchor: SpanAnchor, |
| /// The syntax context of the span. |
| pub ctx: 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.file_id().index(), f)?; |
| f.write_char(':')?; |
| fmt::Debug::fmt(&self.anchor.ast_id.into_raw(), f)?; |
| f.write_char('@')?; |
| fmt::Debug::fmt(&self.range, f)?; |
| f.write_char('#')?; |
| self.ctx.fmt(f) |
| } else { |
| f.debug_struct("SpanData") |
| .field("range", &self.range) |
| .field("anchor", &self.anchor) |
| .field("ctx", &self.ctx) |
| .finish() |
| } |
| } |
| } |
| |
| impl<Ctx: Copy> SpanData<Ctx> { |
| pub fn eq_ignoring_ctx(self, other: Self) -> bool { |
| self.anchor == other.anchor && self.range == other.range |
| } |
| } |
| |
| impl fmt::Display for Span { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 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('@')?; |
| fmt::Debug::fmt(&self.range, f)?; |
| f.write_char('#')?; |
| self.ctx.fmt(f) |
| } |
| } |
| |
| #[derive(Copy, Clone, PartialEq, Eq, Hash)] |
| pub struct SpanAnchor { |
| pub file_id: EditionedFileId, |
| pub ast_id: ErasedFileAstId, |
| } |
| |
| impl fmt::Debug for SpanAnchor { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.debug_tuple("SpanAnchor").field(&self.file_id).field(&self.ast_id.into_raw()).finish() |
| } |
| } |
| |
| /// 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) } |
| } |
| } |
| |
| #[cfg(not(feature = "salsa"))] |
| mod salsa { |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| pub(crate) struct Id(u32); |
| |
| impl Id { |
| pub(crate) const fn from_u32(u32: u32) -> Self { |
| Self(u32) |
| } |
| |
| pub(crate) const fn as_u32(self) -> u32 { |
| self.0 |
| } |
| } |
| } |
| |
| /// 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 |
| /// file". To assign an id to such a file, we use the id of the macro call that |
| /// produced the file. So, a `HirFileId` is either a `FileId` (source code |
| /// written by user), or a `MacroCallId` (source code produced by macro). |
| /// |
| /// What is a `MacroCallId`? Simplifying, it's a `HirFileId` of a file |
| /// containing the call plus the offset of the macro call in the file. Note that |
| /// this is a recursive definition! However, the size_of of `HirFileId` is |
| /// finite (because everything bottoms out at the real `FileId`) and small |
| /// (`MacroCallId` uses the location interning. You can check details here: |
| /// <https://en.wikipedia.org/wiki/String_interning>). |
| /// |
| /// The two variants are encoded in a single u32 which are differentiated by the MSB. |
| /// If the MSB is 0, the value represents a `FileId`, otherwise the remaining 31 bits represent a |
| /// `MacroCallId`. |
| // FIXME: Give this a better fitting name |
| #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| pub struct HirFileId(salsa::Id); |
| |
| #[cfg(feature = "salsa")] |
| impl salsa::plumbing::AsId for HirFileId { |
| fn as_id(&self) -> salsa::Id { |
| self.0 |
| } |
| } |
| |
| #[cfg(feature = "salsa")] |
| impl salsa::plumbing::FromId for HirFileId { |
| fn from_id(id: salsa::Id) -> Self { |
| HirFileId(id) |
| } |
| } |
| |
| impl From<HirFileId> for u32 { |
| fn from(value: HirFileId) -> Self { |
| value.0.as_u32() |
| } |
| } |
| |
| impl From<MacroCallId> for HirFileId { |
| fn from(value: MacroCallId) -> Self { |
| value.as_file() |
| } |
| } |
| |
| impl fmt::Debug for HirFileId { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.repr().fmt(f) |
| } |
| } |
| |
| 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, |
| } |
| |
| /// `MacroCallId` identifies a particular macro invocation, like |
| /// `println!("Hello, {}", world)`. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct MacroCallId(salsa::Id); |
| |
| #[cfg(feature = "salsa")] |
| impl salsa::plumbing::AsId for MacroCallId { |
| fn as_id(&self) -> salsa::Id { |
| self.0 |
| } |
| } |
| |
| #[cfg(feature = "salsa")] |
| impl salsa::plumbing::FromId for MacroCallId { |
| fn from_id(id: salsa::Id) -> Self { |
| MacroCallId(id) |
| } |
| } |
| |
| impl MacroCallId { |
| pub const MAX_ID: u32 = 0x7fff_ffff; |
| |
| pub fn as_file(self) -> HirFileId { |
| MacroFileId { macro_call_id: self }.into() |
| } |
| |
| pub fn as_macro_file(self) -> MacroFileId { |
| MacroFileId { macro_call_id: self } |
| } |
| } |
| |
| #[derive(Clone, Copy, PartialEq, Eq, Hash)] |
| pub enum HirFileIdRepr { |
| FileId(EditionedFileId), |
| MacroFile(MacroFileId), |
| } |
| |
| impl fmt::Debug for HirFileIdRepr { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| Self::FileId(arg0) => arg0.fmt(f), |
| Self::MacroFile(arg0) => { |
| f.debug_tuple("MacroFile").field(&arg0.macro_call_id.0).finish() |
| } |
| } |
| } |
| } |
| |
| impl From<EditionedFileId> for HirFileId { |
| #[allow(clippy::let_unit_value)] |
| fn from(id: EditionedFileId) -> Self { |
| assert!(id.as_u32() <= Self::MAX_HIR_FILE_ID, "FileId index {} is too large", id.as_u32()); |
| HirFileId(salsa::Id::from_u32(id.0)) |
| } |
| } |
| |
| impl From<MacroFileId> for HirFileId { |
| #[allow(clippy::let_unit_value)] |
| fn from(MacroFileId { macro_call_id: MacroCallId(id) }: MacroFileId) -> Self { |
| let id: u32 = id.as_u32(); |
| assert!(id <= Self::MAX_HIR_FILE_ID, "MacroCallId index {id} is too large"); |
| HirFileId(salsa::Id::from_u32(id | Self::MACRO_FILE_TAG_MASK)) |
| } |
| } |
| |
| impl HirFileId { |
| const MAX_HIR_FILE_ID: u32 = u32::MAX ^ Self::MACRO_FILE_TAG_MASK; |
| const MACRO_FILE_TAG_MASK: u32 = 1 << 31; |
| |
| #[inline] |
| pub fn is_macro(self) -> bool { |
| self.0.as_u32() & Self::MACRO_FILE_TAG_MASK != 0 |
| } |
| |
| #[inline] |
| pub fn macro_file(self) -> Option<MacroFileId> { |
| match self.0.as_u32() & Self::MACRO_FILE_TAG_MASK { |
| 0 => None, |
| _ => Some(MacroFileId { |
| macro_call_id: MacroCallId(salsa::Id::from_u32( |
| self.0.as_u32() ^ Self::MACRO_FILE_TAG_MASK, |
| )), |
| }), |
| } |
| } |
| |
| #[inline] |
| pub fn file_id(self) -> Option<EditionedFileId> { |
| match self.0.as_u32() & Self::MACRO_FILE_TAG_MASK { |
| 0 => Some(EditionedFileId(self.0.as_u32())), |
| _ => None, |
| } |
| } |
| |
| #[inline] |
| pub fn repr(self) -> HirFileIdRepr { |
| match self.0.as_u32() & Self::MACRO_FILE_TAG_MASK { |
| 0 => HirFileIdRepr::FileId(EditionedFileId(self.0.as_u32())), |
| _ => HirFileIdRepr::MacroFile(MacroFileId { |
| macro_call_id: MacroCallId(salsa::Id::from_u32( |
| self.0.as_u32() ^ Self::MACRO_FILE_TAG_MASK, |
| )), |
| }), |
| } |
| } |
| } |
| |
| /// Legacy span type, only defined here as it is still used by the proc-macro server. |
| /// While rust-analyzer doesn't use this anymore at all, RustRover relies on the legacy type for |
| /// proc-macro expansion. |
| #[derive(Clone, Copy, PartialEq, Eq, Hash)] |
| pub struct TokenId(pub u32); |
| |
| impl std::fmt::Debug for TokenId { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| self.0.fmt(f) |
| } |
| } |