naga/
span.rs

1use crate::{Arena, Handle, UniqueArena};
2use std::{error::Error, fmt, ops::Range};
3
4/// A source code span, used for error reporting.
5#[derive(Clone, Copy, Debug, PartialEq, Default)]
6#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
7pub struct Span {
8    start: u32,
9    end: u32,
10}
11
12impl Span {
13    pub const UNDEFINED: Self = Self { start: 0, end: 0 };
14    /// Creates a new `Span` from a range of byte indices
15    ///
16    /// Note: end is exclusive, it doesn't belong to the `Span`
17    pub const fn new(start: u32, end: u32) -> Self {
18        Span { start, end }
19    }
20
21    /// Returns a new `Span` starting at `self` and ending at `other`
22    pub const fn until(&self, other: &Self) -> Self {
23        Span {
24            start: self.start,
25            end: other.end,
26        }
27    }
28
29    /// Modifies `self` to contain the smallest `Span` possible that
30    /// contains both `self` and `other`
31    pub fn subsume(&mut self, other: Self) {
32        *self = if !self.is_defined() {
33            // self isn't defined so use other
34            other
35        } else if !other.is_defined() {
36            // other isn't defined so don't try to subsume
37            *self
38        } else {
39            // Both self and other are defined so calculate the span that contains them both
40            Span {
41                start: self.start.min(other.start),
42                end: self.end.max(other.end),
43            }
44        }
45    }
46
47    /// Returns the smallest `Span` possible that contains all the `Span`s
48    /// defined in the `from` iterator
49    pub fn total_span<T: Iterator<Item = Self>>(from: T) -> Self {
50        let mut span: Self = Default::default();
51        for other in from {
52            span.subsume(other);
53        }
54        span
55    }
56
57    /// Converts `self` to a range if the span is not unknown
58    pub fn to_range(self) -> Option<Range<usize>> {
59        if self.is_defined() {
60            Some(self.start as usize..self.end as usize)
61        } else {
62            None
63        }
64    }
65
66    /// Check whether `self` was defined or is a default/unknown span
67    pub fn is_defined(&self) -> bool {
68        *self != Self::default()
69    }
70
71    /// Return a [`SourceLocation`] for this span in the provided source.
72    pub fn location(&self, source: &str) -> SourceLocation {
73        let prefix = &source[..self.start as usize];
74        let line_number = prefix.matches('\n').count() as u32 + 1;
75        let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0);
76        let line_position = source[line_start..self.start as usize].chars().count() as u32 + 1;
77
78        SourceLocation {
79            line_number,
80            line_position,
81            offset: self.start,
82            length: self.end - self.start,
83        }
84    }
85}
86
87impl From<Range<usize>> for Span {
88    fn from(range: Range<usize>) -> Self {
89        Span {
90            start: range.start as u32,
91            end: range.end as u32,
92        }
93    }
94}
95
96impl std::ops::Index<Span> for str {
97    type Output = str;
98
99    #[inline]
100    fn index(&self, span: Span) -> &str {
101        &self[span.start as usize..span.end as usize]
102    }
103}
104
105/// A human-readable representation for a span, tailored for text source.
106///
107/// Corresponds to the positional members of [`GPUCompilationMessage`][gcm] from
108/// the WebGPU specification, except that `offset` and `length` are in bytes
109/// (UTF-8 code units), instead of UTF-16 code units.
110///
111/// [gcm]: https://www.w3.org/TR/webgpu/#gpucompilationmessage
112#[derive(Copy, Clone, Debug, PartialEq, Eq)]
113pub struct SourceLocation {
114    /// 1-based line number.
115    pub line_number: u32,
116    /// 1-based column of the start of this span
117    pub line_position: u32,
118    /// 0-based Offset in code units (in bytes) of the start of the span.
119    pub offset: u32,
120    /// Length in code units (in bytes) of the span.
121    pub length: u32,
122}
123
124/// A source code span together with "context", a user-readable description of what part of the error it refers to.
125pub type SpanContext = (Span, String);
126
127/// Wrapper class for [`Error`], augmenting it with a list of [`SpanContext`]s.
128#[derive(Debug, Clone)]
129pub struct WithSpan<E> {
130    inner: E,
131    #[cfg(feature = "span")]
132    spans: Vec<SpanContext>,
133}
134
135impl<E> fmt::Display for WithSpan<E>
136where
137    E: fmt::Display,
138{
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
140        self.inner.fmt(f)
141    }
142}
143
144#[cfg(test)]
145impl<E> PartialEq for WithSpan<E>
146where
147    E: PartialEq,
148{
149    fn eq(&self, other: &Self) -> bool {
150        self.inner.eq(&other.inner)
151    }
152}
153
154impl<E> Error for WithSpan<E>
155where
156    E: Error,
157{
158    fn source(&self) -> Option<&(dyn Error + 'static)> {
159        self.inner.source()
160    }
161}
162
163impl<E> WithSpan<E> {
164    /// Create a new [`WithSpan`] from an [`Error`], containing no spans.
165    pub const fn new(inner: E) -> Self {
166        Self {
167            inner,
168            #[cfg(feature = "span")]
169            spans: Vec::new(),
170        }
171    }
172
173    /// Reverse of [`Self::new`], discards span information and returns an inner error.
174    #[allow(clippy::missing_const_for_fn)] // ignore due to requirement of #![feature(const_precise_live_drops)]
175    pub fn into_inner(self) -> E {
176        self.inner
177    }
178
179    pub const fn as_inner(&self) -> &E {
180        &self.inner
181    }
182
183    /// Iterator over stored [`SpanContext`]s.
184    pub fn spans(&self) -> impl Iterator<Item = &SpanContext> + ExactSizeIterator {
185        #[cfg(feature = "span")]
186        return self.spans.iter();
187        #[cfg(not(feature = "span"))]
188        return std::iter::empty();
189    }
190
191    /// Add a new span with description.
192    #[cfg_attr(not(feature = "span"), allow(unused_variables, unused_mut))]
193    pub fn with_span<S>(mut self, span: Span, description: S) -> Self
194    where
195        S: ToString,
196    {
197        #[cfg(feature = "span")]
198        if span.is_defined() {
199            self.spans.push((span, description.to_string()));
200        }
201        self
202    }
203
204    /// Add a [`SpanContext`].
205    pub fn with_context(self, span_context: SpanContext) -> Self {
206        let (span, description) = span_context;
207        self.with_span(span, description)
208    }
209
210    /// Add a [`Handle`] from either [`Arena`] or [`UniqueArena`], borrowing its span information from there
211    /// and annotating with a type and the handle representation.
212    pub(crate) fn with_handle<T, A: SpanProvider<T>>(self, handle: Handle<T>, arena: &A) -> Self {
213        self.with_context(arena.get_span_context(handle))
214    }
215
216    /// Convert inner error using [`From`].
217    pub fn into_other<E2>(self) -> WithSpan<E2>
218    where
219        E2: From<E>,
220    {
221        WithSpan {
222            inner: self.inner.into(),
223            #[cfg(feature = "span")]
224            spans: self.spans,
225        }
226    }
227
228    /// Convert inner error into another type. Joins span information contained in `self`
229    /// with what is returned from `func`.
230    pub fn and_then<F, E2>(self, func: F) -> WithSpan<E2>
231    where
232        F: FnOnce(E) -> WithSpan<E2>,
233    {
234        #[cfg_attr(not(feature = "span"), allow(unused_mut))]
235        let mut res = func(self.inner);
236        #[cfg(feature = "span")]
237        res.spans.extend(self.spans);
238        res
239    }
240
241    #[cfg(feature = "span")]
242    /// Return a [`SourceLocation`] for our first span, if we have one.
243    pub fn location(&self, source: &str) -> Option<SourceLocation> {
244        if self.spans.is_empty() {
245            return None;
246        }
247
248        Some(self.spans[0].0.location(source))
249    }
250
251    #[cfg(not(feature = "span"))]
252    /// Return a [`SourceLocation`] for our first span, if we have one.
253    pub fn location(&self, _source: &str) -> Option<SourceLocation> {
254        None
255    }
256
257    #[cfg(feature = "span")]
258    fn diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic<()>
259    where
260        E: Error,
261    {
262        use codespan_reporting::diagnostic::{Diagnostic, Label};
263        let diagnostic = Diagnostic::error()
264            .with_message(self.inner.to_string())
265            .with_labels(
266                self.spans()
267                    .map(|&(span, ref desc)| {
268                        Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned())
269                    })
270                    .collect(),
271            )
272            .with_notes({
273                let mut notes = Vec::new();
274                let mut source: &dyn Error = &self.inner;
275                while let Some(next) = Error::source(source) {
276                    notes.push(next.to_string());
277                    source = next;
278                }
279                notes
280            });
281        diagnostic
282    }
283
284    /// Emits a summary of the error to standard error stream.
285    #[cfg(feature = "span")]
286    pub fn emit_to_stderr(&self, source: &str)
287    where
288        E: Error,
289    {
290        self.emit_to_stderr_with_path(source, "wgsl")
291    }
292
293    /// Emits a summary of the error to standard error stream.
294    #[cfg(feature = "span")]
295    pub fn emit_to_stderr_with_path(&self, source: &str, path: &str)
296    where
297        E: Error,
298    {
299        use codespan_reporting::{files, term};
300        use term::termcolor::{ColorChoice, StandardStream};
301
302        let files = files::SimpleFile::new(path, source);
303        let config = term::Config::default();
304        let writer = StandardStream::stderr(ColorChoice::Auto);
305        term::emit(&mut writer.lock(), &config, &files, &self.diagnostic())
306            .expect("cannot write error");
307    }
308
309    /// Emits a summary of the error to a string.
310    #[cfg(feature = "span")]
311    pub fn emit_to_string(&self, source: &str) -> String
312    where
313        E: Error,
314    {
315        self.emit_to_string_with_path(source, "wgsl")
316    }
317
318    /// Emits a summary of the error to a string.
319    #[cfg(feature = "span")]
320    pub fn emit_to_string_with_path(&self, source: &str, path: &str) -> String
321    where
322        E: Error,
323    {
324        use codespan_reporting::{files, term};
325        use term::termcolor::NoColor;
326
327        let files = files::SimpleFile::new(path, source);
328        let config = codespan_reporting::term::Config::default();
329        let mut writer = NoColor::new(Vec::new());
330        term::emit(&mut writer, &config, &files, &self.diagnostic()).expect("cannot write error");
331        String::from_utf8(writer.into_inner()).unwrap()
332    }
333}
334
335/// Convenience trait for [`Error`] to be able to apply spans to anything.
336pub(crate) trait AddSpan: Sized {
337    type Output;
338    /// See [`WithSpan::new`].
339    fn with_span(self) -> Self::Output;
340    /// See [`WithSpan::with_span`].
341    fn with_span_static(self, span: Span, description: &'static str) -> Self::Output;
342    /// See [`WithSpan::with_context`].
343    fn with_span_context(self, span_context: SpanContext) -> Self::Output;
344    /// See [`WithSpan::with_handle`].
345    fn with_span_handle<T, A: SpanProvider<T>>(self, handle: Handle<T>, arena: &A) -> Self::Output;
346}
347
348/// Trait abstracting over getting a span from an [`Arena`] or a [`UniqueArena`].
349pub(crate) trait SpanProvider<T> {
350    fn get_span(&self, handle: Handle<T>) -> Span;
351    fn get_span_context(&self, handle: Handle<T>) -> SpanContext {
352        match self.get_span(handle) {
353            x if !x.is_defined() => (Default::default(), "".to_string()),
354            known => (
355                known,
356                format!("{} {:?}", std::any::type_name::<T>(), handle),
357            ),
358        }
359    }
360}
361
362impl<T> SpanProvider<T> for Arena<T> {
363    fn get_span(&self, handle: Handle<T>) -> Span {
364        self.get_span(handle)
365    }
366}
367
368impl<T> SpanProvider<T> for UniqueArena<T> {
369    fn get_span(&self, handle: Handle<T>) -> Span {
370        self.get_span(handle)
371    }
372}
373
374impl<E> AddSpan for E
375where
376    E: Error,
377{
378    type Output = WithSpan<Self>;
379    fn with_span(self) -> WithSpan<Self> {
380        WithSpan::new(self)
381    }
382
383    fn with_span_static(self, span: Span, description: &'static str) -> WithSpan<Self> {
384        WithSpan::new(self).with_span(span, description)
385    }
386
387    fn with_span_context(self, span_context: SpanContext) -> WithSpan<Self> {
388        WithSpan::new(self).with_context(span_context)
389    }
390
391    fn with_span_handle<T, A: SpanProvider<T>>(
392        self,
393        handle: Handle<T>,
394        arena: &A,
395    ) -> WithSpan<Self> {
396        WithSpan::new(self).with_handle(handle, arena)
397    }
398}
399
400/// Convenience trait for [`Result`], adding a [`MapErrWithSpan::map_err_inner`]
401/// mapping to [`WithSpan::and_then`].
402pub trait MapErrWithSpan<E, E2>: Sized {
403    type Output: Sized;
404    fn map_err_inner<F, E3>(self, func: F) -> Self::Output
405    where
406        F: FnOnce(E) -> WithSpan<E3>,
407        E2: From<E3>;
408}
409
410impl<T, E, E2> MapErrWithSpan<E, E2> for Result<T, WithSpan<E>> {
411    type Output = Result<T, WithSpan<E2>>;
412    fn map_err_inner<F, E3>(self, func: F) -> Result<T, WithSpan<E2>>
413    where
414        F: FnOnce(E) -> WithSpan<E3>,
415        E2: From<E3>,
416    {
417        self.map_err(|e| e.and_then(func).into_other::<E2>())
418    }
419}
420
421#[test]
422fn span_location() {
423    let source = "12\n45\n\n89\n";
424    assert_eq!(
425        Span { start: 0, end: 1 }.location(source),
426        SourceLocation {
427            line_number: 1,
428            line_position: 1,
429            offset: 0,
430            length: 1
431        }
432    );
433    assert_eq!(
434        Span { start: 1, end: 2 }.location(source),
435        SourceLocation {
436            line_number: 1,
437            line_position: 2,
438            offset: 1,
439            length: 1
440        }
441    );
442    assert_eq!(
443        Span { start: 2, end: 3 }.location(source),
444        SourceLocation {
445            line_number: 1,
446            line_position: 3,
447            offset: 2,
448            length: 1
449        }
450    );
451    assert_eq!(
452        Span { start: 3, end: 5 }.location(source),
453        SourceLocation {
454            line_number: 2,
455            line_position: 1,
456            offset: 3,
457            length: 2
458        }
459    );
460    assert_eq!(
461        Span { start: 4, end: 6 }.location(source),
462        SourceLocation {
463            line_number: 2,
464            line_position: 2,
465            offset: 4,
466            length: 2
467        }
468    );
469    assert_eq!(
470        Span { start: 5, end: 6 }.location(source),
471        SourceLocation {
472            line_number: 2,
473            line_position: 3,
474            offset: 5,
475            length: 1
476        }
477    );
478    assert_eq!(
479        Span { start: 6, end: 7 }.location(source),
480        SourceLocation {
481            line_number: 3,
482            line_position: 1,
483            offset: 6,
484            length: 1
485        }
486    );
487    assert_eq!(
488        Span { start: 7, end: 8 }.location(source),
489        SourceLocation {
490            line_number: 4,
491            line_position: 1,
492            offset: 7,
493            length: 1
494        }
495    );
496    assert_eq!(
497        Span { start: 8, end: 9 }.location(source),
498        SourceLocation {
499            line_number: 4,
500            line_position: 2,
501            offset: 8,
502            length: 1
503        }
504    );
505    assert_eq!(
506        Span { start: 9, end: 10 }.location(source),
507        SourceLocation {
508            line_number: 4,
509            line_position: 3,
510            offset: 9,
511            length: 1
512        }
513    );
514    assert_eq!(
515        Span { start: 10, end: 11 }.location(source),
516        SourceLocation {
517            line_number: 5,
518            line_position: 1,
519            offset: 10,
520            length: 1
521        }
522    );
523}