[spir-reader] Emit alias types (named types)

Bug: tint:3
Change-Id: I4882160d9fe533d956f29ca15e65bf99eb80f5df
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/18360
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2c45704..ba455ac 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -327,6 +327,7 @@
     reader/spirv/parser_impl_entry_point_test.cc
     reader/spirv/parser_impl_get_decorations_test.cc
     reader/spirv/parser_impl_import_test.cc
+    reader/spirv/parser_impl_named_types_test.cc
     reader/spirv/parser_impl_user_name_test.cc
     reader/spirv/parser_impl_test.cc
     reader/spirv/parser_test.cc
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 571bd57..2480a34 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -32,6 +32,7 @@
 #include "src/ast/struct_member.h"
 #include "src/ast/struct_member_decoration.h"
 #include "src/ast/struct_member_offset_decoration.h"
+#include "src/ast/type/alias_type.h"
 #include "src/ast/type/array_type.h"
 #include "src/ast/type/bool_type.h"
 #include "src/ast/type/f32_type.h"
@@ -77,7 +78,6 @@
         // For binary validation errors, we only have the instruction
         // number.  It's not text, so there is no column number.
         this->Fail() << "line:" << position.index << ": " << message;
-        this->Fail() << "error: line " << position.index << ": " << message;
     }
   };
 }
