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