1use crate::{Arena, Handle, UniqueArena};
2use std::{error::Error, fmt, ops::Range};
3
4#[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 pub const fn new(start: u32, end: u32) -> Self {
18 Span { start, end }
19 }
20
21 pub const fn until(&self, other: &Self) -> Self {
23 Span {
24 start: self.start,
25 end: other.end,
26 }
27 }
28
29 pub fn subsume(&mut self, other: Self) {
32 *self = if !self.is_defined() {
33 other
35 } else if !other.is_defined() {
36 *self
38 } else {
39 Span {
41 start: self.start.min(other.start),
42 end: self.end.max(other.end),
43 }
44 }
45 }
46
47 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 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 pub fn is_defined(&self) -> bool {
68 *self != Self::default()
69 }
70
71 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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
114pub struct SourceLocation {
115 pub line_number: u32,
117 pub line_position: u32,
119 pub offset: u32,
121 pub length: u32,
123}
124
125pub type SpanContext = (Span, String);
127
128#[derive(Debug, Clone)]
130pub struct WithSpan<E> {
131 inner: E,
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<'_>) -> 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 pub const fn new(inner: E) -> Self {
166 Self {
167 inner,
168 spans: Vec::new(),
169 }
170 }
171
172 #[allow(clippy::missing_const_for_fn)] pub fn into_inner(self) -> E {
175 self.inner
176 }
177
178 pub const fn as_inner(&self) -> &E {
179 &self.inner
180 }
181
182 pub fn spans(&self) -> impl ExactSizeIterator<Item = &SpanContext> {
184 self.spans.iter()
185 }
186
187 pub fn with_span<S>(mut self, span: Span, description: S) -> Self
189 where
190 S: ToString,
191 {
192 if span.is_defined() {
193 self.spans.push((span, description.to_string()));
194 }
195 self
196 }
197
198 pub fn with_context(self, span_context: SpanContext) -> Self {
200 let (span, description) = span_context;
201 self.with_span(span, description)
202 }
203
204 pub(crate) fn with_handle<T, A: SpanProvider<T>>(self, handle: Handle<T>, arena: &A) -> Self {
207 self.with_context(arena.get_span_context(handle))
208 }
209
210 pub fn into_other<E2>(self) -> WithSpan<E2>
212 where
213 E2: From<E>,
214 {
215 WithSpan {
216 inner: self.inner.into(),
217 spans: self.spans,
218 }
219 }
220
221 pub fn and_then<F, E2>(self, func: F) -> WithSpan<E2>
224 where
225 F: FnOnce(E) -> WithSpan<E2>,
226 {
227 let mut res = func(self.inner);
228 res.spans.extend(self.spans);
229 res
230 }
231
232 pub fn location(&self, source: &str) -> Option<SourceLocation> {
234 if self.spans.is_empty() {
235 return None;
236 }
237
238 Some(self.spans[0].0.location(source))
239 }
240
241 fn diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic<()>
242 where
243 E: Error,
244 {
245 use codespan_reporting::diagnostic::{Diagnostic, Label};
246 let diagnostic = Diagnostic::error()
247 .with_message(self.inner.to_string())
248 .with_labels(
249 self.spans()
250 .map(|&(span, ref desc)| {
251 Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned())
252 })
253 .collect(),
254 )
255 .with_notes({
256 let mut notes = Vec::new();
257 let mut source: &dyn Error = &self.inner;
258 while let Some(next) = Error::source(source) {
259 notes.push(next.to_string());
260 source = next;
261 }
262 notes
263 });
264 diagnostic
265 }
266
267 pub fn emit_to_stderr(&self, source: &str)
269 where
270 E: Error,
271 {
272 self.emit_to_stderr_with_path(source, "wgsl")
273 }
274
275 pub fn emit_to_stderr_with_path(&self, source: &str, path: &str)
277 where
278 E: Error,
279 {
280 use codespan_reporting::{files, term};
281 use term::termcolor::{ColorChoice, StandardStream};
282
283 let files = files::SimpleFile::new(path, source);
284 let config = term::Config::default();
285 let writer = StandardStream::stderr(ColorChoice::Auto);
286 term::emit(&mut writer.lock(), &config, &files, &self.diagnostic())
287 .expect("cannot write error");
288 }
289
290 pub fn emit_to_string(&self, source: &str) -> String
292 where
293 E: Error,
294 {
295 self.emit_to_string_with_path(source, "wgsl")
296 }
297
298 pub fn emit_to_string_with_path(&self, source: &str, path: &str) -> String
300 where
301 E: Error,
302 {
303 use codespan_reporting::{files, term};
304 use term::termcolor::NoColor;
305
306 let files = files::SimpleFile::new(path, source);
307 let config = term::Config::default();
308 let mut writer = NoColor::new(Vec::new());
309 term::emit(&mut writer, &config, &files, &self.diagnostic()).expect("cannot write error");
310 String::from_utf8(writer.into_inner()).unwrap()
311 }
312}
313
314pub(crate) trait AddSpan: Sized {
316 type Output;
317 fn with_span(self) -> Self::Output;
319 fn with_span_static(self, span: Span, description: &'static str) -> Self::Output;
321 fn with_span_context(self, span_context: SpanContext) -> Self::Output;
323 fn with_span_handle<T, A: SpanProvider<T>>(self, handle: Handle<T>, arena: &A) -> Self::Output;
325}
326
327pub(crate) trait SpanProvider<T> {
329 fn get_span(&self, handle: Handle<T>) -> Span;
330 fn get_span_context(&self, handle: Handle<T>) -> SpanContext {
331 match self.get_span(handle) {
332 x if !x.is_defined() => (Default::default(), "".to_string()),
333 known => (
334 known,
335 format!("{} {:?}", std::any::type_name::<T>(), handle),
336 ),
337 }
338 }
339}
340
341impl<T> SpanProvider<T> for Arena<T> {
342 fn get_span(&self, handle: Handle<T>) -> Span {
343 self.get_span(handle)
344 }
345}
346
347impl<T> SpanProvider<T> for UniqueArena<T> {
348 fn get_span(&self, handle: Handle<T>) -> Span {
349 self.get_span(handle)
350 }
351}
352
353impl<E> AddSpan for E
354where
355 E: Error,
356{
357 type Output = WithSpan<Self>;
358 fn with_span(self) -> WithSpan<Self> {
359 WithSpan::new(self)
360 }
361
362 fn with_span_static(self, span: Span, description: &'static str) -> WithSpan<Self> {
363 WithSpan::new(self).with_span(span, description)
364 }
365
366 fn with_span_context(self, span_context: SpanContext) -> WithSpan<Self> {
367 WithSpan::new(self).with_context(span_context)
368 }
369
370 fn with_span_handle<T, A: SpanProvider<T>>(
371 self,
372 handle: Handle<T>,
373 arena: &A,
374 ) -> WithSpan<Self> {
375 WithSpan::new(self).with_handle(handle, arena)
376 }
377}
378
379pub trait MapErrWithSpan<E, E2>: Sized {
382 type Output: Sized;
383 fn map_err_inner<F, E3>(self, func: F) -> Self::Output
384 where
385 F: FnOnce(E) -> WithSpan<E3>,
386 E2: From<E3>;
387}
388
389impl<T, E, E2> MapErrWithSpan<E, E2> for Result<T, WithSpan<E>> {
390 type Output = Result<T, WithSpan<E2>>;
391 fn map_err_inner<F, E3>(self, func: F) -> Result<T, WithSpan<E2>>
392 where
393 F: FnOnce(E) -> WithSpan<E3>,
394 E2: From<E3>,
395 {
396 self.map_err(|e| e.and_then(func).into_other::<E2>())
397 }
398}
399
400#[test]
401fn span_location() {
402 let source = "12\n45\n\n89\n";
403 assert_eq!(
404 Span { start: 0, end: 1 }.location(source),
405 SourceLocation {
406 line_number: 1,
407 line_position: 1,
408 offset: 0,
409 length: 1
410 }
411 );
412 assert_eq!(
413 Span { start: 1, end: 2 }.location(source),
414 SourceLocation {
415 line_number: 1,
416 line_position: 2,
417 offset: 1,
418 length: 1
419 }
420 );
421 assert_eq!(
422 Span { start: 2, end: 3 }.location(source),
423 SourceLocation {
424 line_number: 1,
425 line_position: 3,
426 offset: 2,
427 length: 1
428 }
429 );
430 assert_eq!(
431 Span { start: 3, end: 5 }.location(source),
432 SourceLocation {
433 line_number: 2,
434 line_position: 1,
435 offset: 3,
436 length: 2
437 }
438 );
439 assert_eq!(
440 Span { start: 4, end: 6 }.location(source),
441 SourceLocation {
442 line_number: 2,
443 line_position: 2,
444 offset: 4,
445 length: 2
446 }
447 );
448 assert_eq!(
449 Span { start: 5, end: 6 }.location(source),
450 SourceLocation {
451 line_number: 2,
452 line_position: 3,
453 offset: 5,
454 length: 1
455 }
456 );
457 assert_eq!(
458 Span { start: 6, end: 7 }.location(source),
459 SourceLocation {
460 line_number: 3,
461 line_position: 1,
462 offset: 6,
463 length: 1
464 }
465 );
466 assert_eq!(
467 Span { start: 7, end: 8 }.location(source),
468 SourceLocation {
469 line_number: 4,
470 line_position: 1,
471 offset: 7,
472 length: 1
473 }
474 );
475 assert_eq!(
476 Span { start: 8, end: 9 }.location(source),
477 SourceLocation {
478 line_number: 4,
479 line_position: 2,
480 offset: 8,
481 length: 1
482 }
483 );
484 assert_eq!(
485 Span { start: 9, end: 10 }.location(source),
486 SourceLocation {
487 line_number: 4,
488 line_position: 3,
489 offset: 9,
490 length: 1
491 }
492 );
493 assert_eq!(
494 Span { start: 10, end: 11 }.location(source),
495 SourceLocation {
496 line_number: 5,
497 line_position: 1,
498 offset: 10,
499 length: 1
500 }
501 );
502}