oxc_span/span.rs
1use std::{
2 fmt::{self, Debug},
3 hash::{Hash, Hasher},
4 ops::{Index, IndexMut, Range},
5};
6
7use miette::{LabeledSpan, SourceOffset, SourceSpan};
8#[cfg(feature = "serialize")]
9use serde::{Serialize, Serializer as SerdeSerializer, ser::SerializeMap};
10
11use oxc_allocator::{Allocator, CloneIn, Dummy};
12use oxc_ast_macros::ast;
13use oxc_estree::ESTree;
14
15#[cfg(feature = "serialize")]
16use oxc_estree::ESTreeSpan;
17
18/// An empty span.
19///
20/// Should be used for newly created new AST nodes.
21pub const SPAN: Span = Span::new(0, 0);
22
23/// A range in text, represented by a zero-indexed start and end offset.
24///
25/// It is a logical error for `end` to be less than `start`.
26///
27/// ```
28/// # use oxc_span::Span;
29/// let text = "foo bar baz";
30/// let span = Span::new(4, 7);
31/// assert_eq!(&text[span], "bar");
32/// ```
33///
34/// Spans use `u32` for offsets, meaning only files up to 4GB are supported.
35/// This is sufficient for "all" reasonable programs. This tradeof cuts the size
36/// of `Span` in half, offering a sizeable performance improvement and memory
37/// footprint reduction.
38///
39/// ## Creating Spans
40/// Span offers several constructors, each of which is more or less convenient
41/// depending on the context. In general, [`Span::new`] is sufficient for most
42/// cases. If you want to create a span starting at some point of a certain
43/// length, you can use [`Span::sized`].
44///
45/// ```
46/// # use oxc_span::Span;
47/// let a = Span::new(5, 10); // Start and end offsets
48/// let b = Span::sized(5, 5); // Start offset and size
49/// assert_eq!(a, b);
50/// ```
51///
52/// ## Re-Sizing Spans
53/// Span offsets can be mutated directly, but it is often more convenient to use
54/// one of the [`expand`] or [`shrink`] methods. Each of these create a new span
55/// without modifying the original.
56///
57/// ```
58/// # use oxc_span::Span;
59/// let s = Span::new(5, 10);
60/// assert_eq!(s.shrink(2), Span::new(7, 8));
61/// assert_eq!(s.shrink(2), s.shrink_left(2).shrink_right(2));
62///
63/// assert_eq!(s.expand(5), Span::new(0, 15));
64/// assert_eq!(s.expand(5), s.expand_left(5).expand_right(5));
65/// ```
66///
67/// ## Comparison
68/// [`Span`] has a normal implementation of [`PartialEq`]. If you want to compare two
69/// AST nodes without considering their locations (e.g. to see if they have the
70/// same content), use [`ContentEq`] instead.
71///
72/// ## Implementation Notes
73/// See the [`text-size`](https://docs.rs/text-size) crate for details.
74/// Utility methods can be copied from the `text-size` crate if they are needed.
75///
76/// [`expand`]: Span::expand
77/// [`shrink`]: Span::shrink
78/// [`ContentEq`]: crate::ContentEq
79#[ast(visit)]
80#[derive(Default, Clone, Copy, Eq, PartialOrd, Ord)]
81#[generate_derive(ESTree)]
82#[builder(skip)]
83#[content_eq(skip)]
84#[estree(
85 no_type,
86 flatten,
87 no_ts_def,
88 add_ts_def = "interface Span { start: number; end: number; range?: [number, number]; }"
89)]
90pub struct Span {
91 /// The zero-based start offset of the span
92 pub start: u32,
93 /// The zero-based end offset of the span. This may be equal to [`start`](Span::start) if
94 /// the span is empty, but should not be less than it.
95 pub end: u32,
96 /// Align `Span` on 8 on 64-bit platforms
97 #[estree(skip)]
98 _align: PointerAlign,
99}
100
101impl Span {
102 /// Create a new [`Span`] from a start and end position.
103 ///
104 /// # Invariants
105 /// The `start` position must be less than or equal to `end`. Note that this
106 /// invariant is only checked in debug builds to avoid a performance
107 /// penalty.
108 ///
109 #[inline]
110 pub const fn new(start: u32, end: u32) -> Self {
111 Self { start, end, _align: PointerAlign::new() }
112 }
113
114 /// Create a new empty [`Span`] that starts and ends at an offset position.
115 ///
116 /// # Examples
117 /// ```
118 /// use oxc_span::Span;
119 ///
120 /// let fifth = Span::empty(5);
121 /// assert!(fifth.is_empty());
122 /// assert_eq!(fifth, Span::sized(5, 0));
123 /// assert_eq!(fifth, Span::new(5, 5));
124 /// ```
125 pub fn empty(at: u32) -> Self {
126 Self::new(at, at)
127 }
128
129 /// Create a new [`Span`] starting at `start` and covering `size` bytes.
130 ///
131 /// # Example
132 /// ```
133 /// use oxc_span::Span;
134 ///
135 /// let span = Span::sized(2, 4);
136 /// assert_eq!(span.size(), 4);
137 /// assert_eq!(span, Span::new(2, 6));
138 /// ```
139 pub const fn sized(start: u32, size: u32) -> Self {
140 Self::new(start, start + size)
141 }
142
143 /// Get the number of bytes covered by the [`Span`].
144 ///
145 /// # Example
146 /// ```
147 /// use oxc_span::Span;
148 ///
149 /// assert_eq!(Span::new(1, 1).size(), 0);
150 /// assert_eq!(Span::new(0, 5).size(), 5);
151 /// assert_eq!(Span::new(5, 10).size(), 5);
152 /// ```
153 pub const fn size(self) -> u32 {
154 debug_assert!(self.start <= self.end);
155 self.end - self.start
156 }
157
158 /// Returns `true` if `self` covers a range of zero length.
159 ///
160 /// # Example
161 /// ```
162 /// use oxc_span::Span;
163 ///
164 /// assert!(Span::new(0, 0).is_empty());
165 /// assert!(Span::new(5, 5).is_empty());
166 /// assert!(!Span::new(0, 5).is_empty());
167 /// ```
168 pub const fn is_empty(self) -> bool {
169 debug_assert!(self.start <= self.end);
170 self.start == self.end
171 }
172
173 /// Returns `true` if `self` is not a real span.
174 /// i.e. `SPAN` which is used for generated nodes which are not in source code.
175 ///
176 /// # Example
177 /// ```
178 /// use oxc_span::{Span, SPAN};
179 ///
180 /// assert!(SPAN.is_unspanned());
181 /// assert!(!Span::new(0, 5).is_unspanned());
182 /// assert!(!Span::new(5, 5).is_unspanned());
183 /// ```
184 #[inline]
185 pub const fn is_unspanned(self) -> bool {
186 self.const_eq(SPAN)
187 }
188
189 /// Check if this [`Span`] contains another [`Span`].
190 ///
191 /// [`Span`]s that start & end at the same position as this [`Span`] are
192 /// considered contained.
193 ///
194 /// # Examples
195 ///
196 /// ```rust
197 /// # use oxc_span::Span;
198 /// let span = Span::new(5, 10);
199 ///
200 /// assert!(span.contains_inclusive(span)); // always true for itself
201 /// assert!(span.contains_inclusive(Span::new(5, 5)));
202 /// assert!(span.contains_inclusive(Span::new(6, 10)));
203 /// assert!(span.contains_inclusive(Span::empty(5)));
204 ///
205 /// assert!(!span.contains_inclusive(Span::new(4, 10)));
206 /// assert!(!span.contains_inclusive(Span::empty(0)));
207 /// ```
208 #[inline]
209 pub const fn contains_inclusive(self, span: Span) -> bool {
210 self.start <= span.start && span.end <= self.end
211 }
212
213 /// Create a [`Span`] covering the maximum range of two [`Span`]s.
214 ///
215 /// # Example
216 /// ```
217 /// use oxc_span::Span;
218 ///
219 /// let span1 = Span::new(0, 5);
220 /// let span2 = Span::new(3, 8);
221 /// let merged_span = span1.merge(span2);
222 /// assert_eq!(merged_span, Span::new(0, 8));
223 /// ```
224 #[must_use]
225 pub fn merge(self, other: Self) -> Self {
226 Self::new(self.start.min(other.start), self.end.max(other.end))
227 }
228
229 /// Create a [`Span`] that is grown by `offset` on either side.
230 ///
231 /// This is equivalent to `span.expand_left(offset).expand_right(offset)`.
232 /// See [`expand_left`] and [`expand_right`] for more info.
233 ///
234 /// # Example
235 /// ```
236 /// use oxc_span::Span;
237 ///
238 /// let span = Span::new(3, 5);
239 /// assert_eq!(span.expand(1), Span::new(2, 6));
240 /// // start and end cannot be expanded past `0` and `u32::MAX`, respectively
241 /// assert_eq!(span.expand(5), Span::new(0, 10));
242 /// ```
243 ///
244 /// [`expand_left`]: Span::expand_left
245 /// [`expand_right`]: Span::expand_right
246 #[must_use]
247 pub fn expand(self, offset: u32) -> Self {
248 Self::new(self.start.saturating_sub(offset), self.end.saturating_add(offset))
249 }
250
251 /// Create a [`Span`] that has its start and end positions shrunk by
252 /// `offset` amount.
253 ///
254 /// It is a logical error to shrink the start of the [`Span`] past its end
255 /// position. This will panic in debug builds.
256 ///
257 /// This is equivalent to `span.shrink_left(offset).shrink_right(offset)`.
258 /// See [`shrink_left`] and [`shrink_right`] for more info.
259 ///
260 /// # Example
261 /// ```
262 /// use oxc_span::Span;
263 /// let span = Span::new(5, 10);
264 /// assert_eq!(span.shrink(2), Span::new(7, 8));
265 /// ```
266 ///
267 /// [`shrink_left`]: Span::shrink_left
268 /// [`shrink_right`]: Span::shrink_right
269 #[must_use]
270 pub fn shrink(self, offset: u32) -> Self {
271 let start = self.start.saturating_add(offset);
272 let end = self.end.saturating_sub(offset);
273 debug_assert!(start <= end, "Cannot shrink span past zero length");
274 Self::new(start, end)
275 }
276
277 /// Create a [`Span`] that has its start position moved to the left by
278 /// `offset` bytes.
279 ///
280 /// # Example
281 ///
282 /// ```
283 /// use oxc_span::Span;
284 ///
285 /// let a = Span::new(5, 10);
286 /// assert_eq!(a.expand_left(5), Span::new(0, 10));
287 /// ```
288 ///
289 /// ## Bounds
290 ///
291 /// The leftmost bound of the span is clamped to 0. It is safe to call this
292 /// method with a value larger than the start position.
293 ///
294 /// ```
295 /// use oxc_span::Span;
296 ///
297 /// let a = Span::new(0, 5);
298 /// assert_eq!(a.expand_left(5), Span::new(0, 5));
299 /// ```
300 #[must_use]
301 pub const fn expand_left(self, offset: u32) -> Self {
302 Self::new(self.start.saturating_sub(offset), self.end)
303 }
304
305 /// Create a [`Span`] that has its start position moved to the right by
306 /// `offset` bytes.
307 ///
308 /// It is a logical error to shrink the start of the [`Span`] past its end
309 /// position.
310 ///
311 /// # Example
312 ///
313 /// ```
314 /// use oxc_span::Span;
315 ///
316 /// let a = Span::new(5, 10);
317 /// let shrunk = a.shrink_left(5);
318 /// assert_eq!(shrunk, Span::new(10, 10));
319 ///
320 /// // Shrinking past the end of the span is a logical error that will panic
321 /// // in debug builds.
322 /// std::panic::catch_unwind(|| {
323 /// shrunk.shrink_left(5);
324 /// });
325 /// ```
326 ///
327 #[must_use]
328 pub const fn shrink_left(self, offset: u32) -> Self {
329 let start = self.start.saturating_add(offset);
330 debug_assert!(start <= self.end);
331 Self::new(self.start.saturating_add(offset), self.end)
332 }
333
334 /// Create a [`Span`] that has its end position moved to the right by
335 /// `offset` bytes.
336 ///
337 /// # Example
338 ///
339 /// ```
340 /// use oxc_span::Span;
341 ///
342 /// let a = Span::new(5, 10);
343 /// assert_eq!(a.expand_right(5), Span::new(5, 15));
344 /// ```
345 ///
346 /// ## Bounds
347 ///
348 /// The rightmost bound of the span is clamped to `u32::MAX`. It is safe to
349 /// call this method with a value larger than the end position.
350 ///
351 /// ```
352 /// use oxc_span::Span;
353 ///
354 /// let a = Span::new(0, u32::MAX);
355 /// assert_eq!(a.expand_right(5), Span::new(0, u32::MAX));
356 /// ```
357 #[must_use]
358 pub const fn expand_right(self, offset: u32) -> Self {
359 Self::new(self.start, self.end.saturating_add(offset))
360 }
361
362 /// Create a [`Span`] that has its end position moved to the left by
363 /// `offset` bytes.
364 ///
365 /// It is a logical error to shrink the end of the [`Span`] past its start
366 /// position.
367 ///
368 /// # Example
369 ///
370 /// ```
371 /// use oxc_span::Span;
372 ///
373 /// let a = Span::new(5, 10);
374 /// let shrunk = a.shrink_right(5);
375 /// assert_eq!(shrunk, Span::new(5, 5));
376 ///
377 /// // Shrinking past the start of the span is a logical error that will panic
378 /// // in debug builds.
379 /// std::panic::catch_unwind(|| {
380 /// shrunk.shrink_right(5);
381 /// });
382 /// ```
383 #[must_use]
384 pub const fn shrink_right(self, offset: u32) -> Self {
385 let end = self.end.saturating_sub(offset);
386 debug_assert!(self.start <= end);
387 Self::new(self.start, end)
388 }
389
390 /// Create a [`Span`] that has its start and end position moved to the left by
391 /// `offset` bytes.
392 ///
393 /// # Example
394 ///
395 /// ```
396 /// use oxc_span::Span;
397 ///
398 /// let a = Span::new(5, 10);
399 /// let moved = a.move_left(5);
400 /// assert_eq!(moved, Span::new(0, 5));
401 ///
402 /// // Moving the start over 0 is logical error that will panic in debug builds.
403 /// std::panic::catch_unwind(|| {
404 /// moved.move_left(5);
405 /// });
406 /// ```
407 #[must_use]
408 pub const fn move_left(self, offset: u32) -> Self {
409 let start = self.start.saturating_sub(offset);
410 #[cfg(debug_assertions)]
411 if start == 0 {
412 debug_assert!(self.start == offset, "Cannot move span past zero length");
413 }
414 Self::new(start, self.end.saturating_sub(offset))
415 }
416
417 /// Create a [`Span`] that has its start and end position moved to the right by
418 /// `offset` bytes.
419 ///
420 /// # Example
421 ///
422 /// ```
423 /// use oxc_span::Span;
424 ///
425 /// let a = Span::new(5, 10);
426 /// let moved = a.move_right(5);
427 /// assert_eq!(moved, Span::new(10, 15));
428 ///
429 /// // Moving the end over `u32::MAX` is logical error that will panic in debug builds.
430 /// std::panic::catch_unwind(|| {
431 /// moved.move_right(u32::MAX);
432 /// });
433 /// ```
434 #[must_use]
435 pub const fn move_right(self, offset: u32) -> Self {
436 let end = self.end.saturating_add(offset);
437 #[cfg(debug_assertions)]
438 if end == u32::MAX {
439 debug_assert!(
440 u32::MAX.saturating_sub(offset) == self.end,
441 "Cannot move span past `u32::MAX` length"
442 );
443 }
444 Self::new(self.start.saturating_add(offset), end)
445 }
446
447 /// Get a snippet of text from a source string that the [`Span`] covers.
448 ///
449 /// # Example
450 /// ```
451 /// use oxc_span::Span;
452 ///
453 /// let source = "function add (a, b) { return a + b; }";
454 /// let name_span = Span::new(9, 12);
455 /// let name = name_span.source_text(source);
456 /// assert_eq!(name_span.size(), name.len() as u32);
457 /// ```
458 pub fn source_text(self, source_text: &str) -> &str {
459 &source_text[self.start as usize..self.end as usize]
460 }
461
462 /// Create a [`LabeledSpan`] covering this [`Span`] with the given label.
463 ///
464 /// Use [`Span::primary_label`] if this is the primary span for the diagnostic.
465 #[must_use]
466 pub fn label<S: Into<String>>(self, label: S) -> LabeledSpan {
467 LabeledSpan::new_with_span(Some(label.into()), self)
468 }
469
470 /// Creates a primary [`LabeledSpan`] covering this [`Span`] with the given label.
471 #[must_use]
472 pub fn primary_label<S: Into<String>>(self, label: S) -> LabeledSpan {
473 LabeledSpan::new_primary_with_span(Some(label.into()), self)
474 }
475
476 /// Creates a primary [`LabeledSpan`] covering this [`Span`].
477 #[must_use]
478 pub fn primary(self) -> LabeledSpan {
479 LabeledSpan::new_primary_with_span(None, self)
480 }
481
482 /// Convert [`Span`] to a single `u64`.
483 ///
484 /// On 64-bit platforms, `Span` is aligned on 8, so equivalent to a `u64`.
485 /// Compiler boils this conversion down to a no-op on 64-bit platforms.
486 /// <https://godbolt.org/z/9rcMoT1fc>
487 ///
488 /// Do not use this on 32-bit platforms as it's likely to be less efficient.
489 ///
490 /// Note: `#[ast]` macro adds `#[repr(C)]` to the struct, so field order is guaranteed.
491 #[expect(clippy::inline_always)] // Because this is a no-op on 64-bit platforms.
492 #[inline(always)]
493 const fn as_u64(self) -> u64 {
494 if cfg!(target_endian = "little") {
495 ((self.end as u64) << 32) | (self.start as u64)
496 } else {
497 ((self.start as u64) << 32) | (self.end as u64)
498 }
499 }
500
501 /// Compare two [`Span`]s.
502 ///
503 /// Same as `PartialEq::eq`, but a const function, and takes owned `Span`s.
504 //
505 // `#[inline(always)]` because want to make sure this is inlined into `PartialEq::eq`.
506 #[expect(clippy::inline_always)]
507 #[inline(always)]
508 const fn const_eq(self, other: Self) -> bool {
509 if cfg!(target_pointer_width = "64") {
510 self.as_u64() == other.as_u64()
511 } else {
512 self.start == other.start && self.end == other.end
513 }
514 }
515}
516
517impl Index<Span> for str {
518 type Output = str;
519
520 #[inline]
521 fn index(&self, index: Span) -> &Self::Output {
522 &self[index.start as usize..index.end as usize]
523 }
524}
525
526impl IndexMut<Span> for str {
527 #[inline]
528 fn index_mut(&mut self, index: Span) -> &mut Self::Output {
529 &mut self[index.start as usize..index.end as usize]
530 }
531}
532
533impl From<Range<u32>> for Span {
534 #[inline]
535 fn from(range: Range<u32>) -> Self {
536 Self::new(range.start, range.end)
537 }
538}
539
540impl From<Span> for SourceSpan {
541 fn from(val: Span) -> Self {
542 Self::new(SourceOffset::from(val.start as usize), val.size() as usize)
543 }
544}
545
546impl From<Span> for LabeledSpan {
547 fn from(val: Span) -> Self {
548 LabeledSpan::underline(val)
549 }
550}
551
552// On 64-bit platforms, compare `Span`s as single `u64`s, which is faster when used with `&Span` refs.
553// https://godbolt.org/z/sEf9MGvsr
554impl PartialEq for Span {
555 #[inline]
556 fn eq(&self, other: &Self) -> bool {
557 self.const_eq(*other)
558 }
559}
560
561// Skip hashing `_align` field.
562// On 64-bit platforms, hash `Span` as a single `u64`, which is faster with `FxHash`.
563// https://godbolt.org/z/4fbvcsTxM
564impl Hash for Span {
565 #[inline] // We exclusively use `FxHasher`, which produces small output hashing `u64`s and `u32`s
566 fn hash<H: Hasher>(&self, hasher: &mut H) {
567 if cfg!(target_pointer_width = "64") {
568 self.as_u64().hash(hasher);
569 } else {
570 self.start.hash(hasher);
571 self.end.hash(hasher);
572 }
573 }
574}
575
576// Skip `_align` field in `Debug` output
577#[expect(clippy::missing_fields_in_debug)]
578impl Debug for Span {
579 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
580 f.debug_struct("Span").field("start", &self.start).field("end", &self.end).finish()
581 }
582}
583
584/// Get the span for an AST node.
585pub trait GetSpan {
586 /// Get the [`Span`] for an AST node.
587 fn span(&self) -> Span;
588}
589
590/// Get mutable ref to span for an AST node.
591pub trait GetSpanMut {
592 /// Get a mutable reference to an AST node's [`Span`].
593 fn span_mut(&mut self) -> &mut Span;
594}
595
596impl GetSpan for Span {
597 #[inline]
598 fn span(&self) -> Span {
599 *self
600 }
601}
602
603impl GetSpanMut for Span {
604 #[inline]
605 fn span_mut(&mut self) -> &mut Span {
606 self
607 }
608}
609
610impl<'a> CloneIn<'a> for Span {
611 type Cloned = Self;
612
613 #[inline]
614 fn clone_in(&self, _: &'a Allocator) -> Self {
615 *self
616 }
617}
618
619impl<'a> Dummy<'a> for Span {
620 /// Create a dummy [`Span`].
621 #[expect(clippy::inline_always)]
622 #[inline(always)]
623 fn dummy(_allocator: &'a Allocator) -> Self {
624 SPAN
625 }
626}
627
628#[cfg(feature = "serialize")]
629impl Serialize for Span {
630 fn serialize<S: SerdeSerializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
631 let mut map = serializer.serialize_map(None)?;
632 map.serialize_entry("start", &self.start)?;
633 map.serialize_entry("end", &self.end)?;
634 map.end()
635 }
636}
637
638#[cfg(feature = "serialize")]
639impl ESTreeSpan for Span {
640 #[expect(clippy::inline_always)] // `#[inline(always)]` because it's a no-op
641 #[inline(always)]
642 fn range(self) -> [u32; 2] {
643 [self.start, self.end]
644 }
645}
646
647/// Zero-sized type which has pointer alignment (8 on 64-bit, 4 on 32-bit).
648#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
649#[repr(transparent)]
650struct PointerAlign([usize; 0]);
651
652impl PointerAlign {
653 #[inline]
654 const fn new() -> Self {
655 Self([])
656 }
657}
658
659#[cfg(test)]
660mod test {
661 use super::Span;
662
663 #[test]
664 fn test_size() {
665 let s = Span::sized(0, 5);
666 assert_eq!(s.size(), 5);
667 assert!(!s.is_empty());
668
669 let s = Span::sized(5, 0);
670 assert_eq!(s.size(), 0);
671 assert!(s.is_empty());
672 }
673
674 #[test]
675 fn test_hash() {
676 use std::hash::{DefaultHasher, Hash, Hasher};
677 fn hash<T: Hash>(value: T) -> u64 {
678 let mut hasher = DefaultHasher::new();
679 value.hash(&mut hasher);
680 hasher.finish()
681 }
682
683 let first_hash = hash(Span::new(1, 5));
684 let second_hash = hash(Span::new(1, 5));
685 assert_eq!(first_hash, second_hash);
686
687 // On 64-bit platforms, check hash is equivalent to `u64`
688 #[cfg(target_pointer_width = "64")]
689 {
690 let u64_equivalent: u64 =
691 if cfg!(target_endian = "little") { 1 + (5 << 32) } else { (1 << 32) + 5 };
692 let u64_hash = hash(u64_equivalent);
693 assert_eq!(first_hash, u64_hash);
694 }
695
696 // On 32-bit platforms, check `_align` field does not alter hash
697 #[cfg(not(target_pointer_width = "64"))]
698 {
699 #[derive(Hash)]
700 #[repr(C)]
701 struct PlainSpan {
702 start: u32,
703 end: u32,
704 }
705
706 let plain_hash = hash(PlainSpan { start: 1, end: 5 });
707 assert_eq!(first_hash, plain_hash);
708 }
709 }
710
711 #[test]
712 fn test_eq() {
713 assert_eq!(Span::new(0, 0), Span::new(0, 0));
714 assert_eq!(Span::new(0, 1), Span::new(0, 1));
715 assert_eq!(Span::new(1, 5), Span::new(1, 5));
716
717 assert_ne!(Span::new(0, 0), Span::new(0, 1));
718 assert_ne!(Span::new(1, 5), Span::new(0, 5));
719 assert_ne!(Span::new(1, 5), Span::new(2, 5));
720 assert_ne!(Span::new(1, 5), Span::new(1, 4));
721 assert_ne!(Span::new(1, 5), Span::new(1, 6));
722 }
723
724 #[test]
725 fn test_ordering_less() {
726 assert!(Span::new(0, 0) < Span::new(0, 1));
727 assert!(Span::new(0, 3) < Span::new(2, 5));
728 }
729
730 #[test]
731 fn test_ordering_greater() {
732 assert!(Span::new(0, 1) > Span::new(0, 0));
733 assert!(Span::new(2, 5) > Span::new(0, 3));
734 }
735
736 #[test]
737 fn test_contains() {
738 let span = Span::new(5, 10);
739
740 assert!(span.contains_inclusive(span));
741 assert!(span.contains_inclusive(Span::new(5, 5)));
742 assert!(span.contains_inclusive(Span::new(10, 10)));
743 assert!(span.contains_inclusive(Span::new(6, 9)));
744
745 assert!(!span.contains_inclusive(Span::new(0, 0)));
746 assert!(!span.contains_inclusive(Span::new(4, 10)));
747 assert!(!span.contains_inclusive(Span::new(5, 11)));
748 assert!(!span.contains_inclusive(Span::new(4, 11)));
749 }
750
751 #[test]
752 fn test_expand() {
753 let span = Span::new(3, 5);
754 assert_eq!(span.expand(0), Span::new(3, 5));
755 assert_eq!(span.expand(1), Span::new(2, 6));
756 // start and end cannot be expanded past `0` and `u32::MAX`, respectively
757 assert_eq!(span.expand(5), Span::new(0, 10));
758 }
759
760 #[test]
761 fn test_shrink() {
762 let span = Span::new(4, 8);
763 assert_eq!(span.shrink(0), Span::new(4, 8));
764 assert_eq!(span.shrink(1), Span::new(5, 7));
765 // can be equal
766 assert_eq!(span.shrink(2), Span::new(6, 6));
767 }
768
769 #[test]
770 #[should_panic(expected = "Cannot shrink span past zero length")]
771 fn test_shrink_past_start() {
772 let span = Span::new(5, 10);
773 let _ = span.shrink(5);
774 }
775
776 #[test]
777 fn test_move_left() {
778 let span = Span::new(5, 10);
779 assert_eq!(span.move_left(1), Span::new(4, 9));
780 assert_eq!(span.move_left(2), Span::new(3, 8));
781 assert_eq!(span.move_left(5), Span::new(0, 5));
782 }
783
784 #[test]
785 #[should_panic(expected = "Cannot move span past zero length")]
786 fn test_move_past_start() {
787 let span = Span::new(5, 10);
788 let _ = span.move_left(6);
789 }
790
791 #[test]
792 fn test_move_right() {
793 let span: Span = Span::new(5, 10);
794 assert_eq!(span.move_right(1), Span::new(6, 11));
795 assert_eq!(span.move_right(2), Span::new(7, 12));
796 assert_eq!(
797 span.move_right(u32::MAX.saturating_sub(10)),
798 Span::new(u32::MAX.saturating_sub(5), u32::MAX)
799 );
800 }
801
802 #[test]
803 #[should_panic(expected = "Cannot move span past `u32::MAX` length")]
804 fn test_move_past_end() {
805 let span = Span::new(u32::MAX.saturating_sub(2), u32::MAX.saturating_sub(1));
806 let _ = span.move_right(2);
807 }
808}
809
810#[cfg(test)]
811mod doctests {
812 use super::Span;
813
814 /// Tests from [`Span`] docs, since rustdoc test runner is disabled
815 #[test]
816 fn doctest() {
817 // 1
818 let text = "foo bar baz";
819 let span = Span::new(4, 7);
820 assert_eq!(&text[span], "bar");
821
822 // 2
823 let a = Span::new(5, 10); // Start and end offsets
824 let b = Span::sized(5, 5); // Start offset and size
825 assert_eq!(a, b);
826
827 // 3
828 let s = Span::new(5, 10);
829 assert_eq!(s.shrink(2), Span::new(7, 8));
830 assert_eq!(s.shrink(2), s.shrink_left(2).shrink_right(2));
831
832 assert_eq!(s.expand(5), Span::new(0, 15));
833 assert_eq!(s.expand(5), s.expand_left(5).expand_right(5));
834 }
835}
836
837#[cfg(test)]
838mod size_asserts {
839 use std::mem::{align_of, size_of};
840
841 use super::Span;
842
843 const _: () = assert!(size_of::<Span>() == 8);
844
845 #[cfg(target_pointer_width = "64")]
846 const _: () = assert!(align_of::<Span>() == 8);
847
848 #[cfg(not(target_pointer_width = "64"))]
849 const _: () = assert!(align_of::<Span>() == 4);
850}