wgsl: Deprecate [[access]] decorations

Handle access control on var declarations instead of via [[access]]
decorations. This change does the minimal work to migrate the WGSL
parser over to the new syntax. Additional changes will be needed
to correctly generate defaulted access qualifiers, as well as
validating access usage.

The [[access]] decorations are still supported by the WGSL parser,
with new deprecated warnings, but not for aliases. Example:
   var x : [[access(x)]] alias_to_struct;

Making this work is far more effort than I want to dedicate to backwards
compatibility, and I do not beleive any real-world usage will be doing
this.

Still TODO:
* Adding access control as the optional, third parameter to ptr<>.
* Calculating default accesses for the various storage types.
* Validating usage of variables against the different accesses.

Bug: tint:846
Change-Id: If8ca82e5d16ec319ecd01f9a2cafffd930963bde
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/53088
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/resolver/assignment_validation_test.cc b/src/resolver/assignment_validation_test.cc
index dab2610..b908662 100644
--- a/src/resolver/assignment_validation_test.cc
+++ b/src/resolver/assignment_validation_test.cc
@@ -156,14 +156,14 @@
 }
 
 TEST_F(ResolverAssignmentValidationTest, AssignNonStorable_Fail) {
-  // var a : [[access(read)]] texture_storage_1d<rgba8unorm>;
-  // var b : [[access(read)]] texture_storage_1d<rgba8unorm>;
+  // var a : texture_storage_1d<rgba8unorm, read>;
+  // var b : texture_storage_1d<rgba8unorm, read>;
   // a = b;
 
   auto make_type = [&] {
-    auto* tex_type = ty.storage_texture(ast::TextureDimension::k1d,
-                                        ast::ImageFormat::kRgba8Unorm);
-    return ty.access(ast::AccessControl::kRead, tex_type);
+    return ty.storage_texture(ast::TextureDimension::k1d,
+                              ast::ImageFormat::kRgba8Unorm,
+                              ast::Access::kRead);
   };
 
   Global("a", make_type(), ast::StorageClass::kNone,
@@ -182,7 +182,7 @@
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(
       r()->error(),
-      R"(12:34 error: '[[access(read)]] texture_storage_1d<rgba8unorm>' is not storable)");
+      R"(12:34 error: 'texture_storage_1d<rgba8unorm, read>' is not storable)");
 }
 
 }  // namespace
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index e24dc70..1de719b 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -30,7 +30,6 @@
 namespace {
 
 enum class DecorationKind {
-  kAccess,
   kAlign,
   kBinding,
   kBuiltin,
@@ -68,9 +67,6 @@
                                              ProgramBuilder& builder,
                                              DecorationKind kind) {
   switch (kind) {
-    case DecorationKind::kAccess:
-      return {builder.create<ast::AccessDecoration>(source,
-                                                    ast::AccessControl::kRead)};
     case DecorationKind::kAlign:
       return {builder.create<ast::StructMemberAlignDecoration>(source, 4u)};
     case DecorationKind::kBinding:
@@ -122,8 +118,7 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverDecorationValidationTest,
     FunctionReturnTypeDecorationTest,
-    testing::Values(TestParams{DecorationKind::kAccess, false},
-                    TestParams{DecorationKind::kAlign, false},
+    testing::Values(TestParams{DecorationKind::kAlign, false},
                     TestParams{DecorationKind::kBinding, false},
                     TestParams{DecorationKind::kBuiltin, true},
                     TestParams{DecorationKind::kGroup, false},
@@ -162,8 +157,7 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverDecorationValidationTest,
     ArrayDecorationTest,
-    testing::Values(TestParams{DecorationKind::kAccess, false},
-                    TestParams{DecorationKind::kAlign, false},
+    testing::Values(TestParams{DecorationKind::kAlign, false},
                     TestParams{DecorationKind::kBinding, false},
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
@@ -197,8 +191,7 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverDecorationValidationTest,
     StructDecorationTest,
-    testing::Values(TestParams{DecorationKind::kAccess, false},
-                    TestParams{DecorationKind::kAlign, false},
+    testing::Values(TestParams{DecorationKind::kAlign, false},
                     TestParams{DecorationKind::kBinding, false},
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
@@ -234,8 +227,7 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverDecorationValidationTest,
     StructMemberDecorationTest,
-    testing::Values(TestParams{DecorationKind::kAccess, false},
-                    TestParams{DecorationKind::kAlign, true},
+    testing::Values(TestParams{DecorationKind::kAlign, true},
                     TestParams{DecorationKind::kBinding, false},
                     TestParams{DecorationKind::kBuiltin, true},
                     TestParams{DecorationKind::kGroup, false},
@@ -277,8 +269,7 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverDecorationValidationTest,
     VariableDecorationTest,
-    testing::Values(TestParams{DecorationKind::kAccess, false},
-                    TestParams{DecorationKind::kAlign, false},
+    testing::Values(TestParams{DecorationKind::kAlign, false},
                     TestParams{DecorationKind::kBinding, false},
                     TestParams{DecorationKind::kBuiltin, true},
                     TestParams{DecorationKind::kGroup, false},
@@ -312,8 +303,7 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverDecorationValidationTest,
     ConstantDecorationTest,
-    testing::Values(TestParams{DecorationKind::kAccess, false},
-                    TestParams{DecorationKind::kAlign, false},
+    testing::Values(TestParams{DecorationKind::kAlign, false},
                     TestParams{DecorationKind::kBinding, false},
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
@@ -346,8 +336,7 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverDecorationValidationTest,
     FunctionDecorationTest,
-    testing::Values(TestParams{DecorationKind::kAccess, false},
-                    TestParams{DecorationKind::kAlign, false},
+    testing::Values(TestParams{DecorationKind::kAlign, false},
                     TestParams{DecorationKind::kBinding, false},
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
@@ -523,8 +512,8 @@
 TEST_F(ResourceDecorationTest, StorageBufferMissingBinding) {
   auto* s = Structure("S", {Member("x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kRead, s);
-  Global(Source{{12, 34}}, "G", ac, ast::StorageClass::kStorage);
+  Global(Source{{12, 34}}, "G", s, ast::StorageClass::kStorage,
+         ast::Access::kRead);
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
diff --git a/src/resolver/host_shareable_validation_test.cc b/src/resolver/host_shareable_validation_test.cc
index bb73ad9..57e6fbc 100644
--- a/src/resolver/host_shareable_validation_test.cc
+++ b/src/resolver/host_shareable_validation_test.cc
@@ -28,8 +28,9 @@
 TEST_F(ResolverHostShareableValidationTest, BoolMember) {
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())},
                       {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kRead, s);
-  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
+
+  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
+         ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -47,8 +48,9 @@
 TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())},
                       {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kRead, s);
-  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
+
+  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
+         ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -68,10 +70,10 @@
   AST().AddConstructedType(a1);
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", a1)},
                       {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kRead, s);
-  auto* a2 = ty.alias("a2", ac);
+  auto* a2 = ty.alias("a2", s);
   AST().AddConstructedType(a2);
   Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage,
+         ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -93,8 +95,9 @@
 
   auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
                       {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kRead, s);
-  Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage,
+
+  Global(Source{{9, 10}}, "g", s, ast::StorageClass::kStorage,
+         ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -135,8 +138,9 @@
 
   auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
                       {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kRead, s);
-  Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage,
+
+  Global(Source{{9, 10}}, "g", s, ast::StorageClass::kStorage,
+         ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
diff --git a/src/resolver/intrinsic_test.cc b/src/resolver/intrinsic_test.cc
index 701796f..39d1b18 100644
--- a/src/resolver/intrinsic_test.cc
+++ b/src/resolver/intrinsic_test.cc
@@ -276,12 +276,11 @@
   auto format = GetParam().format;
 
   auto* coords_type = GetCoordsType(dim, ty.i32());
-  auto* texture_type = ty.storage_texture(dim, format);
-  auto* ro_texture_type = ty.access(ast::AccessControl::kRead, texture_type);
+  auto* texture_type = ty.storage_texture(dim, format, ast::Access::kRead);
 
   ast::ExpressionList call_params;
 
-  add_call_param("texture", ro_texture_type, &call_params);
+  add_call_param("texture", texture_type, &call_params);
   add_call_param("coords", coords_type, &call_params);
 
   if (ast::IsTextureArray(dim)) {
@@ -769,8 +768,7 @@
   auto* ary = ty.array<i32>();
   auto* str = Structure("S", {Member("x", ary)},
                         {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kRead, str);
-  Global("a", ac, ast::StorageClass::kStorage,
+  Global("a", str, ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 9f81e32..14f4b7d 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -287,13 +287,6 @@
     if (ty->Is<ast::F32>()) {
       return builder_->create<sem::F32>();
     }
-    if (auto* t = ty->As<ast::AccessControl>()) {
-      TINT_SCOPED_ASSIGNMENT(current_access_control_, t);
-      if (auto* el = Type(t->type())) {
-        return el;
-      }
-      return nullptr;
-    }
     if (auto* t = ty->As<ast::Vector>()) {
       if (auto* el = Type(t->type())) {
         return builder_->create<sem::Vector>(const_cast<sem::Type*>(el),
@@ -341,14 +334,11 @@
     }
     if (auto* t = ty->As<ast::StorageTexture>()) {
       if (auto* el = Type(t->type())) {
-        if (!current_access_control_) {
-          diagnostics_.add_error("storage textures must have access control",
-                                 t->source());
+        if (!ValidateStorageTexture(t)) {
           return nullptr;
         }
         return builder_->create<sem::StorageTexture>(
-            t->dim(), t->image_format(),
-            current_access_control_->access_control(),
+            t->dim(), t->image_format(), t->access(),
             const_cast<sem::Type*>(el));
       }
       return nullptr;
@@ -377,6 +367,39 @@
   return s;
 }
 
+bool Resolver::ValidateStorageTexture(const ast::StorageTexture* t) {
+  switch (t->access()) {
+    case ast::Access::kUndefined:
+      diagnostics_.add_error("storage textures must have access control",
+                             t->source());
+      return false;
+    case ast::Access::kReadWrite:
+      diagnostics_.add_error(
+          "storage textures only support read-only and write-only access",
+          t->source());
+      return false;
+
+    case ast::Access::kRead:
+    case ast::Access::kWrite:
+      break;
+  }
+
+  if (!IsValidStorageTextureDimension(t->dim())) {
+    diagnostics_.add_error(
+        "cube dimensions for storage textures are not supported", t->source());
+    return false;
+  }
+
+  if (!IsValidStorageTextureImageFormat(t->image_format())) {
+    diagnostics_.add_error(
+        "image format must be one of the texel formats specified for storage "
+        "textues in https://gpuweb.github.io/gpuweb/wgsl/#texel-formats",
+        t->source());
+    return false;
+  }
+  return true;
+}
+
 Resolver::VariableInfo* Resolver::Variable(ast::Variable* var,
                                            VariableKind kind) {
   if (variable_to_info_.count(var)) {
@@ -466,35 +489,17 @@
     return nullptr;
   }
 
-  // TODO(crbug.com/tint/802): Temporary while ast::AccessControl exits.
-  auto find_first_access_control =
-      [this](const ast::Type* ty) -> const ast::AccessControl* {
-    if (ty == nullptr) {
-      return nullptr;
-    }
-    if (const ast::AccessControl* ac = ty->As<ast::AccessControl>()) {
-      return ac;
-    }
-    while (auto* tn = ty->As<ast::TypeName>()) {
-      auto it = named_type_info_.find(tn->name());
-      if (it == named_type_info_.end()) {
-        break;
-      }
-      auto* alias = it->second.ast->As<ast::Alias>();
-      if (!alias) {
-        break;
-      }
-      ty = alias->type();
-      if (auto* ac = ty->As<ast::AccessControl>()) {
-        return ac;
-      }
-    }
-    return nullptr;
-  };
+  auto access = var->declared_access();
+  if (access == ast::Access::kUndefined &&
+      storage_class == ast::StorageClass::kStorage) {
+    // https://gpuweb.github.io/gpuweb/wgsl/#access-mode-defaults
+    // For the storage storage class, the access mode is optional, and defaults
+    // to read.
+    access = ast::Access::kRead;
+  }
 
-  auto* access_control = find_first_access_control(var->type());
   auto* info = variable_infos_.Create(var, const_cast<sem::Type*>(type),
-                                      type_name, storage_class, access_control);
+                                      type_name, storage_class, access);
   variable_to_info_.emplace(var, info);
 
   return info;
@@ -658,25 +663,31 @@
       }
   }
 
+  // https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration
+  // The access mode always has a default, and except for variables in the
+  // storage storage class, must not be written.
+  if (info->storage_class != ast::StorageClass::kStorage &&
+      info->declaration->declared_access() != ast::Access::kUndefined) {
+    diagnostics_.add_error(
+        "variables declared not declared in the <storage> storage class must "
+        "not declare an access control",
+        info->declaration->source());
+    return false;
+  }
+
   switch (info->storage_class) {
     case ast::StorageClass::kStorage: {
-      // https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration
-      // Variables in the storage storage class and variables with a storage
-      // texture type must have an access attribute applied to the store type.
-
       // https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
       // A variable in the storage storage class is a storage buffer variable.
       // Its store type must be a host-shareable structure type with block
       // attribute, satisfying the storage class constraints.
 
-      auto* str = info->access_control
-                      ? info->type->UnwrapRef()->As<sem::Struct>()
-                      : nullptr;
+      auto* str = info->type->UnwrapRef()->As<sem::Struct>();
 
       if (!str) {
         diagnostics_.add_error(
-            "variables declared in the <storage> storage class must be of an "
-            "[[access]] qualified structure type",
+            "variables declared in the <storage> storage class must be of a "
+            "structure type",
             info->declaration->source());
         return false;
       }
@@ -756,33 +767,6 @@
     }
   }
 
-  if (auto* storage_tex = info->type->UnwrapRef()->As<sem::StorageTexture>()) {
-    if (info->access_control->access_control() ==
-        ast::AccessControl::kReadWrite) {
-      diagnostics_.add_error(
-          "storage textures only support read-only and write-only access",
-          var->source());
-      return false;
-    }
-
-    if (!IsValidStorageTextureDimension(storage_tex->dim())) {
-      diagnostics_.add_error(
-          "cube dimensions for storage textures are not "
-          "supported",
-          var->source());
-      return false;
-    }
-
-    if (!IsValidStorageTextureImageFormat(storage_tex->image_format())) {
-      diagnostics_.add_error(
-          "image format must be one of the texel formats specified for "
-          "storage textues in "
-          "https://gpuweb.github.io/gpuweb/wgsl/#texel-formats",
-          var->source());
-      return false;
-    }
-  }
-
   if (storage_type->is_handle() &&
       var->declared_storage_class() != ast::StorageClass::kNone) {
     // https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
@@ -2584,9 +2568,7 @@
       sem_var = builder_->create<sem::Variable>(var, info->type, constant_id);
     } else {
       sem_var = builder_->create<sem::Variable>(
-          var, info->type, info->storage_class,
-          info->access_control ? info->access_control->access_control()
-                               : ast::AccessControl::kReadWrite);
+          var, info->type, info->storage_class, info->access);
     }
 
     std::vector<const sem::VariableUser*> users;
@@ -3274,12 +3256,12 @@
                                      sem::Type* ty,
                                      const std::string& tn,
                                      ast::StorageClass sc,
-                                     const ast::AccessControl* ac)
+                                     ast::Access ac)
     : declaration(decl),
       type(ty),
       type_name(tn),
       storage_class(sc),
-      access_control(ac) {}
+      access(ac) {}
 
 Resolver::VariableInfo::~VariableInfo() = default;
 
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 2efdf7c..4b893bf 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -89,14 +89,14 @@
                  sem::Type* type,
                  const std::string& type_name,
                  ast::StorageClass storage_class,
-                 const ast::AccessControl* ac);
+                 ast::Access ac);
     ~VariableInfo();
 
     ast::Variable const* const declaration;
     sem::Type* type;
     std::string const type_name;
     ast::StorageClass storage_class;
-    ast::AccessControl const* const access_control;
+    ast::Access const access;
     std::vector<ast::IdentifierExpression*> users;
     sem::BindingPoint binding_point;
   };
@@ -255,6 +255,7 @@
                                  const sem::Matrix* matrix_type);
   bool ValidateParameter(const VariableInfo* info);
   bool ValidateReturn(const ast::ReturnStatement* ret);
+  bool ValidateStorageTexture(const ast::StorageTexture* t);
   bool ValidateStructure(const sem::Struct* str);
   bool ValidateSwitch(const ast::SwitchStatement* s);
   bool ValidateVariable(const VariableInfo* info);
@@ -394,7 +395,6 @@
 
   FunctionInfo* current_function_ = nullptr;
   sem::Statement* current_statement_ = nullptr;
-  const ast::AccessControl* current_access_control_ = nullptr;
   BlockAllocator<VariableInfo> variable_infos_;
   BlockAllocator<FunctionInfo> function_infos_;
 };
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index 84935c4..ba3ef08 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -784,15 +784,15 @@
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
   auto* s = Structure("S", {Member("m", ty.u32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kRead, s);
 
   auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
   auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
-  auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage,
-                        ast::DecorationList{
-                            create<ast::BindingDecoration>(0),
-                            create<ast::GroupDecoration>(0),
-                        });
+  auto* sb_var =
+      Global("sb_var", s, ast::StorageClass::kStorage, ast::Access::kRead,
+             ast::DecorationList{
+                 create<ast::BindingDecoration>(0),
+                 create<ast::GroupDecoration>(0),
+             });
   auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
   auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -823,15 +823,15 @@
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
   auto* s = Structure("S", {Member("m", ty.u32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kRead, s);
 
   auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
   auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
-  auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage,
-                        ast::DecorationList{
-                            create<ast::BindingDecoration>(0),
-                            create<ast::GroupDecoration>(0),
-                        });
+  auto* sb_var =
+      Global("sb_var", s, ast::StorageClass::kStorage, ast::Access::kRead,
+             ast::DecorationList{
+                 create<ast::BindingDecoration>(0),
+                 create<ast::GroupDecoration>(0),
+             });
   auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
   auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
@@ -1757,8 +1757,7 @@
 
 TEST_F(ResolverTest, StorageClass_SetForTexture) {
   auto* t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
-  auto* ac = ty.access(ast::AccessControl::kRead, t);
-  auto* var = Global("var", ac,
+  auto* var = Global("var", t,
                      ast::DecorationList{
                          create<ast::BindingDecoration>(0),
                          create<ast::GroupDecoration>(0),
@@ -1780,6 +1779,22 @@
   EXPECT_EQ(Sem().Get(var)->StorageClass(), ast::StorageClass::kNone);
 }
 
+TEST_F(ResolverTest, Access_SetForStorageBuffer) {
+  // [[block]] struct S { x : i32 };
+  // var<storage> g : S;
+  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
+                      {create<ast::StructBlockDecoration>()});
+  auto* var = Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
+                     ast::DecorationList{
+                         create<ast::BindingDecoration>(0),
+                         create<ast::GroupDecoration>(0),
+                     });
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+  EXPECT_EQ(Sem().Get(var)->Access(), ast::Access::kRead);
+}
+
 TEST_F(ResolverTest, Function_EntryPoints_StageDecoration) {
   // fn b() {}
   // fn c() { b(); }
diff --git a/src/resolver/resolver_test_helper.h b/src/resolver/resolver_test_helper.h
index 675fa33..3597e27 100644
--- a/src/resolver/resolver_test_helper.h
+++ b/src/resolver/resolver_test_helper.h
@@ -226,12 +226,6 @@
   return ty.builder->create<ast::TypeName>(name);
 }
 
-template <create_ast_type_func_ptr create_type>
-ast::Type* ast_access(const ProgramBuilder::TypesBuilder& ty) {
-  auto* type = create_type(ty);
-  return ty.access(ast::AccessControl::kRead, type);
-}
-
 inline sem::Type* sem_bool(const ProgramBuilder::TypesBuilder& ty) {
   return ty.builder->create<sem::Bool>();
 }
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index 67efdd7..29c8945 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -46,7 +46,7 @@
 
   EXPECT_EQ(
       r()->error(),
-      R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
+      R"(56:78 error: variables declared in the <storage> storage class must be of a structure type)");
 }
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferPointer) {
@@ -62,15 +62,15 @@
 
   EXPECT_EQ(
       r()->error(),
-      R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
+      R"(56:78 error: variables declared in the <storage> storage class must be of a structure type)");
 }
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferArray) {
-  // var<storage> g : [[access(read)]] array<S, 3>;
+  // var<storage, read> g : array<S, 3>;
   auto* s = Structure("S", {Member("a", ty.f32())});
   auto* a = ty.array(s, 3);
-  auto* ac = ty.access(ast::AccessControl::kRead, a);
-  Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kStorage,
+  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
+         ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -80,12 +80,12 @@
 
   EXPECT_EQ(
       r()->error(),
-      R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
+      R"(56:78 error: variables declared in the <storage> storage class must be of a structure type)");
 }
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferBoolAlias) {
   // type a = bool;
-  // var<storage> g : [[access(read)]] a;
+  // var<storage, read> g : a;
   auto* a = ty.alias("a", ty.bool_());
   AST().AddConstructedType(a);
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
@@ -98,31 +98,15 @@
 
   EXPECT_EQ(
       r()->error(),
-      R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
-}
-
-TEST_F(ResolverStorageClassValidationTest, StorageBufferNoAccessControl) {
-  // var<storage> g : S;
-  auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
-  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
-         ast::DecorationList{
-             create<ast::BindingDecoration>(0),
-             create<ast::GroupDecoration>(0),
-         });
-
-  ASSERT_FALSE(r()->Resolve());
-
-  EXPECT_EQ(
-      r()->error(),
-      R"(56:78 error: variables declared in the <storage> storage class must be of an [[access]] qualified structure type)");
+      R"(56:78 error: variables declared in the <storage> storage class must be of a structure type)");
 }
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferNoBlockDecoration) {
   // struct S { x : i32 };
-  // var<storage> g : [[access(read)]] S;
+  // var<storage, read> g : S;
   auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
-  auto* a = ty.access(ast::AccessControl::kRead, s);
-  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
+  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
+         ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -138,11 +122,11 @@
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Basic) {
   // [[block]] struct S { x : i32 };
-  // var<storage> g : [[access(read)]] S;
+  // var<storage, read> g : S;
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* a = ty.access(ast::AccessControl::kRead, s);
-  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
+  Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage,
+         ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -154,16 +138,15 @@
 TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Aliases) {
   // [[block]] struct S { x : i32 };
   // type a1 = S;
-  // type a2 = [[access(read)]] a1;
-  // var<storage> g : a2;
+  // var<storage, read> g : a1;
   auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
                       {create<ast::StructBlockDecoration>()});
   auto* a1 = ty.alias("a1", s);
   AST().AddConstructedType(a1);
-  auto* ac = ty.access(ast::AccessControl::kRead, a1);
-  auto* a2 = ty.alias("a2", ac);
+  auto* a2 = ty.alias("a2", a1);
   AST().AddConstructedType(a2);
   Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage,
+         ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -206,11 +189,10 @@
 }
 
 TEST_F(ResolverStorageClassValidationTest, UniformBufferArray) {
-  // var<uniform> g : [[access(read)]] array<S, 3>;
+  // var<uniform> g : array<S, 3>;
   auto* s = Structure("S", {Member("a", ty.f32())});
   auto* a = ty.array(s, 3);
-  auto* ac = ty.access(ast::AccessControl::kRead, a);
-  Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kUniform,
+  Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -225,7 +207,7 @@
 
 TEST_F(ResolverStorageClassValidationTest, UniformBufferBoolAlias) {
   // type a = bool;
-  // var<uniform> g : [[access(read)]] a;
+  // var<uniform> g : a;
   auto* a = ty.alias("a", ty.bool_());
   AST().AddConstructedType(a);
   Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform,
diff --git a/src/resolver/struct_storage_class_use_test.cc b/src/resolver/struct_storage_class_use_test.cc
index 46fe48d..7a83ab0 100644
--- a/src/resolver/struct_storage_class_use_test.cc
+++ b/src/resolver/struct_storage_class_use_test.cc
@@ -172,13 +172,12 @@
 TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
   auto* s = Structure("S", {Member("a", ty.f32())},
                       {create<ast::StructBlockDecoration>()});
-  auto* ac = ty.access(ast::AccessControl::kRead, s);
   Global("x", s, ast::StorageClass::kUniform,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
          });
-  Global("y", ac, ast::StorageClass::kStorage,
+  Global("y", s, ast::StorageClass::kStorage, ast::Access::kRead,
          ast::DecorationList{
              create<ast::BindingDecoration>(1),
              create<ast::GroupDecoration>(0),
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index 38375f8..c7882c0 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -66,10 +66,10 @@
 
 TEST_F(ResolverTypeValidationTest, GlobalConstantWithStorageClass_Fail) {
   // const<in> global_var: f32;
-  AST().AddGlobalVariable(
-      create<ast::Variable>(Source{{12, 34}}, Symbols().Register("global_var"),
-                            ast::StorageClass::kInput, ty.f32(), true,
-                            Expr(1.23f), ast::DecorationList{}));
+  AST().AddGlobalVariable(create<ast::Variable>(
+      Source{{12, 34}}, Symbols().Register("global_var"),
+      ast::StorageClass::kInput, ast::Access::kUndefined, ty.f32(), true,
+      Expr(1.23f), ast::DecorationList{}));
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
@@ -393,11 +393,9 @@
     Params{ast_alias<ast_alias<ast_mat3x3<ast_alias<ast_alias<ast_f32>>>>>,
            sem_mat3x3<sem_f32>},
 
-    Params{ast_alias<ast_access<ast_alias<ast_bool>>>, sem_bool},
-    Params{ast_alias<ast_access<ast_alias<ast_vec3<ast_access<ast_f32>>>>>,
-           sem_vec3<sem_f32>},
-    Params{ast_alias<ast_access<ast_alias<ast_mat3x3<ast_access<ast_f32>>>>>,
-           sem_mat3x3<sem_f32>},
+    Params{ast_alias<ast_alias<ast_bool>>, sem_bool},
+    Params{ast_alias<ast_alias<ast_vec3<ast_f32>>>, sem_vec3<sem_f32>},
+    Params{ast_alias<ast_alias<ast_mat3x3<ast_f32>>>, sem_mat3x3<sem_f32>},
 };
 
 using CanonicalTest = ResolverTestWithParam<Params>;
@@ -526,13 +524,13 @@
 using StorageTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
 TEST_P(StorageTextureDimensionTest, All) {
   // [[group(0), binding(0)]]
-  // var a : [[access(read)]] texture_storage_*<ru32int>;
+  // var a : texture_storage_*<ru32int, read>;
   auto& params = GetParam();
 
-  auto* st = ty.storage_texture(params.dim, ast::ImageFormat::kR32Uint);
-  auto* ac = ty.access(ast::AccessControl::kRead, st);
+  auto* st = ty.storage_texture(Source{{12, 34}}, params.dim,
+                                ast::ImageFormat::kR32Uint, ast::Access::kRead);
 
-  Global(Source{{12, 34}}, "a", ac, ast::StorageClass::kNone, nullptr,
+  Global("a", st, ast::StorageClass::kNone,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -597,42 +595,41 @@
 TEST_P(StorageTextureFormatTest, All) {
   auto& params = GetParam();
   // [[group(0), binding(0)]]
-  // var a : [[access(read)]] texture_storage_1d<*>;
+  // var a : texture_storage_1d<*, read>;
   // [[group(0), binding(1)]]
-  // var b : [[access(read)]] texture_storage_2d<*>;
+  // var b : texture_storage_2d<*, read>;
   // [[group(0), binding(2)]]
-  // var c : [[access(read)]] texture_storage_2d_array<*>;
+  // var c : texture_storage_2d_array<*, read>;
   // [[group(0), binding(3)]]
-  // var d : [[access(read)]] texture_storage_3d<*>;
+  // var d : texture_storage_3d<*, read>;
 
-  auto* st_a = ty.storage_texture(ast::TextureDimension::k1d, params.format);
-  auto* ac_a = ty.access(ast::AccessControl::kRead, st_a);
-  Global(Source{{12, 34}}, "a", ac_a, ast::StorageClass::kNone, nullptr,
+  auto* st_a = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
+                                  params.format, ast::Access::kRead);
+  Global("a", st_a, ast::StorageClass::kNone,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
          });
 
-  auto* st_b = ty.storage_texture(ast::TextureDimension::k2d, params.format);
-  auto* ac_b = ty.access(ast::AccessControl::kRead, st_b);
-  Global("b", ac_b, ast::StorageClass::kNone, nullptr,
+  auto* st_b = ty.storage_texture(ast::TextureDimension::k2d, params.format,
+                                  ast::Access::kRead);
+  Global("b", st_b, ast::StorageClass::kNone,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(1),
          });
 
-  auto* st_c =
-      ty.storage_texture(ast::TextureDimension::k2dArray, params.format);
-  auto* ac_c = ty.access(ast::AccessControl::kRead, st_c);
-  Global("c", ac_c, ast::StorageClass::kNone, nullptr,
+  auto* st_c = ty.storage_texture(ast::TextureDimension::k2dArray,
+                                  params.format, ast::Access::kRead);
+  Global("c", st_c, ast::StorageClass::kNone,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(2),
          });
 
-  auto* st_d = ty.storage_texture(ast::TextureDimension::k3d, params.format);
-  auto* ac_d = ty.access(ast::AccessControl::kRead, st_d);
-  Global("d", ac_d, ast::StorageClass::kNone, nullptr,
+  auto* st_d = ty.storage_texture(ast::TextureDimension::k3d, params.format,
+                                  ast::Access::kRead);
+  Global("d", st_d, ast::StorageClass::kNone,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(3),
@@ -652,16 +649,17 @@
                          StorageTextureFormatTest,
                          testing::ValuesIn(format_cases));
 
-using StorageTextureAccessControlTest = ResolverTest;
+using StorageTextureAccessTest = ResolverTest;
 
-TEST_F(StorageTextureAccessControlTest, MissingAccessControl_Fail) {
+TEST_F(StorageTextureAccessTest, MissingAccess_Fail) {
   // [[group(0), binding(0)]]
   // var a : texture_storage_1d<ru32int>;
 
-  auto* st = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
-                                ast::ImageFormat::kR32Uint);
+  auto* st =
+      ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
+                         ast::ImageFormat::kR32Uint, ast::Access::kUndefined);
 
-  Global("a", st, ast::StorageClass::kNone, nullptr,
+  Global("a", st, ast::StorageClass::kNone,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -672,15 +670,15 @@
             "12:34 error: storage textures must have access control");
 }
 
-TEST_F(StorageTextureAccessControlTest, RWAccessControl_Fail) {
+TEST_F(StorageTextureAccessTest, RWAccess_Fail) {
   // [[group(0), binding(0)]]
-  // var a : [[access(readwrite)]] texture_storage_1d<ru32int>;
+  // var a : texture_storage_1d<ru32int, read_write>;
 
-  auto* st = ty.storage_texture(ast::TextureDimension::k1d,
-                                ast::ImageFormat::kR32Uint);
-  auto* ac = ty.access(ast::AccessControl::kReadWrite, st);
+  auto* st =
+      ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
+                         ast::ImageFormat::kR32Uint, ast::Access::kReadWrite);
 
-  Global(Source{{12, 34}}, "a", ac, ast::StorageClass::kNone, nullptr,
+  Global("a", st, ast::StorageClass::kNone, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -692,15 +690,14 @@
             "write-only access");
 }
 
-TEST_F(StorageTextureAccessControlTest, ReadOnlyAccessControl_Pass) {
+TEST_F(StorageTextureAccessTest, ReadOnlyAccess_Pass) {
   // [[group(0), binding(0)]]
-  // var a : [[access(read)]] texture_storage_1d<ru32int>;
+  // var a : texture_storage_1d<ru32int, read>;
 
   auto* st = ty.storage_texture(ast::TextureDimension::k1d,
-                                ast::ImageFormat::kR32Uint);
-  auto* ac = ty.access(ast::AccessControl::kRead, st);
+                                ast::ImageFormat::kR32Uint, ast::Access::kRead);
 
-  Global("a", ac, ast::StorageClass::kNone, nullptr,
+  Global("a", st, ast::StorageClass::kNone, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),
@@ -709,15 +706,15 @@
   EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(StorageTextureAccessControlTest, WriteOnlyAccessControl_Pass) {
+TEST_F(StorageTextureAccessTest, WriteOnlyAccess_Pass) {
   // [[group(0), binding(0)]]
-  // var a : [[access(write)]] texture_storage_1d<ru32int>;
+  // var a : texture_storage_1d<ru32int, write>;
 
-  auto* st = ty.storage_texture(ast::TextureDimension::k1d,
-                                ast::ImageFormat::kR32Uint);
-  auto* ac = ty.access(ast::AccessControl::kWrite, st);
+  auto* st =
+      ty.storage_texture(ast::TextureDimension::k1d, ast::ImageFormat::kR32Uint,
+                         ast::Access::kWrite);
 
-  Global("a", ac, ast::StorageClass::kNone, nullptr,
+  Global("a", st, ast::StorageClass::kNone, nullptr,
          ast::DecorationList{
              create<ast::BindingDecoration>(0),
              create<ast::GroupDecoration>(0),