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 writer = {
48            let mut writer = DiagnosticBuffer::new();
49            term::emit(
50                writer.inner_mut(),
51                &config,
52                &files,
53                &self.inner.diagnostic(),
54            )
55            .expect("cannot write error");
56            writer.into_string()
57        };
58
59        write!(f, "\nShader validation {writer}")
60    }
61}
62
63cfg_if::cfg_if! {
64    if #[cfg(feature = "termcolor")] {
65        type DiagnosticBufferInner = codespan_reporting::term::termcolor::NoColor<alloc::vec::Vec<u8>>;
66        pub(crate) use codespan_reporting::term::termcolor::WriteColor as _ErrorWrite;
67    } else if #[cfg(feature = "stderr")] {
68        type DiagnosticBufferInner = alloc::vec::Vec<u8>;
69        pub(crate) use std::io::Write as _ErrorWrite;
70    } else {
71        type DiagnosticBufferInner = String;
72        pub(crate) use core::fmt::Write as _ErrorWrite;
73    }
74}
75
76// Using this indirect export to avoid duplicating the expect(...) for all three cases above.
77#[cfg_attr(
78    not(any(feature = "spv-in", feature = "glsl-in")),
79    expect(
80        unused_imports,
81        reason = "only need `ErrorWrite` with an appropriate front-end."
82    )
83)]
84pub(crate) use _ErrorWrite as ErrorWrite;
85
86pub(crate) struct DiagnosticBuffer {
87    inner: DiagnosticBufferInner,
88}
89
90impl DiagnosticBuffer {
91    #[cfg_attr(
92        not(feature = "termcolor"),
93        expect(
94            clippy::missing_const_for_fn,
95            reason = "`NoColor::new` isn't `const`, but other `inner`s are."
96        )
97    )]
98    pub fn new() -> Self {
99        cfg_if::cfg_if! {
100            if #[cfg(feature = "termcolor")] {
101                let inner = codespan_reporting::term::termcolor::NoColor::new(alloc::vec::Vec::new());
102            } else if #[cfg(feature = "stderr")] {
103                let inner = alloc::vec::Vec::new();
104            } else {
105                let inner = String::new();
106            }
107        };
108
109        Self { inner }
110    }
111
112    pub fn inner_mut(&mut self) -> &mut DiagnosticBufferInner {
113        &mut self.inner
114    }
115
116    pub fn into_string(self) -> String {
117        let Self { inner } = self;
118
119        cfg_if::cfg_if! {
120            if #[cfg(feature = "termcolor")] {
121                String::from_utf8(inner.into_inner()).unwrap()
122            } else if #[cfg(feature = "stderr")] {
123                String::from_utf8(inner).unwrap()
124            } else {
125                inner
126            }
127        }
128    }
129}
130
131impl<E> Error for ShaderError<E>
132where
133    ShaderError<E>: fmt::Display,
134    E: Error + 'static,
135{
136    fn source(&self) -> Option<&(dyn Error + 'static)> {
137        Some(&self.inner)
138    }
139}
140
141pub(crate) fn replace_control_chars(s: &str) -> Cow<'_, str> {
142    const REPLACEMENT_CHAR: &str = "\u{FFFD}";
143    debug_assert_eq!(
144        REPLACEMENT_CHAR.chars().next().unwrap(),
145        char::REPLACEMENT_CHARACTER
146    );
147
148    let mut res = Cow::Borrowed(s);
149    let mut offset = 0;
150
151    while let Some(found_pos) = res[offset..].find(|c: char| c.is_control() && !c.is_whitespace()) {
152        offset += found_pos;
153        let found_len = res[offset..].chars().next().unwrap().len_utf8();
154        res.to_mut()
155            .replace_range(offset..offset + found_len, REPLACEMENT_CHAR);
156        offset += REPLACEMENT_CHAR.len();
157    }
158
159    res
160}
161
162#[test]
163fn test_replace_control_chars() {
164    // The UTF-8 encoding of \u{0080} is multiple bytes.
165    let input = "Foo\u{0080}Bar\u{0001}Baz\n";
166    let expected = "Foo\u{FFFD}Bar\u{FFFD}Baz\n";
167    assert_eq!(replace_control_chars(input), expected);
168}