spirv_tools/
error.rs

1use spirv_tools_sys::{diagnostics, shared};
2
3pub use diagnostics::MessageLevel;
4pub use shared::SpirvResult;
5
6#[derive(Debug, PartialEq)]
7pub struct Error {
8    pub inner: shared::SpirvResult,
9    pub diagnostic: Option<Diagnostic>,
10}
11
12use std::fmt;
13
14impl fmt::Display for Error {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        match &self.diagnostic {
17            Some(diag) => {
18                f.write_fmt(format_args!(
19                    "error:{}:{} - {}",
20                    diag.line, diag.column, diag.message
21                ))?;
22
23                if !diag.notes.is_empty() {
24                    f.write_fmt(format_args!("\n{}", diag.notes))?;
25                }
26
27                Ok(())
28            }
29            None => f.write_str("an unknown error occurred"),
30        }
31    }
32}
33
34impl std::error::Error for Error {
35    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
36        Some(&self.inner)
37    }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct Diagnostic {
42    pub line: usize,
43    pub column: usize,
44    pub index: usize,
45    pub message: String,
46    pub notes: String,
47    pub is_text: bool,
48}
49
50#[cfg(feature = "use-compiled-tools")]
51impl Diagnostic {
52    pub(crate) unsafe fn from_diag(
53        diag: *mut diagnostics::Diagnostic,
54    ) -> Result<Self, shared::SpirvResult> {
55        unsafe {
56            if diag.is_null() {
57                return Err(shared::SpirvResult::Success);
58            }
59
60            let (message, notes) = Message::message_and_notes_from_cstr((*diag).error);
61
62            let res = Self {
63                line: (*diag).position.line,
64                column: (*diag).position.column,
65                index: (*diag).position.index,
66                message,
67                notes,
68                is_text: (*diag).is_text_source,
69            };
70
71            diagnostics::diagnostic_destroy(diag);
72            Ok(res)
73        }
74    }
75}
76
77impl From<String> for Diagnostic {
78    fn from(message: String) -> Self {
79        Self {
80            line: 0,
81            column: 0,
82            index: 0,
83            is_text: false,
84            message,
85            notes: String::new(),
86        }
87    }
88}
89
90impl From<Message> for Diagnostic {
91    fn from(msg: Message) -> Self {
92        Self {
93            line: msg.line,
94            column: msg.column,
95            index: msg.index,
96            message: msg.message,
97            notes: msg.notes,
98            is_text: false,
99        }
100    }
101}
102
103#[derive(Debug)]
104pub struct Message {
105    pub level: MessageLevel,
106    pub source: Option<String>,
107    pub line: usize,
108    pub column: usize,
109    pub index: usize,
110    pub message: String,
111    /// Some messages can include additional information, typically instructions
112    pub notes: String,
113}
114
115impl Message {
116    #[cfg(feature = "use-installed-tools")]
117    pub(crate) fn fatal(message: String) -> Self {
118        Self {
119            level: MessageLevel::Fatal,
120            source: None,
121            line: 0,
122            column: 0,
123            index: 0,
124            message,
125            notes: String::new(),
126        }
127    }
128
129    #[cfg(feature = "use-compiled-tools")]
130    unsafe fn message_and_notes_from_cstr(msg: *const std::os::raw::c_char) -> (String, String) {
131        unsafe {
132            let full_message = std::ffi::CStr::from_ptr(msg).to_string_lossy();
133
134            if let Some(ind) = full_message.find('\n') {
135                (
136                    full_message[..ind].to_owned(),
137                    full_message[ind + 1..].to_owned(),
138                )
139            } else {
140                (full_message.into_owned(), String::new())
141            }
142        }
143    }
144
145    #[cfg(feature = "use-compiled-tools")]
146    pub(crate) fn from_parts(
147        level: MessageLevel,
148        source: *const std::os::raw::c_char,
149        source_pos: *const diagnostics::Position,
150        msg: *const std::os::raw::c_char,
151    ) -> Self {
152        unsafe {
153            let source = if source.is_null() {
154                None
155            } else {
156                Some(std::ffi::CStr::from_ptr(source).to_string_lossy())
157            };
158
159            let (message, notes) = Self::message_and_notes_from_cstr(msg);
160
161            let (line, column, index) = if source_pos.is_null() {
162                (0, 0, 0)
163            } else {
164                (
165                    (*source_pos).line,
166                    (*source_pos).column,
167                    (*source_pos).index,
168                )
169            };
170
171            Self {
172                level,
173                source: source.and_then(|source| {
174                    if source.is_empty() {
175                        None
176                    } else {
177                        Some(source.into_owned())
178                    }
179                }),
180                line,
181                column,
182                index,
183                message,
184                notes,
185            }
186        }
187    }
188
189    #[cfg(feature = "use-installed-tools")]
190    pub(crate) fn parse(s: &str) -> Option<Self> {
191        s.find(": ")
192            .and_then(|i| {
193                let level = match &s[..i] {
194                    "error" => MessageLevel::Error,
195                    "warning" => MessageLevel::Warning,
196                    "info" => MessageLevel::Info,
197                    _ => return None,
198                };
199
200                Some((level, i))
201            })
202            .and_then(|(level, i)| {
203                s[i + 7..]
204                    .find(": ")
205                    .and_then(|i2| {
206                        s[i + 7..i + 7 + i2]
207                            .parse::<usize>()
208                            .ok()
209                            .map(|index| (index, i2))
210                    })
211                    .map(|(index, i2)| (level, index, i + 7 + i2 + 2))
212            })
213            .map(|(level, index, last)| Self {
214                level,
215                index,
216                message: s[last..].to_owned(),
217                source: None,
218                line: 0,
219                column: 0,
220                notes: String::new(),
221            })
222    }
223}
224
225pub trait MessageCallback {
226    fn on_message(&mut self, msg: Message);
227}
228
229impl<F> MessageCallback for F
230where
231    F: FnMut(Message),
232{
233    fn on_message(&mut self, msg: Message) {
234        self(msg);
235    }
236}