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