naga/
span.rs

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