Skip to content

Commit 97d3556

Browse files
feat(wgsl-in): parse diagnostic(…) directives (with unimpl. err.)
1 parent ca6f6f3 commit 97d3556

File tree

6 files changed

+238
-28
lines changed

6 files changed

+238
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ By @bradwerth [#6216](https://github.com/gfx-rs/wgpu/pull/6216).
8787
- Support local `const` declarations in WGSL. By @sagudev in [#6156](https://github.com/gfx-rs/wgpu/pull/6156).
8888
- Implemented `const_assert` in WGSL. By @sagudev in [#6198](https://github.com/gfx-rs/wgpu/pull/6198).
8989
- Support polyfilling `inverse` in WGSL. By @chyyran in [#6385](https://github.com/gfx-rs/wgpu/pull/6385).
90-
- Add base support for parsing `requires`, `enable`, and `diagnostic` directives. No extensions or diagnostic filters are yet supported, but diagnostics have improved dramatically. By @ErichDonGubler in [#6352](https://github.com/gfx-rs/wgpu/pull/6352), [#6424](https://github.com/gfx-rs/wgpu/pull/6424), [#6437](https://github.com/gfx-rs/wgpu/pull/6437).
90+
- Add base support for parsing `requires`, `enable`, and `diagnostic` directives. No extensions or diagnostic filters are yet supported, but diagnostics have improved dramatically. By @ErichDonGubler in [#6352](https://github.com/gfx-rs/wgpu/pull/6352), [#6424](https://github.com/gfx-rs/wgpu/pull/6424), [#6437](https://github.com/gfx-rs/wgpu/pull/6437), [#6456](https://github.com/gfx-rs/wgpu/pull/6456).
9191
- Include error chain information as a message and notes in shader compilation messages. By @ErichDonGubler in [#6436](https://github.com/gfx-rs/wgpu/pull/6436).
9292
- Unify Naga CLI error output with the format of shader compilation messages. By @ErichDonGubler in [#6436](https://github.com/gfx-rs/wgpu/pull/6436).
9393

naga/src/diagnostic_filter.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//! [`DiagnosticFilter`]s and supporting functionality.
2+
3+
/// A severity set on a [`DiagnosticFilter`].
4+
///
5+
/// <https://www.w3.org/TR/WGSL/#diagnostic-severity>
6+
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7+
pub enum Severity {
8+
Off,
9+
Info,
10+
Warning,
11+
Error,
12+
}
13+
14+
impl Severity {
15+
const ERROR: &'static str = "error";
16+
const WARNING: &'static str = "warning";
17+
const INFO: &'static str = "info";
18+
const OFF: &'static str = "off";
19+
20+
/// Convert from a sentinel word in WGSL into its associated [`Severity`], if possible.
21+
pub fn from_ident(s: &str) -> Option<Self> {
22+
Some(match s {
23+
Self::ERROR => Self::Error,
24+
Self::WARNING => Self::Warning,
25+
Self::INFO => Self::Info,
26+
Self::OFF => Self::Off,
27+
_ => return None,
28+
})
29+
}
30+
31+
/// Checks whether this severity is [`Self::Error`].
32+
///
33+
/// Naga does not yet support diagnostic items at lesser severities than
34+
/// [`Severity::Error`]. When this is implemented, this method should be deleted, and the
35+
/// severity should be used directly for reporting diagnostics.
36+
#[cfg(feature = "wgsl-in")]
37+
pub(crate) fn report_diag<E>(
38+
self,
39+
err: E,
40+
log_handler: impl FnOnce(E, log::Level),
41+
) -> Result<(), E> {
42+
let log_level = match self {
43+
Severity::Off => return Ok(()),
44+
45+
// NOTE: These severities are not yet reported.
46+
Severity::Info => log::Level::Info,
47+
Severity::Warning => log::Level::Warn,
48+
49+
Severity::Error => return Err(err),
50+
};
51+
log_handler(err, log_level);
52+
Ok(())
53+
}
54+
}
55+
56+
/// A filterable triggering rule in a [`DiagnosticFilter`].
57+
///
58+
/// <https://www.w3.org/TR/WGSL/#filterable-triggering-rules>
59+
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
60+
pub enum FilterableTriggeringRule {
61+
DerivativeUniformity,
62+
}
63+
64+
impl FilterableTriggeringRule {
65+
const DERIVATIVE_UNIFORMITY: &'static str = "derivative_uniformity";
66+
67+
/// Convert from a sentinel word in WGSL into its associated [`FilterableTriggeringRule`], if possible.
68+
pub fn from_ident(s: &str) -> Option<Self> {
69+
Some(match s {
70+
Self::DERIVATIVE_UNIFORMITY => Self::DerivativeUniformity,
71+
_ => return None,
72+
})
73+
}
74+
75+
/// Maps this [`FilterableTriggeringRule`] into the sentinel word associated with it in WGSL.
76+
pub const fn to_ident(self) -> &'static str {
77+
match self {
78+
Self::DerivativeUniformity => Self::DERIVATIVE_UNIFORMITY,
79+
}
80+
}
81+
82+
#[cfg(feature = "wgsl-in")]
83+
pub(crate) const fn tracking_issue_num(self) -> u16 {
84+
match self {
85+
FilterableTriggeringRule::DerivativeUniformity => 5320,
86+
}
87+
}
88+
}
89+
90+
/// A filter that modifies how diagnostics are emitted for shaders.
91+
///
92+
/// <https://www.w3.org/TR/WGSL/#diagnostic-filter>
93+
#[derive(Clone, Debug)]
94+
pub struct DiagnosticFilter {
95+
pub new_severity: Severity,
96+
pub triggering_rule: FilterableTriggeringRule,
97+
}

naga/src/front/wgsl/error.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::diagnostic_filter::FilterableTriggeringRule;
12
use crate::front::wgsl::parse::directive::enable_extension::{
23
EnableExtension, UnimplementedEnableExtension,
34
};
@@ -194,6 +195,7 @@ pub(crate) enum Error<'a> {
194195
UnknownConservativeDepth(Span),
195196
UnknownEnableExtension(Span, &'a str),
196197
UnknownLanguageExtension(Span, &'a str),
198+
UnknownDiagnosticRuleName(Span),
197199
SizeAttributeTooLow(Span, u32),
198200
AlignAttributeTooLow(Span, Alignment),
199201
NonPowerOfTwoAlignAttribute(Span),
@@ -295,6 +297,13 @@ pub(crate) enum Error<'a> {
295297
kind: UnimplementedLanguageExtension,
296298
span: Span,
297299
},
300+
DiagnosticInvalidSeverity {
301+
severity_control_name_span: Span,
302+
},
303+
DiagnosticNotYetImplemented {
304+
triggering_rule: FilterableTriggeringRule,
305+
span: Span,
306+
},
298307
}
299308

300309
#[derive(Clone, Debug)]
@@ -562,6 +571,15 @@ impl<'a> Error<'a> {
562571
)
563572
.into()],
564573
},
574+
Error::UnknownDiagnosticRuleName(span) => ParseError {
575+
message: format!("unknown `diagnostic(…)` rule name `{}`", &source[span]),
576+
labels: vec![(span, "not a valid diagnostic rule name".into())],
577+
notes: vec![concat!(
578+
"See available trigger rules at ",
579+
"<https://www.w3.org/TR/WGSL/#filterable-triggering-rules>."
580+
)
581+
.into()],
582+
},
565583
Error::SizeAttributeTooLow(bad_span, min_size) => ParseError {
566584
message: format!("struct member size must be at least {min_size}"),
567585
labels: vec![(bad_span, format!("must be at least {min_size}").into())],
@@ -1008,6 +1026,38 @@ impl<'a> Error<'a> {
10081026
kind.tracking_issue_num()
10091027
)],
10101028
},
1029+
Error::DiagnosticInvalidSeverity {
1030+
severity_control_name_span,
1031+
} => ParseError {
1032+
message: "invalid `diagnostic(…)` severity".into(),
1033+
labels: vec![(
1034+
severity_control_name_span,
1035+
"not a valid severity level".into(),
1036+
)],
1037+
notes: vec![concat!(
1038+
"See available severities at ",
1039+
"<https://www.w3.org/TR/WGSL/#diagnostic-severity>."
1040+
)
1041+
.into()],
1042+
},
1043+
Error::DiagnosticNotYetImplemented {
1044+
triggering_rule,
1045+
span,
1046+
} => ParseError {
1047+
message: format!(
1048+
"the `{}` diagnostic filter is not yet supported",
1049+
triggering_rule.to_ident()
1050+
),
1051+
labels: vec![(span, "".into())],
1052+
notes: vec![format!(
1053+
concat!(
1054+
"Let Naga maintainers know that you ran into this at ",
1055+
"<https://github.com/gfx-rs/wgpu/issues/{}>, ",
1056+
"so they can prioritize it!"
1057+
),
1058+
triggering_rule.tracking_issue_num()
1059+
)],
1060+
},
10111061
}
10121062
}
10131063
}

naga/src/front/wgsl/parse/directive.rs

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ pub(crate) mod language_extension;
88
/// A parsed sentinel word indicating the type of directive to be parsed next.
99
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
1010
pub(crate) enum DirectiveKind {
11+
/// A [`crate::diagnostic_filter`].
12+
Diagnostic,
1113
/// An [`enable_extension`].
1214
Enable,
1315
/// A [`language_extension`].
@@ -23,7 +25,7 @@ impl DirectiveKind {
2325
/// Convert from a sentinel word in WGSL into its associated [`DirectiveKind`], if possible.
2426
pub fn from_ident(s: &str) -> Option<Self> {
2527
Some(match s {
26-
Self::DIAGNOSTIC => Self::Unimplemented(UnimplementedDirectiveKind::Diagnostic),
28+
Self::DIAGNOSTIC => Self::Diagnostic,
2729
Self::ENABLE => Self::Enable,
2830
Self::REQUIRES => Self::Requires,
2931
_ => return None,
@@ -33,19 +35,18 @@ impl DirectiveKind {
3335
/// Maps this [`DirectiveKind`] into the sentinel word associated with it in WGSL.
3436
pub const fn to_ident(self) -> &'static str {
3537
match self {
38+
Self::Diagnostic => Self::DIAGNOSTIC,
3639
Self::Enable => Self::ENABLE,
3740
Self::Requires => Self::REQUIRES,
38-
Self::Unimplemented(kind) => match kind {
39-
UnimplementedDirectiveKind::Diagnostic => Self::DIAGNOSTIC,
40-
},
41+
Self::Unimplemented(kind) => match kind {},
4142
}
4243
}
4344

4445
#[cfg(test)]
4546
fn iter() -> impl Iterator<Item = Self> {
4647
use strum::IntoEnumIterator;
4748

48-
[Self::Enable, Self::Requires]
49+
[Self::Diagnostic, Self::Enable, Self::Requires]
4950
.into_iter()
5051
.chain(UnimplementedDirectiveKind::iter().map(Self::Unimplemented))
5152
}
@@ -54,15 +55,25 @@ impl DirectiveKind {
5455
/// A [`DirectiveKind`] that is not yet implemented. See [`DirectiveKind::Unimplemented`].
5556
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
5657
#[cfg_attr(test, derive(strum::EnumIter))]
57-
pub(crate) enum UnimplementedDirectiveKind {
58-
Diagnostic,
59-
}
58+
pub(crate) enum UnimplementedDirectiveKind {}
6059

6160
impl UnimplementedDirectiveKind {
6261
pub const fn tracking_issue_num(self) -> u16 {
63-
match self {
64-
Self::Diagnostic => 5320,
65-
}
62+
match self {}
63+
}
64+
}
65+
66+
impl crate::diagnostic_filter::Severity {
67+
#[cfg(feature = "wgsl-in")]
68+
pub(crate) fn report_wgsl_parse_diag<'a>(
69+
self,
70+
err: crate::front::wgsl::error::Error<'a>,
71+
source: &str,
72+
) -> Result<(), crate::front::wgsl::error::Error<'a>> {
73+
self.report_diag(err, |e, level| {
74+
let e = e.as_parse_error(source);
75+
log::log!(level, "{}", e.emit_to_string(source));
76+
})
6677
}
6778
}
6879

@@ -75,25 +86,12 @@ mod test {
7586
use super::{DirectiveKind, UnimplementedDirectiveKind};
7687

7788
#[test]
89+
#[allow(clippy::never_loop, unreachable_code, unused_variables)]
7890
fn unimplemented_directives() {
7991
for unsupported_shader in UnimplementedDirectiveKind::iter() {
8092
let shader;
8193
let expected_msg;
82-
match unsupported_shader {
83-
UnimplementedDirectiveKind::Diagnostic => {
84-
shader = "diagnostic(off,derivative_uniformity);";
85-
expected_msg = "\
86-
error: the `diagnostic` directive is not yet implemented
87-
┌─ wgsl:1:1
88-
89-
1 │ diagnostic(off,derivative_uniformity);
90-
│ ^^^^^^^^^^ this global directive is standard, but not yet implemented
91-
92-
= note: Let Naga maintainers know that you ran into this at <https://github.com/gfx-rs/wgpu/issues/5320>, so they can prioritize it!
93-
94-
";
95-
}
96-
};
94+
match unsupported_shader {};
9795

9896
assert_parse_err(shader, expected_msg);
9997
}
@@ -105,7 +103,7 @@ error: the `diagnostic` directive is not yet implemented
105103
let directive;
106104
let expected_msg;
107105
match unsupported_shader {
108-
DirectiveKind::Unimplemented(UnimplementedDirectiveKind::Diagnostic) => {
106+
DirectiveKind::Diagnostic => {
109107
directive = "diagnostic(off,derivative_uniformity)";
110108
expected_msg = "\
111109
error: expected global declaration, but found a global directive
@@ -144,6 +142,7 @@ error: expected global declaration, but found a global directive
144142
145143
";
146144
}
145+
DirectiveKind::Unimplemented(kind) => match kind {},
147146
}
148147

149148
let shader = format!(

naga/src/front/wgsl/parse/mod.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::diagnostic_filter::{self, DiagnosticFilter, FilterableTriggeringRule};
12
use crate::front::wgsl::error::{Error, ExpectedToken};
23
use crate::front::wgsl::parse::directive::enable_extension::{
34
EnableExtension, EnableExtensions, UnimplementedEnableExtension,
@@ -2529,6 +2530,17 @@ impl Parser {
25292530
self.push_rule_span(Rule::Directive, &mut lexer);
25302531
let _ = lexer.next_ident_with_span().unwrap();
25312532
match kind {
2533+
DirectiveKind::Diagnostic => {
2534+
if let Some(diagnostic_filter) = self.diagnostic_filter(&mut lexer)? {
2535+
let triggering_rule = diagnostic_filter.triggering_rule;
2536+
let span = self.peek_rule_span(&lexer);
2537+
Err(Error::DiagnosticNotYetImplemented {
2538+
triggering_rule,
2539+
span,
2540+
})?;
2541+
}
2542+
lexer.expect(Token::Separator(';'))?;
2543+
}
25322544
DirectiveKind::Enable => {
25332545
self.directive_ident_list(&mut lexer, |ident, span| {
25342546
let kind = EnableExtension::from_ident(ident, span)?;
@@ -2614,4 +2626,55 @@ impl Parser {
26142626
}
26152627
Ok(brace_nesting_level + 1)
26162628
}
2629+
2630+
fn diagnostic_filter<'a>(
2631+
&self,
2632+
lexer: &mut Lexer<'a>,
2633+
) -> Result<Option<DiagnosticFilter>, Error<'a>> {
2634+
lexer.expect(Token::Paren('('))?;
2635+
2636+
let (severity_control_name, severity_control_name_span) = lexer.next_ident_with_span()?;
2637+
let new_severity = diagnostic_filter::Severity::from_ident(severity_control_name).ok_or(
2638+
Error::DiagnosticInvalidSeverity {
2639+
severity_control_name_span,
2640+
},
2641+
)?;
2642+
2643+
lexer.expect(Token::Separator(','))?;
2644+
2645+
let (diagnostic_name_token, diagnostic_name_token_span) = lexer.next_ident_with_span()?;
2646+
let diagnostic_rule_name = if lexer.skip(Token::Separator('.')) {
2647+
// Don't try to validate these name tokens on two tokens, which is conventionally used
2648+
// for third-party tooling.
2649+
lexer.next_ident_with_span()?;
2650+
None
2651+
} else {
2652+
Some(diagnostic_name_token)
2653+
};
2654+
let diagnostic_rule_name_span = diagnostic_name_token_span;
2655+
2656+
let filter = diagnostic_rule_name
2657+
.and_then(|name| {
2658+
FilterableTriggeringRule::from_ident(name)
2659+
.map(Ok)
2660+
.or_else(|| {
2661+
diagnostic_filter::Severity::Warning
2662+
.report_wgsl_parse_diag(
2663+
Error::UnknownDiagnosticRuleName(diagnostic_rule_name_span),
2664+
lexer.source,
2665+
)
2666+
.err()
2667+
.map(Err)
2668+
})
2669+
})
2670+
.transpose()?
2671+
.map(|triggering_rule| DiagnosticFilter {
2672+
new_severity,
2673+
triggering_rule,
2674+
});
2675+
lexer.skip(Token::Separator(','));
2676+
lexer.expect(Token::Paren(')'))?;
2677+
2678+
Ok(filter)
2679+
}
26172680
}

naga/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ pub mod back;
255255
mod block;
256256
#[cfg(feature = "compact")]
257257
pub mod compact;
258+
pub mod diagnostic_filter;
258259
pub mod error;
259260
pub mod front;
260261
pub mod keywords;

0 commit comments

Comments
 (0)