[wgsl] Parse RW storage texture language feature

Bug: tint:2088
Change-Id: I4ee9bde261bad9c3776f79e4b3dcb91472de6d99
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/158631
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/wgsl/reader/parser/parser.cc b/src/tint/lang/wgsl/reader/parser/parser.cc
index 40d1484..54aeb27 100644
--- a/src/tint/lang/wgsl/reader/parser/parser.cc
+++ b/src/tint/lang/wgsl/reader/parser/parser.cc
@@ -456,6 +456,7 @@
 //  : require identifier (COMMA identifier)* COMMA? SEMICOLON
 Maybe<Void> Parser::requires_directive() {
     return sync(Token::Type::kSemicolon, [&]() -> Maybe<Void> {
+        MultiTokenSource decl_source(this);
         if (!match(Token::Type::kRequires)) {
             return Failure::kNoMatch;
         }
@@ -473,34 +474,43 @@
             return add_error(t.source(), "requires directives don't take parenthesis");
         }
 
+        wgsl::LanguageFeatures features;
         while (continue_parsing()) {
-            auto& t2 = peek();
-
-            // Match the require name.
+            auto& t2 = next();
             if (handle_error(t2)) {
                 // The token might itself be an error.
                 return Failure::kErrored;
             }
 
+            // Match the require name.
             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)) {
+                auto feature = wgsl::ParseLanguageFeature(t2.to_str_view());
+                if (feature == LanguageFeature::kUndefined) {
+                    // Any identifier 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");
+                }
+                features.Add(feature);
+            } else {
                 return add_error(t2.source(), "invalid feature name for requires");
             }
+
+            if (!match(Token::Type::kComma)) {
+                break;
+            }
+            if (peek_is(Token::Type::kSemicolon)) {
+                break;
+            }
         }
-        // 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 (!expect("requires directive", Token::Type::kSemicolon)) {
+            return Failure::kErrored;
+        }
+
+        builder_.AST().AddRequires(
+            create<ast::Requires>(decl_source.Source(), std::move(features)));
+        return kSuccess;
     });
 }
 
diff --git a/src/tint/lang/wgsl/reader/parser/require_directive_test.cc b/src/tint/lang/wgsl/reader/parser/require_directive_test.cc
index 43ea6be..8e7ff03 100644
--- a/src/tint/lang/wgsl/reader/parser/require_directive_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/require_directive_test.cc
@@ -33,11 +33,64 @@
 using RequiresDirectiveTest = WGSLParserTest;
 
 // Test a valid require directive.
-// There currently are no valid require directives
-TEST_F(RequiresDirectiveTest, DISABLED_Valid) {
-    auto p = parser("requires <sometime>;");
+TEST_F(RequiresDirectiveTest, Single) {
+    auto p = parser("requires readonly_and_readwrite_storage_textures;");
     p->requires_directive();
     EXPECT_FALSE(p->has_error()) << p->error();
+
+    auto program = p->program();
+    auto& ast = program.AST();
+    ASSERT_EQ(ast.Requires().Length(), 1u);
+    auto* req = ast.Requires()[0];
+    EXPECT_EQ(req->source.range.begin.line, 1u);
+    EXPECT_EQ(req->source.range.begin.column, 1u);
+    EXPECT_EQ(req->source.range.end.line, 1u);
+    EXPECT_EQ(req->source.range.end.column, 50u);
+    ASSERT_EQ(req->features.Length(), 1u);
+    EXPECT_EQ(req->features[0], wgsl::LanguageFeature::kReadonlyAndReadwriteStorageTextures);
+    ASSERT_EQ(ast.GlobalDeclarations().Length(), 1u);
+    EXPECT_EQ(ast.GlobalDeclarations()[0], req);
+}
+
+// Test a valid require directive with a trailing comma.
+TEST_F(RequiresDirectiveTest, Single_TrailingComma) {
+    auto p = parser("requires readonly_and_readwrite_storage_textures,;");
+    p->requires_directive();
+    EXPECT_FALSE(p->has_error()) << p->error();
+
+    auto program = p->program();
+    auto& ast = program.AST();
+    ASSERT_EQ(ast.Requires().Length(), 1u);
+    auto* req = ast.Requires()[0];
+    EXPECT_EQ(req->source.range.begin.line, 1u);
+    EXPECT_EQ(req->source.range.begin.column, 1u);
+    EXPECT_EQ(req->source.range.end.line, 1u);
+    EXPECT_EQ(req->source.range.end.column, 51u);
+    ASSERT_EQ(req->features.Length(), 1u);
+    EXPECT_EQ(req->features[0], wgsl::LanguageFeature::kReadonlyAndReadwriteStorageTextures);
+    ASSERT_EQ(ast.GlobalDeclarations().Length(), 1u);
+    EXPECT_EQ(ast.GlobalDeclarations()[0], req);
+}
+
+TEST_F(RequiresDirectiveTest, Multiple_Repeated) {
+    auto p = parser(
+        "requires readonly_and_readwrite_storage_textures, "
+        "readonly_and_readwrite_storage_textures;");
+    p->requires_directive();
+    EXPECT_FALSE(p->has_error()) << p->error();
+
+    auto program = p->program();
+    auto& ast = program.AST();
+    ASSERT_EQ(ast.Requires().Length(), 1u);
+    auto* req = ast.Requires()[0];
+    EXPECT_EQ(req->source.range.begin.line, 1u);
+    EXPECT_EQ(req->source.range.begin.column, 1u);
+    EXPECT_EQ(req->source.range.end.line, 1u);
+    EXPECT_EQ(req->source.range.end.column, 91u);
+    ASSERT_EQ(req->features.Length(), 1u);
+    EXPECT_EQ(req->features[0], wgsl::LanguageFeature::kReadonlyAndReadwriteStorageTextures);
+    ASSERT_EQ(ast.GlobalDeclarations().Length(), 1u);
+    EXPECT_EQ(ast.GlobalDeclarations()[0], req);
 }
 
 // Test an unknown require identifier.
@@ -76,7 +129,7 @@
         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)");
+        EXPECT_EQ(p->error(), R"(1:9: invalid feature name for requires)");
     }
 }