Skip to content

Commit d139d1c

Browse files
feat(wgsl-in): parse diagnostic(…) directives (with no effect)
1 parent 334d36d commit d139d1c

File tree

6 files changed

+268
-27
lines changed

6 files changed

+268
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ By @bradwerth [#6216](https://github.com/gfx-rs/wgpu/pull/6216).
8585
- Support local `const` declarations in WGSL. By @sagudev in [#6156](https://github.com/gfx-rs/wgpu/pull/6156).
8686
- Implemented `const_assert` in WGSL. By @sagudev in [#6198](https://github.com/gfx-rs/wgpu/pull/6198).
8787
- Support polyfilling `inverse` in WGSL. By @chyyran in [#6385](https://github.com/gfx-rs/wgpu/pull/6385).
88-
- 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).
88+
- 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).
8989
- 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).
9090
- Unify Naga CLI error output with the format of shader compilation messages. By @ErichDonGubler in [#6436](https://github.com/gfx-rs/wgpu/pull/6436).
9191

naga/src/diagnostic_filter.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//! Functionality for managing [`DiagnosticFilter`]s.
2+
//!
3+
//! This functionality is typically used by front ends via [`DiagnosticFilterMap`].
4+
5+
use crate::{Handle, Span};
6+
7+
use indexmap::IndexMap;
8+
9+
/// A severity set on a [`DiagnosticFilter`].
10+
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11+
pub enum Severity {
12+
Off,
13+
Info,
14+
Warning,
15+
Error,
16+
}
17+
18+
impl Severity {
19+
const ERROR: &'static str = "error";
20+
const WARNING: &'static str = "warning";
21+
const INFO: &'static str = "info";
22+
const OFF: &'static str = "off";
23+
24+
/// Convert from a sentinel word in WGSL into its associated [`Severity`], if possible.
25+
pub fn from_ident(s: &str) -> Option<Self> {
26+
Some(match s {
27+
Self::ERROR => Self::Error,
28+
Self::WARNING => Self::Warning,
29+
Self::INFO => Self::Info,
30+
Self::OFF => Self::Off,
31+
_ => return None,
32+
})
33+
}
34+
}
35+
36+
/// The rule being configured in a [`DiagnosticFilter`].
37+
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
38+
pub enum DiagnosticTriggeringRule {
39+
DerivativeUniformity,
40+
}
41+
42+
impl DiagnosticTriggeringRule {
43+
const DERIVATIVE_UNIFORMITY: &'static str = "derivative_uniformity";
44+
45+
/// Convert from a sentinel word in WGSL into its associated [`DiagnosticTriggeringRule`], if possible.
46+
pub fn from_ident(s: &str) -> Option<Self> {
47+
Some(match s {
48+
Self::DERIVATIVE_UNIFORMITY => Self::DerivativeUniformity,
49+
_ => return None,
50+
})
51+
}
52+
53+
/// Maps this [`DiagnosticTriggeringRule`] into the sentinel word associated with it in WGSL.
54+
pub const fn to_ident(self) -> &'static str {
55+
match self {
56+
Self::DerivativeUniformity => Self::DERIVATIVE_UNIFORMITY,
57+
}
58+
}
59+
}
60+
61+
/// A filter that modifies how diagnostics are emitted for shaders.
62+
///
63+
/// <https://www.w3.org/TR/WGSL/#diagnostic-filter>
64+
#[derive(Clone, Debug)]
65+
pub struct DiagnosticFilter {
66+
pub new_severity: Severity,
67+
pub triggering_rule: DiagnosticTriggeringRule,
68+
}
69+
70+
/// A map of diagnostic filters to their severity and first occurrence's span.
71+
///
72+
/// Intended for front ends' first step into storing parsed [`DiagnosticFilter`]s.
73+
#[derive(Clone, Debug, Default)]
74+
pub(crate) struct DiagnosticFilterMap(IndexMap<DiagnosticTriggeringRule, (Severity, Span)>);
75+
76+
#[cfg(feature = "wgsl-in")]
77+
impl DiagnosticFilterMap {
78+
pub(crate) fn new() -> Self {
79+
Self::default()
80+
}
81+
82+
/// Add the given `diagnostic_filter` parsed at the given `span` to this map.
83+
pub(crate) fn add(
84+
&mut self,
85+
diagnostic_filter: DiagnosticFilter,
86+
span: Span,
87+
) -> Result<(), ConflictingDiagnosticRuleError> {
88+
use indexmap::map::Entry;
89+
90+
let &mut Self(ref mut diagnostic_filters) = self;
91+
let DiagnosticFilter {
92+
new_severity,
93+
triggering_rule,
94+
} = diagnostic_filter;
95+
96+
match diagnostic_filters.entry(triggering_rule) {
97+
Entry::Vacant(entry) => {
98+
entry.insert((new_severity, span));
99+
Ok(())
100+
}
101+
Entry::Occupied(entry) => {
102+
return Err(ConflictingDiagnosticRuleError {
103+
triggering_rule,
104+
triggering_rule_spans: [entry.get().1, span],
105+
})
106+
}
107+
}
108+
}
109+
}
110+
111+
/// An error yielded by [`DiagnosticFilterMap::add`].
112+
#[cfg(feature = "wgsl-in")]
113+
#[derive(Clone, Debug)]
114+
pub(crate) struct ConflictingDiagnosticRuleError {
115+
pub triggering_rule: DiagnosticTriggeringRule,
116+
pub triggering_rule_spans: [Span; 2],
117+
}
118+
119+
/// Represents a single link in a linked list of [`DiagnosticFilter`]s backed by a
120+
/// [`crate::Arena`].
121+
#[derive(Clone, Debug)]
122+
pub struct DiagnosticFilterNode {
123+
pub inner: DiagnosticFilter,
124+
pub parent: Option<Handle<DiagnosticFilterNode>>,
125+
}

naga/src/front/wgsl/error.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::diagnostic_filter::{ConflictingDiagnosticRuleError, DiagnosticTriggeringRule};
12
use crate::front::wgsl::parse::directive::enable_extension::{
23
EnableExtension, UnimplementedEnableExtension,
34
};
@@ -295,6 +296,29 @@ pub(crate) enum Error<'a> {
295296
kind: UnimplementedLanguageExtension,
296297
span: Span,
297298
},
299+
DiagnosticInvalidSeverity {
300+
severity_control_name_span: Span,
301+
},
302+
DiagnosticInvalidRuleName {
303+
diagnostic_rule_name_span: Span,
304+
},
305+
DiagnosticDuplicateTriggeringRule {
306+
triggering_rule: DiagnosticTriggeringRule,
307+
triggering_rule_spans: [Span; 2],
308+
},
309+
}
310+
311+
impl<'a> From<ConflictingDiagnosticRuleError> for Error<'a> {
312+
fn from(value: ConflictingDiagnosticRuleError) -> Self {
313+
let ConflictingDiagnosticRuleError {
314+
triggering_rule,
315+
triggering_rule_spans,
316+
} = value;
317+
Self::DiagnosticDuplicateTriggeringRule {
318+
triggering_rule,
319+
triggering_rule_spans,
320+
}
321+
}
298322
}
299323

