use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::Range;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub const ZERO: Self = Self { start: 0, end: 0 };
#[must_use]
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
#[must_use]
pub const fn from_range(range: Range<usize>) -> Self {
Self {
start: range.start,
end: range.end,
}
}
#[must_use]
pub const fn len(&self) -> usize {
self.end - self.start
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.start == self.end
}
#[must_use]
pub fn merge(&self, other: &Self) -> Self {
Self {
start: self.start.min(other.start),
end: self.end.max(other.end),
}
}
#[must_use]
pub fn text<'a>(&self, source: &'a str) -> &'a str {
&source[self.start..self.end]
}
#[must_use]
pub const fn into_range(self) -> Range<usize> {
self.start..self.end
}
}
impl From<Range<usize>> for Span {
fn from(range: Range<usize>) -> Self {
Self::from_range(range)
}
}
impl From<Span> for Range<usize> {
fn from(span: Span) -> Self {
span.start..span.end
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
pub const SYNTHESIZED_FILE_ID: u16 = u16::MAX;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub struct Spanned<T> {
pub value: T,
pub span: Span,
pub file_id: u16,
}
impl<T> Spanned<T> {
#[must_use]
pub const fn new(value: T, span: Span) -> Self {
Self {
value,
span,
file_id: 0,
}
}
#[must_use]
pub const fn synthesized(value: T) -> Self {
Self {
value,
span: Span::ZERO,
file_id: SYNTHESIZED_FILE_ID,
}
}
#[must_use]
pub fn with_file_id(mut self, file_id: usize) -> Self {
debug_assert!(
u16::try_from(file_id).is_ok(),
"file_id {} exceeds u16::MAX; at most {} files are supported",
file_id,
u16::MAX
);
self.file_id = file_id as u16;
self
}
#[must_use]
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Spanned<U> {
Spanned {
value: f(self.value),
span: self.span,
file_id: self.file_id,
}
}
#[must_use]
pub const fn inner(&self) -> &T {
&self.value
}
#[must_use]
pub fn into_inner(self) -> T {
self.value
}
}
impl<T: fmt::Display> fmt::Display for Spanned<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value)
}
}
impl<T: PartialEq> PartialEq for Spanned<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T: Eq> Eq for Spanned<T> {}
impl<T: std::hash::Hash> std::hash::Hash for Spanned<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl<T> std::ops::Deref for Spanned<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
impl<T> std::ops::DerefMut for Spanned<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_span_new() {
let span = Span::new(10, 20);
assert_eq!(span.start, 10);
assert_eq!(span.end, 20);
}
#[test]
fn test_span_from_range() {
let span = Span::from_range(5..15);
assert_eq!(span.start, 5);
assert_eq!(span.end, 15);
}
#[test]
fn test_span_len() {
let span = Span::new(10, 25);
assert_eq!(span.len(), 15);
}
#[test]
fn test_span_is_empty() {
let empty = Span::new(5, 5);
let non_empty = Span::new(5, 10);
assert!(empty.is_empty());
assert!(!non_empty.is_empty());
}
#[test]
fn test_span_merge() {
let a = Span::new(10, 20);
let b = Span::new(15, 30);
let merged = a.merge(&b);
assert_eq!(merged.start, 10);
assert_eq!(merged.end, 30);
let c = Span::new(5, 8);
let merged2 = a.merge(&c);
assert_eq!(merged2.start, 5);
assert_eq!(merged2.end, 20);
}
#[test]
fn test_span_text() {
let source = "hello world";
let span = Span::new(0, 5);
assert_eq!(span.text(source), "hello");
let span2 = Span::new(6, 11);
assert_eq!(span2.text(source), "world");
}
#[test]
fn test_span_into_range() {
let span = Span::new(3, 7);
let range: Range<usize> = span.into_range();
assert_eq!(range, 3..7);
}
#[test]
fn test_span_from_impl() {
let span: Span = (5..10).into();
assert_eq!(span.start, 5);
assert_eq!(span.end, 10);
}
#[test]
fn test_range_from_span() {
let span = Span::new(2, 8);
let range: Range<usize> = span.into();
assert_eq!(range, 2..8);
}
#[test]
fn test_span_display() {
let span = Span::new(10, 20);
assert_eq!(format!("{span}"), "10..20");
}
#[test]
fn test_spanned_new() {
let spanned = Spanned::new("value", Span::new(0, 5));
assert_eq!(spanned.value, "value");
assert_eq!(spanned.span, Span::new(0, 5));
}
#[test]
fn test_spanned_map() {
let spanned = Spanned::new(5, Span::new(0, 1));
let mapped = spanned.map(|x| x * 2);
assert_eq!(mapped.value, 10);
assert_eq!(mapped.span, Span::new(0, 1));
}
#[test]
fn test_spanned_inner() {
let spanned = Spanned::new("test", Span::new(0, 4));
assert_eq!(spanned.inner(), &"test");
}
#[test]
fn test_spanned_into_inner() {
let spanned = Spanned::new(String::from("owned"), Span::new(0, 5));
let inner = spanned.into_inner();
assert_eq!(inner, "owned");
}
#[test]
fn test_spanned_display() {
let spanned = Spanned::new(42, Span::new(0, 2));
assert_eq!(format!("{spanned}"), "42");
}
#[test]
fn test_spanned_with_file_id() {
let spanned = Spanned::new("value", Span::new(0, 5)).with_file_id(3);
assert_eq!(spanned.value, "value");
assert_eq!(spanned.span, Span::new(0, 5));
assert_eq!(spanned.file_id, 3);
}
#[test]
fn test_spanned_eq_ignores_location() {
use std::collections::HashSet;
let a = Spanned::new("x", Span::new(0, 1)).with_file_id(0);
let b = Spanned::new("x", Span::new(100, 200)).with_file_id(7);
let c = Spanned::new("y", Span::new(0, 1)).with_file_id(0);
assert_eq!(a, b, "different locations, same value → equal");
assert_ne!(a, c, "same location, different value → not equal");
let mut set: HashSet<Spanned<&str>> = HashSet::new();
set.insert(a);
set.insert(b);
assert_eq!(set.len(), 1, "Hash also delegates to inner value");
}
#[test]
fn test_span_zero_constant() {
assert_eq!(Span::ZERO, Span::new(0, 0));
assert!(Span::ZERO.is_empty());
}
#[test]
fn test_spanned_synthesized_uses_synth_file_id_and_zero_span() {
let s = Spanned::synthesized("anything");
assert_eq!(s.span, Span::ZERO);
assert_eq!(s.file_id, SYNTHESIZED_FILE_ID);
}
#[test]
fn test_shift_spans_recurses_through_spanned() {
let mut sp = Spanned::new(Span::new(10, 20), Span::new(100, 200));
sp.shift_spans(&|s: &mut Span| {
s.start += 3;
s.end += 3;
});
assert_eq!(sp.span, Span::new(103, 203));
assert_eq!(sp.value, Span::new(13, 23));
}
}
pub trait ShiftSpans {
fn shift_spans<F: Fn(&mut Span)>(&mut self, shift: &F);
}
impl ShiftSpans for Span {
#[allow(clippy::use_self, reason = "trait bound names the literal type Span")]
fn shift_spans<F: Fn(&mut Span)>(&mut self, shift: &F) {
shift(self);
}
}
impl<T: ShiftSpans> ShiftSpans for Spanned<T> {
fn shift_spans<F: Fn(&mut Span)>(&mut self, shift: &F) {
shift(&mut self.span);
self.value.shift_spans(shift);
}
}
impl<T: ShiftSpans> ShiftSpans for Vec<T> {
fn shift_spans<F: Fn(&mut Span)>(&mut self, shift: &F) {
for item in self {
item.shift_spans(shift);
}
}
}
impl<T: ShiftSpans> ShiftSpans for Option<T> {
fn shift_spans<F: Fn(&mut Span)>(&mut self, shift: &F) {
if let Some(v) = self {
v.shift_spans(shift);
}
}
}
impl<T: ShiftSpans + ?Sized> ShiftSpans for Box<T> {
fn shift_spans<F: Fn(&mut Span)>(&mut self, shift: &F) {
(**self).shift_spans(shift);
}
}
#[macro_export]
macro_rules! impl_shift_spans_noop {
($($t:ty),* $(,)?) => {
$(
impl $crate::ShiftSpans for $t {
#[inline]
fn shift_spans<F: Fn(&mut $crate::Span)>(&mut self, _shift: &F) {}
}
)*
};
}
impl_shift_spans_noop!(
String, bool, u8, u16, u32, u64, i8, i16, i32, i64, usize, isize,
);