[wgsl-reader] Add support for read only storage buffers.

This Cl adds the necessary infrastructure to parse the access decoration
for storage buffers.

This CL incorporates changes from bclayton@ from
https://dawn-review.googlesource.com/c/tint/+/33202 and
https://dawn-review.googlesource.com/c/tint/+/33201

Bug: tint:287
Change-Id: I7479f2cf7ab794b24c682b9927c4c68f6d325839
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/33161
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 0610dfc..758fa7d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -219,6 +219,8 @@
   sources = [
     "src/ast/access_control.cc",
     "src/ast/access_control.h",
+    "src/ast/access_decoration.cc",
+    "src/ast/access_decoration.h",
     "src/ast/array_accessor_expression.cc",
     "src/ast/array_accessor_expression.h",
     "src/ast/array_decoration.cc",
@@ -367,6 +369,8 @@
     "src/ast/type/void_type.h",
     "src/ast/type_constructor_expression.cc",
     "src/ast/type_constructor_expression.h",
+    "src/ast/type_decoration.cc",
+    "src/ast/type_decoration.h",
     "src/ast/uint_literal.cc",
     "src/ast/uint_literal.h",
     "src/ast/unary_op.cc",
@@ -755,9 +759,9 @@
     "src/ast/function_test.cc",
     "src/ast/identifier_expression_test.cc",
     "src/ast/if_statement_test.cc",
+    "src/ast/int_literal_test.cc",
     "src/ast/intrinsic_texture_helper_test.cc",
     "src/ast/intrinsic_texture_helper_test.h",
-    "src/ast/int_literal_test.cc",
     "src/ast/location_decoration_test.cc",
     "src/ast/loop_statement_test.cc",
     "src/ast/member_accessor_expression_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8ab0eaa..47390ee 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -40,6 +40,8 @@
   ../include/tint/tint.h
   ast/access_control.cc
   ast/access_control.h
+  ast/access_decoration.cc
+  ast/access_decoration.h
   ast/array_accessor_expression.cc
   ast/array_accessor_expression.h
   ast/array_decoration.cc
@@ -150,6 +152,8 @@
   ast/switch_statement.h
   ast/type_constructor_expression.h
   ast/type_constructor_expression.cc
+  ast/type_decoration.cc
+  ast/type_decoration.h
   ast/type/access_control_type.cc
   ast/type/access_control_type.h
   ast/type/alias_type.cc
diff --git a/src/ast/access_decoration.cc b/src/ast/access_decoration.cc
new file mode 100644
index 0000000..ae0fdef
--- /dev/null
+++ b/src/ast/access_decoration.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 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/ast/access_decoration.h"
+
+namespace tint {
+namespace ast {
+
+constexpr const DecorationKind AccessDecoration::Kind;
+
+AccessDecoration::AccessDecoration(AccessControl val, const Source& source)
+    : TypeDecoration(Kind, source), value_(val) {}
+
+AccessDecoration::~AccessDecoration() = default;
+
+bool AccessDecoration::IsKind(DecorationKind kind) const {
+  return kind == Kind || TypeDecoration::IsKind(kind);
+}
+
+bool AccessDecoration::IsAccess() const {
+  return true;
+}
+
+void AccessDecoration::to_str(std::ostream& out, size_t indent) const {
+  make_indent(out, indent);
+  out << "AccessDecoration{" << value_ << "}" << std::endl;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/access_decoration.h b/src/ast/access_decoration.h
new file mode 100644
index 0000000..69a7244
--- /dev/null
+++ b/src/ast/access_decoration.h
@@ -0,0 +1,61 @@
+// Copyright 2020 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.
+
+#ifndef SRC_AST_ACCESS_DECORATION_H_
+#define SRC_AST_ACCESS_DECORATION_H_
+
+#include <stddef.h>
+
+#include "src/ast/access_control.h"
+#include "src/ast/type_decoration.h"
+
+namespace tint {
+namespace ast {
+
+/// An access decoration
+class AccessDecoration : public TypeDecoration {
+ public:
+  /// The kind of decoration that this type represents
+  static constexpr const DecorationKind Kind = DecorationKind::kAccess;
+
+  /// constructor
+  /// @param value the access value
+  /// @param source the source of this decoration
+  explicit AccessDecoration(AccessControl value, const Source& source);
+  ~AccessDecoration() override;
+
+  /// @param kind the decoration kind
+  /// @return true if this Decoration is of the (or derives from) the given
+  /// kind.
+  bool IsKind(DecorationKind kind) const override;
+
+  /// @returns true if this is an access decoration
+  bool IsAccess() const override;
+
+  /// @returns the access control value
+  AccessControl value() const { return value_; }
+
+  /// Outputs the decoration to the given stream
+  /// @param out the stream to write to
+  /// @param indent number of spaces to indent the node when writing
+  void to_str(std::ostream& out, size_t indent) const override;
+
+ private:
+  AccessControl value_ = AccessControl::kReadWrite;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_ACCESS_DECORATION_H_
diff --git a/src/ast/access_decoration_test.cc b/src/ast/access_decoration_test.cc
new file mode 100644
index 0000000..4a15fa4
--- /dev/null
+++ b/src/ast/access_decoration_test.cc
@@ -0,0 +1,47 @@
+// Copyright 2020 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/ast/access_decoration.h"
+
+#include <sstream>
+
+#include "src/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using AccessDecorationTest = TestHelper;
+
+TEST_F(AccessDecorationTest, Creation) {
+  AccessDecoration d{AccessControl::kWriteOnly, Source{}};
+  EXPECT_EQ(AccessControl::kWriteOnly, d.value());
+}
+
+TEST_F(AccessDecorationTest, Is) {
+  AccessDecoration d{AccessControl::kReadWrite, Source{}};
+  EXPECT_FALSE(d.IsAccess());
+}
+
+TEST_F(AccessDecorationTest, ToStr) {
+  AccessDecoration d{AccessControl::kReadOnly, Source{}};
+  std::ostringstream out;
+  d.to_str(out, 0);
+  EXPECT_EQ(out.str(), R"(AccessDecoration{read}
+)");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/decoration.cc b/src/ast/decoration.cc
index 2ce8037..5c8af19 100644
--- a/src/ast/decoration.cc
+++ b/src/ast/decoration.cc
@@ -37,6 +37,10 @@
       return out << "struct member";
     case DecorationKind::kStructMemberOffset:
       return out << "offset";
+    case DecorationKind::kType:
+      return out << "type";
+    case DecorationKind::kAccess:
+      return out << "access";
     case DecorationKind::kVariable:
       return out << "variable";
     case DecorationKind::kBinding:
diff --git a/src/ast/decoration.h b/src/ast/decoration.h
index a07c0bf..61c4cff 100644
--- a/src/ast/decoration.h
+++ b/src/ast/decoration.h
@@ -35,6 +35,8 @@
   kStruct,
   kStructMember,
   /*|*/ kStructMemberOffset,
+  kType,
+  /*|*/ kAccess,
   kVariable,
   /*|*/ kBinding,
   /*|*/ kBuiltin,
diff --git a/src/ast/decoration_test.cc b/src/ast/decoration_test.cc
index 3548654..a9beaac 100644
--- a/src/ast/decoration_test.cc
+++ b/src/ast/decoration_test.cc
@@ -19,6 +19,7 @@
 #include <unordered_set>
 #include <utility>
 
+#include "src/ast/access_decoration.h"
 #include "src/ast/array_decoration.h"
 #include "src/ast/binding_decoration.h"
 #include "src/ast/builtin_decoration.h"
@@ -31,6 +32,7 @@
 #include "src/ast/struct_member_decoration.h"
 #include "src/ast/struct_member_offset_decoration.h"
 #include "src/ast/test_helper.h"
+#include "src/ast/type_decoration.h"
 #include "src/ast/variable_decoration.h"
 #include "src/ast/workgroup_decoration.h"
 
@@ -70,6 +72,8 @@
   EXPECT_EQ(StructMemberDecoration::Kind, DecorationKind::kStructMember);
   EXPECT_EQ(StructMemberOffsetDecoration::Kind,
             DecorationKind::kStructMemberOffset);
+  EXPECT_EQ(TypeDecoration::Kind, DecorationKind::kType);
+  EXPECT_EQ(AccessDecoration::Kind, DecorationKind::kAccess);
   EXPECT_EQ(VariableDecoration::Kind, DecorationKind::kVariable);
   EXPECT_EQ(BindingDecoration::Kind, DecorationKind::kBinding);
   EXPECT_EQ(BuiltinDecoration::Kind, DecorationKind::kBuiltin);
@@ -83,6 +87,7 @@
       DecorationKind::kFunction,     DecorationKind::kStage,
       DecorationKind::kWorkgroup,    DecorationKind::kStruct,
       DecorationKind::kStructMember, DecorationKind::kStructMemberOffset,
+      DecorationKind::kType,         DecorationKind::kAccess,
       DecorationKind::kVariable,     DecorationKind::kBinding,
       DecorationKind::kBuiltin,      DecorationKind::kConstantId,
       DecorationKind::kLocation,
@@ -101,6 +106,8 @@
   //  kStruct
   //  kStructMember
   //   | kStructMemberOffset
+  //  kType
+  //   | kAccess
   //  kVariable
   //   | kBinding
   //   | kBuiltin
@@ -130,6 +137,10 @@
                DecorationKind::kStructMemberOffset},
           },
           {
+              DecorationKind::kAccess,
+              {DecorationKind::kType, DecorationKind::kAccess},
+          },
+          {
               DecorationKind::kBinding,
               {DecorationKind::kVariable, DecorationKind::kBinding},
           },
@@ -159,6 +170,7 @@
   StageDecoration stage(PipelineStage::kNone, {});
   WorkgroupDecoration workgroup(0, {});
   StructMemberOffsetDecoration struct_member_offset(0, {});
+  AccessDecoration access(AccessControl::kReadOnly, {});
   BindingDecoration binding(0, {});
   BuiltinDecoration builtin(Builtin::kNone, {});
   ConstantIdDecoration constant_id(0, {});
@@ -168,6 +180,7 @@
   check(&stage);
   check(&workgroup);
   check(&struct_member_offset);
+  check(&access);
   check(&binding);
   check(&builtin);
   check(&constant_id);
diff --git a/src/ast/type_decoration.cc b/src/ast/type_decoration.cc
new file mode 100644
index 0000000..3f13343
--- /dev/null
+++ b/src/ast/type_decoration.cc
@@ -0,0 +1,45 @@
+// Copyright 2020 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/ast/type_decoration.h"
+
+#include <assert.h>
+
+#include "src/ast/access_decoration.h"
+
+namespace tint {
+namespace ast {
+
+constexpr const DecorationKind TypeDecoration::Kind;
+
+TypeDecoration::TypeDecoration(DecorationKind kind, const Source& source)
+    : Decoration(kind, source) {}
+
+TypeDecoration::~TypeDecoration() = default;
+
+bool TypeDecoration::IsKind(DecorationKind kind) const {
+  return kind == Kind;
+}
+
+bool TypeDecoration::IsAccess() const {
+  return false;
+}
+
+AccessDecoration* TypeDecoration::AsAccess() {
+  assert(IsAccess());
+  return static_cast<AccessDecoration*>(this);
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/type_decoration.h b/src/ast/type_decoration.h
new file mode 100644
index 0000000..f457e39
--- /dev/null
+++ b/src/ast/type_decoration.h
@@ -0,0 +1,62 @@
+// Copyright 2020 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.
+
+#ifndef SRC_AST_TYPE_DECORATION_H_
+#define SRC_AST_TYPE_DECORATION_H_
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "src/ast/decoration.h"
+
+namespace tint {
+namespace ast {
+
+class AccessDecoration;
+
+/// A decoration attached to a type
+class TypeDecoration : public Decoration {
+ public:
+  /// The kind of decoration that this type represents
+  static constexpr const DecorationKind Kind = DecorationKind::kType;
+
+  ~TypeDecoration() override;
+
+  /// @param kind the decoration kind
+  /// @return true if this Decoration is of the (or derives from) the given
+  /// kind.
+  bool IsKind(DecorationKind kind) const override;
+
+  /// @returns true if this is an access decoration
+  virtual bool IsAccess() const;
+
+  /// @returns the decoration as an access decoration
+  AccessDecoration* AsAccess();
+
+ protected:
+  /// Constructor
+  /// @param kind the decoration kind
+  /// @param source the source of this decoration
+  explicit TypeDecoration(DecorationKind kind, const Source& source);
+};
+
+/// A list of type decorations
+using TypeDecorationList = std::vector<TypeDecoration*>;
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_TYPE_DECORATION_H_
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index cecf9b8..0d6f4d5 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -17,6 +17,7 @@
 #include <memory>
 #include <vector>
 
+#include "src/ast/access_decoration.h"
 #include "src/ast/array_accessor_expression.h"
 #include "src/ast/binary_expression.h"
 #include "src/ast/binding_decoration.h"
@@ -45,6 +46,7 @@
 #include "src/ast/struct_block_decoration.h"
 #include "src/ast/struct_member_offset_decoration.h"
 #include "src/ast/switch_statement.h"
+#include "src/ast/type/access_control_type.h"
 #include "src/ast/type/alias_type.h"
 #include "src/ast/type/array_type.h"
 #include "src/ast/type/bool_type.h"
@@ -61,6 +63,7 @@
 #include "src/ast/type/vector_type.h"
 #include "src/ast/type/void_type.h"
 #include "src/ast/type_constructor_expression.h"
+#include "src/ast/type_decoration.h"
 #include "src/ast/uint_literal.h"
 #include "src/ast/unary_op.h"
 #include "src/ast/unary_op_expression.h"
@@ -797,7 +800,7 @@
 }
 
 // variable_ident_decl
-//   : IDENT COLON type_decl
+//   : IDENT COLON variable_decoration_list* type_decl
 Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_variable_ident_decl(
     const std::string& use) {
   auto ident = expect_ident(use);
@@ -807,14 +810,47 @@
   if (!expect(use, Token::Type::kColon))
     return Failure::kErrored;
 
+  auto decos = decoration_list();
+  if (decos.errored)
+    return Failure::kErrored;
+
+  auto access_decos = take_decorations<ast::AccessDecoration>(decos.value);
+
   auto t = peek();
-  auto type = type_decl();
+  auto type = type_decl(decos.value);
   if (type.errored)
     return Failure::kErrored;
   if (!type.matched)
     return add_error(t.source(), "invalid type", use);
 
-  return TypedIdentifier{type.value, ident.value, ident.source};
+  if (!expect_decorations_consumed(decos.value))
+    return Failure::kErrored;
+
+  if (access_decos.size() > 1)
+    return add_error(ident.source, "multiple access decorations not allowed");
+
+  auto* ty = type.value;
+  for (auto* deco : access_decos) {
+    // If we have an access control decoration then we take it and wrap our
+    // type up with that decoration
+    ty = ctx_.type_mgr().Get(std::make_unique<ast::type::AccessControlType>(
+        deco->AsAccess()->value(), ty));
+  }
+
+  return TypedIdentifier{ty, ident.value, ident.source};
+}
+
+Expect<ast::AccessControl> ParserImpl::expect_access_type() {
+  auto ident = expect_ident("access_type");
+  if (ident.errored)
+    return Failure::kErrored;
+
+  if (ident.value == "read")
+    return {ast::AccessControl::kReadOnly, ident.source};
+  if (ident.value == "read_write")
+    return {ast::AccessControl::kReadWrite, ident.source};
+
+  return add_error(ident.source, "invalid value for access decoration");
 }
 
 // variable_storage_decoration
@@ -889,6 +925,23 @@
 //   | MAT4x4 LESS_THAN type_decl GREATER_THAN
 //   | texture_sampler_types
 Maybe<ast::type::Type*> ParserImpl::type_decl() {
+  auto decos = decoration_list();
+  if (decos.errored)
+    return Failure::kErrored;
+
+  auto type = type_decl(decos.value);
+  if (type.errored)
+    return Failure::kErrored;
+  if (!type.matched)
+    return Failure::kNoMatch;
+
+  if (!expect_decorations_consumed(decos.value))
+    return Failure::kErrored;
+
+  return type.value;
+}
+
+Maybe<ast::type::Type*> ParserImpl::type_decl(ast::DecorationList& decos) {
   auto t = peek();
   if (match(Token::Type::kIdentifier)) {
     auto* ty = get_constructed(t.to_str());
@@ -918,21 +971,14 @@
   if (match(Token::Type::kPtr))
     return expect_type_decl_pointer();
 
-  auto decos = decoration_list();
-  if (decos.errored)
-    return Failure::kErrored;
-
   if (match(Token::Type::kArray)) {
-    auto array_decos = cast_decorations<ast::ArrayDecoration>(decos.value);
+    auto array_decos = cast_decorations<ast::ArrayDecoration>(decos);
     if (array_decos.errored)
       return Failure::kErrored;
 
     return expect_type_decl_array(std::move(array_decos.value));
   }
 
-  if (!expect_decorations_consumed(decos.value))
-    return Failure::kErrored;
-
   if (t.IsMat2x2() || t.IsMat2x3() || t.IsMat2x4() || t.IsMat3x2() ||
       t.IsMat3x3() || t.IsMat3x4() || t.IsMat4x2() || t.IsMat4x3() ||
       t.IsMat4x4()) {
@@ -2714,6 +2760,16 @@
 Maybe<ast::Decoration*> ParserImpl::decoration() {
   using Result = Maybe<ast::Decoration*>;
   auto t = next();
+  if (t.IsIdentifier() && t.to_str() == "access") {
+    const char* use = "access decoration";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_access_type();
+      if (val.errored)
+        return Failure::kErrored;
+
+      return create<ast::AccessDecoration>(val.value, val.source);
+    });
+  }
   if (t.IsLocation()) {
     const char* use = "location decoration";
     return expect_paren_block(use, [&]() -> Result {
@@ -2817,23 +2873,37 @@
 }
 
 template <typename T>
-Expect<std::vector<T*>> ParserImpl::cast_decorations(ast::DecorationList& in) {
-  bool ok = true;
+std::vector<T*> ParserImpl::take_decorations(ast::DecorationList& in) {
+  ast::DecorationList remaining;
   std::vector<T*> out;
   out.reserve(in.size());
   for (auto* deco : in) {
-    if (!deco->Is<T>()) {
-      std::stringstream msg;
-      msg << deco->GetKind() << " decoration type cannot be used for "
-          << T::Kind;
-      add_error(deco->source(), msg.str());
-      ok = false;
-      continue;
+    if (deco->Is<T>()) {
+      out.emplace_back(ast::As<T>(deco));
+    } else {
+      remaining.emplace_back(deco);
     }
-    out.emplace_back(ast::As<T>(deco));
   }
-  // clear in so that we can verify decorations were consumed with
-  // expect_decorations_consumed()
+
+  in = std::move(remaining);
+  return out;
+}
+
+template <typename T>
+Expect<std::vector<T*>> ParserImpl::cast_decorations(ast::DecorationList& in) {
+  auto out = take_decorations<T>(in);
+
+  bool ok = true;
+
+  for (auto* deco : in) {
+    std::stringstream msg;
+    msg << deco->GetKind() << " decoration type cannot be used for " << T::Kind;
+    add_error(deco->source(), msg.str());
+    ok = false;
+  }
+
+  // clear in so that expect_decorations_consumed() doesn't error again on the
+  // decorations we've already errored on.
   in.clear();
 
   if (!ok)
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 6604102..ab476c9 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -24,6 +24,7 @@
 #include <utility>
 #include <vector>
 
+#include "src/ast/access_control.h"
 #include "src/ast/array_decoration.h"
 #include "src/ast/assignment_statement.h"
 #include "src/ast/builtin.h"
@@ -320,6 +321,11 @@
   /// Parses a `type_decl` grammar element
   /// @returns the parsed Type or nullptr if none matched.
   Maybe<ast::type::Type*> type_decl();
+  /// Parses a `type_decl` grammar element with the given pre-parsed
+  /// decorations.
+  /// @param decos the list of decorations for the type.
+  /// @returns the parsed Type or nullptr if none matched.
+  Maybe<ast::type::Type*> type_decl(ast::DecorationList& decos);
   /// Parses a `storage_class` grammar element, erroring on parse failure.
   /// @param use a description of what was being parsed if an error was raised.
   /// @returns the storage class or StorageClass::kNone if none matched
@@ -383,6 +389,10 @@
   /// not match a stage name.
   /// @returns the pipeline stage.
   Expect<ast::PipelineStage> expect_pipeline_stage();
+  /// Parses an access type identifier, erroring if the next token does not
+  /// match a valid access type name.
+  /// @returns the parsed access control.
+  Expect<ast::AccessControl> expect_access_type();
   /// Parses a builtin identifier, erroring if the next token does not match a
   /// valid builtin name.
   /// @returns the parsed builtin.
@@ -720,10 +730,16 @@
   template <typename F, typename T = ReturnType<F>>
   T without_error(F&& func);
 
+  /// Returns all the decorations taken from |list| that matches the type |T|.
+  /// Those that do not match are kept in |list|.
+  template <typename T>
+  std::vector<T*> take_decorations(ast::DecorationList& list);
+
   /// Downcasts all the decorations in |list| to the type |T|, raising a parser
   /// error if any of the decorations aren't of the type |T|.
   template <typename T>
-  Expect<std::vector<T*>> cast_decorations(ast::DecorationList& in);
+  Expect<std::vector<T*>> cast_decorations(ast::DecorationList& list);
+
   /// Reports an error if the decoration list |list| is not empty.
   /// Used to ensure that all decorations are consumed.
   bool expect_decorations_consumed(const ast::DecorationList& list);
diff --git a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc b/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
index f4af027..2ace2a9 100644
--- a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
@@ -13,6 +13,11 @@
 // limitations under the License.
 
 #include "gtest/gtest.h"
+#include "src/ast/struct.h"
+#include "src/ast/struct_block_decoration.h"
+#include "src/ast/type/access_control_type.h"
+#include "src/ast/type/i32_type.h"
+#include "src/ast/type/struct_type.h"
 #include "src/reader/wgsl/parser_impl.h"
 #include "src/reader/wgsl/parser_impl_test_helper.h"
 
@@ -24,7 +29,7 @@
 TEST_F(ParserImplTest, VariableIdentDecl_Parses) {
   auto p = parser("my_var : f32");
   auto decl = p->expect_variable_ident_decl("test");
-  ASSERT_FALSE(p->has_error());
+  ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(decl.errored);
   ASSERT_EQ(decl->name, "my_var");
   ASSERT_NE(decl->type, nullptr);
@@ -76,6 +81,175 @@
   ASSERT_EQ(p->error(), "1:10: unknown constructed type 'invalid'");
 }
 
+TEST_F(ParserImplTest, VariableIdentDecl_ParsesWithAccessDeco_Read) {
+  ast::type::I32Type i32;
+
+  ast::StructMember mem("a", &i32, ast::StructMemberDecorationList{});
+  ast::StructMemberList members;
+  members.push_back(&mem);
+
+  ast::StructBlockDecoration block_deco(Source{});
+  ast::StructDecorationList decos;
+  decos.push_back(&block_deco);
+
+  ast::Struct str(decos, members);
+  ast::type::StructType s("S", &str);
+
+  auto p = parser("my_var : [[access(read)]] S");
+  p->register_constructed("S", &s);
+
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decl.errored);
+  ASSERT_EQ(decl->name, "my_var");
+  ASSERT_NE(decl->type, nullptr);
+  ASSERT_TRUE(decl->type->IsAccessControl());
+  EXPECT_TRUE(decl->type->AsAccessControl()->IsReadOnly());
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_ParsesWithAccessDeco_ReadWrite) {
+  ast::type::I32Type i32;
+
+  ast::StructMember mem("a", &i32, ast::StructMemberDecorationList{});
+  ast::StructMemberList members;
+  members.push_back(&mem);
+
+  ast::StructBlockDecoration block_deco(Source{});
+  ast::StructDecorationList decos;
+  decos.push_back(&block_deco);
+
+  ast::Struct str(decos, members);
+  ast::type::StructType s("S", &str);
+
+  auto p = parser("my_var : [[access(read_write)]] S");
+  p->register_constructed("S", &s);
+
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decl.errored);
+  ASSERT_EQ(decl->name, "my_var");
+  ASSERT_NE(decl->type, nullptr);
+  ASSERT_TRUE(decl->type->IsAccessControl());
+  EXPECT_TRUE(decl->type->AsAccessControl()->IsReadWrite());
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_MultipleAccessDecoFail) {
+  ast::type::I32Type i32;
+
+  ast::StructMember mem("a", &i32, ast::StructMemberDecorationList{});
+  ast::StructMemberList members;
+  members.push_back(&mem);
+
+  ast::StructBlockDecoration block_deco(Source{});
+  ast::StructDecorationList decos;
+  decos.push_back(&block_deco);
+
+  ast::Struct str(decos, members);
+  ast::type::StructType s("S", &str);
+
+  auto p = parser("my_var : [[access(read), access(read_write)]] S");
+  p->register_constructed("S", &s);
+
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:1: multiple access decorations not allowed");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_MultipleAccessDeco_MultiBlock_Fail) {
+  ast::type::I32Type i32;
+
+  ast::StructMember mem("a", &i32, ast::StructMemberDecorationList{});
+  ast::StructMemberList members;
+  members.push_back(&mem);
+
+  ast::StructBlockDecoration block_deco(Source{});
+  ast::StructDecorationList decos;
+  decos.push_back(&block_deco);
+
+  ast::Struct str(decos, members);
+  ast::type::StructType s("S", &str);
+
+  auto p = parser("my_var : [[access(read)]][[access(read_write)]] S");
+  p->register_constructed("S", &s);
+
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:1: multiple access decorations not allowed");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_AccessDecoBadValue) {
+  auto p = parser("my_var : [[access(unknown)]] S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:19: invalid value for access decoration");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_AccessDecoIllegalValue) {
+  auto p = parser("my_var : [[access(1)]] S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:19: expected identifier for access_type");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_NonAccessDecoFail) {
+  ast::type::I32Type i32;
+
+  ast::StructMember mem("a", &i32, ast::StructMemberDecorationList{});
+  ast::StructMemberList members;
+  members.push_back(&mem);
+
+  ast::StructBlockDecoration block_deco(Source{});
+  ast::StructDecorationList decos;
+  decos.push_back(&block_deco);
+
+  ast::Struct str(decos, members);
+  ast::type::StructType s("S", &str);
+
+  auto p = parser("my_var : [[stride(1)]] S");
+  p->register_constructed("S", &s);
+
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:12: unexpected decorations");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_DecorationMissingRightBlock) {
+  auto p = parser("my_var : [[access(read) S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:25: expected ']]' for decoration list");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_DecorationMissingRightParen) {
+  auto p = parser("my_var : [[access(read]] S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:23: expected ')' for access decoration");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_DecorationMissingLeftParen) {
+  auto p = parser("my_var : [[access read)]] S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:19: expected '(' for access decoration");
+}
+
+TEST_F(ParserImplTest, VariableIdentDecl_DecorationEmpty) {
+  auto p = parser("my_var : [[]] S");
+  auto decl = p->expect_variable_ident_decl("test");
+  ASSERT_TRUE(p->has_error());
+  ASSERT_TRUE(decl.errored);
+  ASSERT_EQ(p->error(), "1:12: empty decoration list");
+}
+
 }  // namespace
 }  // namespace wgsl
 }  // namespace reader
diff --git a/test/compute_boids.wgsl b/test/compute_boids.wgsl
index 6bee6d3..99c0549 100644
--- a/test/compute_boids.wgsl
+++ b/test/compute_boids.wgsl
@@ -56,9 +56,9 @@
   [[offset(0)]] particles : [[stride(16)]] array<Particle, 5>;
 };
 
-[[binding(0), set(0)]] var<uniform> params : SimParams;
-[[binding(1), set(0)]] var<storage_buffer> particlesA : Particles;
-[[binding(2), set(0)]] var<storage_buffer> particlesB : Particles;
+[[binding(0), set(0)]] var<uniform> params : [[access(read)]] SimParams;
+[[binding(1), set(0)]] var<storage_buffer> particlesA : [[access(read_write)]] Particles;
+[[binding(2), set(0)]] var<storage_buffer> particlesB : [[access(read_write)]] Particles;
 
 [[builtin(global_invocation_id)]] var<in> gl_GlobalInvocationID : vec3<u32>;
 
diff --git a/test/cube.wgsl b/test/cube.wgsl
index d56f32f..ee2d2cb 100644
--- a/test/cube.wgsl
+++ b/test/cube.wgsl
@@ -17,7 +17,7 @@
   [[offset(0)]] modelViewProjectionMatrix : mat4x4<f32>;
 };
 
-[[binding(0), set(0)]] var<uniform> uniforms : Uniforms;
+[[binding(0), set(0)]] var<uniform> uniforms : [[access(read)]] Uniforms;
 
 [[location(0)]] var<in> cur_position : vec4<f32>;
 [[location(1)]] var<in> color : vec4<f32>;