swc_common/
syntax_pos.rs

1use std::{
2    borrow::Cow,
3    cmp, fmt,
4    hash::{Hash, Hasher},
5    ops::{Add, Sub},
6    path::PathBuf,
7    sync::{atomic::AtomicU32, Mutex},
8};
9
10use bytes_str::BytesStr;
11use serde::{Deserialize, Serialize};
12use url::Url;
13
14use self::hygiene::MarkData;
15pub use self::hygiene::{Mark, SyntaxContext};
16use crate::{cache::CacheCell, rustc_data_structures::stable_hasher::StableHasher, sync::Lrc};
17
18mod analyze_source_file;
19pub mod hygiene;
20
21/// Spans represent a region of code, used for error reporting.
22///
23/// Positions in
24/// spans are *absolute* positions from the beginning of the `source_map`, not
25/// positions relative to `SourceFile`s. Methods on the `SourceMap` can be used
26/// to relate spans back to the original source.
27/// You must be careful if the span crosses more than one file - you will not be
28/// able to use many of the functions on spans in `source_map` and you cannot
29/// assume that the length of the `span = hi - lo`; there may be space in the
30/// `BytePos` range between files.
31#[derive(Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
32#[cfg_attr(
33    any(feature = "rkyv-impl"),
34    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
35)]
36#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
37#[cfg_attr(feature = "rkyv-impl", repr(C))]
38#[cfg_attr(
39    feature = "encoding-impl",
40    derive(::ast_node::Encode, ::ast_node::Decode)
41)]
42#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
43pub struct Span {
44    #[serde(rename = "start")]
45    #[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))]
46    pub lo: BytePos,
47    #[serde(rename = "end")]
48    #[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))]
49    pub hi: BytePos,
50}
51
52impl std::fmt::Debug for Span {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        write!(f, "{}..{}", self.lo.0, self.hi.0,)
55    }
56}
57
58impl From<(BytePos, BytePos)> for Span {
59    #[inline]
60    fn from(sp: (BytePos, BytePos)) -> Self {
61        Span::new(sp.0, sp.1)
62    }
63}
64
65impl From<Span> for (BytePos, BytePos) {
66    #[inline]
67    fn from(sp: Span) -> Self {
68        (sp.lo, sp.hi)
69    }
70}
71
72#[cfg(feature = "arbitrary")]
73#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
74impl<'a> arbitrary::Arbitrary<'a> for Span {
75    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
76        let lo = u.arbitrary::<BytePos>()?;
77        let hi = u.arbitrary::<BytePos>()?;
78
79        Ok(Self::new(lo, hi))
80    }
81}
82
83/// Dummy span, both position and length are zero, syntax context is zero as
84/// well.
85pub const DUMMY_SP: Span = Span {
86    lo: BytePos::DUMMY,
87    hi: BytePos::DUMMY,
88};
89
90/// PURE span, will emit `/* #__PURE__ */` comment in codegen.
91pub const PURE_SP: Span = Span {
92    lo: BytePos::PURE,
93    hi: BytePos::PURE,
94};
95
96/// Used for some special cases. e.g. mark the generated AST.
97pub const PLACEHOLDER_SP: Span = Span {
98    lo: BytePos::PLACEHOLDER,
99    hi: BytePos::PLACEHOLDER,
100};
101
102pub struct Globals {
103    hygiene_data: Mutex<hygiene::HygieneData>,
104    #[allow(unused)]
105    dummy_cnt: AtomicU32,
106    #[allow(unused)]
107    marks: Mutex<Vec<MarkData>>,
108}
109
110const DUMMY_RESERVE: u32 = u32::MAX - 2_u32.pow(16);
111
112impl Default for Globals {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118impl Globals {
119    pub fn new() -> Globals {
120        Globals {
121            hygiene_data: Mutex::new(hygiene::HygieneData::new()),
122            marks: Mutex::new(vec![MarkData {
123                parent: Mark::root(),
124            }]),
125            dummy_cnt: AtomicU32::new(DUMMY_RESERVE),
126        }
127    }
128
129    /// Clone the data from the current globals.
130    ///
131    /// Do not use this unless you know what you are doing.
132    pub fn clone_data(&self) -> Self {
133        Globals {
134            hygiene_data: Mutex::new(self.hygiene_data.lock().unwrap().clone()),
135            marks: Mutex::new(self.marks.lock().unwrap().clone()),
136            dummy_cnt: AtomicU32::new(self.dummy_cnt.load(std::sync::atomic::Ordering::SeqCst)),
137        }
138    }
139}
140
141better_scoped_tls::scoped_tls!(
142
143    /// Storage for span hygiene data.
144    ///
145    /// This variable is used to manage identifiers or to identify nodes.
146    /// Note that it's stored as a thread-local storage, but actually it's shared
147    /// between threads.
148    ///
149    /// # Usages
150    ///
151    /// ## Configuring
152    ///
153    /// ```rust
154    /// use swc_common::GLOBALS;
155    ///
156    /// GLOBALS.set(&Default::default(), || {
157    ///     // Do operations that require span hygiene
158    /// });
159    /// ```
160    ///
161    /// ## Span hygiene
162    ///
163    /// [Mark]s are stored in this variable.
164    ///
165    /// You can see the document how swc uses the span hygiene info at
166    /// https://rustdoc.swc.rs/swc_ecma_transforms_base/resolver/fn.resolver_with_mark.html
167    pub static GLOBALS: Globals
168);
169
170#[cfg_attr(
171    any(feature = "rkyv-impl"),
172    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
173)]
174#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
175#[cfg_attr(feature = "rkyv-impl", repr(u32))]
176#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
177pub enum FileName {
178    Real(
179        #[cfg_attr(
180            any(feature = "rkyv-impl"),
181            rkyv(with = crate::source_map::EncodePathBuf)
182        )]
183        PathBuf,
184    ),
185    /// A macro. This includes the full name of the macro, so that there are no
186    /// clashes.
187    Macros(String),
188    /// call to `quote!`
189    QuoteExpansion,
190    /// Command line
191    Anon,
192    /// Hack in src/libsyntax/parse.rs
193    MacroExpansion,
194    ProcMacroSourceCode,
195    Url(#[cfg_attr(any(feature = "rkyv-impl"), rkyv(with = crate::source_map::EncodeUrl))] Url),
196    Internal(String),
197    /// Custom sources for explicit parser calls from plugins and drivers
198    Custom(String),
199}
200
201/// A wrapper that attempts to convert a type to and from UTF-8.
202///
203/// Types like `OsString` and `PathBuf` aren't guaranteed to be encoded as
204/// UTF-8, but they usually are anyway. Using this wrapper will archive them as
205/// if they were regular `String`s.
206///
207/// There is built-in `AsString` supports PathBuf but it requires custom
208/// serializer wrapper to handle conversion errors. This wrapper is simplified
209/// version accepts errors
210#[cfg(feature = "rkyv-impl")]
211#[derive(Debug, Clone, Copy)]
212#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
213#[cfg_attr(feature = "rkyv-impl", repr(C))]
214pub struct EncodePathBuf;
215
216#[cfg(feature = "rkyv-impl")]
217impl rkyv::with::ArchiveWith<PathBuf> for EncodePathBuf {
218    type Archived = rkyv::string::ArchivedString;
219    type Resolver = rkyv::string::StringResolver;
220
221    #[inline]
222    fn resolve_with(field: &PathBuf, resolver: Self::Resolver, out: rkyv::Place<Self::Archived>) {
223        // It's safe to unwrap here because if the OsString wasn't valid UTF-8 it would
224        // have failed to serialize
225        rkyv::string::ArchivedString::resolve_from_str(field.to_str().unwrap(), resolver, out);
226    }
227}
228
229#[cfg(feature = "rkyv-impl")]
230impl<S> rkyv::with::SerializeWith<PathBuf, S> for EncodePathBuf
231where
232    S: ?Sized + rancor::Fallible + rkyv::ser::Writer,
233    S::Error: rancor::Source,
234{
235    #[inline]
236    fn serialize_with(field: &PathBuf, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
237        let s = field.to_str().unwrap_or_default();
238        rkyv::string::ArchivedString::serialize_from_str(s, serializer)
239    }
240}
241
242#[cfg(feature = "rkyv-impl")]
243impl<D> rkyv::with::DeserializeWith<rkyv::string::ArchivedString, PathBuf, D> for EncodePathBuf
244where
245    D: ?Sized + rancor::Fallible,
246{
247    #[inline]
248    fn deserialize_with(
249        field: &rkyv::string::ArchivedString,
250        _: &mut D,
251    ) -> Result<PathBuf, D::Error> {
252        Ok(<PathBuf as std::str::FromStr>::from_str(field.as_str()).unwrap())
253    }
254}
255
256/// A wrapper that attempts to convert a Url to and from String.
257#[cfg(feature = "rkyv-impl")]
258#[derive(Debug, Clone, Copy)]
259#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
260#[cfg_attr(feature = "rkyv-impl", repr(C))]
261pub struct EncodeUrl;
262
263#[cfg(feature = "rkyv-impl")]
264impl rkyv::with::ArchiveWith<Url> for EncodeUrl {
265    type Archived = rkyv::string::ArchivedString;
266    type Resolver = rkyv::string::StringResolver;
267
268    #[inline]
269    fn resolve_with(field: &Url, resolver: Self::Resolver, out: rkyv::Place<Self::Archived>) {
270        rkyv::string::ArchivedString::resolve_from_str(field.as_str(), resolver, out);
271    }
272}
273
274#[cfg(feature = "rkyv-impl")]
275impl<S> rkyv::with::SerializeWith<Url, S> for EncodeUrl
276where
277    S: ?Sized + rancor::Fallible + rkyv::ser::Writer,
278    S::Error: rancor::Source,
279{
280    #[inline]
281    fn serialize_with(field: &Url, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
282        let field = field.as_str();
283        rkyv::string::ArchivedString::serialize_from_str(field, serializer)
284    }
285}
286
287#[cfg(feature = "rkyv-impl")]
288impl<D> rkyv::with::DeserializeWith<rkyv::Archived<String>, Url, D> for EncodeUrl
289where
290    D: ?Sized + rancor::Fallible,
291{
292    #[inline]
293    fn deserialize_with(field: &rkyv::string::ArchivedString, _: &mut D) -> Result<Url, D::Error> {
294        Ok(Url::parse(field.as_str()).unwrap())
295    }
296}
297
298impl std::fmt::Display for FileName {
299    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        match *self {
301            FileName::Real(ref path) => write!(fmt, "{}", path.display()),
302            FileName::Macros(ref name) => write!(fmt, "<{name} macros>"),
303            FileName::QuoteExpansion => write!(fmt, "<quote expansion>"),
304            FileName::MacroExpansion => write!(fmt, "<macro expansion>"),
305            FileName::Anon => write!(fmt, "<anon>"),
306            FileName::ProcMacroSourceCode => write!(fmt, "<proc-macro source code>"),
307            FileName::Url(ref u) => write!(fmt, "{u}"),
308            FileName::Custom(ref s) => {
309                write!(fmt, "{s}")
310            }
311            FileName::Internal(ref s) => write!(fmt, "<{s}>"),
312        }
313    }
314}
315
316impl From<PathBuf> for FileName {
317    fn from(p: PathBuf) -> Self {
318        assert!(!p.to_string_lossy().ends_with('>'));
319        FileName::Real(p)
320    }
321}
322
323impl From<Url> for FileName {
324    fn from(url: Url) -> Self {
325        FileName::Url(url)
326    }
327}
328
329impl FileName {
330    pub fn is_real(&self) -> bool {
331        match *self {
332            FileName::Real(_) => true,
333            FileName::Macros(_)
334            | FileName::Anon
335            | FileName::MacroExpansion
336            | FileName::ProcMacroSourceCode
337            | FileName::Custom(_)
338            | FileName::QuoteExpansion
339            | FileName::Internal(_)
340            | FileName::Url(_) => false,
341        }
342    }
343
344    pub fn is_macros(&self) -> bool {
345        match *self {
346            FileName::Real(_)
347            | FileName::Anon
348            | FileName::MacroExpansion
349            | FileName::ProcMacroSourceCode
350            | FileName::Custom(_)
351            | FileName::QuoteExpansion
352            | FileName::Internal(_)
353            | FileName::Url(_) => false,
354            FileName::Macros(_) => true,
355        }
356    }
357}
358
359#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
360#[cfg_attr(
361    feature = "diagnostic-serde",
362    derive(serde::Serialize, serde::Deserialize)
363)]
364#[cfg_attr(
365    any(feature = "rkyv-impl"),
366    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
367)]
368#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
369#[cfg_attr(feature = "rkyv-impl", repr(C))]
370pub struct PrimarySpanLabel(pub Span, pub String);
371
372/// A collection of spans. Spans have two orthogonal attributes:
373///
374/// - they can be *primary spans*. In this case they are the locus of the error,
375///   and would be rendered with `^^^`.
376/// - they can have a *label*. In this case, the label is written next to the
377///   mark in the snippet when we render.
378#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
379#[cfg_attr(
380    feature = "diagnostic-serde",
381    derive(serde::Serialize, serde::Deserialize)
382)]
383#[cfg_attr(
384    any(feature = "rkyv-impl"),
385    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
386)]
387#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
388#[cfg_attr(feature = "rkyv-impl", repr(C))]
389pub struct MultiSpan {
390    primary_spans: Vec<Span>,
391    span_labels: Vec<PrimarySpanLabel>,
392}
393
394extern "C" {
395    fn __span_dummy_with_cmt_proxy() -> u32;
396}
397
398impl Span {
399    #[inline]
400    pub fn lo(self) -> BytePos {
401        self.lo
402    }
403
404    #[inline]
405    pub fn new(mut lo: BytePos, mut hi: BytePos) -> Self {
406        if lo > hi {
407            std::mem::swap(&mut lo, &mut hi);
408        }
409
410        Span { lo, hi }
411    }
412
413    #[inline]
414    #[track_caller]
415    pub fn new_with_checked(lo: BytePos, hi: BytePos) -> Self {
416        debug_assert!(lo <= hi, "lo: {lo:#?}, hi: {hi:#?}");
417        Span { lo, hi }
418    }
419
420    #[inline]
421    pub fn with_lo(&self, lo: BytePos) -> Span {
422        Span::new(lo, self.hi)
423    }
424
425    #[inline(always)]
426    pub fn hi(self) -> BytePos {
427        self.hi
428    }
429
430    #[inline]
431    pub fn with_hi(&self, hi: BytePos) -> Span {
432        Span::new(self.lo, hi)
433    }
434
435    /// Returns `true` if this is a dummy span with any hygienic context.
436    #[inline]
437    pub fn is_dummy(self) -> bool {
438        self.lo.0 == 0 && self.hi.0 == 0 || self.lo.0 >= DUMMY_RESERVE
439    }
440
441    #[inline]
442    pub fn is_pure(self) -> bool {
443        self.lo.is_pure()
444    }
445
446    #[inline]
447    pub fn is_placeholder(self) -> bool {
448        self.lo.is_placeholder()
449    }
450
451    /// Returns `true` if this is a dummy span with any hygienic context.
452    #[inline]
453    pub fn is_dummy_ignoring_cmt(self) -> bool {
454        self.lo.0 == 0 && self.hi.0 == 0
455    }
456
457    /// Returns a new span representing an empty span at the beginning of this
458    /// span
459    #[inline]
460    pub fn shrink_to_lo(self) -> Span {
461        self.with_hi(self.lo)
462    }
463
464    /// Returns a new span representing an empty span at the end of this span
465    #[inline]
466    pub fn shrink_to_hi(self) -> Span {
467        self.with_lo(self.hi)
468    }
469
470    /// Returns `self` if `self` is not the dummy span, and `other` otherwise.
471    pub fn substitute_dummy(self, other: Span) -> Span {
472        if self.is_dummy() {
473            other
474        } else {
475            self
476        }
477    }
478
479    /// Return true if `self` fully encloses `other`.
480    pub fn contains(self, other: Span) -> bool {
481        self.lo <= other.lo && other.hi <= self.hi
482    }
483
484    /// Return true if the spans are equal with regards to the source text.
485    ///
486    /// Use this instead of `==` when either span could be generated code,
487    /// and you only care that they point to the same bytes of source text.
488    pub fn source_equal(self, other: Span) -> bool {
489        self.lo == other.lo && self.hi == other.hi
490    }
491
492    /// Returns `Some(span)`, where the start is trimmed by the end of `other`
493    pub fn trim_start(self, other: Span) -> Option<Span> {
494        if self.hi > other.hi {
495            Some(self.with_lo(cmp::max(self.lo, other.hi)))
496        } else {
497            None
498        }
499    }
500
501    /// Return a `Span` that would enclose both `self` and `end`.
502    pub fn to(self, end: Span) -> Span {
503        let span_data = self;
504        let end_data = end;
505        // FIXME(jseyfried): self.ctxt should always equal end.ctxt here (c.f. issue
506        // #23480) Return the macro span on its own to avoid weird diagnostic
507        // output. It is preferable to have an incomplete span than a completely
508        // nonsensical one.
509
510        Span::new(
511            cmp::min(span_data.lo, end_data.lo),
512            cmp::max(span_data.hi, end_data.hi),
513        )
514    }
515
516    /// Return a `Span` between the end of `self` to the beginning of `end`.
517    pub fn between(self, end: Span) -> Span {
518        let span = self;
519        Span::new(span.hi, end.lo)
520    }
521
522    /// Return a `Span` between the beginning of `self` to the beginning of
523    /// `end`.
524    pub fn until(self, end: Span) -> Span {
525        let span = self;
526        Span::new(span.lo, end.lo)
527    }
528
529    pub fn from_inner_byte_pos(self, start: usize, end: usize) -> Span {
530        let span = self;
531        Span::new(
532            span.lo + BytePos::from_usize(start),
533            span.lo + BytePos::from_usize(end),
534        )
535    }
536
537    /// Dummy span, both position are extremely large numbers so they would be
538    /// ignore by sourcemap, but can still have comments
539    pub fn dummy_with_cmt() -> Self {
540        #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
541        {
542            let lo = BytePos(unsafe { __span_dummy_with_cmt_proxy() });
543
544            return Span { lo, hi: lo };
545        }
546
547        #[cfg(not(all(any(feature = "__plugin_mode"), target_arch = "wasm32")))]
548        return GLOBALS.with(|globals| {
549            let lo = BytePos(
550                globals
551                    .dummy_cnt
552                    .fetch_add(1, std::sync::atomic::Ordering::SeqCst),
553            );
554            Span { lo, hi: lo }
555        });
556    }
557}
558
559#[derive(Clone, Debug)]
560pub struct SpanLabel {
561    /// The span we are going to include in the final snippet.
562    pub span: Span,
563
564    /// Is this a primary span? This is the "locus" of the message,
565    /// and is indicated with a `^^^^` underline, versus `----`.
566    pub is_primary: bool,
567
568    /// What label should we attach to this span (if any)?
569    pub label: Option<String>,
570}
571
572impl Default for Span {
573    fn default() -> Self {
574        DUMMY_SP
575    }
576}
577
578impl MultiSpan {
579    #[inline]
580    pub fn new() -> MultiSpan {
581        Self::default()
582    }
583
584    pub fn from_span(primary_span: Span) -> MultiSpan {
585        MultiSpan {
586            primary_spans: vec![primary_span],
587            span_labels: Vec::new(),
588        }
589    }
590
591    pub fn from_spans(vec: Vec<Span>) -> MultiSpan {
592        MultiSpan {
593            primary_spans: vec,
594            span_labels: Vec::new(),
595        }
596    }
597
598    pub fn push_span_label(&mut self, span: Span, label: String) {
599        self.span_labels.push(PrimarySpanLabel(span, label));
600    }
601
602    /// Selects the first primary span (if any)
603    pub fn primary_span(&self) -> Option<Span> {
604        self.primary_spans.first().cloned()
605    }
606
607    /// Returns all primary spans.
608    pub fn primary_spans(&self) -> &[Span] {
609        &self.primary_spans
610    }
611
612    /// Returns `true` if this contains only a dummy primary span with any
613    /// hygienic context.
614    pub fn is_dummy(&self) -> bool {
615        let mut is_dummy = true;
616        for span in &self.primary_spans {
617            if !span.is_dummy() {
618                is_dummy = false;
619            }
620        }
621        is_dummy
622    }
623
624    /// Replaces all occurrences of one Span with another. Used to move Spans in
625    /// areas that don't display well (like std macros). Returns true if
626    /// replacements occurred.
627    pub fn replace(&mut self, before: Span, after: Span) -> bool {
628        let mut replacements_occurred = false;
629        for primary_span in &mut self.primary_spans {
630            if *primary_span == before {
631                *primary_span = after;
632                replacements_occurred = true;
633            }
634        }
635        for span_label in &mut self.span_labels {
636            if span_label.0 == before {
637                span_label.0 = after;
638                replacements_occurred = true;
639            }
640        }
641        replacements_occurred
642    }
643
644    /// Returns the strings to highlight. We always ensure that there
645    /// is an entry for each of the primary spans -- for each primary
646    /// span P, if there is at least one label with span P, we return
647    /// those labels (marked as primary). But otherwise we return
648    /// `SpanLabel` instances with empty labels.
649    pub fn span_labels(&self) -> Vec<SpanLabel> {
650        let is_primary = |span| self.primary_spans.contains(&span);
651
652        let mut span_labels = self
653            .span_labels
654            .iter()
655            .map(|&PrimarySpanLabel(span, ref label)| SpanLabel {
656                span,
657                is_primary: is_primary(span),
658                label: Some(label.clone()),
659            })
660            .collect::<Vec<_>>();
661
662        for &span in &self.primary_spans {
663            if !span_labels.iter().any(|sl| sl.span == span) {
664                span_labels.push(SpanLabel {
665                    span,
666                    is_primary: true,
667                    label: None,
668                });
669            }
670        }
671
672        span_labels
673    }
674}
675
676impl From<Span> for MultiSpan {
677    fn from(span: Span) -> MultiSpan {
678        MultiSpan::from_span(span)
679    }
680}
681
682impl From<Vec<Span>> for MultiSpan {
683    fn from(spans: Vec<Span>) -> MultiSpan {
684        MultiSpan::from_spans(spans)
685    }
686}
687
688pub const NO_EXPANSION: SyntaxContext = SyntaxContext::empty();
689
690/// Identifies an offset of a multi-byte character in a SourceFile
691#[cfg_attr(
692    any(feature = "rkyv-impl"),
693    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
694)]
695#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
696#[cfg_attr(feature = "rkyv-impl", repr(C))]
697#[derive(Copy, Clone, Eq, PartialEq, Debug)]
698pub struct MultiByteChar {
699    /// The absolute offset of the character in the SourceMap
700    pub pos: BytePos,
701    /// The number of bytes, >=2
702    pub bytes: u8,
703}
704
705impl MultiByteChar {
706    /// Computes the extra number of UTF-8 bytes necessary to encode a code
707    /// point, compared to UTF-16 encoding.
708    ///
709    /// 1, 2, and 3 UTF-8 bytes encode into 1 UTF-16 char, but 4 UTF-8 bytes
710    /// encode into 2.
711    pub fn byte_to_char_diff(&self) -> u8 {
712        if self.bytes == 4 {
713            2
714        } else {
715            self.bytes - 1
716        }
717    }
718}
719
720/// Identifies an offset of a non-narrow character in a SourceFile
721#[cfg_attr(
722    any(feature = "rkyv-impl"),
723    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
724)]
725#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
726#[cfg_attr(feature = "rkyv-impl", repr(u32))]
727#[derive(Copy, Clone, Eq, PartialEq, Debug)]
728pub enum NonNarrowChar {
729    /// Represents a zero-width character
730    ZeroWidth(BytePos),
731    /// Represents a wide (fullwidth) character
732    Wide(BytePos, usize),
733    /// Represents a tab character, represented visually with a width of 4
734    /// characters
735    Tab(BytePos),
736}
737
738impl NonNarrowChar {
739    fn new(pos: BytePos, width: usize) -> Self {
740        match width {
741            0 => NonNarrowChar::ZeroWidth(pos),
742            4 => NonNarrowChar::Tab(pos),
743            w => NonNarrowChar::Wide(pos, w),
744        }
745    }
746
747    /// Returns the absolute offset of the character in the SourceMap
748    pub fn pos(self) -> BytePos {
749        match self {
750            NonNarrowChar::ZeroWidth(p) | NonNarrowChar::Wide(p, _) | NonNarrowChar::Tab(p) => p,
751        }
752    }
753
754    /// Returns the width of the character, 0 (zero-width) or 2 (wide)
755    pub fn width(self) -> usize {
756        match self {
757            NonNarrowChar::ZeroWidth(_) => 0,
758            NonNarrowChar::Wide(_, width) => width,
759            NonNarrowChar::Tab(_) => 4,
760        }
761    }
762}
763
764impl Add<BytePos> for NonNarrowChar {
765    type Output = Self;
766
767    fn add(self, rhs: BytePos) -> Self {
768        match self {
769            NonNarrowChar::ZeroWidth(pos) => NonNarrowChar::ZeroWidth(pos + rhs),
770            NonNarrowChar::Wide(pos, width) => NonNarrowChar::Wide(pos + rhs, width),
771            NonNarrowChar::Tab(pos) => NonNarrowChar::Tab(pos + rhs),
772        }
773    }
774}
775
776impl Sub<BytePos> for NonNarrowChar {
777    type Output = Self;
778
779    fn sub(self, rhs: BytePos) -> Self {
780        match self {
781            NonNarrowChar::ZeroWidth(pos) => NonNarrowChar::ZeroWidth(pos - rhs),
782            NonNarrowChar::Wide(pos, width) => NonNarrowChar::Wide(pos - rhs, width),
783            NonNarrowChar::Tab(pos) => NonNarrowChar::Tab(pos - rhs),
784        }
785    }
786}
787
788/// A single source in the SourceMap.
789#[cfg_attr(
790    any(feature = "rkyv-impl"),
791    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
792)]
793#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
794#[cfg_attr(feature = "rkyv-impl", repr(C))]
795#[derive(Clone)]
796pub struct SourceFile {
797    /// The name of the file that the source came from. Source that doesn't
798    /// originate from files has names between angle brackets by convention,
799    /// e.g. `<anon>`
800    pub name: Lrc<FileName>,
801    /// True if the `name` field above has been modified by
802    /// `--remap-path-prefix`
803    pub name_was_remapped: bool,
804    /// The unmapped path of the file that the source came from.
805    /// Set to `None` if the `SourceFile` was imported from an external crate.
806    pub unmapped_path: Option<Lrc<FileName>>,
807    /// Indicates which crate this `SourceFile` was imported from.
808    pub crate_of_origin: u32,
809    /// The complete source code
810    pub src: BytesStr,
811    /// The source code's hash
812    pub src_hash: u128,
813    /// The start position of this source in the `SourceMap`
814    pub start_pos: BytePos,
815    /// The end position of this source in the `SourceMap`
816    pub end_pos: BytePos,
817    /// A hash of the filename, used for speeding up the incr. comp. hashing.
818    pub name_hash: u128,
819
820    lazy: CacheCell<SourceFileAnalysis>,
821}
822
823#[cfg_attr(
824    any(feature = "rkyv-impl"),
825    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
826)]
827#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
828#[cfg_attr(feature = "rkyv-impl", repr(C))]
829#[derive(Clone)]
830pub struct SourceFileAnalysis {
831    /// Locations of lines beginnings in the source code
832    pub lines: Vec<BytePos>,
833    /// Locations of multi-byte characters in the source code
834    pub multibyte_chars: Vec<MultiByteChar>,
835    /// Width of characters that are not narrow in the source code
836    pub non_narrow_chars: Vec<NonNarrowChar>,
837}
838
839impl fmt::Debug for SourceFile {
840    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
841        write!(fmt, "SourceFile({})", self.name)
842    }
843}
844
845impl SourceFile {
846    /// `src` should not have UTF8 BOM
847    pub fn new(
848        name: Lrc<FileName>,
849        name_was_remapped: bool,
850        unmapped_path: Lrc<FileName>,
851        src: BytesStr,
852        start_pos: BytePos,
853    ) -> SourceFile {
854        debug_assert_ne!(
855            start_pos,
856            BytePos::DUMMY,
857            "BytePos::DUMMY is reserved and `SourceFile` should not use it"
858        );
859
860        let src_hash = {
861            let mut hasher: StableHasher = StableHasher::new();
862            hasher.write(src.as_bytes());
863            hasher.finish()
864        };
865        let name_hash = {
866            let mut hasher: StableHasher = StableHasher::new();
867            name.hash(&mut hasher);
868            hasher.finish()
869        };
870        let end_pos = start_pos.to_usize() + src.len();
871
872        SourceFile {
873            name,
874            name_was_remapped,
875            unmapped_path: Some(unmapped_path),
876            crate_of_origin: 0,
877            src,
878            src_hash,
879            start_pos,
880            end_pos: SmallPos::from_usize(end_pos),
881            name_hash,
882            lazy: CacheCell::new(),
883        }
884    }
885
886    /// Return the BytePos of the beginning of the current line.
887    pub fn line_begin_pos(&self, pos: BytePos) -> BytePos {
888        let line_index = self.lookup_line(pos).unwrap();
889        let analysis = self.analyze();
890        analysis.lines[line_index]
891    }
892
893    /// Get a line from the list of pre-computed line-beginnings.
894    /// The line number here is 0-based.
895    pub fn get_line(&self, line_number: usize) -> Option<Cow<'_, str>> {
896        fn get_until_newline(src: &str, begin: usize) -> &str {
897            // We can't use `lines.get(line_number+1)` because we might
898            // be parsing when we call this function and thus the current
899            // line is the last one we have line info for.
900            let slice = &src[begin..];
901            match slice.find('\n') {
902                Some(e) => &slice[..e],
903                None => slice,
904            }
905        }
906
907        let begin = {
908            let analysis = self.analyze();
909            let line = analysis.lines.get(line_number)?;
910            let begin: BytePos = *line - self.start_pos;
911            begin.to_usize()
912        };
913
914        Some(Cow::from(get_until_newline(&self.src, begin)))
915    }
916
917    pub fn is_real_file(&self) -> bool {
918        self.name.is_real()
919    }
920
921    pub fn byte_length(&self) -> u32 {
922        self.end_pos.0 - self.start_pos.0
923    }
924
925    pub fn count_lines(&self) -> usize {
926        let analysis = self.analyze();
927        analysis.lines.len()
928    }
929
930    /// Find the line containing the given position. The return value is the
931    /// index into the `lines` array of this SourceFile, not the 1-based line
932    /// number. If the `source_file` is empty or the position is located before
933    /// the first line, `None` is returned.
934    pub fn lookup_line(&self, pos: BytePos) -> Option<usize> {
935        let analysis = self.analyze();
936        if analysis.lines.is_empty() {
937            return None;
938        }
939
940        let line_index = lookup_line(&analysis.lines, pos);
941        assert!(line_index < analysis.lines.len() as isize);
942        if line_index >= 0 {
943            Some(line_index as usize)
944        } else {
945            None
946        }
947    }
948
949    pub fn line_bounds(&self, line_index: usize) -> (BytePos, BytePos) {
950        if self.start_pos == self.end_pos {
951            return (self.start_pos, self.end_pos);
952        }
953
954        let analysis = self.analyze();
955
956        assert!(line_index < analysis.lines.len());
957        if line_index == (analysis.lines.len() - 1) {
958            (analysis.lines[line_index], self.end_pos)
959        } else {
960            (analysis.lines[line_index], analysis.lines[line_index + 1])
961        }
962    }
963
964    #[inline]
965    pub fn contains(&self, byte_pos: BytePos) -> bool {
966        byte_pos >= self.start_pos && byte_pos <= self.end_pos
967    }
968
969    pub fn analyze(&self) -> &SourceFileAnalysis {
970        self.lazy.get_or_init(|| {
971            let (lines, multibyte_chars, non_narrow_chars) =
972                analyze_source_file::analyze_source_file(&self.src[..], self.start_pos);
973            SourceFileAnalysis {
974                lines,
975                multibyte_chars,
976                non_narrow_chars,
977            }
978        })
979    }
980}
981
982// _____________________________________________________________________________
983// Pos, BytePos, CharPos
984//
985
986pub trait SmallPos {
987    fn from_usize(n: usize) -> Self;
988    fn to_usize(&self) -> usize;
989    fn from_u32(n: u32) -> Self;
990    fn to_u32(&self) -> u32;
991}
992
993/// A byte offset. Keep this small (currently 32-bits), as AST contains
994/// a lot of them.
995///
996///
997/// # Reserved
998///
999///  - 0 is reserved for dummy spans. It means `BytePos(0)` means the `BytePos`
1000///    is synthesized by the compiler.
1001///
1002///  - Values larger than `u32::MAX - 2^16` are reserved for the comments.
1003///
1004/// `u32::MAX` is special value used to generate source map entries.
1005#[derive(
1006    Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize, Default,
1007)]
1008#[serde(transparent)]
1009#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1010#[cfg_attr(
1011    any(feature = "rkyv-impl"),
1012    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1013)]
1014#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1015#[cfg_attr(feature = "rkyv-impl", repr(C))]
1016#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
1017pub struct BytePos(#[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))] pub u32);
1018
1019#[cfg(feature = "encoding-impl")]
1020impl cbor4ii::core::enc::Encode for BytePos {
1021    #[inline]
1022    fn encode<W: cbor4ii::core::enc::Write>(
1023        &self,
1024        writer: &mut W,
1025    ) -> Result<(), cbor4ii::core::enc::Error<W::Error>> {
1026        self.0.encode(writer)
1027    }
1028}
1029
1030#[cfg(feature = "encoding-impl")]
1031impl<'de> cbor4ii::core::dec::Decode<'de> for BytePos {
1032    #[inline]
1033    fn decode<R: cbor4ii::core::dec::Read<'de>>(
1034        reader: &mut R,
1035    ) -> Result<Self, cbor4ii::core::dec::Error<R::Error>> {
1036        u32::decode(reader).map(BytePos)
1037    }
1038}
1039
1040impl BytePos {
1041    /// Dummy position. This is reserved for synthesized spans.
1042    pub const DUMMY: Self = BytePos(0);
1043    const MIN_RESERVED: Self = BytePos(DUMMY_RESERVE);
1044    /// Placeholders, commonly used where names are required, but the names are
1045    /// not referenced elsewhere.
1046    pub const PLACEHOLDER: Self = BytePos(u32::MAX - 2);
1047    /// Reserved for PURE comments. e.g. `/* #__PURE__ */`
1048    pub const PURE: Self = BytePos(u32::MAX - 1);
1049    /// Synthesized, but should be stored in a source map.
1050    pub const SYNTHESIZED: Self = BytePos(u32::MAX);
1051
1052    pub const fn is_reserved_for_comments(self) -> bool {
1053        self.0 >= Self::MIN_RESERVED.0 && self.0 != u32::MAX
1054    }
1055
1056    /// Returns `true`` if this is synthesized and has no relevant input source
1057    /// code.
1058    pub const fn is_dummy(self) -> bool {
1059        self.0 == 0
1060    }
1061
1062    pub const fn is_pure(self) -> bool {
1063        self.0 == Self::PURE.0
1064    }
1065
1066    pub const fn is_placeholder(self) -> bool {
1067        self.0 == Self::PLACEHOLDER.0
1068    }
1069
1070    /// Returns `true`` if this is explicitly synthesized or has relevant input
1071    /// source so can have a comment.
1072    pub const fn can_have_comment(self) -> bool {
1073        self.0 != 0
1074    }
1075}
1076
1077/// A character offset. Because of multibyte utf8 characters, a byte offset
1078/// is not equivalent to a character offset. The SourceMap will convert BytePos
1079/// values to CharPos values as necessary.
1080#[cfg_attr(
1081    any(feature = "rkyv-impl"),
1082    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1083)]
1084#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1085#[cfg_attr(feature = "rkyv-impl", repr(C))]
1086#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
1087pub struct CharPos(pub usize);
1088
1089// FIXME: Lots of boilerplate in these impls, but so far my attempts to fix
1090// have been unsuccessful
1091
1092impl SmallPos for BytePos {
1093    #[inline(always)]
1094    fn from_usize(n: usize) -> BytePos {
1095        BytePos(n as u32)
1096    }
1097
1098    #[inline(always)]
1099    fn to_usize(&self) -> usize {
1100        self.0 as usize
1101    }
1102
1103    #[inline(always)]
1104    fn from_u32(n: u32) -> BytePos {
1105        BytePos(n)
1106    }
1107
1108    #[inline(always)]
1109    fn to_u32(&self) -> u32 {
1110        self.0
1111    }
1112}
1113
1114impl Add for BytePos {
1115    type Output = BytePos;
1116
1117    #[inline(always)]
1118    fn add(self, rhs: BytePos) -> BytePos {
1119        BytePos((self.to_usize() + rhs.to_usize()) as u32)
1120    }
1121}
1122
1123impl Sub for BytePos {
1124    type Output = BytePos;
1125
1126    #[inline(always)]
1127    fn sub(self, rhs: BytePos) -> BytePos {
1128        BytePos((self.to_usize() - rhs.to_usize()) as u32)
1129    }
1130}
1131
1132impl SmallPos for CharPos {
1133    #[inline(always)]
1134    fn from_usize(n: usize) -> CharPos {
1135        CharPos(n)
1136    }
1137
1138    #[inline(always)]
1139    fn to_usize(&self) -> usize {
1140        self.0
1141    }
1142
1143    #[inline(always)]
1144    fn from_u32(n: u32) -> CharPos {
1145        CharPos(n as usize)
1146    }
1147
1148    #[inline(always)]
1149    fn to_u32(&self) -> u32 {
1150        self.0 as u32
1151    }
1152}
1153
1154impl Add for CharPos {
1155    type Output = CharPos;
1156
1157    #[inline(always)]
1158    fn add(self, rhs: CharPos) -> CharPos {
1159        CharPos(self.to_usize() + rhs.to_usize())
1160    }
1161}
1162
1163impl Sub for CharPos {
1164    type Output = CharPos;
1165
1166    #[inline(always)]
1167    fn sub(self, rhs: CharPos) -> CharPos {
1168        CharPos(self.to_usize() - rhs.to_usize())
1169    }
1170}
1171
1172// _____________________________________________________________________________
1173// Loc, LocWithOpt, SourceFileAndLine, SourceFileAndBytePos
1174//
1175
1176/// A source code location used for error reporting.
1177///
1178/// Note: This struct intentionally does not implement rkyv's archieve
1179/// to avoid redundant data copy (https://github.com/swc-project/swc/issues/5471)
1180/// source_map_proxy constructs plugin-side Loc instead with shared SourceFile
1181/// instance.
1182#[derive(Debug, Clone)]
1183pub struct Loc {
1184    /// Information about the original source
1185    pub file: Lrc<SourceFile>,
1186    /// The (1-based) line number
1187    pub line: usize,
1188    /// The (0-based) column offset
1189    pub col: CharPos,
1190    /// The (0-based) column offset when displayed
1191    pub col_display: usize,
1192}
1193
1194/// A struct to exchange `Loc` with omitting SourceFile as needed.
1195/// This is internal struct between plugins to the host, not a public interface.
1196#[cfg_attr(
1197    any(feature = "rkyv-impl"),
1198    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Debug, Clone)
1199)]
1200#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1201#[cfg_attr(feature = "rkyv-impl", repr(C))]
1202pub struct PartialLoc {
1203    pub source_file: Option<Lrc<SourceFile>>,
1204    pub line: usize,
1205    pub col: usize,
1206    pub col_display: usize,
1207}
1208
1209/// A source code location used as the result of `lookup_char_pos_adj`
1210// Actually, *none* of the clients use the filename *or* file field;
1211// perhaps they should just be removed.
1212#[derive(Debug)]
1213pub struct LocWithOpt {
1214    pub filename: Lrc<FileName>,
1215    pub line: usize,
1216    pub col: CharPos,
1217    pub file: Option<Lrc<SourceFile>>,
1218}
1219
1220// used to be structural records. Better names, anyone?
1221#[derive(Debug)]
1222pub struct SourceFileAndLine {
1223    pub sf: Lrc<SourceFile>,
1224    pub line: usize,
1225}
1226
1227#[cfg_attr(
1228    any(feature = "rkyv-impl"),
1229    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1230)]
1231#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1232#[cfg_attr(feature = "rkyv-impl", repr(C))]
1233#[derive(Debug)]
1234pub struct SourceFileAndBytePos {
1235    pub sf: Lrc<SourceFile>,
1236    pub pos: BytePos,
1237}
1238
1239#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1240#[cfg_attr(
1241    any(feature = "rkyv-impl"),
1242    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1243)]
1244#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1245#[cfg_attr(feature = "rkyv-impl", repr(C))]
1246pub struct LineInfo {
1247    /// Index of line, starting from 0.
1248    pub line_index: usize,
1249
1250    /// Column in line where span begins, starting from 0.
1251    pub start_col: CharPos,
1252
1253    /// Column in line where span ends, starting from 0, exclusive.
1254    pub end_col: CharPos,
1255}
1256
1257/// Used to create a `.map` file.
1258#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1259pub struct LineCol {
1260    /// Index of line, starting from 0.
1261    pub line: u32,
1262
1263    /// UTF-16 column in line, starting from 0.
1264    pub col: u32,
1265}
1266
1267/// A struct to represent lines of a source file.
1268///
1269/// Note: This struct intentionally does not implement rkyv's archieve
1270/// to avoid redundant data copy (https://github.com/swc-project/swc/issues/5471)
1271/// source_map_proxy constructs plugin-side Loc instead with shared SourceFile
1272/// instance.
1273pub struct FileLines {
1274    pub file: Lrc<SourceFile>,
1275    pub lines: Vec<LineInfo>,
1276}
1277
1278/// A struct to exchange `FileLines` with omitting SourceFile as needed.
1279/// This is internal struct between plugins to the host, not a public interface.
1280#[cfg_attr(
1281    any(feature = "rkyv-impl"),
1282    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Debug, Clone)
1283)]
1284#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1285#[cfg_attr(feature = "rkyv-impl", repr(C))]
1286pub struct PartialFileLines {
1287    pub file: Option<Lrc<SourceFile>>,
1288    pub lines: Vec<LineInfo>,
1289}
1290
1291// _____________________________________________________________________________
1292// SpanLinesError, SpanSnippetError, DistinctSources,
1293// MalformedSourceMapPositions
1294//
1295
1296pub type FileLinesResult = Result<FileLines, Box<SpanLinesError>>;
1297#[cfg(feature = "__plugin")]
1298pub type PartialFileLinesResult = Result<PartialFileLines, Box<SpanLinesError>>;
1299
1300#[derive(Clone, PartialEq, Eq, Debug)]
1301#[cfg_attr(
1302    any(feature = "rkyv-impl"),
1303    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1304)]
1305#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1306#[cfg_attr(feature = "rkyv-impl", repr(u32))]
1307pub enum SpanLinesError {
1308    IllFormedSpan(Span),
1309    DistinctSources(DistinctSources),
1310}
1311
1312#[derive(Clone, PartialEq, Eq, Debug)]
1313#[cfg_attr(
1314    any(feature = "rkyv-impl"),
1315    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1316)]
1317#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1318#[cfg_attr(feature = "rkyv-impl", repr(u32))]
1319pub enum SpanSnippetError {
1320    DummyBytePos,
1321    IllFormedSpan(Span),
1322    DistinctSources(DistinctSources),
1323    MalformedForSourcemap(MalformedSourceMapPositions),
1324    SourceNotAvailable { filename: FileName },
1325    LookupFailed(SourceMapLookupError),
1326}
1327
1328/// An error type for looking up source maps.
1329///
1330///
1331/// This type is small.
1332#[derive(Clone, PartialEq, Eq, Debug)]
1333#[cfg_attr(
1334    any(feature = "rkyv-impl"),
1335    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1336)]
1337#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1338#[cfg_attr(feature = "rkyv-impl", repr(u32))]
1339pub enum SourceMapLookupError {
1340    NoFileFor(BytePos),
1341}
1342
1343#[derive(Clone, PartialEq, Eq, Debug)]
1344#[cfg_attr(
1345    any(feature = "rkyv-impl"),
1346    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1347)]
1348#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1349#[cfg_attr(feature = "rkyv-impl", repr(C))]
1350pub struct FilePos(pub Lrc<FileName>, pub BytePos);
1351
1352#[derive(Clone, PartialEq, Eq, Debug)]
1353#[cfg_attr(
1354    any(feature = "rkyv-impl"),
1355    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1356)]
1357#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1358#[cfg_attr(feature = "rkyv-impl", repr(C))]
1359pub struct DistinctSources {
1360    pub begin: FilePos,
1361    pub end: FilePos,
1362}
1363
1364#[derive(Clone, PartialEq, Eq, Debug)]
1365#[cfg_attr(
1366    any(feature = "rkyv-impl"),
1367    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1368)]
1369#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1370#[cfg_attr(feature = "rkyv-impl", repr(C))]
1371pub struct MalformedSourceMapPositions {
1372    pub name: Lrc<FileName>,
1373    pub source_len: usize,
1374    pub begin_pos: BytePos,
1375    pub end_pos: BytePos,
1376}
1377
1378// Given a slice of line start positions and a position, returns the index of
1379// the line the position is on. Returns -1 if the position is located before
1380// the first line.
1381fn lookup_line(lines: &[BytePos], pos: BytePos) -> isize {
1382    match lines.binary_search(&pos) {
1383        Ok(line) => line as isize,
1384        Err(line) => line as isize - 1,
1385    }
1386}
1387
1388impl From<SourceMapLookupError> for Box<SpanSnippetError> {
1389    #[cold]
1390    fn from(err: SourceMapLookupError) -> Self {
1391        Box::new(SpanSnippetError::LookupFailed(err))
1392    }
1393}
1394
1395#[cfg(test)]
1396mod tests {
1397    use super::{lookup_line, BytePos, Span};
1398
1399    #[test]
1400    fn test_lookup_line() {
1401        let lines = &[BytePos(3), BytePos(17), BytePos(28)];
1402
1403        assert_eq!(lookup_line(lines, BytePos(0)), -1);
1404        assert_eq!(lookup_line(lines, BytePos(3)), 0);
1405        assert_eq!(lookup_line(lines, BytePos(4)), 0);
1406
1407        assert_eq!(lookup_line(lines, BytePos(16)), 0);
1408        assert_eq!(lookup_line(lines, BytePos(17)), 1);
1409        assert_eq!(lookup_line(lines, BytePos(18)), 1);
1410
1411        assert_eq!(lookup_line(lines, BytePos(28)), 2);
1412        assert_eq!(lookup_line(lines, BytePos(29)), 2);
1413    }
1414
1415    #[test]
1416    fn size_of_span() {
1417        assert_eq!(std::mem::size_of::<Span>(), 8);
1418    }
1419}