diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc index 2b23a0c706..0bc19ad132 100644 --- a/src/tint/reader/wgsl/lexer.cc +++ b/src/tint/reader/wgsl/lexer.cc @@ -1243,6 +1243,9 @@ Token Lexer::check_keyword(const Source& source, std::string_view str) { if (str == "sampler_comparison") { return {Token::Type::kComparisonSampler, source, "sampler_comparison"}; } + if (str == "static_assert") { + return {Token::Type::kStaticAssert, source, "static_assert"}; + } if (str == "struct") { return {Token::Type::kStruct, source, "struct"}; } diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc index f1818abe78..6cac798edd 100644 --- a/src/tint/reader/wgsl/lexer_test.cc +++ b/src/tint/reader/wgsl/lexer_test.cc @@ -1145,6 +1145,7 @@ INSTANTIATE_TEST_SUITE_P( TokenData{"return", Token::Type::kReturn}, TokenData{"sampler", Token::Type::kSampler}, TokenData{"sampler_comparison", Token::Type::kComparisonSampler}, + TokenData{"static_assert", Token::Type::kStaticAssert}, TokenData{"struct", Token::Type::kStruct}, TokenData{"switch", Token::Type::kSwitch}, TokenData{"texture_1d", Token::Type::kTextureSampled1d}, diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc index 57cfd11bad..459a21f567 100644 --- a/src/tint/reader/wgsl/parser_impl.cc +++ b/src/tint/reader/wgsl/parser_impl.cc @@ -111,13 +111,13 @@ bool is_reserved(const Token& t) { t == "readonly" || t == "ref" || t == "regardless" || t == "register" || t == "reinterpret_cast" || t == "requires" || t == "resource" || t == "restrict" || t == "self" || t == "set" || t == "shared" || t == "signed" || t == "sizeof" || - t == "smooth" || t == "snorm" || t == "static" || t == "static_assert" || - t == "static_cast" || t == "std" || t == "subroutine" || t == "super" || t == "target" || - t == "template" || t == "this" || t == "thread_local" || t == "throw" || t == "trait" || - t == "try" || t == "typedef" || t == "typeid" || t == "typename" || t == "typeof" || - t == "union" || t == "unless" || t == "unorm" || t == "unsafe" || t == "unsized" || - t == "use" || t == "using" || t == "varying" || t == "virtual" || t == "volatile" || - t == "wgsl" || t == "where" || t == "with" || t == "writeonly" || t == "yield"; + t == "smooth" || t == "snorm" || t == "static" || t == "static_cast" || t == "std" || + t == "subroutine" || t == "super" || t == "target" || t == "template" || t == "this" || + t == "thread_local" || t == "throw" || t == "trait" || t == "try" || t == "typedef" || + t == "typeid" || t == "typename" || t == "typeof" || t == "union" || t == "unless" || + t == "unorm" || t == "unsafe" || t == "unsized" || t == "use" || t == "using" || + t == "varying" || t == "virtual" || t == "volatile" || t == "wgsl" || t == "where" || + t == "with" || t == "writeonly" || t == "yield"; } /// Enter-exit counters for block token types. @@ -438,11 +438,12 @@ Maybe ParserImpl::enable_directive() { // global_decl // : SEMICOLON -// | global_variable_decl SEMICLON +// | global_variable_decl SEMICOLON // | global_constant_decl SEMICOLON // | type_alias SEMICOLON // | struct_decl // | function_decl +// | static_assert_statement SEMICOLON Maybe ParserImpl::global_decl() { if (match(Token::Type::kSemicolon) || match(Token::Type::kEOF)) { return true; @@ -476,7 +477,6 @@ Maybe ParserImpl::global_decl() { if (gc.errored) { return Failure::kErrored; } - if (gc.matched) { // Avoid the cost of the string allocation for the common no-error case if (!peek().Is(Token::Type::kSemicolon)) { @@ -494,7 +494,6 @@ Maybe ParserImpl::global_decl() { if (ta.errored) { return Failure::kErrored; } - if (ta.matched) { if (!expect("type alias", Token::Type::kSemicolon)) { return Failure::kErrored; @@ -508,12 +507,23 @@ Maybe ParserImpl::global_decl() { if (str.errored) { return Failure::kErrored; } - if (str.matched) { builder_.AST().AddTypeDecl(str.value); return true; } + auto assertion = static_assert_stmt(); + if (assertion.errored) { + return Failure::kErrored; + } + if (assertion.matched) { + builder_.AST().AddStaticAssert(assertion.value); + if (!expect("static assertion declaration", Token::Type::kSemicolon)) { + return Failure::kErrored; + } + return true; + } + return Failure::kNoMatch; }); @@ -1365,6 +1375,24 @@ Expect ParserImpl::expect_struct_member() { decl->type, std::move(attrs.value)); } +Maybe ParserImpl::static_assert_stmt() { + Source start; + if (!match(Token::Type::kStaticAssert, &start)) { + return Failure::kNoMatch; + } + + auto condition = logical_or_expression(); + if (condition.errored) { + return Failure::kErrored; + } + if (!condition.matched) { + return add_error(peek(), "unable to parse condition expression"); + } + + Source source = make_source_range_from(start); + return create(source, condition.value); +} + // function_decl // : function_header body_stmt Maybe ParserImpl::function_decl(AttributeList& attrs) { @@ -1613,12 +1641,13 @@ Expect ParserImpl::expect_statements() { // | assignment_stmt SEMICOLON // | increment_stmt SEMICOLON // | decrement_stmt SEMICOLON +// | static_assert_stmt SEMICOLON Maybe ParserImpl::statement() { while (match(Token::Type::kSemicolon)) { // Skip empty statements } - // Non-block statments that error can resynchronize on semicolon. + // Non-block statements that error can resynchronize on semicolon. auto stmt = sync(Token::Type::kSemicolon, [&] { return non_block_statement(); }); if (stmt.errored) { @@ -1739,6 +1768,14 @@ Maybe ParserImpl::non_block_statement() { return assign.value; } + auto stmt_static_assert = static_assert_stmt(); + if (stmt_static_assert.errored) { + return Failure::kErrored; + } + if (stmt_static_assert.matched) { + return stmt_static_assert.value; + } + Source source; if (match(Token::Type::kDiscard, &source)) { return create(source); diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h index 6dfc13f401..63e1499b14 100644 --- a/src/tint/reader/wgsl/parser_impl.h +++ b/src/tint/reader/wgsl/parser_impl.h @@ -472,6 +472,9 @@ class ParserImpl { /// @param use a description of what was being parsed if an error was raised /// @returns returns the texel format or kNone if none matched. Expect expect_texel_format(std::string_view use); + /// Parses a `static_assert_statement` grammar element + /// @returns returns the static assert, if it matched. + Maybe static_assert_stmt(); /// Parses a `function_header` grammar element /// @returns the parsed function header Maybe function_header(); diff --git a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc index 81613b429f..85e888dffb 100644 --- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc +++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc @@ -306,6 +306,51 @@ fn f() { for (var i : i32 = 0; i < 8; i=i+1) { )"); } +TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingCondThenEOF) { + EXPECT("fn f() { static_assert }", R"(test.wgsl:1:24 error: unable to parse condition expression +fn f() { static_assert } + ^ +)"); +} + +TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingCondThenSemicolon) { + EXPECT("fn f() { static_assert; }", + R"(test.wgsl:1:23 error: unable to parse condition expression +fn f() { static_assert; } + ^ +)"); +} + +TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingCondThenLet) { + EXPECT("fn f() { static_assert\nlet x = 0; }", + R"(test.wgsl:2:1 error: unable to parse condition expression +let x = 0; } +^^^ +)"); +} + +TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingLParen) { + EXPECT("fn f() { static_assert true);", R"(test.wgsl:1:28 error: expected ';' for statement +fn f() { static_assert true); + ^ +)"); +} + +TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingRParen) { + EXPECT("fn f() { static_assert (true;", R"(test.wgsl:1:29 error: expected ')' +fn f() { static_assert (true; + ^ +)"); +} + +TEST_F(ParserImplErrorTest, FunctionDeclStaticAssertMissingSemicolon) { + EXPECT("fn f() { static_assert true }", + R"(test.wgsl:1:29 error: expected ';' for statement +fn f() { static_assert true } + ^ +)"); +} + // TODO(crbug.com/tint/1503): Remove this when @stage is removed TEST_F(ParserImplErrorTest, FunctionDeclStageMissingLParen) { EXPECT("@stage vertex) fn f() {}", @@ -697,6 +742,50 @@ var x : texture_multisampled_2d<1>; )"); } +TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingCondThenEOF) { + EXPECT("static_assert", R"(test.wgsl:1:14 error: unable to parse condition expression +static_assert + ^ +)"); +} + +TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingCondThenSemicolon) { + EXPECT("static_assert;", R"(test.wgsl:1:14 error: unable to parse condition expression +static_assert; + ^ +)"); +} + +TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingCondThenAlias) { + EXPECT("static_assert\ntype T = i32;", + R"(test.wgsl:2:1 error: unable to parse condition expression +type T = i32; +^^^^ +)"); +} + +TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingLParen) { + EXPECT("static_assert true);", R"(test.wgsl:1:19 error: expected ';' for static assertion declaration +static_assert true); + ^ +)"); +} + +TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingRParen) { + EXPECT("static_assert (true;", R"(test.wgsl:1:20 error: expected ')' +static_assert (true; + ^ +)"); +} + +TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingSemicolon) { + EXPECT("static_assert true static_assert true;", + R"(test.wgsl:1:20 error: expected ';' for static assertion declaration +static_assert true static_assert true; + ^^^^^^^^^^^^^ +)"); +} + TEST_F(ParserImplErrorTest, GlobalDeclStorageTextureMissingLessThan) { EXPECT("var x : texture_storage_2d;", R"(test.wgsl:1:27 error: expected '<' for storage texture type diff --git a/src/tint/reader/wgsl/parser_impl_global_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_decl_test.cc index 020e5153a0..d9001c5616 100644 --- a/src/tint/reader/wgsl/parser_impl_global_decl_test.cc +++ b/src/tint/reader/wgsl/parser_impl_global_decl_test.cc @@ -233,5 +233,47 @@ TEST_F(ParserImplTest, GlobalDecl_Struct_Invalid) { } } +TEST_F(ParserImplTest, GlobalDecl_StaticAssert_WithParen) { + auto p = parser("static_assert(true);"); + p->global_decl(); + ASSERT_FALSE(p->has_error()) << p->error(); + + auto program = p->program(); + ASSERT_EQ(program.AST().StaticAsserts().Length(), 1u); + auto* sa = program.AST().StaticAsserts()[0]; + EXPECT_EQ(sa->source.range.begin.line, 1u); + EXPECT_EQ(sa->source.range.begin.column, 1u); + EXPECT_EQ(sa->source.range.end.line, 1u); + EXPECT_EQ(sa->source.range.end.column, 20u); + + EXPECT_TRUE(sa->condition->Is()); + EXPECT_EQ(sa->condition->source.range.begin.line, 1u); + EXPECT_EQ(sa->condition->source.range.begin.column, 15u); + EXPECT_EQ(sa->condition->source.range.end.line, 1u); + EXPECT_EQ(sa->condition->source.range.end.column, 19u); +} + +TEST_F(ParserImplTest, GlobalDecl_StaticAssert_WithoutParen) { + auto p = parser("static_assert true;"); + p->global_decl(); + ASSERT_FALSE(p->has_error()) << p->error(); + + auto program = p->program(); + ASSERT_EQ(program.AST().StaticAsserts().Length(), 1u); + auto* sa = program.AST().StaticAsserts()[0]; + EXPECT_TRUE(sa->condition->Is()); + + EXPECT_EQ(sa->source.range.begin.line, 1u); + EXPECT_EQ(sa->source.range.begin.column, 1u); + EXPECT_EQ(sa->source.range.end.line, 1u); + EXPECT_EQ(sa->source.range.end.column, 20u); + + EXPECT_TRUE(sa->condition->Is()); + EXPECT_EQ(sa->condition->source.range.begin.line, 1u); + EXPECT_EQ(sa->condition->source.range.begin.column, 16u); + EXPECT_EQ(sa->condition->source.range.end.line, 1u); + EXPECT_EQ(sa->condition->source.range.end.column, 20u); +} + } // namespace } // namespace tint::reader::wgsl diff --git a/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc index 07a88cd4da..0ad3cc1c5f 100644 --- a/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc +++ b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc @@ -215,7 +215,6 @@ INSTANTIATE_TEST_SUITE_P(ParserImplReservedKeywordTest, "smooth", "snorm", "static", - "static_assert", "static_cast", "std", "subroutine", diff --git a/src/tint/reader/wgsl/parser_impl_statement_test.cc b/src/tint/reader/wgsl/parser_impl_statement_test.cc index 677daa03a4..7bb51d3d37 100644 --- a/src/tint/reader/wgsl/parser_impl_statement_test.cc +++ b/src/tint/reader/wgsl/parser_impl_statement_test.cc @@ -272,5 +272,47 @@ TEST_F(ParserImplTest, Statement_Body_Invalid) { EXPECT_EQ(p->error(), "1:3: expected '}'"); } +TEST_F(ParserImplTest, Statement_StaticAssert_WithParen) { + auto p = parser("static_assert(true);"); + auto e = p->statement(); + ASSERT_FALSE(p->has_error()) << p->error(); + EXPECT_TRUE(e.matched); + EXPECT_FALSE(e.errored); + + auto* sa = As(e.value); + ASSERT_NE(sa, nullptr); + EXPECT_EQ(sa->source.range.begin.line, 1u); + EXPECT_EQ(sa->source.range.begin.column, 1u); + EXPECT_EQ(sa->source.range.end.line, 1u); + EXPECT_EQ(sa->source.range.end.column, 20u); + + EXPECT_TRUE(sa->condition->Is()); + EXPECT_EQ(sa->condition->source.range.begin.line, 1u); + EXPECT_EQ(sa->condition->source.range.begin.column, 15u); + EXPECT_EQ(sa->condition->source.range.end.line, 1u); + EXPECT_EQ(sa->condition->source.range.end.column, 19u); +} + +TEST_F(ParserImplTest, Statement_StaticAssert_WithoutParen) { + auto p = parser("static_assert true;"); + auto e = p->statement(); + ASSERT_FALSE(p->has_error()) << p->error(); + EXPECT_TRUE(e.matched); + EXPECT_FALSE(e.errored); + + auto* sa = As(e.value); + ASSERT_NE(sa, nullptr); + EXPECT_EQ(sa->source.range.begin.line, 1u); + EXPECT_EQ(sa->source.range.begin.column, 1u); + EXPECT_EQ(sa->source.range.end.line, 1u); + EXPECT_EQ(sa->source.range.end.column, 20u); + + EXPECT_TRUE(sa->condition->Is()); + EXPECT_EQ(sa->condition->source.range.begin.line, 1u); + EXPECT_EQ(sa->condition->source.range.begin.column, 16u); + EXPECT_EQ(sa->condition->source.range.end.line, 1u); + EXPECT_EQ(sa->condition->source.range.end.column, 20u); +} + } // namespace } // namespace tint::reader::wgsl diff --git a/src/tint/reader/wgsl/token.cc b/src/tint/reader/wgsl/token.cc index 777492ce09..ac5eb08922 100644 --- a/src/tint/reader/wgsl/token.cc +++ b/src/tint/reader/wgsl/token.cc @@ -209,6 +209,8 @@ std::string_view Token::TypeToName(Type type) { return "sampler"; case Token::Type::kComparisonSampler: return "sampler_comparison"; + case Token::Type::kStaticAssert: + return "static_assert"; case Token::Type::kStruct: return "struct"; case Token::Type::kSwitch: diff --git a/src/tint/reader/wgsl/token.h b/src/tint/reader/wgsl/token.h index 4f8a9d6abf..68cb6c6d4f 100644 --- a/src/tint/reader/wgsl/token.h +++ b/src/tint/reader/wgsl/token.h @@ -219,6 +219,8 @@ class Token { kSampler, /// A 'sampler_comparison' kComparisonSampler, + /// A 'static_assert' + kStaticAssert, /// A 'struct' kStruct, /// A 'switch'