@@ -85,23 +85,27 @@
 ParserImpl::~ParserImpl() = default;
 
 bool ParserImpl::Parse() {
+  // Set up use of SPIRV-Tools utilities.
+  spvtools::SpirvTools spv_tools(kTargetEnv);
+
+  // Error messages from SPIRV-Tools are forwarded as failures, including
+  // setting |success_| to false.
+  spv_tools.SetMessageConsumer(message_consumer_);
+
   if (!success_) {
     return false;
   }
 
-  // Set up use of SPIRV-Tools utilities.
-  spvtools::SpirvTools spv_tools(kTargetEnv);
-
-  // Error messages from SPIRV-Tools are forwarded as failures.
-  spv_tools.SetMessageConsumer(message_consumer_);
-
-  // Only consider valid modules.
-  if (success_) {
-    success_ = spv_tools.Validate(spv_binary_);
+  // Only consider valid modules.  On failure, the message consumer
+  // will set the error status.
+  if (!spv_tools.Validate(spv_binary_)) {
+    return false;
   }
-
-  if (success_) {
-    success_ = BuildInternalModule();
+  if (!BuildInternalModule()) {
+    return false;
+  }
+  if (!ParseInternalModule()) {
+    return false;
   }
 
   return success_;
@@ -160,6 +164,11 @@
       return save(ConvertType(spirv_type->AsArray()));
     case spvtools::opt::analysis::Type::kStruct:
       return save(ConvertType(spirv_type->AsStruct()));
+    case spvtools::opt::analysis::Type::kFunction:
+    case spvtools::opt::analysis::Type::kPointer:
+      // For now, just return null without erroring out.
+      // TODO(dneto)
+      return nullptr;
     default:
       break;
   }
@@ -228,6 +237,9 @@
 }
 
 bool ParserImpl::BuildInternalModule() {
+  if (!success_) {
+    return false;
+  }
   tools_.SetMessageConsumer(message_consumer_);
 
   const spv_context& context = tools_context_.CContext();
@@ -243,7 +255,7 @@
   type_mgr_ = ir_context_->get_type_mgr();
   deco_mgr_ = ir_context_->get_decoration_mgr();
 
-  return true;
+  return success_;
 }
 
 void ParserImpl::ResetInternalModule() {
@@ -265,12 +277,18 @@
   if (!RegisterExtendedInstructionImports()) {
     return false;
   }
-  if (!RegisterUserNames()) {
+  if (!RegisterUserAndStructMemberNames()) {
     return false;
   }
   if (!EmitEntryPoints()) {
     return false;
   }
+  if (!RegisterTypes()) {
+    return false;
+  }
+  if (!EmitAliasTypes()) {
+    return false;
+  }
   // TODO(dneto): fill in the rest
   return true;
 }
@@ -297,7 +315,10 @@
   return true;
 }
 
-bool ParserImpl::RegisterUserNames() {
+bool ParserImpl::RegisterUserAndStructMemberNames() {
+  if (!success_) {
+    return false;
+  }
   // Register entry point names. An entry point name is the point of contact
   // between the API and the shader. It has the highest priority for
   // preservation, so register it first.
@@ -490,14 +511,77 @@
   // Now make the struct.
   auto ast_struct = std::make_unique<ast::Struct>(ast_struct_decoration,
                                                   std::move(ast_members));
+  // The struct type will be assigned a name during EmitAliasTypes.
   auto ast_struct_type =
       std::make_unique<ast::type::StructType>(std::move(ast_struct));
-  // The struct might not have a name yet. Suggest one.
+  // Set the struct name before registering it.
   namer_.SuggestSanitizedName(type_id, "S");
   ast_struct_type->set_name(namer_.GetName(type_id));
   return ctx_.type_mgr().Get(std::move(ast_struct_type));
 }
 
+bool ParserImpl::RegisterTypes() {
+  if (!success_) {
+    return false;
+  }
+  for (auto& type_or_const : module_->types_values()) {
+    const auto* type = type_mgr_->GetType(type_or_const.result_id());
+    if (type == nullptr) {
+      continue;
+    }
+    ConvertType(type_or_const.result_id());
+  }
+  return success_;
+}
+
+bool ParserImpl::EmitAliasTypes() {
+  if (!success_) {
+    return false;
+  }
+  // The algorithm here emits type definitions in the order presented in
+  // the SPIR-V module.  This is valid because:
+  //
+  // - There are no back-references.  OpTypeForwarddPointer is not supported
+  //   by the WebGPU shader programming model.
+  // - Arrays are always sized by an OpConstant of scalar integral type.
+  //   WGSL currently doesn't have specialization constants.
+  //   crbug.com/32 tracks implementation in case they are added.
+  for (auto& type_or_const : module_->types_values()) {
+    const auto type_id = type_or_const.result_id();
+    // We only care about struct, arrays, and runtime arrays.
+    switch (type_or_const.opcode()) {
+      case SpvOpTypeStruct:
+        // The struct already got a name when the type was first registered.
+        break;
+      case SpvOpTypeRuntimeArray:
+        // Runtime arrays are always decorated with ArrayStride so always get a
+        // type alias.
+        namer_.SuggestSanitizedName(type_id, "RTArr");
+        break;
+      case SpvOpTypeArray:
+        // Only make a type aliase for arrays with decorations.
+        if (GetDecorationsFor(type_id).empty()) {
+          continue;
+        }
+        namer_.SuggestSanitizedName(type_id, "Arr");
+        break;
+      default:
+        // Ignore constants, and any other types.
+        continue;
+    }
+    auto* ast_underlying_type = id_to_type_[type_id];
+    if (ast_underlying_type == nullptr) {
+      Fail() << "internal error: no type registered for SPIR-V ID: " << type_id;
+      return false;
+    }
+    const auto name = namer_.GetName(type_id);
+    auto* ast_type = ctx_.type_mgr().Get(
+        std::make_unique<ast::type::AliasType>(name, ast_underlying_type));
+    ast_module_.AddAliasType(ast_type->AsAlias());
+  }
+  return success_;
+}
+
 }  // namespace spirv
 }  // namespace reader
 }  // namespace tint
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 8a7b974..a38ddaf 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -126,33 +126,54 @@
   std::unique_ptr<ast::StructMemberDecoration> ConvertMemberDecoration(
       const Decoration& decoration);
 
- private:
   /// Builds the internal representation of the SPIR-V module.
   /// Assumes the module is somewhat well-formed.  Normally you
   /// would want to validate the SPIR-V module before attempting
   /// to build this internal representation.
-  /// @returns true if successful.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if the parser is still successful.
   bool BuildInternalModule();
 
   /// Walks the internal representation of the module to populate
   /// the AST form of the module.
-  /// @returns true on success
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if the parser is still successful.
   bool ParseInternalModule();
 
   /// Destroys the internal representation of the SPIR-V module.
   void ResetInternalModule();
 
   /// Registers extended instruction imports.  Only "GLSL.std.450" is supported.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
   bool RegisterExtendedInstructionImports();
 
   /// Registers user names for SPIR-V objects, from OpName, and OpMemberName.
   /// Also synthesizes struct field names.  Ensures uniqueness for names for
   /// SPIR-V IDs, and uniqueness of names of fields within any single struct.
