Skip to main content

wesl/
error.rs

1//! [`enum@Error`] types.
2
3use std::{fmt::Display, path::PathBuf};
4
5use thiserror::Error;
6use wgsl_parse::{
7    span::Span,
8    syntax::{Expression, Ident, ModulePath, Visibility},
9};
10
11#[cfg(feature = "eval")]
12use crate::eval::EvalError;
13use crate::{Mangler, sourcemap::SourceMap};
14
15/// Conditional translation error.
16#[derive(Debug, Error)]
17pub enum CondCompError {
18    #[error("invalid feature flag: `{0}`")]
19    InvalidFeatureFlag(String),
20    #[error("unexpected feature flag: `{0}`")]
21    UnexpectedFeatureFlag(String),
22    #[error("invalid if attribute expression: `{0}`")]
23    InvalidExpression(Expression),
24    #[error("an @elif or @else attribute must be preceded by a @if or @elif on the previous node")]
25    NoPrecedingIf,
26    #[error("cannot have multiple @if/@elif/@else attributes on the same node")]
27    DuplicateIf,
28}
29
30/// Error produced by module resolution.
31#[derive(Debug, thiserror::Error)]
32pub enum ResolveError {
33    #[error("I/O Error: {0}")]
34    Io(std::io::Error),
35    #[error("file not found: `{0}` ({1})")]
36    FileNotFound(PathBuf, String),
37    #[error("module not found: `{0}` ({1})")]
38    ModuleNotFound(ModulePath, String),
39    #[error("the resolver does not support file system paths")]
40    FilesystemNotSupported,
41    #[error("attempt to access path `{0}`, which escapes the package root path `{1}`")]
42    FileEscapesRoot(PathBuf, PathBuf),
43    #[error("{0}")]
44    Error(#[from] Diagnostic<Error>),
45}
46
47/// WESL or WGSL Validation error.
48#[derive(Clone, Debug, thiserror::Error)]
49pub enum ValidateError {
50    #[error("cannot find declaration of `{0}`")]
51    UndefinedSymbol(String),
52    #[error("incorrect number of arguments to `{0}`, expected `{1}`, got `{2}`")]
53    ParamCount(String, usize, usize),
54    #[error("`{0}` is not callable")]
55    NotCallable(String),
56    #[error("duplicate declaration of `{0}`")]
57    Duplicate(String),
58    #[error("declaration of `{0}` is cyclic via `{1}`")]
59    Cycle(String, String),
60}
61
62/// Error produced during import resolution.
63#[derive(Debug, thiserror::Error)]
64pub enum ImportError {
65    #[error("duplicate declaration of `{0}`")]
66    DuplicateSymbol(String),
67    #[error("{0}")]
68    ResolveError(#[from] ResolveError),
69}
70
71#[derive(Debug, thiserror::Error)]
72pub enum UsageError {
73    #[error("module `{0}` has no declaration `{1}`")]
74    NotFound(ModulePath, String),
75    #[error(
76        "`{decl_path}::{decl_name}` is declared with `{decl_vis}` visibility, but {orig} imports it with `{min_vis}` visibility",
77        decl_path = .decl.0,
78        decl_name = .decl.1,
79        orig = .orig
80            .as_ref()
81            .map(|(path, name)| format!("`{path}::{name}`"))
82            .unwrap_or("another declaration".to_string())
83    )]
84    Visibility {
85        orig: Option<(ModulePath, Ident)>,
86        decl: (ModulePath, Ident),
87        min_vis: Visibility,
88        decl_vis: Visibility,
89    },
90}
91
92#[derive(Debug, thiserror::Error)]
93pub enum TomlError {
94    #[error("wesl.toml not found at `{0}`")]
95    TomlNotFound(PathBuf),
96    #[error("Failed to parse wesl.toml: {0}")]
97    TomlParse(#[from] toml::de::Error),
98    #[error("expected dependencies = \"auto\"")]
99    ExpectedAuto,
100    #[error("Invalid glob pattern `{0}`: {1}")]
101    InvalidGlob(String, glob::PatternError),
102    #[error("File `{0}` is outside root `{1}`")]
103    FileOutsideRoot(PathBuf, PathBuf),
104    #[error("I/O error: {0}")]
105    Io(#[from] std::io::Error),
106    #[error("No source files matched the include patterns")]
107    NoFilesMatched,
108    #[error("Multiple files map to module `{0}`: {1:?}")]
109    ConflictingFiles(String, Vec<PathBuf>),
110}
111
112/// Any WESL error.
113#[derive(Debug, thiserror::Error)]
114pub enum Error {
115    #[error("{0}")]
116    ParseError(#[from] wgsl_parse::Error),
117    #[error("{0}")]
118    ValidateError(#[from] ValidateError),
119    #[error("{0}")]
120    ResolveError(#[from] ResolveError),
121    #[error("{0}")]
122    ImportError(#[from] ImportError),
123    #[error("{0}")]
124    UsageError(#[from] UsageError),
125    #[error("{0}")]
126    CondCompError(#[from] CondCompError),
127    #[error("{0}")]
128    TomlError(#[from] TomlError),
129    #[cfg(feature = "eval")]
130    #[error("{0}")]
131    EvalError(#[from] EvalError),
132    #[error("{0}")]
133    Error(#[from] Diagnostic<Error>),
134    #[error("{0}")]
135    Custom(String),
136}
137
138/// Error diagnostics. Display user-friendly error snippets with `Display`.
139///
140/// A diagnostic is a wrapper around an error with extra contextual metadata: the source,
141/// the declaration name, the span, ...
142#[derive(Debug)]
143pub struct Diagnostic<E = Error> {
144    pub error: Box<E>,
145    pub detail: Box<Detail>,
146}
147
148#[derive(Clone, Debug)]
149pub struct Detail {
150    pub source: Option<String>,
151    pub output: Option<String>,
152    pub module_path: Option<ModulePath>,
153    pub display_name: Option<String>,
154    pub declaration: Option<String>,
155    pub span: Option<Span>,
156}
157
158impl From<wgsl_parse::Error> for Diagnostic<Error> {
159    fn from(error: wgsl_parse::Error) -> Self {
160        let span = error.span;
161        let mut res = Self::new(Error::ParseError(error));
162        res.detail.span = Some(span);
163        res
164    }
165}
166
167impl From<ValidateError> for Diagnostic<Error> {
168    fn from(error: ValidateError) -> Self {
169        Self::new(error.into())
170    }
171}
172
173impl From<ResolveError> for Diagnostic<Error> {
174    fn from(error: ResolveError) -> Self {
175        match error {
176            ResolveError::Io(_)
177            | ResolveError::FileNotFound(_, _)
178            | ResolveError::ModuleNotFound(_, _)
179            | ResolveError::FilesystemNotSupported
180            | ResolveError::FileEscapesRoot(_, _) => Self::new(error.into()),
181            ResolveError::Error(e) => e,
182        }
183    }
184}
185
186impl From<ImportError> for Diagnostic<Error> {
187    fn from(error: ImportError) -> Self {
188        match error {
189            ImportError::ResolveError(e) => Self::from(e),
190            _ => Self::new(error.into()),
191        }
192    }
193}
194
195impl From<UsageError> for Diagnostic<Error> {
196    fn from(error: UsageError) -> Self {
197        Self::new(error.into())
198    }
199}
200
201impl From<CondCompError> for Diagnostic<Error> {
202    fn from(error: CondCompError) -> Self {
203        Self::new(error.into())
204    }
205}
206
207impl From<TomlError> for Diagnostic<Error> {
208    fn from(error: TomlError) -> Self {
209        Self::new(error.into())
210    }
211}
212
213#[cfg(feature = "eval")]
214impl From<EvalError> for Diagnostic<Error> {
215    fn from(error: EvalError) -> Self {
216        Self::new(error.into())
217    }
218}
219
220impl From<Error> for Diagnostic<Error> {
221    fn from(error: Error) -> Self {
222        match error {
223            Error::ParseError(e) => e.into(),
224            Error::ResolveError(e) => e.into(),
225            Error::ImportError(e) => e.into(),
226            Error::UsageError(e) => e.into(),
227            Error::Error(e) => e,
228            Error::ValidateError(e) => e.into(),
229            Error::CondCompError(e) => e.into(),
230            Error::TomlError(e) => e.into(),
231            #[cfg(feature = "eval")]
232            Error::EvalError(e) => e.into(),
233            Error::Custom(_) => Self::new(error),
234        }
235    }
236}
237
238impl<E> Diagnostic<E> {
239    /// Create an empty diagnostic from an error. No metadata is attached.
240    fn new(error: E) -> Diagnostic<E> {
241        Self {
242            error: Box::new(error),
243            detail: Box::new(Detail {
244                source: None,
245                output: None,
246                module_path: None,
247                display_name: None,
248                declaration: None,
249                span: None,
250            }),
251        }
252    }
253    /// Provide the source code from which the error was emitted.
254    /// You should also provide the span with [`Self::with_span`].
255    pub fn with_source(mut self, source: String) -> Self {
256        if self.detail.source.is_none() {
257            self.detail.source = Some(source);
258        }
259        self
260    }
261    /// Provide the span (chunk of source code) where the error originated.
262    /// You should also provide the source with [`Self::with_source`].
263    /// Subsequent calls to this function do not override the span.
264    pub fn with_span(mut self, span: Span) -> Self {
265        if self.detail.span.is_none() {
266            self.detail.span = Some(span);
267        }
268        self
269    }
270    /// Provide the declaration in which the error originated.
271    pub fn with_declaration(mut self, decl: String) -> Self {
272        if self.detail.declaration.is_none() {
273            self.detail.declaration = Some(decl);
274        }
275        self
276    }
277    /// Provide the output code that was generated, even if an error was emitted.
278    pub fn with_output(mut self, out: String) -> Self {
279        if self.detail.output.is_none() {
280            self.detail.output = Some(out);
281        }
282        self
283    }
284    /// Provide the module path in which the error was emitted. The `disp_name` is
285    /// usually the file name of the module.
286    pub fn with_module_path(mut self, path: ModulePath, disp_name: Option<String>) -> Self {
287        if self.detail.module_path.is_none() {
288            self.detail.module_path = Some(path);
289            self.detail.display_name = disp_name;
290        }
291        self
292    }
293    /// Add metadata collected by the evaluation/execution context.
294    #[cfg(feature = "eval")]
295    pub fn with_ctx(mut self, ctx: &crate::eval::Context) -> Self {
296        let (decl, span) = ctx.err_ctx();
297        self.detail.declaration = decl.map(|id| id.to_string());
298        self.detail.span = span;
299        self
300    }
301
302    /// Add metadata collected by the sourcemap. If the mangled declaration name was set,
303    /// this will automatically add the source, the module path and the declaration name.
304    pub fn with_sourcemap(mut self, sourcemap: &impl SourceMap) -> Self {
305        if let Some(decl) = &self.detail.declaration
306            && let Some(entry) = sourcemap.item(decl)
307        {
308            self.detail.module_path = Some(entry.path.clone());
309            self.detail.declaration = Some(entry.name.to_string());
310            self.detail.display_name = sourcemap
311                .display_name(&entry.path)
312                .map(|name| name.to_string());
313            self.detail.source = sourcemap
314                .source(&entry.path)
315                .map(|s| s.to_string())
316                .or(self.detail.source);
317        }
318
319        if self.detail.source.is_none() {
320            if let Some(path) = &self.detail.module_path {
321                self.detail.source = sourcemap.source(path).map(|s| s.to_string());
322            } else {
323                self.detail.source = sourcemap.default_source().map(|s| s.to_string());
324            }
325        }
326
327        self
328    }
329
330    pub(crate) fn display_origin(&self) -> String {
331        match (&self.detail.module_path, &self.detail.display_name) {
332            (Some(res), Some(name)) => {
333                format!("{res} ({name})")
334            }
335            (Some(res), None) => res.to_string(),
336            (None, Some(name)) => name.to_string(),
337            (None, None) => "unknown module".to_string(),
338        }
339    }
340
341    pub(crate) fn display_short_origin(&self) -> Option<String> {
342        self.detail
343            .display_name
344            .clone()
345            .or_else(|| self.detail.module_path.as_ref().map(|res| res.to_string()))
346    }
347}
348
349impl Diagnostic<Error> {
350    /// XXX: this function has issues when the main module identifiers are not mangled.
351    /// unmangle any mangled identifiers in the error.
352    ///
353    /// The mangled must be the same used for compiling the WGSL source. It must have
354    /// unmangling capabilities. If not, you might want to use a [`crate::sourcemap::SourceMapper`].
355    // TODO: this is no longer used, but should
356    #[allow(dead_code, reason = "TODO this should be re-added")]
357    fn unmangle(
358        mut self,
359        sourcemap: Option<&impl SourceMap>,
360        mangler: Option<&impl Mangler>,
361    ) -> Self {
362        fn unmangle_id(
363            id: &mut Ident,
364            sourcemap: Option<&impl SourceMap>,
365            mangler: Option<&impl Mangler>,
366        ) {
367            let path_name = if let Some(sourcemap) = sourcemap {
368                sourcemap
369                    .item(&id.name())
370                    .map(|entry| (entry.path.clone(), entry.name.to_string()))
371            } else if let Some(mangler) = mangler {
372                mangler.unmangle(&id.name())
373            } else {
374                None
375            };
376            if let Some((path, name)) = path_name {
377                *id = Ident::new(format!("{path}::{name}"));
378            }
379        }
380
381        fn unmangle_name(
382            mangled: &mut String,
383            sourcemap: Option<&impl SourceMap>,
384            mangler: Option<&impl Mangler>,
385        ) {
386            let path_name = if let Some(sourcemap) = sourcemap {
387                sourcemap
388                    .item(mangled)
389                    .map(|entry| (entry.path.clone(), entry.name.to_string()))
390            } else if let Some(mangler) = mangler {
391                mangler.unmangle(mangled)
392            } else {
393                None
394            };
395            if let Some((path, name)) = path_name {
396                *mangled = format!("{path}::{name}");
397            }
398        }
399
400        fn unmangle_expr(
401            expr: &mut Expression,
402            sourcemap: Option<&impl SourceMap>,
403            mangler: Option<&impl Mangler>,
404        ) {
405            match expr {
406                Expression::Literal(_) => {}
407                Expression::Parenthesized(e) => {
408                    unmangle_expr(&mut e.expression, sourcemap, mangler)
409                }
410                Expression::NamedComponent(e) => unmangle_expr(&mut e.base, sourcemap, mangler),
411                Expression::Indexing(e) => unmangle_expr(&mut e.base, sourcemap, mangler),
412                Expression::Unary(e) => unmangle_expr(&mut e.operand, sourcemap, mangler),
413                Expression::Binary(e) => {
414                    unmangle_expr(&mut e.left, sourcemap, mangler);
415                    unmangle_expr(&mut e.right, sourcemap, mangler);
416                }
417                Expression::FunctionCall(e) => {
418                    unmangle_id(&mut e.ty.ident, sourcemap, mangler);
419                    for arg in &mut e.arguments {
420                        unmangle_expr(arg, sourcemap, mangler);
421                    }
422                }
423                Expression::TypeOrIdentifier(ty) => unmangle_id(&mut ty.ident, sourcemap, mangler),
424            }
425        }
426
427        #[cfg(feature = "eval")]
428        fn unmangle_ty(
429            mangled: &mut wgsl_types::ty::Type,
430            sourcemap: Option<&impl SourceMap>,
431            mangler: Option<&impl Mangler>,
432        ) {
433            use wgsl_types::ty::Type;
434            match mangled {
435                // TODO unmangle components!
436                Type::Struct(s) => {
437                    unmangle_name(&mut s.name, sourcemap, mangler);
438                    for m in s.members.iter_mut() {
439                        unmangle_ty(&mut m.ty, sourcemap, mangler);
440                    }
441                }
442                Type::Array(ty, _) => unmangle_ty(&mut *ty, sourcemap, mangler),
443                Type::Atomic(ty) => unmangle_ty(&mut *ty, sourcemap, mangler),
444                Type::Ptr(_, ty, _) => unmangle_ty(&mut *ty, sourcemap, mangler),
445                Type::Ref(_, ty, _) => unmangle_ty(&mut *ty, sourcemap, mangler),
446                _ => (),
447            }
448        }
449
450        #[cfg(feature = "eval")]
451        fn unmangle_inst(
452            mangled: &mut wgsl_types::inst::Instance,
453            sourcemap: Option<&impl SourceMap>,
454            mangler: Option<&impl Mangler>,
455        ) {
456            use wgsl_types::inst::Instance;
457            match mangled {
458                Instance::Struct(inst) => {
459                    unmangle_name(&mut inst.ty.name, sourcemap, mangler);
460                    for inst in inst.members.iter_mut() {
461                        unmangle_inst(inst, sourcemap, mangler);
462                    }
463                }
464                Instance::Array(inst) => {
465                    for c in inst.iter_mut() {
466                        unmangle_inst(c, sourcemap, mangler);
467                    }
468                }
469                Instance::Ptr(inst) => {
470                    unmangle_ty(&mut inst.ptr.ty, sourcemap, mangler);
471                }
472                Instance::Ref(inst) => {
473                    unmangle_ty(&mut inst.ty, sourcemap, mangler);
474                }
475                Instance::Atomic(inst) => {
476                    unmangle_inst(inst.inner_mut(), sourcemap, mangler);
477                }
478                Instance::Opaque(ty) => unmangle_ty(ty, sourcemap, mangler),
479                Instance::Literal(_) | Instance::Vec(_) | Instance::Mat(_) => {}
480            }
481        }
482
483        if let Some(decl) = &mut self.detail.declaration {
484            unmangle_name(decl, sourcemap, mangler);
485        }
486
487        match &mut *self.error {
488            Error::ParseError(_) => {}
489            Error::ValidateError(e) => match e {
490                ValidateError::UndefinedSymbol(name)
491                | ValidateError::ParamCount(name, _, _)
492                | ValidateError::NotCallable(name)
493                | ValidateError::Duplicate(name) => unmangle_name(name, sourcemap, mangler),
494                ValidateError::Cycle(name1, name2) => {
495                    unmangle_name(name1, sourcemap, mangler);
496                    unmangle_name(name2, sourcemap, mangler);
497                }
498            },
499            Error::ResolveError(_) => {}
500            Error::ImportError(_) => {}
501            Error::UsageError(e) => match e {
502                UsageError::NotFound(_, _) => todo!(),
503                UsageError::Visibility { orig, decl, .. } => {
504                    if let Some((_, id)) = orig {
505                        unmangle_id(id, sourcemap, mangler);
506                    }
507                    unmangle_id(&mut decl.1, sourcemap, mangler);
508                }
509            },
510            Error::CondCompError(e) => match e {
511                CondCompError::InvalidExpression(expr) => unmangle_expr(expr, sourcemap, mangler),
512                CondCompError::InvalidFeatureFlag(_)
513                | CondCompError::UnexpectedFeatureFlag(_)
514                | CondCompError::NoPrecedingIf
515                | CondCompError::DuplicateIf => {}
516            },
517            Error::TomlError(_) => {}
518            // #[cfg(feature = "generics")]
519            // Error::GenericsError(_) => {}
520            #[cfg(feature = "eval")]
521            Error::EvalError(e) => match e {
522                EvalError::NotScalar(ty) => unmangle_ty(ty, sourcemap, mangler),
523                EvalError::NotConstructible(ty) => unmangle_ty(ty, sourcemap, mangler),
524                EvalError::Type(ty1, ty2) => {
525                    unmangle_ty(ty1, sourcemap, mangler);
526                    unmangle_ty(ty2, sourcemap, mangler);
527                }
528                EvalError::SampledType(ty) => {
529                    unmangle_ty(ty, sourcemap, mangler);
530                }
531                EvalError::NotType(name) => unmangle_name(name, sourcemap, mangler),
532                EvalError::UnknownType(name) => unmangle_name(name, sourcemap, mangler),
533                EvalError::UnknownStruct(name) => unmangle_name(name, sourcemap, mangler),
534                EvalError::NotAccessible(name, _) => unmangle_name(name, sourcemap, mangler),
535                EvalError::UnexpectedTemplate(name) => unmangle_name(name, sourcemap, mangler),
536                EvalError::View(ty, _) => unmangle_ty(ty, sourcemap, mangler),
537                EvalError::RefType(ty1, ty2) => {
538                    unmangle_ty(ty1, sourcemap, mangler);
539                    unmangle_ty(ty2, sourcemap, mangler);
540                }
541                EvalError::WriteRefType(ty1, ty2) => {
542                    unmangle_ty(ty1, sourcemap, mangler);
543                    unmangle_ty(ty2, sourcemap, mangler);
544                }
545                EvalError::Conversion(ty1, ty2) => {
546                    unmangle_ty(ty1, sourcemap, mangler);
547                    unmangle_ty(ty2, sourcemap, mangler);
548                }
549                EvalError::ConvOverflow(_, ty) => unmangle_ty(ty, sourcemap, mangler),
550                EvalError::Component(ty, _) => unmangle_ty(ty, sourcemap, mangler),
551                EvalError::Index(ty) => unmangle_ty(ty, sourcemap, mangler),
552                EvalError::NotIndexable(ty) => unmangle_ty(ty, sourcemap, mangler),
553                EvalError::OutOfBounds(_, ty, _) => unmangle_ty(ty, sourcemap, mangler),
554                EvalError::Unary(_, ty) => unmangle_ty(ty, sourcemap, mangler),
555                EvalError::Binary(_, ty1, ty2) => {
556                    unmangle_ty(ty1, sourcemap, mangler);
557                    unmangle_ty(ty2, sourcemap, mangler);
558                }
559                EvalError::CompwiseBinary(ty1, ty2) => {
560                    unmangle_ty(ty1, sourcemap, mangler);
561                    unmangle_ty(ty2, sourcemap, mangler);
562                }
563                EvalError::UnknownFunction(name) => unmangle_name(name, sourcemap, mangler),
564                EvalError::NotCallable(name) => unmangle_name(name, sourcemap, mangler),
565                EvalError::Signature(sig) => {
566                    unmangle_name(&mut sig.name, sourcemap, mangler);
567                    for tplt in sig.tplt.iter_mut().flatten() {
568                        match tplt {
569                            wgsl_types::tplt::TpltParam::Type(ty) => {
570                                unmangle_ty(ty, sourcemap, mangler)
571                            }
572                            wgsl_types::tplt::TpltParam::Instance(inst) => {
573                                unmangle_inst(inst, sourcemap, mangler)
574                            }
575                            wgsl_types::tplt::TpltParam::Enumerant(_) => {}
576                        }
577                    }
578                    for arg in &mut sig.args {
579                        unmangle_ty(arg, sourcemap, mangler);
580                    }
581                }
582                EvalError::ParamCount(name, _, _) => unmangle_name(name, sourcemap, mangler),
583                EvalError::ParamType(ty1, ty2) => {
584                    unmangle_ty(ty1, sourcemap, mangler);
585                    unmangle_ty(ty2, sourcemap, mangler);
586                }
587                EvalError::ReturnType(ty1, name, ty2) => {
588                    unmangle_ty(ty1, sourcemap, mangler);
589                    unmangle_name(name, sourcemap, mangler);
590                    unmangle_ty(ty2, sourcemap, mangler);
591                }
592                EvalError::NoReturn(name, ty) => {
593                    unmangle_name(name, sourcemap, mangler);
594                    unmangle_ty(ty, sourcemap, mangler);
595                }
596                EvalError::UnexpectedReturn(name, ty) => {
597                    unmangle_name(name, sourcemap, mangler);
598                    unmangle_ty(ty, sourcemap, mangler);
599                }
600                EvalError::NotConst(name) => unmangle_name(name, sourcemap, mangler),
601                EvalError::Void(name) => unmangle_name(name, sourcemap, mangler),
602                EvalError::MustUse(name) => unmangle_name(name, sourcemap, mangler),
603                EvalError::NotEntrypoint(name) => unmangle_name(name, sourcemap, mangler),
604                EvalError::UnknownDecl(name) => unmangle_name(name, sourcemap, mangler),
605                EvalError::UninitConst(name) => unmangle_name(name, sourcemap, mangler),
606                EvalError::UninitLet(name) => unmangle_name(name, sourcemap, mangler),
607                EvalError::UninitOverride(name) => unmangle_name(name, sourcemap, mangler),
608                EvalError::DuplicateDecl(name) => unmangle_name(name, sourcemap, mangler),
609                EvalError::AssignType(ty1, ty2) => {
610                    unmangle_ty(ty1, sourcemap, mangler);
611                    unmangle_ty(ty2, sourcemap, mangler);
612                }
613                EvalError::IncrType(ty) => unmangle_ty(ty, sourcemap, mangler),
614                EvalError::DecrType(ty) => unmangle_ty(ty, sourcemap, mangler),
615                EvalError::ConstAssertFailure(expr) => unmangle_expr(expr, sourcemap, mangler),
616                EvalError::Todo(_)
617                | EvalError::Unreachable
618                | EvalError::MissingTemplate(_)
619                | EvalError::NotWrite
620                | EvalError::NotRead
621                | EvalError::NotReadWrite
622                | EvalError::PtrHandle
623                | EvalError::PtrVecComp
624                | EvalError::Swizzle(_)
625                | EvalError::NegOverflow
626                | EvalError::AddOverflow
627                | EvalError::SubOverflow
628                | EvalError::MulOverflow
629                | EvalError::DivByZero
630                | EvalError::RemZeroDiv
631                | EvalError::ShlOverflow(_, _)
632                | EvalError::ShrOverflow(_, _)
633                | EvalError::Builtin(_)
634                | EvalError::TemplateArgs(_)
635                | EvalError::InvalidEntrypointParam(_)
636                | EvalError::MissingBuiltinInput(_, _)
637                | EvalError::OutputBuiltin(_)
638                | EvalError::InputBuiltin(_)
639                | EvalError::MissingUserInput(_, _)
640                | EvalError::OverrideInConst
641                | EvalError::OverrideInFn
642                | EvalError::LetInMod
643                | EvalError::ForbiddenInitializer(_)
644                | EvalError::UntypedDecl
645                | EvalError::ForbiddenDecl(_, _)
646                | EvalError::MissingResource(_, _)
647                | EvalError::AddressSpace(_, _)
648                | EvalError::AccessMode(_, _)
649                | EvalError::MissingBindAttr
650                | EvalError::MissingWorkgroupSize
651                | EvalError::NegativeAttr(_)
652                | EvalError::InvalidBlendSrc(_)
653                | EvalError::NotRef(_)
654                | EvalError::IncrOverflow
655                | EvalError::DecrOverflow
656                | EvalError::FlowInContinuing(_)
657                | EvalError::DiscardInConst
658                | EvalError::FlowInFunction(_)
659                | EvalError::FlowInModule(_) => {}
660            },
661            Error::Error(_) => {}
662            Error::Custom(_) => {}
663        };
664
665        self
666    }
667}
668
669impl<E: std::error::Error> Diagnostic<E> {
670    fn render_snippet(&self, renderer: &annotate_snippets::Renderer) -> String {
671        use annotate_snippets::*;
672        let msg = format!("{}", self.error);
673        let title = Level::ERROR.primary_title(&msg);
674        let mut group = Group::with_title(title);
675
676        let orig = self.display_origin();
677        let short_orig = self.display_short_origin();
678
679        if let Some(span) = &self.detail.span {
680            let source = self.detail.source.as_deref();
681
682            if let Some(source) = source {
683                if span.range().end <= source.len() {
684                    let annot = AnnotationKind::Primary.span(span.range()).label(&msg);
685                    let mut snip = Snippet::source(source).fold(true).annotation(annot);
686
687                    if let Some(orig) = &short_orig {
688                        snip = snip.path(orig);
689                    }
690
691                    group = group.element(snip);
692                } else {
693                    group = group.element(
694                        Level::NOTE.message("cannot display snippet: invalid source location"),
695                    )
696                }
697            } else {
698                group = group
699                    .element(Level::NOTE.message("cannot display snippet: missing source file"))
700            }
701        }
702
703        let note;
704        if let Some(decl) = &self.detail.declaration {
705            note = format!("in declaration of `{decl}` in {orig}");
706        } else {
707            note = format!("in {orig}");
708        }
709        let group = group.element(Level::NOTE.message(&note));
710
711        renderer.render(&[group])
712    }
713
714    pub fn render_plain(&self) -> String {
715        let renderer = annotate_snippets::Renderer::plain();
716        self.render_snippet(&renderer)
717    }
718
719    pub fn render_colored(&self) -> String {
720        let renderer = annotate_snippets::Renderer::styled();
721        self.render_snippet(&renderer)
722    }
723}
724
725impl<E: std::error::Error> std::error::Error for Diagnostic<E> {}
726
727impl<E: std::error::Error> Display for Diagnostic<E> {
728    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
729        // AutoStream::choice may return `AlwaysAnsi`, `Always` or `Never`.
730        // It never returns `Auto`.
731        // It checks terminal capabilities as well as env vars:
732        // `NO_COLOR`, `CLICOLOR`, `CLICOLOR_FORCE` and `CI`.
733        let choice = anstream::AutoStream::choice(&std::io::stdout());
734        if choice == anstream::ColorChoice::Always || choice == anstream::ColorChoice::AlwaysAnsi {
735            write!(f, "{}", self.render_colored())
736        } else {
737            write!(f, "{}", self.render_plain())
738        }
739    }
740}
741
742/// Wrapper type that provides forces a colored/non-colored output.
743#[derive(Debug)]
744pub struct ColoredDiagnostic<'a, E>(&'a Diagnostic<E>, bool);
745
746impl<E> Diagnostic<E> {
747    /// A [`std::fmt::Display`] adapter to get a colored output (with ANSI codes).
748    ///
749    /// Diagnostics auto-detect if colors are supported using `anstream`.
750    /// With this adapter, you can force a colored or non-colored output.
751    pub fn colored(&self, colored: bool) -> ColoredDiagnostic<'_, E> {
752        ColoredDiagnostic(self, colored)
753    }
754}
755
756impl Error {
757    /// Convert this error to a [`Diagnostic`].
758    ///
759    /// Diagnostics provide better-looking error messages.
760    /// Use [`Diagnostic::colored`] to get a colored output similar to Rust's compiler errors.
761    pub fn diagnostic(self) -> Diagnostic<Error> {
762        Diagnostic::from(self)
763    }
764}
765
766impl<E: std::error::Error> std::error::Error for ColoredDiagnostic<'_, E> {}
767
768impl<E: std::error::Error> std::fmt::Display for ColoredDiagnostic<'_, E> {
769    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
770        if self.1 {
771            write!(f, "{}", self.0.render_colored())
772        } else {
773            write!(f, "{}", self.0.render_plain())
774        }
775    }
776}