naga/
diagnostic_filter.rs

1//! [`DiagnosticFilter`]s and supporting functionality.
2
3use alloc::boxed::Box;
4
5use crate::{Arena, Handle};
6
7#[cfg(feature = "wgsl-in")]
8use crate::FastIndexMap;
9#[cfg(feature = "wgsl-in")]
10use crate::Span;
11#[cfg(feature = "arbitrary")]
12use arbitrary::Arbitrary;
13#[cfg(feature = "deserialize")]
14use serde::Deserialize;
15#[cfg(feature = "serialize")]
16use serde::Serialize;
17
18/// A severity set on a [`DiagnosticFilter`].
19///
20/// <https://www.w3.org/TR/WGSL/#diagnostic-severity>
21#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
22#[cfg_attr(feature = "serialize", derive(Serialize))]
23#[cfg_attr(feature = "deserialize", derive(Deserialize))]
24#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
25pub enum Severity {
26    Off,
27    Info,
28    Warning,
29    Error,
30}
31
32impl Severity {
33    /// Checks whether this severity is [`Self::Error`].
34    ///
35    /// Naga does not yet support diagnostic items at lesser severities than
36    /// [`Severity::Error`]. When this is implemented, this method should be deleted, and the
37    /// severity should be used directly for reporting diagnostics.
38    pub(crate) fn report_diag<E>(
39        self,
40        err: E,
41        log_handler: impl FnOnce(E, log::Level),
42    ) -> Result<(), E> {
43        let log_level = match self {
44            Severity::Off => return Ok(()),
45
46            // NOTE: These severities are not yet reported.
47            Severity::Info => log::Level::Info,
48            Severity::Warning => log::Level::Warn,
49
50            Severity::Error => return Err(err),
51        };
52        log_handler(err, log_level);
53        Ok(())
54    }
55}
56
57/// A filterable triggering rule in a [`DiagnosticFilter`].
58///
59/// <https://www.w3.org/TR/WGSL/#filterable-triggering-rules>
60#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
61#[cfg_attr(feature = "serialize", derive(Serialize))]
62#[cfg_attr(feature = "deserialize", derive(Deserialize))]
63#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
64pub enum FilterableTriggeringRule {
65    Standard(StandardFilterableTriggeringRule),
66    Unknown(Box<str>),
67    User(Box<[Box<str>; 2]>),
68}
69
70/// A filterable triggering rule in a [`DiagnosticFilter`].
71///
72/// <https://www.w3.org/TR/WGSL/#filterable-triggering-rules>
73#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
74#[cfg_attr(feature = "serialize", derive(Serialize))]
75#[cfg_attr(feature = "deserialize", derive(Deserialize))]
76#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
77pub enum StandardFilterableTriggeringRule {
78    DerivativeUniformity,
79}
80
81impl StandardFilterableTriggeringRule {
82    /// The default severity associated with this triggering rule.
83    ///
84    /// See <https://www.w3.org/TR/WGSL/#filterable-triggering-rules> for a table of default
85    /// severities.
86    pub(crate) const fn default_severity(self) -> Severity {
87        match self {
88            Self::DerivativeUniformity => Severity::Error,
89        }
90    }
91}
92
93/// A filtering rule that modifies how diagnostics are emitted for shaders.
94///
95/// <https://www.w3.org/TR/WGSL/#diagnostic-filter>
96#[derive(Clone, Debug)]
97#[cfg_attr(feature = "serialize", derive(Serialize))]
98#[cfg_attr(feature = "deserialize", derive(Deserialize))]
99#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
100pub struct DiagnosticFilter {
101    pub new_severity: Severity,
102    pub triggering_rule: FilterableTriggeringRule,
103}
104
105/// Determines whether [`DiagnosticFilterMap::add`] should consider full duplicates a conflict.
106///
107/// In WGSL, directive position does not consider this case a conflict, while attribute position
108/// does.
109#[cfg(feature = "wgsl-in")]
110pub(crate) enum ShouldConflictOnFullDuplicate {
111    /// Use this for attributes in WGSL.
112    Yes,
113    /// Use this for directives in WGSL.
114    No,
115}
116
117/// A map from diagnostic filters to their severity and span.
118///
119/// Front ends can use this to collect the set of filters applied to a
120/// particular language construct, and detect duplicate/conflicting filters.
121///
122/// For example, WGSL has global diagnostic filters that apply to the entire
123/// module, and diagnostic range filter attributes that apply to a specific
124/// function, statement, or other smaller construct. The set of filters applied
125/// to any given construct must not conflict, but they can be overridden by
126/// filters on other constructs nested within it. A front end can use a
127/// `DiagnosticFilterMap` to collect the filters applied to a single construct,
128/// using the [`add`] method's error checking to forbid conflicts.
129///
130/// For each filter it contains, a `DiagnosticFilterMap` records the requested
131/// severity, and the source span of the filter itself.
132///
133/// [`add`]: DiagnosticFilterMap::add
134#[derive(Clone, Debug, Default)]
135#[cfg(feature = "wgsl-in")]
136pub(crate) struct DiagnosticFilterMap(FastIndexMap<FilterableTriggeringRule, (Severity, Span)>);
137
138#[cfg(feature = "wgsl-in")]
139impl DiagnosticFilterMap {
140    pub(crate) fn new() -> Self {
141        Self::default()
142    }
143
144    /// Add the given `diagnostic_filter` parsed at the given `span` to this map.
145    pub(crate) fn add(
146        &mut self,
147        diagnostic_filter: DiagnosticFilter,
148        span: Span,
149        should_conflict_on_full_duplicate: ShouldConflictOnFullDuplicate,
150    ) -> Result<(), ConflictingDiagnosticRuleError> {
151        use indexmap::map::Entry;
152
153        let &mut Self(ref mut diagnostic_filters) = self;
154        let DiagnosticFilter {
155            new_severity,
156            triggering_rule,
157        } = diagnostic_filter;
158
159        match diagnostic_filters.entry(triggering_rule.clone()) {
160            Entry::Vacant(entry) => {
161                entry.insert((new_severity, span));
162            }
163            Entry::Occupied(entry) => {
164                let &(first_severity, first_span) = entry.get();
165                let should_conflict_on_full_duplicate = match should_conflict_on_full_duplicate {
166                    ShouldConflictOnFullDuplicate::Yes => true,
167                    ShouldConflictOnFullDuplicate::No => false,
168                };
169                if first_severity != new_severity || should_conflict_on_full_duplicate {
170                    return Err(ConflictingDiagnosticRuleError {
171                        triggering_rule_spans: [first_span, span],
172                    });
173                }
174            }
175        }
176        Ok(())
177    }
178
179    /// Were any rules specified?
180    pub(crate) fn is_empty(&self) -> bool {
181        let &Self(ref map) = self;
182        map.is_empty()
183    }
184
185    /// Returns the spans of all contained rules.
186    pub(crate) fn spans(&self) -> impl Iterator<Item = Span> + '_ {
187        let &Self(ref map) = self;
188        map.iter().map(|(_, &(_, span))| span)
189    }
190}
191
192#[cfg(feature = "wgsl-in")]
193impl IntoIterator for DiagnosticFilterMap {
194    type Item = (FilterableTriggeringRule, (Severity, Span));
195
196    type IntoIter = indexmap::map::IntoIter<FilterableTriggeringRule, (Severity, Span)>;
197
198    fn into_iter(self) -> Self::IntoIter {
199        let Self(this) = self;
200        this.into_iter()
201    }
202}
203
204/// An error returned by [`DiagnosticFilterMap::add`] when it encounters conflicting rules.
205#[cfg(feature = "wgsl-in")]
206#[derive(Clone, Debug)]
207pub(crate) struct ConflictingDiagnosticRuleError {
208    pub triggering_rule_spans: [Span; 2],
209}
210
211/// Represents a single parent-linking node in a tree of [`DiagnosticFilter`]s backed by a
212/// [`crate::Arena`].
213///
214/// A single element of a _tree_ of diagnostic filter rules stored in
215/// [`crate::Module::diagnostic_filters`]. When nodes are built by a front-end, module-applicable
216/// filter rules are chained together in runs based on parse site.  For instance, given the
217/// following:
218///
219/// - Module-applicable rules `a` and `b`.
220/// - Rules `c` and `d`, applicable to an entry point called `c_and_d_func`.
221/// - Rule `e`, applicable to an entry point called `e_func`.
222///
223/// The tree would be represented as follows:
224///
225/// ```text
226/// a <- b
227///      ^
228///      |- c <- d
229///      |
230///      \- e
231/// ```
232///
233/// ...where:
234///
235/// - `d` is the first leaf consulted by validation in `c_and_d_func`.
236/// - `e` is the first leaf consulted by validation in `e_func`.
237#[derive(Clone, Debug)]
238#[cfg_attr(feature = "serialize", derive(Serialize))]
239#[cfg_attr(feature = "deserialize", derive(Deserialize))]
240#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
241pub struct DiagnosticFilterNode {
242    pub inner: DiagnosticFilter,
243    pub parent: Option<Handle<DiagnosticFilterNode>>,
244}
245
246impl DiagnosticFilterNode {
247    /// Finds the most specific filter rule applicable to `triggering_rule` from the chain of
248    /// diagnostic filter rules in `arena`, starting with `node`, and returns its severity. If none
249    /// is found, return the value of [`StandardFilterableTriggeringRule::default_severity`].
250    ///
251    /// When `triggering_rule` is not applicable to this node, its parent is consulted recursively.
252    pub(crate) fn search(
253        node: Option<Handle<Self>>,
254        arena: &Arena<Self>,
255        triggering_rule: StandardFilterableTriggeringRule,
256    ) -> Severity {
257        let mut next = node;
258        while let Some(handle) = next {
259            let node = &arena[handle];
260            let &Self { ref inner, parent } = node;
261            let &DiagnosticFilter {
262                triggering_rule: ref rule,
263                new_severity,
264            } = inner;
265
266            if rule == &FilterableTriggeringRule::Standard(triggering_rule) {
267                return new_severity;
268            }
269
270            next = parent;
271        }
272        triggering_rule.default_severity()
273    }
274}