feat(wgsl-in): detect conflicting diag. filters

This commit is contained in:
Erich Gubler 2024-10-25 14:21:46 -04:00
parent 44b8e38fd8
commit 7a5d505896
3 changed files with 127 additions and 2 deletions

View File

@ -1,5 +1,11 @@
//! [`DiagnosticFilter`]s and supporting functionality. //! [`DiagnosticFilter`]s and supporting functionality.
use crate::Handle;
#[cfg(feature = "wgsl-in")]
use crate::Span;
#[cfg(feature = "wgsl-in")]
use indexmap::IndexMap;
/// A severity set on a [`DiagnosticFilter`]. /// A severity set on a [`DiagnosticFilter`].
/// ///
/// <https://www.w3.org/TR/WGSL/#diagnostic-severity> /// <https://www.w3.org/TR/WGSL/#diagnostic-severity>
@ -95,3 +101,88 @@ pub struct DiagnosticFilter {
pub new_severity: Severity, pub new_severity: Severity,
pub triggering_rule: FilterableTriggeringRule, pub triggering_rule: FilterableTriggeringRule,
} }
/// A map of diagnostic filters to their severity and first occurrence's span.
///
/// Intended for front ends' first step into storing parsed [`DiagnosticFilter`]s.
#[derive(Clone, Debug, Default)]
#[cfg(feature = "wgsl-in")]
pub(crate) struct DiagnosticFilterMap(IndexMap<FilterableTriggeringRule, (Severity, Span)>);
#[cfg(feature = "wgsl-in")]
impl DiagnosticFilterMap {
pub(crate) fn new() -> Self {
Self::default()
}
/// Add the given `diagnostic_filter` parsed at the given `span` to this map.
pub(crate) fn add(
&mut self,
diagnostic_filter: DiagnosticFilter,
span: Span,
) -> Result<(), ConflictingDiagnosticRuleError> {
use indexmap::map::Entry;
let &mut Self(ref mut diagnostic_filters) = self;
let DiagnosticFilter {
new_severity,
triggering_rule,
} = diagnostic_filter;
match diagnostic_filters.entry(triggering_rule) {
Entry::Vacant(entry) => {
entry.insert((new_severity, span));
}
Entry::Occupied(entry) => {
let &(first_severity, first_span) = entry.get();
if first_severity != new_severity {
return Err(ConflictingDiagnosticRuleError {
triggering_rule,
triggering_rule_spans: [first_span, span],
});
}
}
}
Ok(())
}
}
/// An error returned by [`DiagnosticFilterMap::add`] when it encounters conflicting rules.
#[cfg(feature = "wgsl-in")]
#[derive(Clone, Debug)]
pub(crate) struct ConflictingDiagnosticRuleError {
pub triggering_rule: FilterableTriggeringRule,
pub triggering_rule_spans: [Span; 2],
}
/// Represents a single parent-linking node in a tree of [`DiagnosticFilter`]s backed by a
/// [`crate::Arena`].
///
/// A single element of a _tree_ of diagnostic filter rules stored in
/// [`crate::Module::diagnostic_filters`]. When nodes are built by a front-end, module-applicable
/// filter rules are chained together in runs based on parse site. For instance, given the
/// following:
///
/// - Module-applicable rules `a` and `b`.
/// - Rules `c` and `d`, applicable to an entry point called `c_and_d_func`.
/// - Rule `e`, applicable to an entry point called `e_func`.
///
/// The tree would be represented as follows:
///
/// ```text
/// a <- b
/// ^
/// |- c <- d
/// |
/// \- e
/// ```
///
/// ...where:
///
/// - `d` is the first leaf consulted by validation in `c_and_d_func`.
/// - `e` is the first leaf consulted by validation in `e_func`.
#[derive(Clone, Debug)]
pub struct DiagnosticFilterNode {
pub inner: DiagnosticFilter,
pub parent: Option<Handle<DiagnosticFilterNode>>,
}

View File