300324
#[derive(Clone, Debug)]
@@ -1008,6 +1032,57 @@ impl<'a> Error<'a> {
10081032
kind.tracking_issue_num()
10091033
)],
10101034
},
1035+
Error::DiagnosticInvalidSeverity {
1036+
severity_control_name_span,
1037+
} => ParseError {
1038+
message: "invalid `diagnostic(…)` severity".into(),
1039+
labels: vec![(
1040+
severity_control_name_span,
1041+
"not a valid severity level".into(),
1042+
)],
1043+
notes: vec![concat!(
1044+
"See available severities at ",
1045+
"<https://www.w3.org/TR/WGSL/#diagnostic-severity>."
1046+
)
1047+
.into()],
1048+
},
1049+
Error::DiagnosticInvalidRuleName {
1050+
diagnostic_rule_name_span,
1051+
} => ParseError {
1052+
message: "invalid `diagnostic(…)` rule name".into(),
1053+
labels: vec![(
1054+
diagnostic_rule_name_span,
1055+
"not a valid diagnostic rule name".into(),
1056+
)],
1057+
notes: vec![concat!(
1058+
"See available trigger rules at ",
1059+
"<https://www.w3.org/TR/WGSL/#filterable-triggering-rules>."
1060+
)
1061+
.into()],
1062+
},
1063+
Error::DiagnosticDuplicateTriggeringRule {
1064+
triggering_rule,
1065+
triggering_rule_spans,
1066+
} => {
1067+
let [first_span, second_span] = triggering_rule_spans;
1068+
ParseError {
1069+
message: format!(
1070+
"found conflicting `diagnostic(…)` rule(s) for `{}`",
1071+
triggering_rule.to_ident()
1072+
),
1073+
labels: vec![
1074+
(first_span, "first rule".into()),
1075+
(second_span, "second rule".into()),
1076+
],
1077+
notes: vec![concat!(
1078+
"multiple `@diagnostic(…)` rules with the same rule name ",
1079+
"conflict unless the severity is the same; ",
1080+
"delete the rule you don't want, or ",
1081+
"ensure that all severities with the same rule name match"
1082+
)
1083+
.into()],
1084+
}
1085+
}
10111086
}
10121087
}
10131088
}

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

Lines changed: 11 additions & 26 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,11 +35,10 @@ 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

