Add `requires` directive

This Cl adds the requires directive into Tint. Using the directive is
currently always an error as there is no valid value which can be used.

Bug: tint:1843
Change-Id: Idf77ba4e95ff0c1e177d02d1ba9598edc89a9812
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/120740
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: David Neto <dneto@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
dan sinclair 2023-02-22 20:57:28 +00:00 committed by Dawn LUCI CQ
parent f35f1ff98c
commit 187e0d5fe7
10 changed files with 152 additions and 4 deletions

View File

@ -1716,6 +1716,7 @@ if (tint_build_unittests) {
"reader/wgsl/parser_impl_paren_expression_test.cc",
"reader/wgsl/parser_impl_primary_expression_test.cc",
"reader/wgsl/parser_impl_relational_expression_test.cc",
"reader/wgsl/parser_impl_require_directive_test.cc",
"reader/wgsl/parser_impl_reserved_keyword_test.cc",
"reader/wgsl/parser_impl_shift_expression_test.cc",
"reader/wgsl/parser_impl_singular_expression_test.cc",

View File

@ -1099,6 +1099,7 @@ if(TINT_BUILD_TESTS)
reader/wgsl/parser_impl_primary_expression_test.cc
reader/wgsl/parser_impl_relational_expression_test.cc
reader/wgsl/parser_impl_reserved_keyword_test.cc
reader/wgsl/parser_impl_require_directive_test.cc
reader/wgsl/parser_impl_shift_expression_test.cc
reader/wgsl/parser_impl_singular_expression_test.cc
reader/wgsl/parser_impl_statement_test.cc

View File

@ -1181,6 +1181,9 @@ Token Lexer::check_keyword(const Source& source, std::string_view str) {
if (str == "return") {
return {Token::Type::kReturn, source, "return"};
}
if (str == "requires") {
return {Token::Type::kRequires, source, "requires"};
}
if (str == "struct") {
return {Token::Type::kStruct, source, "struct"};
}

View File

@ -1078,6 +1078,7 @@ INSTANTIATE_TEST_SUITE_P(LexerTest,
TokenData{"loop", Token::Type::kLoop},
TokenData{"override", Token::Type::kOverride},
TokenData{"return", Token::Type::kReturn},
TokenData{"requires", Token::Type::kRequires},
TokenData{"struct", Token::Type::kStruct},
TokenData{"switch", Token::Type::kSwitch},
TokenData{"true", Token::Type::kTrue},

View File

@ -94,9 +94,9 @@ bool is_reserved(const Token& t) {
t == "partition" || t == "pass" || t == "patch" || t == "pixelfragment" ||
t == "precise" || t == "precision" || t == "premerge" || t == "priv" ||
t == "protected" || t == "pub" || t == "public" || 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 == "regardless" || t == "register" || t == "reinterpret_cast" || 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 == "type" || t == "typedef" ||
@ -341,6 +341,7 @@ void ParserImpl::translation_unit() {
// global_directive
// : diagnostic_directive
// | requires_directive
// | enable_directive
Maybe<Void> ParserImpl::global_directive(bool have_parsed_decl) {
auto& p = peek();
@ -348,6 +349,9 @@ Maybe<Void> ParserImpl::global_directive(bool have_parsed_decl) {
if (!result.errored && !result.matched) {
result = enable_directive();
}
if (!result.errored && !result.matched) {
result = requires_directive();
}
if (result.matched && have_parsed_decl) {
return add_error(p, "directives must come before all global declarations");
@ -428,6 +432,67 @@ Maybe<Void> ParserImpl::enable_directive() {
return Failure::kNoMatch;
}
// requires_directive
// : require identifier (COMMA identifier)? SEMICLON
Maybe<Void> ParserImpl::requires_directive() {
auto decl = sync(Token::Type::kSemicolon, [&]() -> Maybe<Void> {
if (!match(Token::Type::kRequires)) {
return Failure::kNoMatch;
}
// Match the require name.
auto& t = peek();
if (handle_error(t)) {
// The token might itself be an error.
return Failure::kErrored;
}
if (t.Is(Token::Type::kParenLeft)) {
// A common error case is writing `require(foo);` instead of `require foo;`.
synchronized_ = false;
return add_error(t.source(), "requires directives don't take parenthesis");
}
while (continue_parsing()) {
auto& t2 = peek();
// Match the require name.
if (handle_error(t2)) {
// The token might itself be an error.
return Failure::kErrored;
}
if (t2.IsIdentifier()) {
// TODO(dsinclair): When there are actual values for a requires directive they
// should be checked here.
// Any identifer is a valid feature name, so we correctly handle new feature
// names getting added in the future, they just all get flagged as not supported.
return add_error(t2.source(), "feature '" + t2.to_str() + "' is not supported");
}
if (t2.Is(Token::Type::kSemicolon)) {
break;
}
if (!match(Token::Type::kComma)) {
return add_error(t2.source(), "invalid feature name for requires");
}
}
// TODO(dsinclair): When there are actual values for a requires directive then the
// `while` will need to keep track if any were seen, and this needs to become
// conditional.
return add_error(t.source(), "missing feature names in requires directive");
});
if (decl.errored) {
return Failure::kErrored;
}
if (decl.matched) {
return kSuccess;
}
return Failure::kNoMatch;
}
// global_decl
// : SEMICOLON
// | global_variable_decl SEMICOLON

View File

@ -381,6 +381,9 @@ class ParserImpl {
/// Parses the `enable_directive` grammar element, erroring on parse failure.
/// @return true on parse success, otherwise an error or no-match.
Maybe<Void> enable_directive();
/// Parses the `requires_directive` grammar element, erroring on parse failure.
/// @return true on parse success, otherwise an error or no-match.
Maybe<Void> requires_directive();
/// Parses the `global_decl` grammar element, erroring on parse failure.
/// @return true on parse success, otherwise an error or no-match.
Maybe<Void> global_decl();

View File

@ -0,0 +1,71 @@
// Copyright 2023 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/reader/wgsl/parser_impl_test_helper.h"
namespace tint::reader::wgsl {
namespace {
using RequiresDirectiveTest = ParserImplTest;
// Test a valid require directive.
// There currently are no valid require directives
TEST_F(RequiresDirectiveTest, DISABLED_Valid) {
auto p = parser("requires <sometime>;");
p->requires_directive();
EXPECT_FALSE(p->has_error()) << p->error();
}
// Test an unknown require identifier.
TEST_F(RequiresDirectiveTest, InvalidIdentifier) {
auto p = parser("requires NotAValidRequireName;");
p->requires_directive();
// Error when unknown require found
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), R"(1:10: feature 'NotAValidRequireName' is not supported)");
}
// Test the special error message when require are used with parenthesis.
TEST_F(RequiresDirectiveTest, ParenthesisSpecialCase) {
auto p = parser("requires(Something);");
p->translation_unit();
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:9: requires directives don't take parenthesis");
}
// Test using invalid tokens in an require directive.
TEST_F(RequiresDirectiveTest, InvalidTokens) {
{
auto p = parser("requires <Something;");
p->translation_unit();
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), R"(1:10: invalid feature name for requires)");
}
{
auto p = parser("requires =;");
p->translation_unit();
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), R"(1:10: invalid feature name for requires)");
}
{
auto p = parser("requires;");
p->translation_unit();
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), R"(1:9: missing feature names in requires directive)");
}
}
} // namespace
} // namespace tint::reader::wgsl

View File

@ -193,7 +193,6 @@ INSTANTIATE_TEST_SUITE_P(ParserImplReservedKeywordTest,
"regardless",
"register",
"reinterpret_cast",
"requires",
"resource",
"restrict",
"self",

View File

@ -181,6 +181,8 @@ std::string_view Token::TypeToName(Type type) {
return "override";
case Token::Type::kReturn:
return "return";
case Token::Type::kRequires:
return "requires";
case Token::Type::kStruct:
return "struct";
case Token::Type::kSwitch:

View File

@ -193,6 +193,8 @@ class Token {
kOverride,
/// A 'return'
kReturn,
/// A 'requires'
kRequires,
/// A 'struct'
kStruct,
/// A 'switch'