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>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index abedec5..4bcfdfd 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1716,6 +1716,7 @@
       "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",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index dfd9624..fff86d0 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -1099,6 +1099,7 @@
       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
diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc
index 74c1dba..5171e4c 100644
--- a/src/tint/reader/wgsl/lexer.cc
+++ b/src/tint/reader/wgsl/lexer.cc
@@ -1181,6 +1181,9 @@
     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"};
     }
diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc
index 5a3fd6d..95b43a7 100644
--- a/src/tint/reader/wgsl/lexer_test.cc
+++ b/src/tint/reader/wgsl/lexer_test.cc
@@ -1078,6 +1078,7 @@
                                          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},
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 2d74b20..703c101 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -94,9 +94,9 @@
            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 @@
 
 // global_directive
 //  : diagnostic_directive
+//  | requires_directive
 //  | enable_directive
 Maybe<Void> ParserImpl::global_directive(bool have_parsed_decl) {
     auto& p = peek();
@@ -348,6 +349,9 @@
     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 @@
     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
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index 34395ed..e26ffbd 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -381,6 +381,9 @@
     /// 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();
diff --git a/src/tint/reader/wgsl/parser_impl_require_directive_test.cc b/src/tint/reader/wgsl/parser_impl_require_directive_test.cc
new file mode 100644
index 0000000..d3cf17c
--- /dev/null
+++ b/src/tint/reader/wgsl/parser_impl_require_directive_test.cc
@@ -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
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 bf91ece..647bb44 100644
--- a/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc
@@ -193,7 +193,6 @@
                                          "regardless",
                                          "register",
                                          "reinterpret_cast",
-                                         "requires",
                                          "resource",
                                          "restrict",
                                          "self",
diff --git a/src/tint/reader/wgsl/token.cc b/src/tint/reader/wgsl/token.cc
index 822f834..e133dfc 100644
--- a/src/tint/reader/wgsl/token.cc
+++ b/src/tint/reader/wgsl/token.cc
@@ -181,6 +181,8 @@
             return "override";
         case Token::Type::kReturn:
             return "return";
+        case Token::Type::kRequires:
+            return "requires";
         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 2e9998d..222f28e 100644
--- a/src/tint/reader/wgsl/token.h
+++ b/src/tint/reader/wgsl/token.h
@@ -193,6 +193,8 @@
         kOverride,
         /// A 'return'
         kReturn,
+        /// A 'requires'
+        kRequires,
         /// A 'struct'
         kStruct,
         /// A 'switch'