naga/front/glsl/
error.rs

1use alloc::{
2    borrow::Cow,
3    string::{String, ToString},
4    vec,
5    vec::Vec,
6};
7
8use codespan_reporting::diagnostic::{Diagnostic, Label};
9use codespan_reporting::files::SimpleFile;
10use codespan_reporting::term;
11use pp_rs::token::PreprocessorError;
12use thiserror::Error;
13
14use super::token::TokenValue;
15#[cfg(feature = "stderr")]
16use crate::error::ErrorWrite;
17use crate::{error::replace_control_chars, SourceLocation};
18use crate::{proc::ConstantEvaluatorError, Span};
19
20fn join_with_comma(list: &[ExpectedToken]) -> String {
21    let mut string = "".to_string();
22    for (i, val) in list.iter().enumerate() {
23        string.push_str(&val.to_string());
24        match i {
25            i if i == list.len() - 1 => {}
26            i if i == list.len() - 2 => string.push_str(" or "),
27            _ => string.push_str(", "),
28        }
29    }
30    string
31}
32
33/// One of the expected tokens returned in [`InvalidToken`](ErrorKind::InvalidToken).
34#[derive(Clone, Debug, PartialEq)]
35pub enum ExpectedToken {
36    /// A specific token was expected.
37    Token(TokenValue),
38    /// A type was expected.
39    TypeName,
40    /// An identifier was expected.
41    Identifier,
42    /// An integer literal was expected.
43    IntLiteral,
44    /// A float literal was expected.
45    FloatLiteral,
46    /// A boolean literal was expected.
47    BoolLiteral,
48    /// The end of file was expected.
49    Eof,
50}
51impl From<TokenValue> for ExpectedToken {
52    fn from(token: TokenValue) -> Self {
53        ExpectedToken::Token(token)
54    }
55}
56impl core::fmt::Display for ExpectedToken {
57    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58        match *self {
59            ExpectedToken::Token(ref token) => write!(f, "{token:?}"),
60            ExpectedToken::TypeName => write!(f, "a type"),
61            ExpectedToken::Identifier => write!(f, "identifier"),
62            ExpectedToken::IntLiteral => write!(f, "integer literal"),
63            ExpectedToken::FloatLiteral => write!(f, "float literal"),
64            ExpectedToken::BoolLiteral => write!(f, "bool literal"),
65            ExpectedToken::Eof => write!(f, "end of file"),
66        }
67    }
68}
69
70/// Information about the cause of an error.
71#[derive(Clone, Debug, Error)]
72#[cfg_attr(test, derive(PartialEq))]
73pub enum ErrorKind {
74    /// Whilst parsing as encountered an unexpected EOF.
75    #[error("Unexpected end of file")]
76    EndOfFile,
77    /// The shader specified an unsupported or invalid profile.
78    #[error("Invalid profile: {0}")]
79    InvalidProfile(String),
80    /// The shader requested an unsupported or invalid version.
81    #[error("Invalid version: {0}")]
82    InvalidVersion(u64),
83    /// Whilst parsing an unexpected token was encountered.
84    ///
85    /// A list of expected tokens is also returned.
86    #[error("Expected {expected_tokens}, found {found_token:?}", found_token = .0, expected_tokens = join_with_comma(.1))]
87    InvalidToken(TokenValue, Vec<ExpectedToken>),
88    /// A specific feature is not yet implemented.
89    ///
90    /// To help prioritize work please open an issue in the github issue tracker
91    /// if none exist already or react to the already existing one.
92    #[error("Not implemented: {0}")]
93    NotImplemented(&'static str),
94    /// A reference to a variable that wasn't declared was used.
95    #[error("Unknown variable: {0}")]
96    UnknownVariable(String),
97    /// A reference to a type that wasn't declared was used.
98    #[error("Unknown type: {0}")]
99    UnknownType(String),
100    /// A reference to a non existent member of a type was made.
101    #[error("Unknown field: {0}")]
102    UnknownField(String),
103    /// An unknown layout qualifier was used.
104    ///
105    /// If the qualifier does exist please open an issue in the github issue tracker
106    /// if none exist already or react to the already existing one to help
107    /// prioritize work.
108    #[error("Unknown layout qualifier: {0}")]
109    UnknownLayoutQualifier(String),
110    /// Unsupported matrix of the form matCx2
111    ///
112    /// Our IR expects matrices of the form matCx2 to have a stride of 8 however
113    /// matrices in the std140 layout have a stride of at least 16.
114    #[error("unsupported matrix of the form matCx2 (in this case mat{columns}x2) in std140 block layout. See https://github.com/gfx-rs/wgpu/issues/4375")]
115    UnsupportedMatrixWithTwoRowsInStd140 { columns: u8 },
116    /// Unsupported matrix of the form f16matCxR
117    ///
118    /// Our IR expects matrices of the form f16matCxR to have a stride of 4/8/8 depending on row-count,
119    /// however matrices in the std140 layout have a stride of at least 16.
120    #[error("unsupported matrix of the form f16matCxR (in this case f16mat{columns}x{rows}) in std140 block layout. See https://github.com/gfx-rs/wgpu/issues/4375")]
121    UnsupportedF16MatrixInStd140 { columns: u8, rows: u8 },
122    /// A variable with the same name already exists in the current scope.
123    #[error("Variable already declared: {0}")]
124    VariableAlreadyDeclared(String),
125    /// A semantic error was detected in the shader.
126    #[error("{0}")]
127    SemanticError(Cow<'static, str>),
128    /// An error was returned by the preprocessor.
129    #[error("{0:?}")]
130    PreprocessorError(PreprocessorError),
131    /// The parser entered an illegal state and exited
132    ///
133    /// This obviously is a bug and as such should be reported in the github issue tracker
134    #[error("Internal error: {0}")]
135    InternalError(&'static str),
136}
137
138impl From<ConstantEvaluatorError> for ErrorKind {
139    fn from(err: ConstantEvaluatorError) -> Self {
140        ErrorKind::SemanticError(err.to_string().into())
141    }
142}
143
144/// Error returned during shader parsing.
145#[derive(Clone, Debug, Error)]
146#[error("{kind}")]
147#[cfg_attr(test, derive(PartialEq))]
148pub struct Error {
149    /// Holds the information about the error itself.
150    pub kind: ErrorKind,
151    /// Holds information about the range of the source code where the error happened.
152    pub meta: Span,
153}
154
155impl Error {
156    /// Returns a [`SourceLocation`] for the error message.
157    pub fn location(&self, source: &str) -> Option<SourceLocation> {
158        Some(self.meta.location(source))
159    }
160}
161
162/// A collection of errors returned during shader parsing.
163#[derive(Clone, Debug)]
164#[cfg_attr(test, derive(PartialEq))]
165pub struct ParseErrors {
166    pub errors: Vec<Error>,
167}
168
169impl ParseErrors {
170    #[cfg(feature = "stderr")]
171    pub fn emit_to_writer(&self, writer: &mut impl ErrorWrite, source: &str) {
172        self.emit_to_writer_with_path(writer, source, "glsl");
173    }
174
175    #[cfg(feature = "stderr")]
176    pub fn emit_to_writer_with_path(&self, writer: &mut impl ErrorWrite, source: &str, path: &str) {
177        let path = path.to_string();
178        let files = SimpleFile::new(path, replace_control_chars(source));
179        let config = term::Config::default();
180
181        for err in &self.errors {
182            let diagnostic = Self::make_diagnostic(err);
183            crate::error::emit_to_writer(writer, &config, &files, &diagnostic)
184                .expect("cannot write error");
185        }
186    }
187
188    /// Emits a summary of the errors to standard error stream.
189    #[cfg(feature = "stderr")]
190    pub fn emit_to_stderr(&self, source: &str) {
191        self.emit_to_stderr_with_path(source, "glsl")
192    }
193
194    /// Emits a summary of the errors to standard error stream.
195    #[cfg(feature = "stderr")]
196    pub fn emit_to_stderr_with_path(&self, source: &str, path: &str) {
197        cfg_if::cfg_if! {
198            if #[cfg(feature = "termcolor")] {
199                let writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Auto);
200                self.emit_to_writer_with_path(&mut writer.lock(), source, path);
201            } else {
202                let writer = std::io::stderr();
203                self.emit_to_writer_with_path(&mut writer.lock(), source, path);
204            }
205        }
206    }
207
208    pub fn emit_to_string(&self, source: &str) -> String {
209        self.emit_to_string_with_path(source, "glsl")
210    }
211
212    pub fn emit_to_string_with_path(&self, source: &str, path: &str) -> String {
213        let path = path.to_string();
214        let files = SimpleFile::new(path, replace_control_chars(source));
215        let config = term::Config::default();
216
217        let mut writer = crate::error::DiagnosticBuffer::new();
218        for err in &self.errors {
219            let diagnostic = Self::make_diagnostic(err);
220            writer
221                .emit_to_self(&config, &files, &diagnostic)
222                .expect("cannot write error");
223        }
224        writer.into_string()
225    }
226
227    fn make_diagnostic(err: &Error) -> Diagnostic<()> {
228        let mut diagnostic = Diagnostic::error().with_message(err.kind.to_string());
229        if let Some(range) = err.meta.to_range() {
230            diagnostic = diagnostic.with_labels(vec![Label::primary((), range)]);
231        }
232        diagnostic
233    }
234}
235
236impl core::fmt::Display for ParseErrors {
237    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
238        self.errors.iter().try_for_each(|e| write!(f, "{e:?}"))
239    }
240}
241
242impl core::error::Error for ParseErrors {}
243
244impl From<Vec<Error>> for ParseErrors {
245    fn from(errors: Vec<Error>) -> Self {
246        Self { errors }
247    }
248}