@ -1,4 +1,4 @@
use crate::diagnostic_filter::FilterableTriggeringRule; use crate::diagnostic_filter::ConflictingDiagnosticRuleError;
use crate::front::wgsl::parse::directive::enable_extension::{ use crate::front::wgsl::parse::directive::enable_extension::{
EnableExtension, UnimplementedEnableExtension, EnableExtension, UnimplementedEnableExtension,
}; };
@ -295,12 +295,19 @@ pub(crate) enum Error<'a> {
DiagnosticInvalidSeverity { DiagnosticInvalidSeverity {
severity_control_name_span: Span, severity_control_name_span: Span,
}, },
DiagnosticDuplicateTriggeringRule(ConflictingDiagnosticRuleError),
DiagnosticNotYetImplemented { DiagnosticNotYetImplemented {
triggering_rule: FilterableTriggeringRule, triggering_rule: FilterableTriggeringRule,
span: Span, span: Span,
}, },
} }
impl<'a> From<ConflictingDiagnosticRuleError> for Error<'a> {
fn from(value: ConflictingDiagnosticRuleError) -> Self {
Self::DiagnosticDuplicateTriggeringRule(value)
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct AutoConversionError { pub(crate) struct AutoConversionError {
pub dest_span: Span, pub dest_span: Span,
@ -1017,6 +1024,29 @@ impl<'a> Error<'a> {
) )
.into()], .into()],
}, },
Error::DiagnosticDuplicateTriggeringRule(ConflictingDiagnosticRuleError {
triggering_rule,
triggering_rule_spans,
}) => {
let [first_span, second_span] = triggering_rule_spans;
ParseError {
message: format!(
"found conflicting `diagnostic(…)` rule(s) for `{}`",
triggering_rule.to_ident()
),
labels: vec![
(first_span, "first rule".into()),
(second_span, "second rule".into()),
],
notes: vec![concat!(
"multiple `diagnostic(…)` rules with the same rule name ",
"conflict unless the severity is the same; ",
"delete the rule you don't want, or ",
"ensure that all severities with the same rule name match"
)
.into()],
}
}
Error::DiagnosticNotYetImplemented { Error::DiagnosticNotYetImplemented {
triggering_rule, triggering_rule,
span, span,

View File

@ -1,4 +1,6 @@
use crate::diagnostic_filter::{self, DiagnosticFilter, FilterableTriggeringRule}; use crate::diagnostic_filter::{
self, DiagnosticFilter, DiagnosticFilterMap, FilterableTriggeringRule,
};
use crate::front::wgsl::error::{Error, ExpectedToken}; use crate::front::wgsl::error::{Error, ExpectedToken};
use crate::front::wgsl::parse::directive::enable_extension::{ use crate::front::wgsl::parse::directive::enable_extension::{
EnableExtension, EnableExtensions, UnimplementedEnableExtension, EnableExtension, EnableExtensions, UnimplementedEnableExtension,
@ -2522,6 +2524,7 @@ impl Parser {
let mut lexer = Lexer::new(source); let mut lexer = Lexer::new(source);
let mut tu = ast::TranslationUnit::default(); let mut tu = ast::TranslationUnit::default();
let mut enable_extensions = EnableExtensions::empty(); let mut enable_extensions = EnableExtensions::empty();
let mut diagnostic_filters = DiagnosticFilterMap::new();
// Parse directives. // Parse directives.
while let Ok((ident, _directive_ident_span)) = lexer.peek_ident_with_span() { while let Ok((ident, _directive_ident_span)) = lexer.peek_ident_with_span() {
@ -2533,6 +2536,7 @@ impl Parser {
if let Some(diagnostic_filter) = self.diagnostic_filter(&mut lexer)? { if let Some(diagnostic_filter) = self.diagnostic_filter(&mut lexer)? {
let triggering_rule = diagnostic_filter.triggering_rule; let triggering_rule = diagnostic_filter.triggering_rule;
let span = self.peek_rule_span(&lexer); let span = self.peek_rule_span(&lexer);
diagnostic_filters.add(diagnostic_filter, span)?;
Err(Error::DiagnosticNotYetImplemented { Err(Error::DiagnosticNotYetImplemented {
triggering_rule, triggering_rule,
span, span,