tint/reader: Parse static_assert
Bug: tint:1625
Change-Id: I440de9a5f0f6817520d59d0d918acc6ac0555c42
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/97963
Commit-Queue: Ben Clayton <bclayton@chromium.org>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc
index 2b23a0c..0bc19ad 100644
--- a/src/tint/reader/wgsl/lexer.cc
+++ b/src/tint/reader/wgsl/lexer.cc
@@ -1243,6 +1243,9 @@
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 f1818ab..6cac798 100644
--- a/src/tint/reader/wgsl/lexer_test.cc
+++ b/src/tint/reader/wgsl/lexer_test.cc
@@ -1145,6 +1145,7 @@
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 57cfd11..459a21f 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -111,13 +111,13 @@
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 @@
// 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<bool> ParserImpl::global_decl() {
if (match(Token::Type::kSemicolon) || match(Token::Type::kEOF)) {
return true;
@@ -476,7 +477,6 @@
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 @@
if (ta.errored) {
return Failure::kErrored;
}
-
if (ta.matched) {
if (!expect("type alias", Token::Type::kSemicolon)) {
return Failure::kErrored;
@@ -508,12 +507,23 @@
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 @@
decl->type, std::move(attrs.value));
}
+Maybe<const ast::StaticAssert*> 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<ast::StaticAssert>(source, condition.value);
+}
+
// function_decl
// : function_header body_stmt
Maybe<const ast::Function*> ParserImpl::function_decl(AttributeList& attrs) {
@@ -1613,12 +1641,13 @@
// | assignment_stmt SEMICOLON
// | increment_stmt SEMICOLON
// | decrement_stmt SEMICOLON
+// | static_assert_stmt SEMICOLON
Maybe<const ast::Statement*> 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 @@
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<ast::DiscardStatement>(source);
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index 6dfc13f..63e1499 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -472,6 +472,9 @@
/// @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<ast::TexelFormat> expect_texel_format(std::string_view use);
+ /// Parses a `static_assert_statement` grammar element
+ /// @returns returns the static assert, if it matched.
+ Maybe<const ast::StaticAssert*> static_assert_stmt();
/// Parses a `function_header` grammar element
/// @returns the parsed function header
Maybe<FunctionHeader> 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 81613b4..85e888d 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 @@
)");
}
+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 @@
)");
}
+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 020e515..d9001c5 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_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<ast::BoolLiteralExpression>());
+ 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<ast::BoolLiteralExpression>());
+
+ 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<ast::BoolLiteralExpression>());
+ 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 07a88cd..0ad3cc1 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 @@
"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 677daa0..7bb51d3 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 @@
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<ast::StaticAssert>(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<ast::BoolLiteralExpression>());
+ 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<ast::StaticAssert>(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<ast::BoolLiteralExpression>());
+ 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 777492c..ac5eb08 100644
--- a/src/tint/reader/wgsl/token.cc
+++ b/src/tint/reader/wgsl/token.cc
@@ -209,6 +209,8 @@
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 4f8a9d6..68cb6c6 100644
--- a/src/tint/reader/wgsl/token.h
+++ b/src/tint/reader/wgsl/token.h
@@ -219,6 +219,8 @@
kSampler,
/// A 'sampler_comparison'
kComparisonSampler,
+ /// A 'static_assert'
+ kStaticAssert,
/// A 'struct'
kStruct,
/// A 'switch'