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#[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 pub const fn new(start: u32, end: u32) -> Self {
26 Span { start, end }
27 }
28
29 pub const fn until(&self, other: &Self) -> Self {
31 Span {
32 start: self.start,
33 end: other.end,
34 }
35 }
36
37 pub fn subsume(&mut self, other: Self) {
40 *self = if !self.is_defined() {
41 other
43 } else if !other.is_defined() {
44 *self
46 } else {
47 Span {
49 start: self.start.min(other.start),
50 end: self.end.max(other.end),
51 }
52 }
53 }
54
55 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 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 pub fn is_defined(&self) -> bool {
76 *self != Self::default()
77 }
78
79 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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
122pub struct SourceLocation {
123 pub line_number: u32,
125 pub line_position: u32,
127 pub offset: u32,
129 pub length: u32,
131}
132
133pub type SpanContext = (Span, String);
135
136#[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 pub const fn new(inner: E) -> Self {
174 Self {
175 inner,
176 spans: Vec::new(),
177 }
178 }
179
180 #[allow(clippy::missing_const_for_fn)] 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 pub fn spans(&self) -> impl ExactSizeIterator<Item = &SpanContext> {
192 self.spans.iter()
193 }
194
195 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 pub fn with_context(self, span_context: SpanContext) -> Self {
208 let (span, description) = span_context;
209 self.with_span(span, description)
210 }
211
212 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 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 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 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 #[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 #[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 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 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
336pub(crate) trait AddSpan: Sized {
338 type Output;
340
341 fn with_span(self) -> Self::Output;
343 fn with_span_static(self, span: Span, description: &'static str) -> Self::Output;
345 fn with_span_context(self, span_context: SpanContext) -> Self::Output;
347 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
375pub(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
401pub(crate) trait MapErrWithSpan<E, E2>: Sized {
404 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}