@@ -52,15 +53,11 @@ impl DirectiveKind {
5253
/// A [`DirectiveKind`] that is not yet implemented. See [`DirectiveKind::Unimplemented`].
5354
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
5455
#[cfg_attr(test, derive(strum::EnumIter))]
55-
pub(crate) enum UnimplementedDirectiveKind {
56-
Diagnostic,
57-
}
56+
pub(crate) enum UnimplementedDirectiveKind {}
5857

5958
impl UnimplementedDirectiveKind {
6059
pub const fn tracking_issue_num(self) -> u16 {
61-
match self {
62-
Self::Diagnostic => 5320,
63-
}
60+
match self {}
6461
}
6562
}
6663

@@ -73,25 +70,12 @@ mod test {
7370
use super::{DirectiveKind, UnimplementedDirectiveKind};
7471

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

9680
assert_parse_err(shader, expected_msg);
9781
}
@@ -103,7 +87,7 @@ error: the `diagnostic` directive is not yet implemented
10387
let directive;
10488
let expected_msg;
10589
match unsupported_shader {
106-
DirectiveKind::Unimplemented(UnimplementedDirectiveKind::Diagnostic) => {
90+
DirectiveKind::Diagnostic => {
10791
directive = "diagnostic(off,derivative_uniformity)";
10892
expected_msg = "\
10993
error: expected global declaration, but found a global directive
@@ -142,6 +126,7 @@ error: expected global declaration, but found a global directive
142126
143127
";
144128
}
129+
DirectiveKind::Unimplemented(kind) => match kind {},
145130
}
146131

147132
let shader = format!(

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use crate::diagnostic_filter::{
2+
DiagnosticFilter, DiagnosticFilterMap, DiagnosticTriggeringRule, Severity,
3+
};
14
use crate::front::wgsl::error::{Error, ExpectedToken};
25
use crate::front::wgsl::parse::directive::enable_extension::{
36
EnableExtension, EnableExtensions, UnimplementedEnableExtension,
@@ -2520,13 +2523,21 @@ impl Parser {
25202523
let mut lexer = Lexer::new(source);
25212524
let mut tu = ast::TranslationUnit::default();
25222525
let mut enable_extensions = EnableExtensions::empty();
2526+
let mut diagnostic_filters = DiagnosticFilterMap::new();
25232527

25242528
// Parse directives.
25252529
while let Ok((ident, span)) = lexer.peek_ident_with_span() {
25262530
if let Some(kind) = DirectiveKind::from_ident(ident) {
25272531
self.push_rule_span(Rule::Directive, &mut lexer);
25282532
let _ = lexer.next_ident_with_span().unwrap();
25292533
match kind {
2534+
DirectiveKind::Diagnostic => {
2535+
if let Some(diagnostic_filter) = self.diagnostic_filter(&mut lexer)? {
2536+
let span = self.peek_rule_span(&lexer);
2537+
diagnostic_filters.add(diagnostic_filter, span)?;
2538+
}
2539+
lexer.expect(Token::Separator(';'))?;
2540+
}
25302541
DirectiveKind::Enable => {
25312542
self.directive_ident_list(&mut lexer, |ident, span| {
25322543
let kind = EnableExtension::from_ident(ident, span)?;
@@ -2612,4 +2623,47 @@ impl Parser {
26122623
}
26132624
Ok(brace_nesting_level + 1)
26142625
}
2626+
2627+
fn diagnostic_filter<'a>(
2628+
&self,
2629+
lexer: &mut Lexer<'a>,
2630+
) -> Result<Option<DiagnosticFilter>, Error<'a>> {
2631+
lexer.expect(Token::Paren('('))?;
2632+
2633+
let (severity_control_name, severity_control_name_span) = lexer.next_ident_with_span()?;
2634+
let new_severity = Severity::from_ident(severity_control_name).ok_or(
2635+
Error::DiagnosticInvalidSeverity {
2636+
severity_control_name_span,
2637+
},
2638+
)?;
2639+
2640+
lexer.expect(Token::Separator(','))?;
2641+
2642+
let (diagnostic_name_token, diagnostic_name_token_span) = lexer.next_ident_with_span()?;
2643+
let diagnostic_rule_name = if lexer.skip(Token::Separator('.')) {
2644+
// Don't try to validate these name tokens on two tokens, which is conventionally used
2645+
// for third-party tooling.
2646+
lexer.next_ident_with_span()?;
2647+
None
2648+
} else {
2649+
Some(diagnostic_name_token)
2650+
};
2651+
let diagnostic_rule_name_span = diagnostic_name_token_span;
2652+
2653+
let filter = diagnostic_rule_name
2654+
.map(|name| {
2655+
DiagnosticTriggeringRule::from_ident(name).ok_or(Error::DiagnosticInvalidRuleName {
2656+
diagnostic_rule_name_span,
2657+
})
2658+
})
2659+
.transpose()?
2660+
.map(|triggering_rule| DiagnosticFilter {
2661+
new_severity,
2662+
triggering_rule,
2663+
});
2664+
lexer.skip(Token::Separator(','));
2665+
lexer.expect(Token::Paren(')'))?;
2666+
2667+
Ok(filter)
2668+
}
26152669
}

0 commit comments

Comments
 (0)