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
15 pub const fn new(start: u32, end: u32) -> Self {
19 Span { start, end }
20 }
21
22 pub const fn until(&self, other: &Self) -> Self {
24 Span {
25 start: self.start,
26 end: other.end,
27 }
28 }
29
30 pub fn subsume(&mut self, other: Self) {
33 *self = if !self.is_defined() {
34 other
36 } else if !other.is_defined() {
37 *self
39 } else {
40 Span {
42 start: self.start.min(other.start),
43 end: self.end.max(other.end),
44 }
45 }
46 }
47
48 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 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 pub fn is_defined(&self) -> bool {
69 *self != Self::default()
70 }
71
72 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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
115pub struct SourceLocation {
116 pub line_number: u32,
118 pub line_position: u32,
120 pub offset: u32,
122 pub length: u32,
124}
125
126pub type SpanContext = (Span, String);
128
129#[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 pub const fn new(inner: E) -> Self {
167 Self {
168 inner,
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 ExactSizeIterator<Item = &SpanContext> {
185 self.spans.iter()
186 }
187
188 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 pub fn with_context(self, span_context: SpanContext) -> Self {
201 let (span, description) = span_context;
202 self.with_span(span, description)
203 }
204
205 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 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 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 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 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 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 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 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
315pub(crate) trait AddSpan: Sized {
317 type Output;
318 fn with_span(self) -> Self::Output;
320 fn with_span_static(self, span: Span, description: &'static str) -> Self::Output;
322 fn with_span_context(self, span_context: SpanContext) -> Self::Output;
324 fn with_span_handle<T, A: SpanProvider<T>>(self, handle: Handle<T>, arena: &A) -> Self::Output;
326}
327
328pub(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
380pub 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}