From e4ad51d4527fd19a48a8dda5a0f365bdc9577cf9 Mon Sep 17 00:00:00 2001 From: Erich Gubler Date: Mon, 4 Nov 2024 17:02:21 -0500 Subject: [PATCH] =?UTF-8?q?feat(wgsl-in):=20add=20`@diagnostic(=E2=80=A6)`?= =?UTF-8?q?=20checks=20on=20global=20decls.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- naga/src/diagnostic_filter.rs | 12 ++++++++ naga/src/front/wgsl/error.rs | 52 ++++++++++++++++++++++++++++++++ naga/src/front/wgsl/parse/mod.rs | 38 ++++++++++++++++++++++- 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/naga/src/diagnostic_filter.rs b/naga/src/diagnostic_filter.rs index ca2c96fa5c7..6f950407ec4 100644 --- a/naga/src/diagnostic_filter.rs +++ b/naga/src/diagnostic_filter.rs @@ -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 + '_ { + let &Self(ref map) = self; + map.iter().map(|(_, &(_, span))| span) + } } #[cfg(feature = "wgsl-in")] diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index 5b8303ac94f..91819264515 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -296,6 +296,14 @@ pub(crate) enum Error<'a> { severity_control_name_span: Span, }, DiagnosticDuplicateTriggeringRule(ConflictingDiagnosticRuleError), + DiagnosticAttributeNotYetImplementedAtParseSite { + site_name_plural: &'static str, + spans: Vec, + }, + DiagnosticAttributeNotSupported { + on_what_plural: &'static str, + spans: Vec, + }, } impl<'a> From for Error<'a> { @@ -1043,6 +1051,50 @@ 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 for {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 ", + ", ", + "so they can prioritize it!" + ))], + }, + Error::DiagnosticAttributeNotSupported { + on_what_plural, + ref spans, + } => ParseError { + message: format!( + "`@diagnostic(…)` attribute(s) {on_what_plural}, which are not supported" + ), + labels: spans + .iter() + .cloned() + .map(|span| (span, "".into())) + .collect(), + notes: vec![ + "`@diagnostic(…)` attributes are only permitted on `fn`s and statements." + .into(), + "These attributes are well-formed, you likely just need to move them.".into(), + ], + }, } } } diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index 72f32f19aff..7d12c541910 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -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,28 @@ 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)?; + } + } match name { "binding" => { lexer.expect(Token::Paren('('))?; @@ -2408,12 +2426,16 @@ impl Parser { 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 +2444,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 +2462,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 +2488,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 +2519,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('('));