feat(wgsl-in): add @diagnostic(…) checks on global decls.

Co-authored-by: Teodor Tanasoaia <28601907+teoxoy@users.noreply.github.com>
This commit is contained in:
Erich Gubler 2024-11-04 17:02:21 -05:00
parent 82e7aa3d5b
commit c259f3b5a0
3 changed files with 109 additions and 2 deletions

View File

@ -129,6 +129,18 @@ impl DiagnosticFilterMap {
}
Ok(())
}
/// Were any rules specified?
pub(crate) fn is_empty(&self) -> bool {
let &Self(ref map) = self;
map.is_empty()
}
/// Returns the spans of all contained rules.
pub(crate) fn spans(&self) -> impl Iterator<Item = Span> + '_ {
let &Self(ref map) = self;
map.iter().map(|(_, &(_, span))| span)
}
}
#[cfg(feature = "wgsl-in")]

View File

@ -296,6 +296,14 @@ pub(crate) enum Error<'a> {
severity_control_name_span: Span,
},
DiagnosticDuplicateTriggeringRule(ConflictingDiagnosticRuleError),
DiagnosticAttributeNotYetImplementedAtParseSite {
site_name_plural: &'static str,
spans: Vec<Span>,
},
DiagnosticAttributeNotSupported {
on_what_plural: &'static str,
spans: Vec<Span>,
},
}
impl<'a> From<ConflictingDiagnosticRuleError> for Error<'a> {
@ -1043,6 +1051,53 @@ impl<'a> Error<'a> {
.into()],
}
}
Error::DiagnosticAttributeNotYetImplementedAtParseSite {
site_name_plural,
ref spans,
} => ParseError {
message: "`@diagnostic(…)` attribute(s) not yet implemented".into(),
labels: {
let mut spans = spans.iter().cloned();
let first = spans
.next()
.map(|span| {
(
span,
format!("can't use this on {site_name_plural} (yet)").into(),
)
})
.expect("internal error: diag. attr. rejection on empty map");
std::iter::once(first)
.chain(spans.map(|span| (span, "".into())))
.collect()
},
notes: vec![format!(concat!(
"Let Naga maintainers know that you ran into this at ",
"<https://github.com/gfx-rs/wgpu/issues/5320>, ",
"so they can prioritize it!"
))],
},
Error::DiagnosticAttributeNotSupported {
on_what_plural,
ref spans,
} => ParseError {
message: format!(
"`@diagnostic(…)` attribute(s) on {on_what_plural} are not supported",
),
labels: spans
.iter()
.cloned()
.map(|span| (span, "".into()))
.collect(),
notes: vec![
concat!(
"`@diagnostic(…)` attributes are only permitted on `fn`s, ",
"some statements, and `switch`/`loop` bodies."
)
.into(),
"These attributes are well-formed, you likely just need to move them.".into(),
],
},
}
}
}

View File

@ -1698,7 +1698,7 @@ impl Parser {
let _ = lexer.next();
self.pop_rule_span(lexer);
}
(Token::Paren('{'), _) => {
(Token::Paren('{') | Token::Attribute, _) => {
let (inner, span) = self.block(lexer, ctx, brace_nesting_level)?;
block.stmts.push(ast::Statement {
kind: ast::StatementKind::Block(inner),
@ -2324,10 +2324,29 @@ impl Parser {
types: &mut out.types,
unresolved: &mut dependencies,
};
let mut diagnostic_filters = DiagnosticFilterMap::new();
let ensure_no_diag_attrs =
|on_what_plural, filters: DiagnosticFilterMap| -> Result<(), Error> {
if filters.is_empty() {
Ok(())
} else {
Err(Error::DiagnosticAttributeNotSupported {
on_what_plural,
spans: filters.spans().collect(),
})
}
};
self.push_rule_span(Rule::Attribute, lexer);
while lexer.skip(Token::Attribute) {
let (name, name_span) = lexer.next_ident_with_span()?;
if let Some(DirectiveKind::Diagnostic) = DirectiveKind::from_ident(name) {
if let Some(filter) = self.diagnostic_filter(lexer)? {
let span = self.peek_rule_span(lexer);
diagnostic_filters.add(filter, span)?;
}
continue;
}
match name {
"binding" => {
lexer.expect(Token::Paren('('))?;
@ -2403,17 +2422,24 @@ impl Parser {
// read item
let start = lexer.start_byte_offset();
let kind = match lexer.next() {
(Token::Separator(';'), _) => None,
(Token::Separator(';'), _) => {
ensure_no_diag_attrs("semicolons", diagnostic_filters)?;
None
}
(Token::Word(word), directive_span) if DirectiveKind::from_ident(word).is_some() => {
return Err(Error::DirectiveAfterFirstGlobalDecl { directive_span });
}
(Token::Word("struct"), _) => {
ensure_no_diag_attrs("`struct`s", diagnostic_filters)?;
let name = lexer.next_ident()?;
let members = self.struct_body(lexer, &mut ctx)?;
Some(ast::GlobalDeclKind::Struct(ast::Struct { name, members }))
}
(Token::Word("alias"), _) => {
ensure_no_diag_attrs("`alias`es", diagnostic_filters)?;
let name = lexer.next_ident()?;
lexer.expect(Token::Operation('='))?;
@ -2422,6 +2448,8 @@ impl Parser {
Some(ast::GlobalDeclKind::Type(ast::TypeAlias { name, ty }))
}
(Token::Word("const"), _) => {
ensure_no_diag_attrs("`const`s", diagnostic_filters)?;
let name = lexer.next_ident()?;
let ty = if lexer.skip(Token::Separator(':')) {
@ -2438,6 +2466,8 @@ impl Parser {
Some(ast::GlobalDeclKind::Const(ast::Const { name, ty, init }))
}
(Token::Word("override"), _) => {
ensure_no_diag_attrs("`override`s", diagnostic_filters)?;
let name = lexer.next_ident()?;
let ty = if lexer.skip(Token::Separator(':')) {
@ -2462,11 +2492,19 @@ impl Parser {
}))
}
(Token::Word("var"), _) => {
ensure_no_diag_attrs("`var`s", diagnostic_filters)?;
let mut var = self.variable_decl(lexer, &mut ctx)?;
var.binding = binding.take();
Some(ast::GlobalDeclKind::Var(var))
}
(Token::Word("fn"), _) => {
if !diagnostic_filters.is_empty() {
return Err(Error::DiagnosticAttributeNotYetImplementedAtParseSite {
site_name_plural: "functions",
spans: diagnostic_filters.spans().collect(),
});
}
let function = self.function_decl(lexer, out, &mut dependencies)?;
Some(ast::GlobalDeclKind::Fn(ast::Function {
entry_point: if let Some(stage) = stage.value {
@ -2485,6 +2523,8 @@ impl Parser {
}))
}
(Token::Word("const_assert"), _) => {
ensure_no_diag_attrs("`const_assert`s", diagnostic_filters)?;
// parentheses are optional
let paren = lexer.skip(Token::Paren('('));