Skip to main content

typst_syntax/
span.rs

1use std::fmt::{self, Debug, Formatter};
2use std::num::{NonZeroU16, NonZeroU32, NonZeroU64};
3use std::ops::Range;
4
5use ecow::{EcoString, eco_format};
6
7use crate::FileId;
8
9/// Defines a range of text in a Typst source file.
10///
11/// Spans are used throughout the compiler to track which source section an
12/// element stems from or an error/warning applies to. Errors and warnings use
13/// the [`DiagSpan`] type which can contain either a normal span or a range
14/// targeting a location in a non-Typst file, such as a JSON parsing error.
15///
16/// - The [`.id()`](Self::id) method can be used to get the [`FileId`] for the
17///   span and, by extension, its file system path.
18/// - The `WorldExt::range` function can be used to map the span to a
19///   `Range<usize>`.
20///
21/// This type is stored compactly in 8 bytes, and is copyable and null-optimized
22/// (i.e. `Option<Span>` also takes 8 bytes), but can be expanded for easier
23/// usage into the [`SpanKind`] enum via [`Self::get()`].
24///
25/// Spans internally distinguish between four kinds of values, these are
26/// accessible as the [`SpanKind`] or [`DiagSpanKind`] enums via the
27/// [`Span::get`] or [`DiagSpan::get`] methods.
28/// 1. They can be detached, originating from nowhere or from the compiler
29///    itself.
30/// 2. They can be numbered values, corresponding to a node in a Typst source
31///    file's concrete syntax tree. These are the most common span type and are
32///    explained more below.
33/// 3. They can be raw range spans, containing a range of two indices that came
34///    from parsing a text as Typst syntax. The file itself is not necessarily a
35///    Typst source file. The maximum value for the start/end of these ranges is
36///    `2^23-1`, larger values will be saturated.
37/// 4. They can be an external start index, used for diagnostics on externally
38///    loaded text files. These are only accessible as part of a [`DiagSpan`]
39///    which also contains the end index. The maximum value for the start/end of
40///    these ranges is `2^46-1`, larger values will be saturated.
41///
42/// # Numbered spans
43/// Typst source files use _numbered spans._ Rather than using byte ranges,
44/// which shift a lot as you type, each syntax tree node gets a unique number.
45///
46/// During editing, the span numbers stay mostly stable, even for nodes behind
47/// an insertion. This is not true for simple ranges as they would shift. This
48/// allows spans to be used as inputs to memoized functions without hurting
49/// cache performance when text is inserted somewhere in the document other than
50/// the end.
51///
52/// Span numbers are ordered in the syntax tree to enable quickly finding the
53/// node of a known span:
54/// - The span number of a parent node is always smaller than the number of any
55///   of its children
56/// - The span numbers of sibling nodes always increase from left to right
57///
58/// Combining those guarantees, we have that for siblings in order [A, B, C],
59/// the span numbers for node A and _all of A's children_ are less than node B's
60/// span number, and the numbers for node C and all of C's children are greater
61/// than B's span number.
62#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
63pub struct Span(NonZeroU64);
64
65/// The unique number of a span within its [`Source`](crate::Source). Known to
66/// be within the range of `Span::FULL`.
67///
68/// This is mainly used externally as an input to the
69/// [`Source::range`](crate::Source::range) method for efficiently finding the
70/// byte range of a span.
71#[derive(Debug, Copy, Clone, Eq, PartialEq)]
72pub struct SpanNumber(pub(crate) u64);
73
74/// The possible kinds of span.
75#[derive(Debug)]
76pub enum SpanKind {
77    /// A span that does not point into any file.
78    Detached,
79    /// A numbered span.
80    Number { id: FileId, num: SpanNumber },
81    /// A raw byte range in a file.
82    Range { id: FileId, range: Range<usize> },
83}
84
85impl Span {
86    /// The full range of numbers available for source file span numbering.
87    pub(crate) const FULL: Range<u64> = 2..(1 << 47);
88
89    /// The value reserved for the detached span.
90    const DETACHED: Self = Self(NonZeroU64::new(1).unwrap());
91
92    /// The span's internal data is laid out with the high 16 bits as the file
93    /// id and the low 48 bits as the span number:
94    /// | 16 bits file id | 48 bits number |
95    ///
96    /// Possible values for the span number are:
97    /// - always non-zero
98    /// - Detached: 1 (file id is 0, otherwise non-zero)
99    /// - Typst source file:           2 ..= 2^47-1      (one 47-bit number)
100    /// - External file start:      2^47 ..= 2^47+2^46-1 (one 46-bit number)
101    /// - Internal range span: 2^47+2^46 ..= 2^48-1      (two 23-bit numbers)
102    const NUMBER_BITS: usize = 48;
103    const RANGE_BITS: usize = 46;
104    const FILE_ID_SHIFT: usize = Self::NUMBER_BITS;
105    const NUMBER_MASK: u64 = (1 << Self::NUMBER_BITS) - 1;
106    const EXTERNAL_BASE: u64 = Self::FULL.end;
107    const EXTERNAL_VALUE_MAX: u64 = (1 << Self::RANGE_BITS) - 1;
108    const RANGE_BASE: u64 = Self::EXTERNAL_BASE + (1 << Self::RANGE_BITS);
109    const RANGE_VALUE_BITS: usize = 23;
110    const RANGE_VALUE_MAX: u64 = (1 << Self::RANGE_VALUE_BITS) - 1;
111
112    /// The detached span that does not point into any file.
113    pub const fn detached() -> Self {
114        Self::DETACHED
115    }
116
117    /// Create a new span from a [`FileId`] and a [`SpanNumber`].
118    pub(crate) const fn from_number(id: FileId, SpanNumber(number): SpanNumber) -> Self {
119        debug_assert!(Self::FULL.start <= number);
120        debug_assert!(number < Self::FULL.end);
121        Self::pack(id, number)
122    }
123
124    /// Create a new span from a raw byte range instead of a span number.
125    ///
126    /// If one of the range's parts exceeds the maximum value of `2^23-1`, it is
127    /// saturated.
128    pub(crate) const fn from_range(id: FileId, range: Range<usize>) -> Self {
129        let start = saturate(range.start, Self::RANGE_VALUE_MAX);
130        let end = saturate(range.end, Self::RANGE_VALUE_MAX);
131        let number = (start << Self::RANGE_VALUE_BITS) | end;
132        Self::pack(id, Self::RANGE_BASE + number)
133    }
134
135    /// Construct from a raw number.
136    ///
137    /// Should only be used with numbers retrieved via
138    /// [`into_raw`](Self::into_raw). Misuse may results in panics, but no
139    /// unsafety.
140    pub const fn from_raw(v: NonZeroU64) -> Self {
141        Self(v)
142    }
143
144    /// Pack a file ID and the low bits into a span.
145    const fn pack(id: FileId, low: u64) -> Self {
146        let bits = ((id.into_raw().get() as u64) << Self::FILE_ID_SHIFT) | low;
147
148        // The file ID is non-zero.
149        Self(NonZeroU64::new(bits).unwrap())
150    }
151
152    /// Whether the span is detached.
153    pub const fn is_detached(self) -> bool {
154        self.0.get() == Self::DETACHED.0.get()
155    }
156
157    /// The id of the file the span points into.
158    ///
159    /// Returns `None` if the span is detached.
160    pub const fn id(self) -> Option<FileId> {
161        // Detached span has only zero high bits, so it will trigger the
162        // `None` case.
163        match NonZeroU16::new((self.0.get() >> Self::FILE_ID_SHIFT) as u16) {
164            Some(v) => Some(FileId::from_raw(v)),
165            None => None,
166        }
167    }
168
169    /// The unique number of the span within its [`Source`](crate::Source).
170    pub(crate) const fn number(self) -> u64 {
171        self.0.get() & Self::NUMBER_MASK
172    }
173
174    /// Unpack the span into the variants of a [`SpanKind`] for easier use.
175    ///
176    /// To access a range, you may want to use `WorldExt::range` instead.
177    pub const fn get(self) -> SpanKind {
178        let Some(id) = self.id() else { return SpanKind::Detached };
179        let num = self.number();
180        if let Some(packed_range) = num.checked_sub(Self::RANGE_BASE) {
181            let start = (packed_range >> Self::RANGE_VALUE_BITS) as usize;
182            let end = (packed_range & Self::RANGE_VALUE_MAX) as usize;
183            SpanKind::Range { id, range: start..end }
184        } else {
185            SpanKind::Number { id, num: SpanNumber(num) }
186        }
187    }
188
189    /// Extract the raw underlying number.
190    pub const fn into_raw(self) -> NonZeroU64 {
191        self.0
192    }
193
194    /// Return `other` if `self` is detached and `self` otherwise.
195    pub fn or(self, other: Self) -> Self {
196        if self.is_detached() { other } else { self }
197    }
198
199    /// Find the first non-detached span in the iterator.
200    pub fn find(iter: impl IntoIterator<Item = Self>) -> Self {
201        iter.into_iter()
202            .find(|span| !span.is_detached())
203            .unwrap_or(Span::detached())
204    }
205}
206
207/// The span of a diagnostic message. Either from a Typst source file or from a
208/// loaded external file.
209///
210/// Typst source spans may additionally contain a sub-range targeting just part
211/// of the overall range of the span.
212///
213/// When storing an external file range, the maximum value of the start/end is
214/// `2^46-1`, larger values are saturated.
215///
216/// This type is stored compactly in 16 bytes and null-optimized, but can be
217/// expanded for easier usage as the [`DiagSpanKind`] enum via [`Self::get()`].
218#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
219pub struct DiagSpan {
220    span: Span,
221    extra: u64,
222}
223
224/// The possible kinds of a diagnostic span.
225#[derive(Debug)]
226pub enum DiagSpanKind {
227    /// A span that does not point into any file.
228    Detached,
229    /// A numbered span with an optional sub-range.
230    Number { id: FileId, num: SpanNumber, sub_range: Option<SubRange> },
231    /// A raw byte range in a file.
232    Range { id: FileId, range: Range<usize> },
233}
234
235impl DiagSpan {
236    /// The detached diagnostic span that does not point into any file.
237    pub const fn detached() -> Self {
238        Self { span: Span::DETACHED, extra: 0 }
239    }
240
241    /// Create a new diagnostic span from an external file's byte range instead
242    /// of an internal span.
243    ///
244    /// If either of the range's parts exceeds the maximum value of `2^46-1`, it
245    /// will be saturated.
246    pub fn from_range(id: FileId, range: Range<usize>) -> Self {
247        let start = saturate(range.start, Span::EXTERNAL_VALUE_MAX);
248        let end = saturate(range.end, Span::EXTERNAL_VALUE_MAX);
249        Self {
250            span: Span::pack(id, Span::EXTERNAL_BASE + start),
251            extra: end,
252        }
253    }
254
255    /// Create a new diagnostic span from a source span and optional sub-range.
256    ///
257    /// The preferred interface for converting a [`Span`] into [`DiagSpan`] is
258    /// using `span.into()`, which calls this with `sub_range: None`.
259    pub fn from_span(span: Span, sub_range: Option<SubRange>) -> Self {
260        let extra = sub_range.map_or(0, |SubRange { start, end }| {
261            ((start as u64) << 32) | (end.get() as u64)
262        });
263        Self { span, extra }
264    }
265
266    /// Whether the diagnostic span is detached.
267    pub const fn is_detached(self) -> bool {
268        self.span.0.get() == Span::DETACHED.0.get()
269    }
270
271    /// The id of the file the span points into.
272    ///
273    /// Returns `None` if the span is detached.
274    pub fn id(self) -> Option<FileId> {
275        self.span.id()
276    }
277
278    /// Return `other` if `self` is detached and `self` otherwise.
279    pub fn or(self, other: Self) -> Self {
280        if self.is_detached() { other } else { self }
281    }
282
283    /// Unpack the diagnostic span into the variants of a [`DiagSpanKind`] for
284    /// easier use.
285    ///
286    /// To access a range, you may want to use `WorldExt::range` instead.
287    pub fn get(self) -> DiagSpanKind {
288        let DiagSpan { span, extra } = self;
289        match span.get() {
290            SpanKind::Detached => DiagSpanKind::Detached,
291            SpanKind::Number { id, num } => {
292                if let Some(start) = num.0.checked_sub(Span::EXTERNAL_BASE) {
293                    // This `checked_sub` must come after the internal range
294                    // check from `span.get()`.
295                    let start = start as usize;
296                    let end = extra as usize;
297                    DiagSpanKind::Range { id, range: start..end }
298                } else {
299                    let sub_range = {
300                        let start = (extra >> 32) as u32;
301                        let end = NonZeroU32::new(extra as u32); // `as` truncates
302                        end.map(|end| SubRange { start, end })
303                    };
304                    DiagSpanKind::Number { id, num, sub_range }
305                }
306            }
307            SpanKind::Range { id, range } => {
308                if let Some(end) = NonZeroU32::new(extra as u32) {
309                    let start = (extra >> 32) as u32;
310                    let sub_range = SubRange { start, end };
311                    let range = sub_range.to_absolute(range.start);
312                    DiagSpanKind::Range { id, range }
313                } else {
314                    DiagSpanKind::Range { id, range }
315                }
316            }
317        }
318    }
319}
320
321impl From<Span> for DiagSpan {
322    fn from(span: Span) -> Self {
323        Self::from_span(span, None)
324    }
325}
326
327/// Saturate a value at a given maximum. Can't use `.min()` since it isn't
328/// stable in const :/
329const fn saturate(value: usize, max: u64) -> u64 {
330    if value as u64 > max { max } else { value as u64 }
331}
332
333/// A non-empty range targeting a smaller part of a spanned section of text.
334#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
335pub struct SubRange {
336    start: u32,
337    end: NonZeroU32,
338}
339
340impl SubRange {
341    /// Create a new sub-range. The given start and end must create a non-empty
342    /// range.
343    ///
344    /// If start or end are above a `2^32-1`, they will be saturated.
345    pub fn new(start: usize, end: usize) -> Option<Self> {
346        if start < end {
347            Some(Self {
348                start: to_u32_saturated(start),
349                // (0 <= start) && (start < end) --> (end != 0)
350                end: NonZeroU32::new(to_u32_saturated(end)).unwrap(),
351            })
352        } else {
353            None
354        }
355    }
356
357    /// Convert to a normal range relative to the spanned range.
358    pub fn to_relative(self) -> Range<usize> {
359        Range {
360            start: self.start as usize,
361            end: self.end.get() as usize,
362        }
363    }
364
365    /// Convert to a normal range at an offset.
366    pub fn to_absolute(self, offset: usize) -> Range<usize> {
367        Range {
368            start: self.start as usize + offset,
369            end: self.end.get() as usize + offset,
370        }
371    }
372}
373
374// Convert a `usize` to a `u32` by saturating at `u32::MAX`.
375fn to_u32_saturated(value: usize) -> u32 {
376    value.try_into().unwrap_or(u32::MAX)
377}
378
379/// A value with a span locating it in the source code.
380#[derive(Copy, Clone, Eq, PartialEq, Hash)]
381#[expect(private_bounds)]
382pub struct Spanned<T, S: Copy + SpanDetached = Span> {
383    /// The spanned value.
384    pub v: T,
385    /// The value's location in source code.
386    pub span: S,
387}
388
389#[expect(private_bounds)]
390impl<T, S: Copy + SpanDetached> Spanned<T, S> {
391    /// Create a new instance from a value and its span.
392    pub const fn new(v: T, span: S) -> Self {
393        Self { v, span }
394    }
395
396    /// Create a new instance with a span that does not point into any file.
397    pub const fn detached(v: T) -> Self {
398        Self { v, span: S::SPAN_DETACHED }
399    }
400
401    /// Convert from `&Spanned<T>` to `Spanned<&T>`
402    pub const fn as_ref(&self) -> Spanned<&T, S> {
403        Spanned { v: &self.v, span: self.span }
404    }
405
406    /// Map the value using a function.
407    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U, S> {
408        Spanned { v: f(self.v), span: self.span }
409    }
410}
411
412impl<T: Debug, S: Copy + SpanDetached> Debug for Spanned<T, S> {
413    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
414        self.v.fmt(f)
415    }
416}
417
418/// Allows generic access to the detached span. Only used to implement
419/// [`Spanned::detached`].
420trait SpanDetached {
421    const SPAN_DETACHED: Self;
422}
423
424impl SpanDetached for Span {
425    const SPAN_DETACHED: Self = Self::detached();
426}
427
428impl SpanDetached for DiagSpan {
429    const SPAN_DETACHED: Self = Self::detached();
430}
431
432/// Remaps ranges.
433///
434/// Useful in combination with
435/// [`SyntaxNode::synthesize_mapped`](super::SyntaxNode::synthesize_mapped) to
436/// have accurate error spans for source text that is non-consecutive in its
437/// source file (for instance, Typst code in a doc comment with start-of-line
438/// slashes).
439#[derive(Hash)]
440pub struct RangeMapper {
441    vec: Vec<Mapping>,
442    total: usize,
443}
444
445/// A mapping from an old index to a new one, guarantees that `old <= new`.
446#[derive(Hash, Clone, Copy)]
447struct Mapping {
448    old: usize,
449    new: usize,
450}
451
452impl RangeMapper {
453    /// Creates a new range mapper.
454    ///
455    /// The iterator should returns ranges in the original text that will be
456    /// consecutively concatenated to produce the derived text.
457    ///
458    /// Segments should be in order. (The start of a later range must not
459    /// precede the end of an earlier range.)
460    ///
461    /// Note that this representation implies that ranges can only ever increase
462    /// in their start position and length when mapped.
463    pub fn new(
464        segments: impl IntoIterator<Item = Range<usize>>,
465    ) -> Result<Self, EcoString> {
466        let mut map = Mapping { old: 0, new: 0 };
467        let vec = segments
468            .into_iter()
469            .map(|Range { start, end }| {
470                if start > end || map.new > start {
471                    return Err(eco_format!("invalid mapper segment: ({start}, {end})"));
472                }
473                map.new = start;
474                let segment_map = map;
475                map.old += end - start;
476                Ok(segment_map)
477            })
478            .collect::<Result<Vec<Mapping>, EcoString>>()?;
479
480        if vec.is_empty() {
481            Ok(Self { vec: vec![map], total: 0 })
482        } else {
483            Ok(Self { vec, total: map.old })
484        }
485    }
486
487    /// The total length of the original text.
488    pub(crate) fn total_len(&self) -> usize {
489        self.total
490    }
491
492    /// Maps a range in the derived text back to a range in the original text.
493    /// If the range spans over multiple segments, the gap between the two
494    /// segments will be included in the resulting range.
495    ///
496    /// Input ranges must have  `start <= end`, and the caller should have
497    /// verified that `end <= self.total`.
498    pub(crate) fn map(&self, range: Range<usize>) -> Range<usize> {
499        debug_assert!(range.start <= range.end);
500        if range.end == 0 {
501            // Handles the panic case of `map_end`.
502            let offset = self.vec[0].new;
503            offset..offset
504        } else if range.start == range.end {
505            // If start/end are at a boundary, map them to the first position,
506            // not the second.
507            let offset = self.map_end(range.start);
508            offset..offset
509        } else {
510            // We now know that `start < end`, so the values from `map_start`
511            // and `map_end` must be non-overlapping.
512            let start = self.map_start(range.start);
513            let end = self.map_end(range.end);
514            start..end
515        }
516    }
517
518    /// Map a relative sub-range at an offset to a new sub-range. If the
519    /// sub-range spans over multiple segments, the gap between them will be
520    /// included in the new sub-range.
521    pub(crate) fn map_sub_range(&self, offset: usize, sub_range: SubRange) -> SubRange {
522        let range = sub_range.to_absolute(offset);
523        let new_offset = self.map_start(offset);
524        let start = self.map_start(range.start);
525        let end = self.map_end(range.end); // sub-ranges have `start < end`.
526        SubRange::new(start - new_offset, end - new_offset).unwrap()
527    }
528
529    /// Map a single offset, preferring the second index if at a boundary.
530    fn map_start(&self, offset: usize) -> usize {
531        let idx = self.vec.partition_point(|&Mapping { old, new: _ }| old <= offset);
532        // Subtracting by 1 is valid: vec is non-empty, index 0 has `old == 0`,
533        // and `partition_point` returns the index of the first item to fail the
534        // predicate (or the length), which is not index 0, since `0 <= usize`
535        // is true for all usize.
536        let Mapping { old, new } = &self.vec[idx - 1];
537        new + (offset - old)
538    }
539
540    /// Map a single offset, preferring the first index if at a boundary.
541    ///
542    /// This will panic if `offset` is 0.
543    fn map_end(&self, offset: usize) -> usize {
544        debug_assert_ne!(offset, 0);
545        let idx = self.vec.partition_point(|&Mapping { old, new: _ }| old < offset);
546        // Unlike `map_start`, this can yield index 0 when `offset == 0`, making
547        // `idx - 1` potentially panicking.
548        let Mapping { old, new } = &self.vec[idx - 1];
549        new + (offset - old)
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use super::*;
556
557    #[test]
558    fn test_span_detached() {
559        let span = Span::detached();
560        assert!(span.is_detached());
561        assert_eq!(span.id(), None);
562    }
563
564    #[test]
565    fn test_span_number_encoding() {
566        let id = FileId::from_raw(NonZeroU16::new(5).unwrap());
567        let span = Span::from_number(id, SpanNumber(10));
568        assert_eq!(span.id(), Some(id));
569        assert_eq!(span.number(), 10);
570    }
571
572    #[test]
573    fn test_span_range_encoding() {
574        let file_id = FileId::from_raw(NonZeroU16::new(u16::MAX).unwrap());
575        let roundtrip = |range: Range<usize>| {
576            let span = Span::from_range(file_id, range.clone());
577            let SpanKind::Range { id, range: actual } = span.get() else {
578                panic!("bad span kind")
579            };
580            assert_eq!(id, file_id);
581            assert_eq!(actual, range);
582        };
583
584        roundtrip(0..0);
585        roundtrip(177..233);
586        roundtrip(0..8388607);
587        roundtrip(8388606..8388607); // 2^23-2 .. 2^23-1
588    }
589
590    #[test]
591    fn test_diag_span_range() {
592        let file_id = FileId::from_raw(NonZeroU16::new(u16::MAX).unwrap());
593        let roundtrip = |range: Range<usize>| {
594            let span = DiagSpan::from_range(file_id, range.clone());
595            let DiagSpanKind::Range { id, range: actual } = span.get() else {
596                panic!("bad diagspan kind")
597            };
598            assert_eq!(id, file_id);
599            assert_eq!(actual, range);
600        };
601
602        roundtrip(0..0);
603        roundtrip(177..233);
604        roundtrip(0..8388607);
605        roundtrip(8388606..8388607); // 2^23-2 .. 2^23-1
606        roundtrip(8388608..8388609); // 2^23   .. 2^23+1
607        #[cfg(target_pointer_width = "64")]
608        roundtrip(70368744177662..70368744177663); // 2^46-2 .. 2^46-1
609    }
610
611    #[test]
612    fn test_sub_range_constructor() {
613        let max = u32::MAX as usize;
614        // valid
615        assert!(SubRange::new(0, 1).is_some());
616        assert!(SubRange::new(4, 5).is_some());
617        assert!(SubRange::new(0, max).is_some());
618        assert!(SubRange::new(0, max - 1).is_some());
619        assert!(SubRange::new(max - 1, max).is_some());
620        // invalid
621        assert!(SubRange::new(0, 0).is_none());
622        assert!(SubRange::new(5, 5).is_none());
623        assert!(SubRange::new(5, 4).is_none());
624        assert!(SubRange::new(max - 1, max - 1).is_none());
625        assert!(SubRange::new(max, max).is_none());
626    }
627
628    #[cfg(target_pointer_width = "64")]
629    #[test]
630    fn test_sub_range_saturating() {
631        // Values saturate at 2^32-1
632        let max = u32::MAX as usize;
633        let maxxed = SubRange::new(max, max + 1).unwrap();
634        assert_eq!(maxxed.start, maxxed.end.get());
635        assert_eq!(SubRange::new(1 << 47, 1 << 63), Some(maxxed));
636    }
637
638    #[test]
639    fn test_range_mapper() {
640        let base = "-- Hello\n-- world\n";
641        let ranges = [(3..9), (12..18)];
642        let mapped = ranges.iter().map(|r| &base[r.clone()]).collect::<String>();
643        let m = RangeMapper::new(ranges).unwrap();
644
645        assert_eq!(mapped, "Hello\nworld\n");
646        assert_eq!(m.map(2..3), 5..6); // l -> l
647        assert_eq!(m.map(4..6), (7..9)); // o\n -> o\n
648        assert_eq!(m.map(6..8), (12..14)); // wo -> wo
649        assert_eq!(m.map(8..11), (14..17)); // rld -> rld
650        assert_eq!(m.map(2..12), (5..18)); // llo\n-- world\n -> llo\n-- world\n
651
652        // Empty ranges on boundaries:
653        assert_eq!(m.map(0..0), (3..3));
654        assert_eq!(m.map(6..6), (9..9)); // maps to the left of the boundary
655        assert_eq!(m.map(12..12), (18..18));
656    }
657
658    /// Small exhaustive edge case tests for the range mapper
659    #[test]
660    fn test_range_mapper_exhaustive() {
661        let empty = RangeMapper::new([]).unwrap();
662        assert_eq!(empty.map(0..0), 0..0);
663
664        let exact = RangeMapper::new(Some(0..1)).unwrap();
665        assert_eq!(exact.map(0..0), 0..0);
666        assert_eq!(exact.map(0..1), 0..1);
667        assert_eq!(exact.map(1..1), 1..1);
668
669        let plus = RangeMapper::new(Some(10..11)).unwrap();
670        assert_eq!(plus.map(0..0), 10..10);
671        assert_eq!(plus.map(0..1), 10..11);
672        assert_eq!(plus.map(1..1), 11..11);
673
674        let disjoint = RangeMapper::new([(10..11), (21..22)]).unwrap();
675        assert_eq!(disjoint.map(0..0), 10..10);
676        assert_eq!(disjoint.map(0..1), 10..11);
677        assert_eq!(disjoint.map(0..2), 10..22);
678        assert_eq!(disjoint.map(1..1), 11..11);
679        assert_eq!(disjoint.map(1..2), 21..22);
680        assert_eq!(disjoint.map(2..2), 22..22);
681
682        // disjoint with interspersed empty ranges.
683        let with_empty = RangeMapper::new([
684            (10..10),
685            (10..11),
686            (11..11),
687            (16..16),
688            (21..21),
689            (21..22),
690            (22..22),
691        ])
692        .unwrap();
693        assert_eq!(with_empty.map(0..0), 10..10);
694        assert_eq!(with_empty.map(0..1), 10..11);
695        assert_eq!(with_empty.map(0..2), 10..22);
696        assert_eq!(with_empty.map(1..1), 11..11);
697        assert_eq!(with_empty.map(1..2), 21..22);
698        assert_eq!(with_empty.map(2..2), 22..22);
699    }
700
701    #[test]
702    fn test_sub_range_mapping() {
703        let base = "01_23__45";
704        let ranges = [(0..2), (3..5), (7..9)];
705        let mapped = ranges.iter().map(|r| &base[r.clone()]).collect::<String>();
706        assert_eq!(mapped, "012345");
707        let m = RangeMapper::new(ranges).unwrap();
708
709        let map_at = |at: usize, sr: Option<SubRange>| {
710            let sub_range = sr.unwrap();
711            m.map_sub_range(at, sub_range).to_relative()
712        };
713
714        // Ranges within each section:
715        assert_eq!(map_at(0, SubRange::new(0, 1)), 0..1); // 0
716        assert_eq!(map_at(0, SubRange::new(2, 3)), 3..4); // 2
717        assert_eq!(map_at(0, SubRange::new(2, 4)), 3..5); // 23
718        assert_eq!(map_at(0, SubRange::new(4, 5)), 7..8); // 4
719        assert_eq!(map_at(1, SubRange::new(0, 1)), 0..1); // 1
720        assert_eq!(map_at(3, SubRange::new(0, 1)), 0..1); // 3
721        assert_eq!(map_at(4, SubRange::new(0, 2)), 0..2); // 45
722        // Across boundaries:
723        assert_eq!(map_at(1, SubRange::new(0, 2)), 0..3); // 12 -> 1_2
724        assert_eq!(map_at(0, SubRange::new(1, 3)), 1..4); // 12 -> 1_2
725        assert_eq!(map_at(3, SubRange::new(0, 2)), 0..4); // 34 -> 3__4
726        assert_eq!(map_at(0, SubRange::new(3, 5)), 4..8); // 34 -> 3__4
727        assert_eq!(map_at(1, SubRange::new(0, 4)), 0..7); // 1234 -> 1_23__4
728        assert_eq!(map_at(0, SubRange::new(1, 5)), 1..8); // 1234 -> 1_23__4
729        assert_eq!(map_at(0, SubRange::new(0, 6)), 0..9); // 012345 -> 01_23__45
730    }
731}