naga/
error.rs

1use alloc::{borrow::Cow, boxed::Box, string::String};
2use core::{error::Error, fmt};
3
4#[derive(Clone, Debug)]
5pub struct ShaderError<E> {
6    /// The source code of the shader.
7    pub source: String,
8    pub label: Option<String>,
9    pub inner: Box<E>,
10}
11
12#[cfg(feature = "wgsl-in")]
13impl fmt::Display for ShaderError<crate::front::wgsl::ParseError> {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        let label = self.label.as_deref().unwrap_or_default();
16        let string = self.inner.emit_to_string(&self.source);
17        write!(f, "\nShader '{label}' parsing {string}")
18    }
19}
20
21#[cfg(feature = "glsl-in")]
22impl fmt::Display for ShaderError<crate::front::glsl::ParseErrors> {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        let label = self.label.as_deref().unwrap_or_default();
25        let string = self.inner.emit_to_string(&self.source);
26        write!(f, "\nShader '{label}' parsing {string}")
27    }
28}
29
30#[cfg(feature = "spv-in")]
31impl fmt::Display for ShaderError<crate::front::spv::Error> {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        let label = self.label.as_deref().unwrap_or_default();
34        let string = self.inner.emit_to_string(&self.source);
35        write!(f, "\nShader '{label}' parsing {string}")
36    }
37}
38
39impl fmt::Display for ShaderError<crate::WithSpan<crate::valid::ValidationError>> {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        use codespan_reporting::{files::SimpleFile, term};
42
43        let label = self.label.as_deref().unwrap_or_default();
44        let files = SimpleFile::new(label, replace_control_chars(&self.source));
45        let config = term::Config::default();
46
47        let mut writer = DiagnosticBuffer::new();
48        writer
49            .emit_to_self(&config, &files, &self.inner.diagnostic())
50            .expect("cannot write error");
51        let writer = writer.into_string();
52
53        write!(f, "\nShader validation {writer}")
54    }
55}
56
57cfg_if::cfg_if! {
58    if #[cfg(feature = "termcolor")] {
59        type DiagnosticBufferInner = codespan_reporting::term::termcolor::NoColor<alloc::vec::Vec<u8>>;
60    } else if #[cfg(feature = "stderr")] {
61        type DiagnosticBufferInner = alloc::vec::Vec<u8>;
62    } else {
63        type DiagnosticBufferInner = String;
64    }
65}
66
67cfg_if::cfg_if! {
68    if #[cfg(all(feature = "stderr", feature = "termcolor"))] {
69        pub(crate) use codespan_reporting::term::termcolor::WriteColor as _ErrorWrite;
70    } else if #[cfg(feature = "stderr")] {
71        pub(crate) use std::io::Write as _ErrorWrite;
72    }
73}
74
75#[cfg(feature = "stderr")]
76pub(crate) use _ErrorWrite as ErrorWrite;
77
78#[cfg(feature = "stderr")]
79#[cfg_attr(
80    not(any(feature = "spv-in", feature = "glsl-in")),
81    expect(
82        dead_code,
83        reason = "only need `emit_to_writer` with an appropriate front-end."
84    )
85)]
86pub(crate) fn emit_to_writer<'files, F: codespan_reporting::files::Files<'files> + ?Sized>(
87    writer: &mut impl ErrorWrite,
88    config: &codespan_reporting::term::Config,
89    files: &'files F,
90    diagnostic: &codespan_reporting::diagnostic::Diagnostic<F::FileId>,
91) -> Result<(), codespan_reporting::files::Error> {
92    cfg_if::cfg_if! {
93        if #[cfg(feature = "termcolor")] {
94            codespan_reporting::term::emit_to_write_style(writer, config, files, diagnostic)
95        } else {
96            codespan_reporting::term::emit_to_io_write(writer, config, files, diagnostic)
97        }
98    }
99}
100
101pub(crate) struct DiagnosticBuffer {
102    inner: DiagnosticBufferInner,
103}
104
105impl DiagnosticBuffer {
106    #[cfg_attr(
107        not(feature = "termcolor"),
108        expect(
109            clippy::missing_const_for_fn,
110            reason = "`NoColor::new` isn't `const`, but other `inner`s are."
111        )
112    )]
113    pub fn new() -> Self {
114        cfg_if::cfg_if! {
115            if #[cfg(feature = "termcolor")] {
116                let inner = codespan_reporting::term::termcolor::NoColor::new(alloc::vec::Vec::new());
117            } else if #[cfg(feature = "stderr")] {
118                let inner = alloc::vec::Vec::new();
119            } else {
120                let inner = String::new();
121            }
122        };
123
124        Self { inner }
125    }
126
127    pub fn emit_to_self<'files, F: codespan_reporting::files::Files<'files> + ?Sized>(
128        &mut self,
129        config: &codespan_reporting::term::Config,
130        files: &'files F,
131        diagnostic: &codespan_reporting::diagnostic::Diagnostic<F::FileId>,
132    ) -> Result<(), codespan_reporting::files::Error> {
133        cfg_if::cfg_if! {
134            if #[cfg(feature = "termcolor")] {
135                codespan_reporting::term::emit_to_write_style(&mut self.inner, config, files, diagnostic)
136            } else if #[cfg(feature = "stderr")] {
137                codespan_reporting::term::emit_to_io_write(&mut self.inner, config, files, diagnostic)
138            } else {
139                codespan_reporting::term::emit_to_string(&mut self.inner, config, files, diagnostic)
140            }
141        }
142    }
143
144    pub fn into_string(self) -> String {
145        let Self { inner } = self;
146
147        cfg_if::cfg_if! {
148            if #[cfg(feature = "termcolor")] {
149                String::from_utf8(inner.into_inner()).unwrap()
150            } else if #[cfg(feature = "stderr")] {
151                String::from_utf8(inner).unwrap()
152            } else {
153                inner
154            }
155        }
156    }
157}
158
159impl<E> Error for ShaderError<E>
160where
161    ShaderError<E>: fmt::Display,
162    E: Error + 'static,
163{
164    fn source(&self) -> Option<&(dyn Error + 'static)> {
165        self.inner.source()
166    }
167}
168
169pub(crate) fn replace_control_chars(s: &str) -> Cow<'_, str> {
170    const REPLACEMENT_CHAR: &str = "\u{FFFD}";
171    debug_assert_eq!(
172        REPLACEMENT_CHAR.chars().next().unwrap(),
173        char::REPLACEMENT_CHARACTER
174    );
175
176    let mut res = Cow::Borrowed(s);
177    let mut offset = 0;
178
179    while let Some(found_pos) = res[offset..].find(|c: char| c.is_control() && !c.is_whitespace()) {
180        offset += found_pos;
181        let found_len = res[offset..].chars().next().unwrap().len_utf8();
182        res.to_mut()
183            .replace_range(offset..offset + found_len, REPLACEMENT_CHAR);
184        offset += REPLACEMENT_CHAR.len();
185    }
186
187    res
188}
189
190#[test]
191fn test_replace_control_chars() {
192    // The UTF-8 encoding of \u{0080} is multiple bytes.
193    let input = "Foo\u{0080}Bar\u{0001}Baz\n";
194    let expected = "Foo\u{FFFD}Bar\u{FFFD}Baz\n";
195    assert_eq!(replace_control_chars(input), expected);
196}