naga_oil/compose/
error.rs

1use std::{borrow::Cow, collections::HashMap, ops::Range};
2
3use codespan_reporting::{
4    diagnostic::{Diagnostic, Label},
5    files::SimpleFile,
6    term,
7};
8use thiserror::Error;
9use tracing::trace;
10
11use super::{preprocess::PreprocessOutput, Composer, ShaderDefValue};
12use crate::{compose::SPAN_SHIFT, redirect::RedirectError};
13
14#[derive(Debug)]
15pub enum ErrSource {
16    Module {
17        name: String,
18        offset: usize,
19        defs: HashMap<String, ShaderDefValue>,
20    },
21    Constructing {
22        path: String,
23        source: String,
24        offset: usize,
25    },
26}
27
28impl ErrSource {
29    pub fn path<'a>(&'a self, composer: &'a Composer) -> &'a String {
30        match self {
31            ErrSource::Module { name, .. } => &composer.module_sets.get(name).unwrap().file_path,
32            ErrSource::Constructing { path, .. } => path,
33        }
34    }
35
36    pub fn source<'a>(&'a self, composer: &'a Composer) -> Cow<'a, String> {
37        match self {
38            ErrSource::Module { name, defs, .. } => {
39                let raw_source = &composer.module_sets.get(name).unwrap().sanitized_source;
40                let Ok(PreprocessOutput {
41                    preprocessed_source: source,
42                    ..
43                }) = composer
44                    .preprocessor
45                    .preprocess(raw_source, defs, composer.validate)
46                else {
47                    return Default::default();
48                };
49
50                Cow::Owned(source)
51            }
52            ErrSource::Constructing { source, .. } => Cow::Borrowed(source),
53        }
54    }
55
56    pub fn offset(&self) -> usize {
57        match self {
58            ErrSource::Module { offset, .. } | ErrSource::Constructing { offset, .. } => *offset,
59        }
60    }
61}
62
63#[derive(Debug, Error)]
64#[error("Composer error: {inner}")]
65pub struct ComposerError {
66    #[source]
67    pub inner: ComposerErrorInner,
68    pub source: ErrSource,
69}
70
71#[derive(Debug, Error)]
72pub enum ComposerErrorInner {
73    #[error("{0}")]
74    ImportParseError(String, usize),
75    #[error("required import '{0}' not found")]
76    ImportNotFound(String, usize),
77    #[error("{0}")]
78    WgslParseError(naga::front::wgsl::ParseError),
79    #[cfg(feature = "glsl")]
80    #[error("{0:?}")]
81    GlslParseError(naga::front::glsl::ParseError),
82    #[error("naga_oil bug, please file a report: failed to convert imported module IR back into WGSL for use with WGSL shaders: {0}")]
83    WgslBackError(naga::back::wgsl::Error),
84    #[cfg(feature = "glsl")]
85    #[error("naga_oil bug, please file a report: failed to convert imported module IR back into GLSL for use with GLSL shaders: {0}")]
86    GlslBackError(naga::back::glsl::Error),
87    #[error("naga_oil bug, please file a report: composer failed to build a valid header: {0}")]
88    HeaderValidationError(naga::WithSpan<naga::valid::ValidationError>),
89    #[error("failed to build a valid final module: {0}")]
90    ShaderValidationError(naga::WithSpan<naga::valid::ValidationError>),
91    #[error(
92        "Not enough '# endif' lines. Each if statement should be followed by an endif statement."
93    )]
94    NotEnoughEndIfs(usize),
95    #[error("Too many '# endif' lines. Each endif should be preceded by an if statement.")]
96    TooManyEndIfs(usize),
97    #[error("'#else' without preceding condition.")]
98    ElseWithoutCondition(usize),
99    #[error("Unknown shader def operator: '{operator}'")]
100    UnknownShaderDefOperator { pos: usize, operator: String },
101    #[error("Unknown shader def: '{shader_def_name}'")]
102    UnknownShaderDef { pos: usize, shader_def_name: String },
103    #[error(
104        "Invalid shader def comparison for '{shader_def_name}': expected {expected}, got {value}"
105    )]
106    InvalidShaderDefComparisonValue {
107        pos: usize,
108        shader_def_name: String,
109        expected: String,
110        value: String,
111    },
112    #[error("multiple inconsistent shader def values: '{def}'")]
113    InconsistentShaderDefValue { def: String },
114    #[error("Attempted to add a module with no #define_import_path")]
115    NoModuleName,
116    #[error("source contains internal decoration string, results probably won't be what you expect. if you have a legitimate reason to do this please file a report")]
117    DecorationInSource(Range<usize>),
118    #[error("naga oil only supports glsl 440 and 450")]
119    GlslInvalidVersion(usize),
120    #[error("invalid override :{0}")]
121    RedirectError(#[from] RedirectError),
122    #[error(
123        "override is invalid as `{name}` is not virtual (this error can be disabled with feature 'override_any')"
124    )]
125    OverrideNotVirtual { name: String, pos: usize },
126    #[error(
127        "Composable module identifiers must not require substitution according to naga writeback rules: `{original}`"
128    )]
129    InvalidIdentifier { original: String, at: naga::Span },
130    #[error("Invalid value for `#define`d shader def {name}: {value}")]
131    InvalidShaderDefDefinitionValue {
132        name: String,
133        value: String,
134        pos: usize,
135    },
136    #[error("#define statements are only allowed at the start of the top-level shaders")]
137    DefineInModule(usize),
138}
139
140struct ErrorSources<'a> {
141    current: Option<&'a (dyn std::error::Error + 'static)>,
142}
143
144impl<'a> ErrorSources<'a> {
145    fn of(error: &'a dyn std::error::Error) -> Self {
146        Self {
147            current: error.source(),
148        }
149    }
150}
151
152impl<'a> Iterator for ErrorSources<'a> {
153    type Item = &'a (dyn std::error::Error + 'static);
154
155    fn next(&mut self) -> Option<Self::Item> {
156        let current = self.current;
157        self.current = self.current.and_then(std::error::Error::source);
158        current
159    }
160}
161
162// impl<'a> FusedIterator for ErrorSources<'a> {}
163
164impl ComposerError {
165    /// format a Composer error
166    pub fn emit_to_string(&self, composer: &Composer) -> String {
167        composer.undecorate(&self.emit_to_string_internal(composer))
168    }
169
170    fn emit_to_string_internal(&self, composer: &Composer) -> String {
171        let path = self.source.path(composer);
172        let source = self.source.source(composer);
173        let source_offset = self.source.offset();
174
175        trace!("source:\n~{}~", source);
176        trace!("source offset: {}", source_offset);
177
178        let map_span = |rng: Range<usize>| -> Range<usize> {
179            ((rng.start & ((1 << SPAN_SHIFT) - 1)).saturating_sub(source_offset))
180                ..((rng.end & ((1 << SPAN_SHIFT) - 1)).saturating_sub(source_offset))
181        };
182
183        let files = SimpleFile::new(path, source.as_str());
184        let config = term::Config::default();
185        #[cfg(any(test, target_arch = "wasm32"))]
186        let mut writer = term::termcolor::NoColor::new(Vec::new());
187        #[cfg(not(any(test, target_arch = "wasm32")))]
188        let mut writer = term::termcolor::Ansi::new(Vec::new());
189
190        let (labels, notes) = match &self.inner {
191            ComposerErrorInner::DecorationInSource(range) => {
192                (vec![Label::primary((), range.clone())], vec![])
193            }
194            ComposerErrorInner::HeaderValidationError(v)
195            | ComposerErrorInner::ShaderValidationError(v) => (
196                v.spans()
197                    .map(|(span, desc)| {
198                        trace!(
199                            "mapping span {:?} -> {:?}",
200                            span.to_range().unwrap(),
201                            map_span(span.to_range().unwrap_or(0..0))
202                        );
203                        Label::primary((), map_span(span.to_range().unwrap_or(0..0)))
204                            .with_message(desc.to_owned())
205                    })
206                    .collect(),
207                ErrorSources::of(&v)
208                    .map(|source| source.to_string())
209                    .collect(),
210            ),
211            ComposerErrorInner::ImportNotFound(msg, pos) => (
212                vec![Label::primary((), *pos..*pos)],
213                vec![format!("missing import '{msg}'")],
214            ),
215            ComposerErrorInner::ImportParseError(msg, pos) => (
216                vec![Label::primary((), *pos..*pos)],
217                vec![format!("invalid import spec: '{msg}'")],
218            ),
219            ComposerErrorInner::WgslParseError(e) => (
220                e.labels()
221                    .map(|(range, msg)| {
222                        Label::primary((), map_span(range.to_range().unwrap())).with_message(msg)
223                    })
224                    .collect(),
225                vec![e.message().to_owned()],
226            ),
227            #[cfg(feature = "glsl")]
228            ComposerErrorInner::GlslParseError(e) => (
229                e.errors
230                    .iter()
231                    .map(|naga::front::glsl::Error { kind, meta }| {
232                        Label::primary((), map_span(meta.to_range().unwrap_or(0..0)))
233                            .with_message(kind.to_string())
234                    })
235                    .collect(),
236                vec![],
237            ),
238            ComposerErrorInner::NotEnoughEndIfs(pos)
239            | ComposerErrorInner::TooManyEndIfs(pos)
240            | ComposerErrorInner::ElseWithoutCondition(pos)
241            | ComposerErrorInner::UnknownShaderDef { pos, .. }
242            | ComposerErrorInner::UnknownShaderDefOperator { pos, .. }
243            | ComposerErrorInner::InvalidShaderDefComparisonValue { pos, .. }
244            | ComposerErrorInner::OverrideNotVirtual { pos, .. }
245            | ComposerErrorInner::GlslInvalidVersion(pos)
246            | ComposerErrorInner::DefineInModule(pos)
247            | ComposerErrorInner::InvalidShaderDefDefinitionValue { pos, .. } => {
248                (vec![Label::primary((), *pos..*pos)], vec![])
249            }
250            ComposerErrorInner::WgslBackError(e) => {
251                return format!("{path}: wgsl back error: {e}");
252            }
253            #[cfg(feature = "glsl")]
254            ComposerErrorInner::GlslBackError(e) => {
255                return format!("{path}: glsl back error: {e}");
256            }
257            ComposerErrorInner::InconsistentShaderDefValue { def } => {
258                return format!("{path}: multiple inconsistent shader def values: '{def}'");
259            }
260            ComposerErrorInner::RedirectError(..) => (
261                vec![Label::primary((), 0..0)],
262                vec![format!("override error")],
263            ),
264            ComposerErrorInner::NoModuleName => {
265                return format!(
266                    "{path}: no #define_import_path declaration found in composable module"
267                );
268            }
269            ComposerErrorInner::InvalidIdentifier { at, .. } => (
270                vec![Label::primary((), map_span(at.to_range().unwrap_or(0..0)))
271                    .with_message(self.inner.to_string())],
272                vec![],
273            ),
274        };
275
276        let diagnostic = Diagnostic::error()
277            .with_message(self.inner.to_string())
278            .with_labels(labels)
279            .with_notes(notes);
280
281        term::emit(&mut writer, &config, &files, &diagnostic).expect("cannot write error");
282
283        let msg = writer.into_inner();
284        let msg = String::from_utf8_lossy(&msg);
285
286        msg.to_string()
287    }
288}