diff --git a/src/front/wgsl/error.rs b/src/front/wgsl/error.rs index 701eca992b..d45cd5ba80 100644 --- a/src/front/wgsl/error.rs +++ b/src/front/wgsl/error.rs @@ -136,6 +136,7 @@ pub enum InvalidAssignmentType { } #[derive(Clone, Debug)] +#[cfg_attr(test, derive(PartialEq))] pub enum Error<'a> { Unexpected(Span, ExpectedToken<'a>), UnexpectedComponents(Span), @@ -168,6 +169,7 @@ pub enum Error<'a> { InvalidIdentifierUnderscore(Span), ReservedIdentifierPrefix(Span), UnknownAddressSpace(Span), + RepeatedAttribute(Span), UnknownAttribute(Span), UnknownBuiltin(Span), UnknownAccess(Span), @@ -430,6 +432,11 @@ impl<'a> Error<'a> { labels: vec![(bad_span, "unknown address space".into())], notes: vec![], }, + Error::RepeatedAttribute(bad_span) => ParseError { + message: format!("repeated attribute: '{}'", &source[bad_span]), + labels: vec![(bad_span, "repated attribute".into())], + notes: vec![], + }, Error::UnknownAttribute(bad_span) => ParseError { message: format!("unknown attribute: '{}'", &source[bad_span]), labels: vec![(bad_span, "unknown attribute".into())], diff --git a/src/front/wgsl/parse/mod.rs b/src/front/wgsl/parse/mod.rs index c13ee52775..fc7ab87012 100644 --- a/src/front/wgsl/parse/mod.rs +++ b/src/front/wgsl/parse/mod.rs @@ -136,21 +136,31 @@ impl BindingParser { name: &'a str, name_span: Span, ) -> Result<(), Error<'a>> { + let fail_if_repeated = |repeated| { + if repeated { + return Err(Error::RepeatedAttribute(name_span)); + } + Ok(()) + }; + match name { "location" => { lexer.expect(Token::Paren('('))?; + fail_if_repeated(self.location.is_some())?; self.location = Some(Parser::non_negative_i32_literal(lexer)?); lexer.expect(Token::Paren(')'))?; } "builtin" => { lexer.expect(Token::Paren('('))?; let (raw, span) = lexer.next_ident_with_span()?; + fail_if_repeated(self.built_in.is_some())?; self.built_in = Some(conv::map_built_in(raw, span)?); lexer.expect(Token::Paren(')'))?; } "interpolate" => { lexer.expect(Token::Paren('('))?; let (raw, span) = lexer.next_ident_with_span()?; + fail_if_repeated(self.interpolation.is_some())?; self.interpolation = Some(conv::map_interpolation(raw, span)?); if lexer.skip(Token::Separator(',')) { let (raw, span) = lexer.next_ident_with_span()?; @@ -158,7 +168,10 @@ impl BindingParser { } lexer.expect(Token::Paren(')'))?; } - "invariant" => self.invariant = true, + "invariant" => { + fail_if_repeated(self.invariant)?; + self.invariant = true; + } _ => return Err(Error::UnknownAttribute(name_span)), } Ok(()) diff --git a/src/front/wgsl/tests.rs b/src/front/wgsl/tests.rs index 3aec0acf2d..9bf08e2ca3 100644 --- a/src/front/wgsl/tests.rs +++ b/src/front/wgsl/tests.rs @@ -509,3 +509,30 @@ fn parse_texture_load_store_expecting_four_args() { ); } } + +#[test] +fn parse_repeated_attributes() { + use crate::{ + front::wgsl::{error::Error, Frontend}, + Span, + }; + + let template = "@vertex fn vs() -> __REPLACE__ vec4 { return vec4(0.0); }"; + for attribute in [ + "location(0)", + "builtin(position)", + "interpolate(flat)", + "invariant", + ] { + let shader = template.replace("__REPLACE__", &format!("@{attribute} @{attribute}")); + let name_length = attribute.rfind('(').unwrap_or(attribute.len()) as u32; + let span_start = shader.rfind(attribute).unwrap() as u32; + let span_end = span_start + name_length; + + let result = Frontend::new().inner(&shader); + assert_eq!( + result.unwrap_err(), + Error::RepeatedAttribute(Span::new(span_start, span_end)) + ); + } +}