[wgsl-reader] Parsing image storage type

Bug: tint:147
Change-Id: I79713b5dfa0a4b219cff7a6bb3c514557cf4f261
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/28121
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: David Neto <dneto@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 0318b62..6cbda0d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -898,6 +898,7 @@
     "src/reader/wgsl/parser_impl_global_decl_test.cc",
     "src/reader/wgsl/parser_impl_global_variable_decl_test.cc",
     "src/reader/wgsl/parser_impl_if_stmt_test.cc",
+    "src/reader/wgsl/parser_impl_image_storage_type_test.cc",
     "src/reader/wgsl/parser_impl_import_decl_test.cc",
     "src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc",
     "src/reader/wgsl/parser_impl_logical_and_expression_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0a2fa9b..43e3524 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -429,6 +429,7 @@
     reader/wgsl/parser_impl_global_decl_test.cc
     reader/wgsl/parser_impl_global_variable_decl_test.cc
     reader/wgsl/parser_impl_if_stmt_test.cc
+    reader/wgsl/parser_impl_image_storage_type_test.cc
     reader/wgsl/parser_impl_import_decl_test.cc
     reader/wgsl/parser_impl_inclusive_or_expression_test.cc
     reader/wgsl/parser_impl_logical_and_expression_test.cc
diff --git a/src/ast/type/storage_texture_type.cc b/src/ast/type/storage_texture_type.cc
index f3f3b9f..550d9de 100644
--- a/src/ast/type/storage_texture_type.cc
+++ b/src/ast/type/storage_texture_type.cc
@@ -48,6 +48,9 @@
 
 std::ostream& operator<<(std::ostream& out, ImageFormat format) {
   switch (format) {
+    case ImageFormat::kNone:
+      out << "none";
+      break;
     case ImageFormat::kR8Unorm:
       out << "r8unorm";
       break;
diff --git a/src/ast/type/storage_texture_type.h b/src/ast/type/storage_texture_type.h
index ae6c4fd..2c916a2 100644
--- a/src/ast/type/storage_texture_type.h
+++ b/src/ast/type/storage_texture_type.h
@@ -29,6 +29,7 @@
 
 /// The image format in the storage texture
 enum class ImageFormat {
+  kNone = -1,
   kR8Unorm,
   kR8Snorm,
   kR8Uint,
diff --git a/src/reader/wgsl/lexer.cc b/src/reader/wgsl/lexer.cc
index eacd7ab..b282685 100644
--- a/src/reader/wgsl/lexer.cc
+++ b/src/reader/wgsl/lexer.cc
@@ -515,6 +515,76 @@
     return {Token::Type::kFn, source, "fn"};
   if (str == "for")
     return {Token::Type::kFor, source, "for"};
+  if (str == "bgra8unorm")
+    return {Token::Type::kFormatBgra8Unorm, source, "bgra8unorm"};
+  if (str == "bgra8unorm_srgb")
+    return {Token::Type::kFormatBgra8UnormSrgb, source, "bgra8unorm_srgb"};
+  if (str == "r16float")
+    return {Token::Type::kFormatR16Float, source, "r16float"};
+  if (str == "r16sint")
+    return {Token::Type::kFormatR16Sint, source, "r16sint"};
+  if (str == "r16uint")
+    return {Token::Type::kFormatR16Uint, source, "r16uint"};
+  if (str == "r32float")
+    return {Token::Type::kFormatR32Float, source, "r32float"};
+  if (str == "r32sint")
+    return {Token::Type::kFormatR32Sint, source, "r32sint"};
+  if (str == "r32uint")
+    return {Token::Type::kFormatR32Uint, source, "r32uint"};
+  if (str == "r8sint")
+    return {Token::Type::kFormatR8Sint, source, "r8sint"};
+  if (str == "r8snorm")
+    return {Token::Type::kFormatR8Snorm, source, "r8snorm"};
+  if (str == "r8uint")
+    return {Token::Type::kFormatR8Uint, source, "r8uint"};
+  if (str == "r8unorm")
+    return {Token::Type::kFormatR8Unorm, source, "r8unorm"};
+  if (str == "rg11b10float")
+    return {Token::Type::kFormatRg11B10Float, source, "rg11b10float"};
+  if (str == "rg16float")
+    return {Token::Type::kFormatRg16Float, source, "rg16float"};
+  if (str == "rg16sint")
+    return {Token::Type::kFormatRg16Sint, source, "rg16sint"};
+  if (str == "rg16uint")
+    return {Token::Type::kFormatRg16Uint, source, "rg16uint"};
+  if (str == "rg32float")
+    return {Token::Type::kFormatRg32Float, source, "rg32float"};
+  if (str == "rg32sint")
+    return {Token::Type::kFormatRg32Sint, source, "rg32sint"};
+  if (str == "rg32uint")
+    return {Token::Type::kFormatRg32Uint, source, "rg32uint"};
+  if (str == "rg8sint")
+    return {Token::Type::kFormatRg8Sint, source, "rg8sint"};
+  if (str == "rg8snorm")
+    return {Token::Type::kFormatRg8Snorm, source, "rg8snorm"};
+  if (str == "rg8uint")
+    return {Token::Type::kFormatRg8Uint, source, "rg8uint"};
+  if (str == "rg8unorm")
+    return {Token::Type::kFormatRg8Unorm, source, "rg8unorm"};
+  if (str == "rgb10a2unorm")
+    return {Token::Type::kFormatRgb10A2Unorm, source, "rgb10a2unorm"};
+  if (str == "rgba16float")
+    return {Token::Type::kFormatRgba16Float, source, "rgba16float"};
+  if (str == "rgba16sint")
+    return {Token::Type::kFormatRgba16Sint, source, "rgba16sint"};
+  if (str == "rgba16uint")
+    return {Token::Type::kFormatRgba16Uint, source, "rgba16uint"};
+  if (str == "rgba32float")
+    return {Token::Type::kFormatRgba32Float, source, "rgba32float"};
+  if (str == "rgba32sint")
+    return {Token::Type::kFormatRgba32Sint, source, "rgba32sint"};
+  if (str == "rgba32uint")
+    return {Token::Type::kFormatRgba32Uint, source, "rgba32uint"};
+  if (str == "rgba8sint")
+    return {Token::Type::kFormatRgba8Sint, source, "rgba8sint"};
+  if (str == "rgba8snorm")
+    return {Token::Type::kFormatRgba8Snorm, source, "rgba8snorm"};
+  if (str == "rgba8uint")
+    return {Token::Type::kFormatRgba8Uint, source, "rgba8uint"};
+  if (str == "rgba8unorm")
+    return {Token::Type::kFormatRgba8Unorm, source, "rgba8unorm"};
+  if (str == "rgba8unorm_srgb")
+    return {Token::Type::kFormatRgba8UnormSrgb, source, "rgba8unorm_srgb"};
   if (str == "fragment")
     return {Token::Type::kFragment, source, "fragment"};
   if (str == "function")
diff --git a/src/reader/wgsl/lexer_test.cc b/src/reader/wgsl/lexer_test.cc
index 0dc5bfa..8cddbf1 100644
--- a/src/reader/wgsl/lexer_test.cc
+++ b/src/reader/wgsl/lexer_test.cc
@@ -435,6 +435,41 @@
         TokenData{"false", Token::Type::kFalse},
         TokenData{"fn", Token::Type::kFn},
         TokenData{"for", Token::Type::kFor},
+        TokenData{"bgra8unorm", Token::Type::kFormatBgra8Unorm},
+        TokenData{"bgra8unorm_srgb", Token::Type::kFormatBgra8UnormSrgb},
+        TokenData{"r16float", Token::Type::kFormatR16Float},
+        TokenData{"r16sint", Token::Type::kFormatR16Sint},
+        TokenData{"r16uint", Token::Type::kFormatR16Uint},
+        TokenData{"r32float", Token::Type::kFormatR32Float},
+        TokenData{"r32sint", Token::Type::kFormatR32Sint},
+        TokenData{"r32uint", Token::Type::kFormatR32Uint},
+        TokenData{"r8sint", Token::Type::kFormatR8Sint},
+        TokenData{"r8snorm", Token::Type::kFormatR8Snorm},
+        TokenData{"r8uint", Token::Type::kFormatR8Uint},
+        TokenData{"r8unorm", Token::Type::kFormatR8Unorm},
+        TokenData{"rg11b10float", Token::Type::kFormatRg11B10Float},
+        TokenData{"rg16float", Token::Type::kFormatRg16Float},
+        TokenData{"rg16sint", Token::Type::kFormatRg16Sint},
+        TokenData{"rg16uint", Token::Type::kFormatRg16Uint},
+        TokenData{"rg32float", Token::Type::kFormatRg32Float},
+        TokenData{"rg32sint", Token::Type::kFormatRg32Sint},
+        TokenData{"rg32uint", Token::Type::kFormatRg32Uint},
+        TokenData{"rg8sint", Token::Type::kFormatRg8Sint},
+        TokenData{"rg8snorm", Token::Type::kFormatRg8Snorm},
+        TokenData{"rg8uint", Token::Type::kFormatRg8Uint},
+        TokenData{"rg8unorm", Token::Type::kFormatRg8Unorm},
+        TokenData{"rgb10a2unorm", Token::Type::kFormatRgb10A2Unorm},
+        TokenData{"rgba16float", Token::Type::kFormatRgba16Float},
+        TokenData{"rgba16sint", Token::Type::kFormatRgba16Sint},
+        TokenData{"rgba16uint", Token::Type::kFormatRgba16Uint},
+        TokenData{"rgba32float", Token::Type::kFormatRgba32Float},
+        TokenData{"rgba32sint", Token::Type::kFormatRgba32Sint},
+        TokenData{"rgba32uint", Token::Type::kFormatRgba32Uint},
+        TokenData{"rgba8sint", Token::Type::kFormatRgba8Sint},
+        TokenData{"rgba8snorm", Token::Type::kFormatRgba8Snorm},
+        TokenData{"rgba8uint", Token::Type::kFormatRgba8Uint},
+        TokenData{"rgba8unorm", Token::Type::kFormatRgba8Unorm},
+        TokenData{"rgba8unorm_srgb", Token::Type::kFormatRgba8UnormSrgb},
         TokenData{"fragment", Token::Type::kFragment},
         TokenData{"function", Token::Type::kFunction},
         TokenData{"i32", Token::Type::kI32},
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 4316930..d8bee52 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -723,6 +723,187 @@
   return nullptr;
 }
 
+// image_storage_type
+//  : R8UNORM
+//  | R8SNORM
+//  | R8UINT
+//  | R8SINT
+//  | R16UINT
+//  | R16SINT
+//  | R16FLOAT
+//  | RG8UNORM
+//  | RG8SNORM
+//  | RG8UINT
+//  | RG8SINT
+//  | R32UINT
+//  | R32SINT
+//  | R32FLOAT
+//  | RG16UINT
+//  | RG16SINT
+//  | RG16FLOAT
+//  | RGBA8UNORM
+/// | RGBA8UNORM-SRGB
+//  | RGBA8SNORM
+//  | RGBA8UINT
+//  | RGBA8SINT
+//  | BGRA8UNORM
+//  | BGRA8UNORM-SRGB
+//  | RGB10A2UNORM
+//  | RG11B10FLOAT
+//  | RG32UINT
+//  | RG32SINT
+//  | RG32FLOAT
+//  | RGBA16UINT
+//  | RGBA16SINT
+//  | RGBA16FLOAT
+//  | RGBA32UINT
+//  | RGBA32SINT
+//  | RGBA32FLOAT
+ast::type::ImageFormat ParserImpl::image_storage_type() {
+  auto t = peek();
+  if (t.IsFormatR8Unorm()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kR8Unorm;
+  }
+  if (t.IsFormatR8Snorm()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kR8Snorm;
+  }
+  if (t.IsFormatR8Uint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kR8Uint;
+  }
+  if (t.IsFormatR8Sint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kR8Sint;
+  }
+  if (t.IsFormatR16Uint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kR16Uint;
+  }
+  if (t.IsFormatR16Sint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kR16Sint;
+  }
+  if (t.IsFormatR16Float()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kR16Float;
+  }
+  if (t.IsFormatRg8Unorm()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg8Unorm;
+  }
+  if (t.IsFormatRg8Snorm()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg8Snorm;
+  }
+  if (t.IsFormatRg8Uint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg8Uint;
+  }
+  if (t.IsFormatRg8Sint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg8Sint;
+  }
+  if (t.IsFormatR32Uint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kR32Uint;
+  }
+  if (t.IsFormatR32Sint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kR32Sint;
+  }
+  if (t.IsFormatR32Float()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kR32Float;
+  }
+  if (t.IsFormatRg16Uint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg16Uint;
+  }
+  if (t.IsFormatRg16Sint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg16Sint;
+  }
+  if (t.IsFormatRg16Float()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg16Float;
+  }
+  if (t.IsFormatRgba8Unorm()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba8Unorm;
+  }
+  if (t.IsFormatRgba8UnormSrgb()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba8UnormSrgb;
+  }
+  if (t.IsFormatRgba8Snorm()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba8Snorm;
+  }
+  if (t.IsFormatRgba8Uint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba8Uint;
+  }
+  if (t.IsFormatRgba8Sint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba8Sint;
+  }
+  if (t.IsFormatBgra8Unorm()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kBgra8Unorm;
+  }
+  if (t.IsFormatBgra8UnormSrgb()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kBgra8UnormSrgb;
+  }
+  if (t.IsFormatRgb10A2Unorm()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgb10A2Unorm;
+  }
+  if (t.IsFormatRg11B10Float()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg11B10Float;
+  }
+  if (t.IsFormatRg32Uint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg32Uint;
+  }
+  if (t.IsFormatRg32Sint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg32Sint;
+  }
+  if (t.IsFormatRg32Float()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRg32Float;
+  }
+  if (t.IsFormatRgba16Uint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba16Uint;
+  }
+  if (t.IsFormatRgba16Sint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba16Sint;
+  }
+  if (t.IsFormatRgba16Float()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba16Float;
+  }
+  if (t.IsFormatRgba32Uint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba32Uint;
+  }
+  if (t.IsFormatRgba32Sint()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba32Sint;
+  }
+  if (t.IsFormatRgba32Float()) {
+    next();  // Consume the peek
+    return ast::type::ImageFormat::kRgba32Float;
+  }
+  return ast::type::ImageFormat::kNone;
+}
+
 // variable_ident_decl
 //   : IDENT COLON type_decl
 std::pair<std::string, ast::type::Type*> ParserImpl::variable_ident_decl() {
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 3f3073f..76dea6d 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -40,6 +40,7 @@
 #include "src/ast/struct_decoration.h"
 #include "src/ast/struct_member.h"
 #include "src/ast/struct_member_decoration.h"
+#include "src/ast/type/storage_texture_type.h"
 #include "src/ast/type/texture_type.h"
 #include "src/ast/type/type.h"
 #include "src/ast/variable.h"
@@ -193,6 +194,9 @@
   /// Parses a `depth_texture_type` grammar element
   /// @returns the parsed Type or nullptr if none matched.
   ast::type::Type* depth_texture_type();
+  /// Parses a `image_storage_type` grammar element
+  /// @returns returns the image format or kNone if none matched.
+  ast::type::ImageFormat image_storage_type();
   /// Parses a `function_type_decl` grammar element
   /// @returns the parsed type or nullptr otherwise
   ast::type::Type* function_type_decl();
diff --git a/src/reader/wgsl/parser_impl_image_storage_type_test.cc b/src/reader/wgsl/parser_impl_image_storage_type_test.cc
new file mode 100644
index 0000000..0caed67
--- /dev/null
+++ b/src/reader/wgsl/parser_impl_image_storage_type_test.cc
@@ -0,0 +1,280 @@
+// 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 "gtest/gtest.h"
+#include "src/ast/type/texture_type.h"
+#include "src/reader/wgsl/parser_impl.h"
+#include "src/reader/wgsl/parser_impl_test_helper.h"
+
+namespace tint {
+namespace reader {
+namespace wgsl {
+namespace {
+
+TEST_F(ParserImplTest, ImageStorageType_Invalid) {
+  auto* p = parser("1234");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kNone);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R8Unorm) {
+  auto* p = parser("r8unorm");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kR8Unorm);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R8Snorm) {
+  auto* p = parser("r8snorm");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kR8Snorm);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R8Uint) {
+  auto* p = parser("r8uint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kR8Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R8Sint) {
+  auto* p = parser("r8sint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kR8Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R16Uint) {
+  auto* p = parser("r16uint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kR16Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R16Sint) {
+  auto* p = parser("r16sint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kR16Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R16Float) {
+  auto* p = parser("r16float");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kR16Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg8Unorm) {
+  auto* p = parser("rg8unorm");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg8Unorm);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg8Snorm) {
+  auto* p = parser("rg8snorm");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg8Snorm);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg8Uint) {
+  auto* p = parser("rg8uint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg8Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg8Sint) {
+  auto* p = parser("rg8sint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg8Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R32Uint) {
+  auto* p = parser("r32uint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kR32Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R32Sint) {
+  auto* p = parser("r32sint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kR32Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_R32Float) {
+  auto* p = parser("r32float");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kR32Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg16Uint) {
+  auto* p = parser("rg16uint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg16Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg16Sint) {
+  auto* p = parser("rg16sint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg16Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg16Float) {
+  auto* p = parser("rg16float");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg16Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba8Unorm) {
+  auto* p = parser("rgba8unorm");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba8Unorm);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba8UnormSrgb) {
+  auto* p = parser("rgba8unorm_srgb");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba8UnormSrgb);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba8Snorm) {
+  auto* p = parser("rgba8snorm");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba8Snorm);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba8Uint) {
+  auto* p = parser("rgba8uint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba8Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba8Sint) {
+  auto* p = parser("rgba8sint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba8Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Bgra8Unorm) {
+  auto* p = parser("bgra8unorm");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kBgra8Unorm);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Bgra8UnormSrgb) {
+  auto* p = parser("bgra8unorm_srgb");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kBgra8UnormSrgb);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgb10A2Unorm) {
+  auto* p = parser("rgb10a2unorm");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgb10A2Unorm);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg11B10Float) {
+  auto* p = parser("rg11b10float");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg11B10Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg32Uint) {
+  auto* p = parser("rg32uint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg32Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg32Sint) {
+  auto* p = parser("rg32sint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg32Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rg32Float) {
+  auto* p = parser("rg32float");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRg32Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba16Uint) {
+  auto* p = parser("rgba16uint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba16Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba16Sint) {
+  auto* p = parser("rgba16sint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba16Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba16Float) {
+  auto* p = parser("rgba16float");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba16Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba32Uint) {
+  auto* p = parser("rgba32uint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba32Uint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba32Sint) {
+  auto* p = parser("rgba32sint");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba32Sint);
+  EXPECT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, ImageStorageType_Rgba32Float) {
+  auto* p = parser("rgba32float");
+  auto t = p->image_storage_type();
+  EXPECT_EQ(t, ast::type::ImageFormat::kRgba32Float);
+  EXPECT_FALSE(p->has_error());
+}
+
+}  // namespace
+}  // namespace wgsl
+}  // namespace reader
+}  // namespace tint
diff --git a/src/reader/wgsl/token.cc b/src/reader/wgsl/token.cc
index be6f752..5b9e595 100644
--- a/src/reader/wgsl/token.cc
+++ b/src/reader/wgsl/token.cc
@@ -151,6 +151,76 @@
       return "fn";
     case Token::Type::kFor:
       return "for";
+    case Token::Type::kFormatBgra8Unorm:
+      return "bgra8unorm";
+    case Token::Type::kFormatBgra8UnormSrgb:
+      return "bgra8unorm_srgb";
+    case Token::Type::kFormatR16Float:
+      return "r16float";
+    case Token::Type::kFormatR16Sint:
+      return "r16sint";
+    case Token::Type::kFormatR16Uint:
+      return "r16uint";
+    case Token::Type::kFormatR32Float:
+      return "r32float";
+    case Token::Type::kFormatR32Sint:
+      return "r32sint";
+    case Token::Type::kFormatR32Uint:
+      return "r32uint";
+    case Token::Type::kFormatR8Sint:
+      return "r8sint";
+    case Token::Type::kFormatR8Snorm:
+      return "r8snorm";
+    case Token::Type::kFormatR8Uint:
+      return "r8uint";
+    case Token::Type::kFormatR8Unorm:
+      return "r8unorm";
+    case Token::Type::kFormatRg11B10Float:
+      return "rg11b10float";
+    case Token::Type::kFormatRg16Float:
+      return "rg16float";
+    case Token::Type::kFormatRg16Sint:
+      return "rg16sint";
+    case Token::Type::kFormatRg16Uint:
+      return "rg16uint";
+    case Token::Type::kFormatRg32Float:
+      return "rg32float";
+    case Token::Type::kFormatRg32Sint:
+      return "rg32sint";
+    case Token::Type::kFormatRg32Uint:
+      return "rg32uint";
+    case Token::Type::kFormatRg8Sint:
+      return "rg8sint";
+    case Token::Type::kFormatRg8Snorm:
+      return "rg8snorm";
+    case Token::Type::kFormatRg8Uint:
+      return "rg8uint";
+    case Token::Type::kFormatRg8Unorm:
+      return "rg8unorm";
+    case Token::Type::kFormatRgb10A2Unorm:
+      return "rgb10a2unorm";
+    case Token::Type::kFormatRgba16Float:
+      return "rgba16float";
+    case Token::Type::kFormatRgba16Sint:
+      return "rgba16sint";
+    case Token::Type::kFormatRgba16Uint:
+      return "rgba16uint";
+    case Token::Type::kFormatRgba32Float:
+      return "rgba32float";
+    case Token::Type::kFormatRgba32Sint:
+      return "rgba32sint";
+    case Token::Type::kFormatRgba32Uint:
+      return "rgba32uint";
+    case Token::Type::kFormatRgba8Sint:
+      return "rgba8sint";
+    case Token::Type::kFormatRgba8Snorm:
+      return "rgba8snorm";
+    case Token::Type::kFormatRgba8Uint:
+      return "rgba8uint";
+    case Token::Type::kFormatRgba8Unorm:
+      return "rgba8unorm";
+    case Token::Type::kFormatRgba8UnormSrgb:
+      return "rgba8unorm_srgb";
     case Token::Type::kFragment:
       return "fragment";
     case Token::Type::kFunction:
diff --git a/src/reader/wgsl/token.h b/src/reader/wgsl/token.h
index 1b8d5f7..e893bca 100644
--- a/src/reader/wgsl/token.h
+++ b/src/reader/wgsl/token.h
@@ -162,6 +162,76 @@
     kFn,
     // A 'for'
     kFor,
+    // A 'Bgra8Unorm' format
+    kFormatBgra8Unorm,
+    // A 'Bgra8UnormSrgb' format
+    kFormatBgra8UnormSrgb,
+    // A 'R16Float' format
+    kFormatR16Float,
+    // A 'R16Sint' format
+    kFormatR16Sint,
+    // A 'R16Uint' format
+    kFormatR16Uint,
+    // A 'R32Float' format
+    kFormatR32Float,
+    // A 'R32Sint' format
+    kFormatR32Sint,
+    // A 'R32Uint' format
+    kFormatR32Uint,
+    // A 'R8Sint' format
+    kFormatR8Sint,
+    // A 'R8Snorm' format
+    kFormatR8Snorm,
+    // A 'R8Uint' format
+    kFormatR8Uint,
+    // A 'R8Unorm' format
+    kFormatR8Unorm,
+    // A 'Rg11B10Float' format
+    kFormatRg11B10Float,
+    // A 'Rg16Float' format
+    kFormatRg16Float,
+    // A 'Rg16Sint' format
+    kFormatRg16Sint,
+    // A 'Rg16Uint' format
+    kFormatRg16Uint,
+    // A 'Rg32Float' format
+    kFormatRg32Float,
+    // A 'Rg32Sint' format
+    kFormatRg32Sint,
+    // A 'Rg32Uint' format
+    kFormatRg32Uint,
+    // A 'Rg8Sint' format
+    kFormatRg8Sint,
+    // A 'Rg8Snorm' format
+    kFormatRg8Snorm,
+    // A 'Rg8Uint' format
+    kFormatRg8Uint,
+    // A 'Rg8Unorm' format
+    kFormatRg8Unorm,
+    // A 'Rgb10A2Unorm' format
+    kFormatRgb10A2Unorm,
+    // A 'Rgba16Float' format
+    kFormatRgba16Float,
+    // A 'Rgba16Sint' format
+    kFormatRgba16Sint,
+    // A 'Rgba16Uint' format
+    kFormatRgba16Uint,
+    // A 'Rgba32Float' format
+    kFormatRgba32Float,
+    // A 'Rgba32Sint' format
+    kFormatRgba32Sint,
+    // A 'Rgba32Uint' format
+    kFormatRgba32Uint,
+    // A 'Rgba8Sint' format
+    kFormatRgba8Sint,
+    // A 'Rgba8Snorm' format
+    kFormatRgba8Snorm,
+    // A 'Rgba8Uint' format
+    kFormatRgba8Uint,
+    // A 'Rgba8Unorm' format
+    kFormatRgba8Unorm,
+    // A 'Rgba8UnormSrgb' format
+    kFormatRgba8UnormSrgb,
     /// A 'fragment'
     kFragment,
     /// A 'function'
@@ -449,6 +519,84 @@
   bool IsFn() const { return type_ == Type::kFn; }
   /// @returns true if token is a 'for'
   bool IsFor() const { return type_ == Type::kFor; }
+  /// @returns true if token is a 'Bgra8Unorm' format
+  bool IsFormatBgra8Unorm() const { return type_ == Type::kFormatBgra8Unorm; }
+  /// @returns true if token is a 'Bgra8UnormSrgb' format
+  bool IsFormatBgra8UnormSrgb() const {
+    return type_ == Type::kFormatBgra8UnormSrgb;
+  }
+  /// @returns true if token is a 'R16Float' format
+  bool IsFormatR16Float() const { return type_ == Type::kFormatR16Float; }
+  /// @returns true if token is a 'R16Sint' format
+  bool IsFormatR16Sint() const { return type_ == Type::kFormatR16Sint; }
+  /// @returns true if token is a 'R16Uint' format
+  bool IsFormatR16Uint() const { return type_ == Type::kFormatR16Uint; }
+  /// @returns true if token is a 'R32Float' format
+  bool IsFormatR32Float() const { return type_ == Type::kFormatR32Float; }
+  /// @returns true if token is a 'R32Sint' format
+  bool IsFormatR32Sint() const { return type_ == Type::kFormatR32Sint; }
+  /// @returns true if token is a 'R32Uint' format
+  bool IsFormatR32Uint() const { return type_ == Type::kFormatR32Uint; }
+  /// @returns true if token is a 'R8Sint' format
+  bool IsFormatR8Sint() const { return type_ == Type::kFormatR8Sint; }
+  /// @returns true if token is a 'R8Snorm' format
+  bool IsFormatR8Snorm() const { return type_ == Type::kFormatR8Snorm; }
+  /// @returns true if token is a 'R8Uint' format
+  bool IsFormatR8Uint() const { return type_ == Type::kFormatR8Uint; }
+  /// @returns true if token is a 'R8Unorm' format
+  bool IsFormatR8Unorm() const { return type_ == Type::kFormatR8Unorm; }
+  /// @returns true if token is a 'Rg11B10Float' format
+  bool IsFormatRg11B10Float() const {
+    return type_ == Type::kFormatRg11B10Float;
+  }
+  /// @returns true if token is a 'Rg16Float' format
+  bool IsFormatRg16Float() const { return type_ == Type::kFormatRg16Float; }
+  /// @returns true if token is a 'Rg16Sint' format
+  bool IsFormatRg16Sint() const { return type_ == Type::kFormatRg16Sint; }
+  /// @returns true if token is a 'Rg16Uint' format
+  bool IsFormatRg16Uint() const { return type_ == Type::kFormatRg16Uint; }
+  /// @returns true if token is a 'Rg32Float' format
+  bool IsFormatRg32Float() const { return type_ == Type::kFormatRg32Float; }
+  /// @returns true if token is a 'Rg32Sint' format
+  bool IsFormatRg32Sint() const { return type_ == Type::kFormatRg32Sint; }
+  /// @returns true if token is a 'Rg32Uint' format
+  bool IsFormatRg32Uint() const { return type_ == Type::kFormatRg32Uint; }
+  /// @returns true if token is a 'Rg8Sint' format
+  bool IsFormatRg8Sint() const { return type_ == Type::kFormatRg8Sint; }
+  /// @returns true if token is a 'Rg8Snorm' format
+  bool IsFormatRg8Snorm() const { return type_ == Type::kFormatRg8Snorm; }
+  /// @returns true if token is a 'Rg8Uint' format
+  bool IsFormatRg8Uint() const { return type_ == Type::kFormatRg8Uint; }
+  /// @returns true if token is a 'Rg8Unorm' format
+  bool IsFormatRg8Unorm() const { return type_ == Type::kFormatRg8Unorm; }
+  /// @returns true if token is a 'Rgb10A2Unorm' format
+  bool IsFormatRgb10A2Unorm() const {
+    return type_ == Type::kFormatRgb10A2Unorm;
+  }
+  /// @returns true if token is a 'Rgba16Float' format
+  bool IsFormatRgba16Float() const { return type_ == Type::kFormatRgba16Float; }
+  /// @returns true if token is a 'Rgba16Sint' format
+  bool IsFormatRgba16Sint() const { return type_ == Type::kFormatRgba16Sint; }
+  /// @returns true if token is a 'Rgba16Uint' format
+  bool IsFormatRgba16Uint() const { return type_ == Type::kFormatRgba16Uint; }
+  /// @returns true if token is a 'Rgba32Float' format
+  bool IsFormatRgba32Float() const { return type_ == Type::kFormatRgba32Float; }
+  /// @returns true if token is a 'Rgba32Sint' format
+  bool IsFormatRgba32Sint() const { return type_ == Type::kFormatRgba32Sint; }
+  /// @returns true if token is a 'Rgba32Uint' format
+  bool IsFormatRgba32Uint() const { return type_ == Type::kFormatRgba32Uint; }
+  /// @returns true if token is a 'Rgba8Sint' format
+  bool IsFormatRgba8Sint() const { return type_ == Type::kFormatRgba8Sint; }
+  /// @returns true if token is a 'Rgba8Snorm' format
+  bool IsFormatRgba8Snorm() const { return type_ == Type::kFormatRgba8Snorm; }
+  /// @returns true if token is a 'Rgba8Uint' format
+  bool IsFormatRgba8Uint() const { return type_ == Type::kFormatRgba8Uint; }
+  /// @returns true if token is a 'Rgba8Unorm' format
+  bool IsFormatRgba8Unorm() const { return type_ == Type::kFormatRgba8Unorm; }
+  /// @returns true if token is a 'Rgba8UnormSrgb' format
+  bool IsFormatRgba8UnormSrgb() const {
+    return type_ == Type::kFormatRgba8UnormSrgb;
+  }
   /// @returns true if token is a 'fragment'
   bool IsFragment() const { return type_ == Type::kFragment; }
   /// @returns true if token is a 'function'
diff --git a/src/type_determiner.cc b/src/type_determiner.cc
index d2d3810..e94f9d6 100644
--- a/src/type_determiner.cc
+++ b/src/type_determiner.cc
@@ -937,6 +937,9 @@
           ctx_.type_mgr().Get(std::make_unique<ast::type::F32Type>()));
       return true;
     }
+
+    case ast::type::ImageFormat::kNone:
+      break;
   }
 
   return false;
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 3ac0751..7bbfa02 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -2407,6 +2407,8 @@
       return SpvImageFormatRgba32i;
     case ast::type::ImageFormat::kRgba32Float:
       return SpvImageFormatRgba32f;
+    case ast::type::ImageFormat::kNone:
+      return SpvImageFormatUnknown;
   }
   return SpvImageFormatUnknown;
 }