Skip to main content

nu_protocol/
span.rs

1//! [`Span`] to point to sections of source code and the [`Spanned`] wrapper type
2use crate::shell_error::generic::GenericError;
3use crate::{FromValue, IntoValue, ShellError, Signals, SpanId, Type, Value, record};
4use miette::SourceSpan;
5use serde::{Deserialize, Serialize};
6use std::borrow::Cow;
7use std::{fmt, ops::Deref};
8
9pub trait GetSpan {
10    fn get_span(&self, span_id: SpanId) -> Span;
11}
12
13/// A spanned area of interest, generic over what kind of thing is of interest
14#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
15pub struct Spanned<T> {
16    pub item: T,
17    pub span: Span,
18}
19
20impl<T> Spanned<T> {
21    /// Map to a spanned reference of the inner type, i.e. `Spanned<T> -> Spanned<&T>`.
22    pub fn as_ref(&self) -> Spanned<&T> {
23        Spanned {
24            item: &self.item,
25            span: self.span,
26        }
27    }
28
29    /// Map to a mutable reference of the inner type, i.e. `Spanned<T> -> Spanned<&mut T>`.
30    pub fn as_mut(&mut self) -> Spanned<&mut T> {
31        Spanned {
32            item: &mut self.item,
33            span: self.span,
34        }
35    }
36
37    /// Map to the result of [`.deref()`](std::ops::Deref::deref) on the inner type.
38    ///
39    /// This can be used for example to turn `Spanned<Vec<T>>` into `Spanned<&[T]>`.
40    pub fn as_deref(&self) -> Spanned<&<T as Deref>::Target>
41    where
42        T: Deref,
43    {
44        Spanned {
45            item: self.item.deref(),
46            span: self.span,
47        }
48    }
49
50    /// Map the spanned item with a function.
51    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
52        Spanned {
53            item: f(self.item),
54            span: self.span,
55        }
56    }
57}
58
59impl<T> Spanned<&T>
60where
61    T: ToOwned + ?Sized,
62{
63    /// Map the spanned to hold an owned value.
64    pub fn to_owned(&self) -> Spanned<T::Owned> {
65        Spanned {
66            item: self.item.to_owned(),
67            span: self.span,
68        }
69    }
70}
71
72impl<T> Spanned<T>
73where
74    T: AsRef<str>,
75{
76    /// Span the value as a string slice.
77    pub fn as_str(&self) -> Spanned<&str> {
78        Spanned {
79            item: self.item.as_ref(),
80            span: self.span,
81        }
82    }
83}
84
85impl<T, E> Spanned<Result<T, E>> {
86    /// Move the `Result` to the outside, resulting in a spanned `Ok` or unspanned `Err`.
87    pub fn transpose(self) -> Result<Spanned<T>, E> {
88        match self {
89            Spanned {
90                item: Ok(item),
91                span,
92            } => Ok(Spanned { item, span }),
93            Spanned {
94                item: Err(err),
95                span: _,
96            } => Err(err),
97        }
98    }
99}
100
101// With both Display and Into<SourceSpan> implemented on Spanned, we can use Spanned<String> in an
102// error in one field instead of splitting it into two fields
103
104impl<T: fmt::Display> fmt::Display for Spanned<T> {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        fmt::Display::fmt(&self.item, f)
107    }
108}
109
110impl<T> From<Spanned<T>> for SourceSpan {
111    fn from(value: Spanned<T>) -> Self {
112        value.span.into()
113    }
114}
115
116/// Helper trait to create [`Spanned`] more ergonomically.
117pub trait IntoSpanned: Sized {
118    /// Wrap items together with a span into [`Spanned`].
119    ///
120    /// # Example
121    ///
122    /// ```
123    /// # use nu_protocol::{Span, IntoSpanned};
124    /// # let span = Span::test_data();
125    /// let spanned = "Hello, world!".into_spanned(span);
126    /// assert_eq!("Hello, world!", spanned.item);
127    /// assert_eq!(span, spanned.span);
128    /// ```
129    fn into_spanned(self, span: Span) -> Spanned<Self>;
130}
131
132impl<T> IntoSpanned for T {
133    fn into_spanned(self, span: Span) -> Spanned<Self> {
134        Spanned { item: self, span }
135    }
136}
137
138/// Spans are a global offset across all seen files, which are cached in the engine's state. The start and
139/// end offset together make the inclusive start/exclusive end pair for where to underline to highlight
140/// a given point of interest.
141#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
142pub struct Span {
143    pub start: usize,
144    pub end: usize,
145}
146
147#[derive(Clone)]
148pub struct ResolvedSpan<'a> {
149    pub file: Cow<'a, str>,
150    pub span: Span,
151}
152
153impl<'a> IntoValue for ResolvedSpan<'a> {
154    fn into_value(self, span: Span) -> Value {
155        let record = record! {
156            "file" => self.file.into_value(span),
157            "start" => Value::int(self.span.start as i64, span),
158            "end" => Value::int(self.span.end as i64, span),
159        };
160        record.into_value(span)
161    }
162}
163
164impl fmt::Debug for Span {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        const TEST_DATA: Span = Span::test_data();
167        const UNKNOWN: Span = Span::unknown();
168
169        match *self {
170            TEST_DATA => write!(f, "Span(TEST)"),
171            UNKNOWN => write!(f, "Span(UNKNOWN)"),
172            Span { start, end } => write!(f, "Span[{start}..{end}]"),
173        }
174    }
175}
176
177impl Span {
178    pub fn new(start: usize, end: usize) -> Self {
179        debug_assert!(
180            end >= start,
181            "Can't create a Span whose end < start, start={start}, end={end}"
182        );
183
184        Self { start, end }
185    }
186
187    pub const fn unknown() -> Self {
188        Self { start: 0, end: 0 }
189    }
190
191    /// Span for testing purposes.
192    ///
193    /// The provided span does not point into any known source but is unequal to [`Span::unknown()`].
194    ///
195    /// Note: Only use this for test data, *not* live data, as it will point into unknown source
196    /// when used in errors
197    pub const fn test_data() -> Self {
198        Self {
199            start: usize::MAX / 2,
200            end: usize::MAX / 2,
201        }
202    }
203
204    pub fn offset(&self, offset: usize) -> Self {
205        Self::new(self.start - offset, self.end - offset)
206    }
207
208    /// Return length of the slice.
209    pub fn len(&self) -> usize {
210        self.end - self.start
211    }
212
213    /// Indicate if slice has length 0.
214    pub fn is_empty(&self) -> bool {
215        self.start == self.end
216    }
217
218    /// Return another span fully inside the [`Span`].
219    ///
220    /// `start` and `end` are relative to `self.start`, and must lie within the `Span`.
221    /// In other words, both `start` and `end` must be `<= self.len()`.
222    pub fn subspan(&self, offset_start: usize, offset_end: usize) -> Option<Self> {
223        let len = self.len();
224
225        if offset_start > len || offset_end > len || offset_start > offset_end {
226            None
227        } else {
228            Some(Self::new(
229                self.start + offset_start,
230                self.start + offset_end,
231            ))
232        }
233    }
234
235    /// Return two spans that split the ['Span'] at the given position.
236    pub fn split_at(&self, offset: usize) -> Option<(Self, Self)> {
237        if offset < self.len() {
238            Some((
239                Self::new(self.start, self.start + offset),
240                Self::new(self.start + offset, self.end),
241            ))
242        } else {
243            None
244        }
245    }
246
247    pub fn contains(&self, pos: usize) -> bool {
248        self.start <= pos && pos < self.end
249    }
250
251    pub fn contains_span(&self, span: Self) -> bool {
252        self.start <= span.start && span.end <= self.end && span.end != 0
253    }
254
255    /// Point at a specific position (zero-width span).
256    pub const fn point(pos: usize) -> Self {
257        Self {
258            start: pos,
259            end: pos,
260        }
261    }
262
263    /// Point to the space just past this span, useful for missing values
264    pub fn past(&self) -> Self {
265        Self {
266            start: self.end,
267            end: self.end,
268        }
269    }
270
271    /// Converts row and column in a String to a Span, assuming bytes (1-based rows)
272    pub fn from_row_column(row: usize, col: usize, contents: &str) -> Span {
273        let mut cur_row = 1;
274        let mut cur_col = 1;
275
276        for (offset, curr_byte) in contents.bytes().enumerate() {
277            if curr_byte == b'\n' {
278                cur_row += 1;
279                cur_col = 1;
280            } else if cur_row >= row && cur_col >= col {
281                return Span::point(offset);
282            } else {
283                cur_col += 1;
284            }
285        }
286
287        Span::point(contents.len())
288    }
289
290    /// Like [`from_row_column`] but checks for signal interruption during scanning.
291    ///
292    /// Signal check interval: every 16384 bytes.
293    /// Returns a span covering at least one byte so it is visible in error diagnostics.
294    pub fn try_from_row_column(
295        row: usize,
296        col: usize,
297        contents: &str,
298        span: &Span,
299        signals: &Signals,
300    ) -> Result<Span, ShellError> {
301        let mut cur_row = 1;
302        let mut cur_col = 1;
303
304        for (offset, curr_byte) in contents.bytes().enumerate() {
305            if offset > 0 && offset % 16384 == 0 {
306                signals.check(span)?;
307            }
308            if curr_byte == b'\n' {
309                cur_row += 1;
310                cur_col = 1;
311            } else if cur_row >= row && cur_col >= col {
312                // Return a span covering at least one byte for visible error highlighting
313                let end = contents.len().min(offset + 1);
314                return Ok(Span::new(offset, end));
315            } else {
316                cur_col += 1;
317            }
318        }
319
320        Ok(Span::point(contents.len()))
321    }
322
323    /// Returns the minimal [`Span`] that encompasses both of the given spans.
324    ///
325    /// The two `Spans` can overlap in the middle,
326    /// but must otherwise be in order by satisfying:
327    /// - `self.start <= after.start`
328    /// - `self.end <= after.end`
329    ///
330    /// If this is not guaranteed to be the case, use [`Span::merge`] instead.
331    pub fn append(self, after: Self) -> Self {
332        debug_assert!(
333            self.start <= after.start && self.end <= after.end,
334            "Can't merge two Spans that are not in order"
335        );
336        Self {
337            start: self.start,
338            end: after.end,
339        }
340    }
341
342    /// Returns the minimal [`Span`] that encompasses both of the given spans.
343    ///
344    /// The spans need not be in order or have any relationship.
345    ///
346    /// [`Span::append`] is slightly more efficient if the spans are known to be in order.
347    pub fn merge(self, other: Self) -> Self {
348        Self {
349            start: usize::min(self.start, other.start),
350            end: usize::max(self.end, other.end),
351        }
352    }
353
354    /// Returns the minimal [`Span`] that encompasses all of the spans in the given slice.
355    ///
356    /// The spans are assumed to be in order, that is, all consecutive spans must satisfy:
357    /// - `spans[i].start <= spans[i + 1].start`
358    /// - `spans[i].end <= spans[i + 1].end`
359    ///
360    /// (Two consecutive spans can overlap as long as the above is true.)
361    ///
362    /// Use [`Span::merge_many`] if the spans are not known to be in order.
363    pub fn concat(spans: &[Self]) -> Self {
364        // TODO: enable assert below
365        // debug_assert!(!spans.is_empty());
366        debug_assert!(spans.windows(2).all(|spans| {
367            let &[a, b] = spans else {
368                return false;
369            };
370            a.start <= b.start && a.end <= b.end
371        }));
372        Self {
373            start: spans.first().map(|s| s.start).unwrap_or(0),
374            end: spans.last().map(|s| s.end).unwrap_or(0),
375        }
376    }
377
378    /// Returns the minimal [`Span`] that encompasses all of the spans in the given iterator.
379    ///
380    /// The spans need not be in order or have any relationship.
381    ///
382    /// [`Span::concat`] is more efficient if the spans are known to be in order.
383    pub fn merge_many(spans: impl IntoIterator<Item = Self>) -> Self {
384        spans
385            .into_iter()
386            .reduce(Self::merge)
387            .unwrap_or(Self::unknown())
388    }
389
390    /// Replace a span with a new one if the previous span was either unknown or test data.
391    pub fn fallback(&mut self, span: Span) -> Span {
392        let current = *self;
393        if current == Span::unknown() || current == Span::test_data() {
394            *self = span;
395        }
396        *self
397    }
398}
399
400impl IntoValue for Span {
401    fn into_value(self, span: Span) -> Value {
402        let record = record! {
403            "start" => Value::int(self.start as i64, self),
404            "end" => Value::int(self.end as i64, self),
405        };
406        record.into_value(span)
407    }
408}
409
410impl FromValue for Span {
411    fn from_value(value: Value) -> Result<Self, ShellError> {
412        let rec = value.as_record();
413        match rec {
414            Ok(val) => {
415                let Some(pre_start) = val.get("start") else {
416                    return Err(ShellError::Generic(GenericError::new(
417                        "Unable to parse Span.",
418                        "`start` must be an `int`",
419                        value.span(),
420                    )));
421                };
422                let Some(pre_end) = val.get("end") else {
423                    return Err(ShellError::Generic(GenericError::new(
424                        "Unable to parse Span.",
425                        "`end` must be an `int`",
426                        value.span(),
427                    )));
428                };
429                let start = pre_start.as_int()? as usize;
430                let end = pre_end.as_int()? as usize;
431                if start <= end {
432                    Ok(Self::new(start, end))
433                } else {
434                    Err(ShellError::Generic(GenericError::new(
435                        "Unable to parse Span.",
436                        "`end` must not be less than `start`",
437                        value.span(),
438                    )))
439                }
440            }
441            _ => Err(ShellError::TypeMismatch {
442                err_message: "Must be a record".into(),
443                span: value.span(),
444            }),
445        }
446    }
447    fn expected_type() -> Type {
448        Type::Record([("start", Type::Int), ("end", Type::Int)].into())
449    }
450}
451
452impl From<Span> for SourceSpan {
453    fn from(s: Span) -> Self {
454        Self::new(s.start.into(), s.end - s.start)
455    }
456}
457
458/// An extension trait for [`Result`], which adds a span to the error type.
459///
460/// This trait might be removed later, since the old [`Spanned<std::io::Error>`] to
461/// [`ShellError`] conversion was replaced by
462/// [`IoError`](crate::shell_error::io::IoError).
463pub trait ErrSpan {
464    type Result;
465
466    /// Adds the given span to the error type, turning it into a [`Spanned<E>`].
467    fn err_span(self, span: Span) -> Self::Result;
468}
469
470impl<T, E> ErrSpan for Result<T, E> {
471    type Result = Result<T, Spanned<E>>;
472
473    fn err_span(self, span: Span) -> Self::Result {
474        self.map_err(|err| err.into_spanned(span))
475    }
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481    use crate::Signals;
482    use std::sync::{Arc, atomic::AtomicBool};
483
484    /// Span from a matched position now covers at least one byte for visible error highlighting.
485    /// Both `start` and `end` indicate that `start == end - 1`.
486
487    #[test]
488    fn try_from_row_column_first_line() {
489        let input = "hello\nworld\nfoo";
490        let signals = Signals::empty();
491        let result = Span::try_from_row_column(1, 3, input, &Span::unknown(), &signals);
492        assert_eq!(result, Ok(Span::new(2, 3))); // 'l' in "hello" at index 2..3
493    }
494
495    #[test]
496    fn try_from_row_column_second_line() {
497        let input = "hello\nworld\nfoo";
498        let signals = Signals::empty();
499        let result = Span::try_from_row_column(2, 1, input, &Span::unknown(), &signals);
500        assert_eq!(result, Ok(Span::new(6, 7))); // 'w' in "world" at index 6..7
501    }
502
503    #[test]
504    fn try_from_row_column_last_char() {
505        let input = "hello\nworld\nfoo";
506        let signals = Signals::empty();
507        // "foo" starts at index 12, third char 'o' at index 14
508        let result = Span::try_from_row_column(3, 3, input, &Span::unknown(), &signals);
509        assert_eq!(result, Ok(Span::new(14, 15)));
510    }
511
512    #[test]
513    fn try_from_row_column_beyond_input() {
514        let input = "hi";
515        let signals = Signals::empty();
516        // Asking for a row beyond the input should return the end
517        let result = Span::try_from_row_column(10, 1, input, &Span::unknown(), &signals);
518        assert_eq!(result, Ok(Span::new(2, 2))); // end of input
519    }
520
521    #[test]
522    fn try_from_row_column_interrupted_triggers_error() {
523        // Input long enough to trigger the 16384-byte signal check
524        let flag = Arc::new(AtomicBool::new(true)); // pre-triggered
525        let signals = Signals::new(flag);
526        let input = "x".repeat(20_000);
527        let result = Span::try_from_row_column(1, 18_000, &input, &Span::unknown(), &signals);
528        assert!(result.is_err());
529        assert!(matches!(result, Err(ShellError::Interrupted { .. })));
530    }
531
532    #[test]
533    fn try_from_row_column_short_input_skips_signal_check() {
534        // Short input completes before hitting the 16384-byte check interval
535        let flag = Arc::new(AtomicBool::new(true)); // pre-triggered
536        let signals = Signals::new(flag);
537        let input = "hello\nworld";
538        let result = Span::try_from_row_column(1, 1, input, &Span::unknown(), &signals);
539        // Should complete successfully since the scan is too short to check signals
540        assert_eq!(result, Ok(Span::new(0, 1))); // covers first byte
541    }
542
543    #[test]
544    fn try_from_row_column_not_interrupted() {
545        let flag = Arc::new(AtomicBool::new(false));
546        let signals = Signals::new(flag);
547        let input = "a\nb\nc";
548        let result = Span::try_from_row_column(1, 1, input, &Span::unknown(), &signals);
549        assert_eq!(result, Ok(Span::new(0, 1))); // covers first byte
550    }
551
552    #[test]
553    fn try_from_row_column_start_matches_from_row_column() {
554        // try_from_row_column should have the same `start` as from_row_column
555        // but covers at least one byte (end > start) for visible error highlighting
556        let input = "line one\nline two\nline three";
557        let signals = Signals::empty();
558        let expected = Span::from_row_column(2, 6, input);
559        let result = Span::try_from_row_column(2, 6, input, &Span::unknown(), &signals)
560            .expect("should succeed");
561        assert_eq!(result.start, expected.start, "start should match");
562        assert!(
563            result.end > result.start,
564            "end should extend past start for visibility"
565        );
566    }
567}