-  bool RegisterUserNames();
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool RegisterUserAndStructMemberNames();
 
   /// Emit entry point AST nodes.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
   bool EmitEntryPoints();
 
+  /// Register Tint AST types for SPIR-V types.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool RegisterTypes();
+
+  /// Emit type alias declarations for types requiring user-specified names:
+  /// - struct types
+  /// - decorated arrays and runtime arrays
+  /// TODO(dneto): I expect images and samplers to require names as well.
+  /// This is a no-op if the parser has already failed.
+  /// @returns true if parser is still successful.
+  bool EmitAliasTypes();
+
+ private:
   /// Converts a specific SPIR-V type to a Tint type. Integer case
   ast::type::Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);
   /// Converts a specific SPIR-V type to a Tint type. Float case
diff --git a/src/reader/spirv/parser_impl_convert_type_test.cc b/src/reader/spirv/parser_impl_convert_type_test.cc
index 7dd24df..f5daed0 100644
--- a/src/reader/spirv/parser_impl_convert_type_test.cc
+++ b/src/reader/spirv/parser_impl_convert_type_test.cc
@@ -53,7 +53,7 @@
 
 TEST_F(SpvParserTest, ConvertType_NotAnId) {
   auto p = parser(test::Assemble("%1 = OpExtInstImport \"GLSL.std.450\""));
-  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
   EXPECT_EQ(type, nullptr);
@@ -63,7 +63,7 @@
 
 TEST_F(SpvParserTest, ConvertType_IdExistsButIsNotAType) {
   auto p = parser(test::Assemble("%1 = OpExtInstImport \"GLSL.std.450\""));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(1);
   EXPECT_EQ(nullptr, type);
@@ -73,7 +73,7 @@
 TEST_F(SpvParserTest, ConvertType_UnhandledType) {
   // Pipes are an OpenCL type. Tint doesn't support them.
   auto p = parser(test::Assemble("%70 = OpTypePipe WriteOnly"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(70);
   EXPECT_EQ(nullptr, type);
@@ -82,7 +82,7 @@
 
 TEST_F(SpvParserTest, ConvertType_Void) {
   auto p = parser(test::Assemble("%1 = OpTypeVoid"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(1);
   EXPECT_TRUE(type->IsVoid());
@@ -91,7 +91,7 @@
 
 TEST_F(SpvParserTest, ConvertType_Bool) {
   auto p = parser(test::Assemble("%100 = OpTypeBool"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(100);
   EXPECT_TRUE(type->IsBool());
@@ -100,7 +100,7 @@
 
 TEST_F(SpvParserTest, ConvertType_I32) {
   auto p = parser(test::Assemble("%2 = OpTypeInt 32 1"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(2);
   EXPECT_TRUE(type->IsI32());
@@ -109,7 +109,7 @@
 
 TEST_F(SpvParserTest, ConvertType_U32) {
   auto p = parser(test::Assemble("%3 = OpTypeInt 32 0"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
   EXPECT_TRUE(type->IsU32());
@@ -118,7 +118,7 @@
 
 TEST_F(SpvParserTest, ConvertType_F32) {
   auto p = parser(test::Assemble("%4 = OpTypeFloat 32"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(4);
   EXPECT_TRUE(type->IsF32());
@@ -127,7 +127,7 @@
 
 TEST_F(SpvParserTest, ConvertType_BadIntWidth) {
   auto p = parser(test::Assemble("%5 = OpTypeInt 17 1"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(5);
   EXPECT_EQ(type, nullptr);
@@ -136,7 +136,7 @@
 
 TEST_F(SpvParserTest, ConvertType_BadFloatWidth) {
   auto p = parser(test::Assemble("%6 = OpTypeFloat 19"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(6);
   EXPECT_EQ(type, nullptr);
@@ -148,7 +148,7 @@
     %5 = OpTypePipe ReadOnly
     %20 = OpTypeVector %5 2
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(20);
   EXPECT_EQ(type, nullptr);
@@ -162,7 +162,7 @@
     %30 = OpTypeVector %float 3
     %40 = OpTypeVector %float 4
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* v2xf32 = p->ConvertType(20);
   EXPECT_TRUE(v2xf32->IsVector());
@@ -189,7 +189,7 @@
     %30 = OpTypeVector %int 3
     %40 = OpTypeVector %int 4
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* v2xi32 = p->ConvertType(20);
   EXPECT_TRUE(v2xi32->IsVector());
@@ -216,7 +216,7 @@
     %30 = OpTypeVector %uint 3
     %40 = OpTypeVector %uint 4
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* v2xu32 = p->ConvertType(20);
   EXPECT_TRUE(v2xu32->IsVector());
@@ -242,7 +242,7 @@
     %10 = OpTypeVector %5 2
     %20 = OpTypeMatrix %10 2
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(20);
   EXPECT_EQ(type, nullptr);
@@ -268,7 +268,7 @@
     %43 = OpTypeMatrix %v4 3
     %44 = OpTypeMatrix %v4 4
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* m22 = p->ConvertType(22);
   EXPECT_TRUE(m22->IsMatrix());
@@ -332,7 +332,7 @@
     %uint = OpTypeInt 32 0
     %10 = OpTypeRuntimeArray %uint
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
   ASSERT_NE(type, nullptr);
@@ -353,7 +353,7 @@
     %uint_42 = OpConstant %uint 42
     %10 = OpTypeArray %uint %uint_42
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
   ASSERT_NE(type, nullptr);
@@ -375,7 +375,7 @@
     %uint_42 = OpSpecConstant %uint 42
     %10 = OpTypeArray %uint %uint_42
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
   ASSERT_EQ(type, nullptr);
@@ -390,7 +390,7 @@
     %sum = OpSpecConstantOp %uint IAdd %uint_42 %uint_42
     %10 = OpTypeArray %uint %sum
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
   ASSERT_EQ(type, nullptr);
@@ -408,7 +408,7 @@
     %uint64_big = OpConstant %uint64 5000000000
     %10 = OpTypeArray %uint64 %uint64_big
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
   ASSERT_EQ(type, nullptr);
@@ -423,10 +423,11 @@
     %float = OpTypeFloat 32
     %10 = OpTypeStruct %uint %float
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
 
   auto* type = p->ConvertType(10);
-  ASSERT_NE(type, nullptr) << p->error();
+  ASSERT_NE(type, nullptr);
   EXPECT_TRUE(type->IsStruct());
   std::stringstream ss;
   type->AsStruct()->impl()->to_str(ss, 0);
@@ -443,7 +444,8 @@
     %uint = OpTypeInt 32 0
     %10 = OpTypeStruct %uint
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
 
   auto* type = p->ConvertType(10);
   ASSERT_NE(type, nullptr);
@@ -466,10 +468,11 @@
     %mat = OpTypeMatrix %vec 2
     %10 = OpTypeStruct %float %vec %mat
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
 
   auto* type = p->ConvertType(10);
-  ASSERT_NE(type, nullptr) << p->error();
+  ASSERT_NE(type, nullptr);
   EXPECT_TRUE(type->IsStruct());
   std::stringstream ss;
   type->AsStruct()->impl()->to_str(ss, 0);
diff --git a/src/reader/spirv/parser_impl_get_decorations_test.cc b/src/reader/spirv/parser_impl_get_decorations_test.cc
index ef5a5f1..113a7a7 100644
--- a/src/reader/spirv/parser_impl_get_decorations_test.cc
+++ b/src/reader/spirv/parser_impl_get_decorations_test.cc
@@ -97,7 +97,8 @@
   EXPECT_TRUE(p->error().empty());
 }
 
-TEST_F(SpvParserTest, GetDecorationsForMember_OneDecoration) {
+// TODO(dneto): Enable when ArrayStride is handled
+TEST_F(SpvParserTest, DISABLED_GetDecorationsForMember_OneDecoration) {
   auto p = parser(test::Assemble(R"(
     OpMemberDecorate %10 1 ArrayStride 12
     %uint = OpTypeInt 32 0
@@ -105,14 +106,17 @@
     %arr = OpTypeArray %uint %uint_2
     %10 = OpTypeStruct %uint %arr
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
   auto decorations = p->GetDecorationsForMember(10, 1);
   EXPECT_THAT(decorations,
               UnorderedElementsAre(Decoration{SpvDecorationArrayStride, 12}));
   EXPECT_TRUE(p->error().empty());
 }
 
-TEST_F(SpvParserTest, GetDecorationsForMember_MultiDecoration) {
+// TODO(dneto): Enable when ArrayStride, MatrixStride, ColMajor are handled
+// crbug.com/tint/30 for ArrayStride
+// crbug.com/tint/31 for matrix layout
+TEST_F(SpvParserTest, DISABLED_GetDecorationsForMember_MultiDecoration) {
   auto p = parser(test::Assemble(R"(
     OpMemberDecorate %50 1 RelaxedPrecision
     OpMemberDecorate %50 2 ArrayStride 16
@@ -126,7 +130,7 @@
     %arr = OpTypeArray %mat %uint_2
     %50 = OpTypeStruct %uint %float %arr
   )"));
-  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
 
   EXPECT_TRUE(p->GetDecorationsForMember(50, 0).empty());
   EXPECT_THAT(p->GetDecorationsForMember(50, 1),
diff --git a/src/reader/spirv/parser_impl_named_types_test.cc b/src/reader/spirv/parser_impl_named_types_test.cc
new file mode 100644
index 0000000..cebfa10
--- /dev/null
+++ b/src/reader/spirv/parser_impl_named_types_test.cc
@@ -0,0 +1,96 @@
+// 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 <cstdint>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "src/ast/struct.h"
+#include "src/ast/type/array_type.h"
+#include "src/ast/type/matrix_type.h"
+#include "src/ast/type/struct_type.h"
+#include "src/ast/type/vector_type.h"
+#include "src/reader/spirv/parser_impl.h"
+#include "src/reader/spirv/parser_impl_test_helper.h"
+#include "src/reader/spirv/spirv_tools_helpers_test.h"
+#include "src/type_manager.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::HasSubstr;
+
+TEST_F(SpvParserTest, NamedTypes_AnonStruct) {
+  auto p = parser(test::Assemble(R"(
+    %uint = OpTypeInt 32 0
+    %s = OpTypeStruct %uint %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->module().to_str(), HasSubstr("S -> __struct_"));
+}
+
+TEST_F(SpvParserTest, NamedTypes_NamedStruct) {
+  auto p = parser(test::Assemble(R"(
+    OpName %s "mystruct"
+    %uint = OpTypeInt 32 0
+    %s = OpTypeStruct %uint %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->module().to_str(), HasSubstr("mystruct -> __struct_"));
+}
+
+// TODO(dneto): Enable this when array types can have ArrayStride
+TEST_F(SpvParserTest, DISABLED_NamedTypes_AnonArrayWithDecoration) {
+  auto p = parser(test::Assemble(R"(
+    OpDecorate %arr ArrayStride 16
+    %uint = OpTypeInt 32 0
+    %uint_3 = OpConstant %uint 3
+    %arr = OpTypeArray %uint %uint_3
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->module().to_str(), HasSubstr("Arr -> __array__u32"));
+}
+
+// TODO(dneto): Should we make an alias for an un-decoratrd array with
+// an OpName?
+
+TEST_F(SpvParserTest, NamedTypes_AnonRTArray) {
+  auto p = parser(test::Assemble(R"(
+    %uint = OpTypeInt 32 0
+    %arr = OpTypeRuntimeArray %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->module().to_str(), HasSubstr("RTArr -> __array__u32"));
+}
+
+TEST_F(SpvParserTest, NamedTypes_NamedRTArray) {
+  auto p = parser(test::Assemble(R"(
+    OpName %arr "myrtarr"
+    %uint = OpTypeInt 32 0
+    %arr = OpTypeRuntimeArray %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->module().to_str(), HasSubstr("myrtarr -> __array__u32"));
+}
+
+// TODO(dneto): Handle arrays sized by a spec constant.
+// Blocked by crbug.com/tint/32
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint