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)]
113pub struct SourceLocation {
114 pub line_number: u32,
116 pub line_position: u32,
118 pub offset: u32,
120 pub length: u32,
122}
123
124pub type SpanContext = (Span, String);
126
127#[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 pub const fn new(inner: E) -> Self {
166 Self {
167 inner,
168 #[cfg(feature = "span")]
169 spans: Vec::new(),
170 }
171 }
172
173 #[allow(clippy::missing_const_for_fn)] 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 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 #[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 pub fn with_context(self, span_context: SpanContext) -> Self {
206 let (span, description) = span_context;
207 self.with_span(span, description)
208 }
209
210 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 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 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 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 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 #[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 #[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 #[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 #[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
335pub(crate) trait AddSpan: Sized {
337 type Output;
338 fn with_span(self) -> Self::Output;
340 fn with_span_static(self, span: Span, description: &'static str) -> Self::Output;
342 fn with_span_context(self, span_context: SpanContext) -> Self::Output;
344 fn with_span_handle<T, A: SpanProvider<T>>(self, handle: Handle<T>, arena: &A) -> Self::Output;
346}
347
348pub(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
400pub 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}