Optimization: Add type manager.

Type manager will construct a map of types gradually from
instructions.
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index ef1b0c6..ed9b99f 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -11,6 +11,7 @@
   passes.h
   pass_manager.h
   types.h
+  type_manager.h
 
   def_use_manager.cpp
   function.cpp
@@ -20,6 +21,7 @@
   module.cpp
   passes.cpp
   types.cpp
+  type_manager.cpp
 )
 
 spvtools_default_compile_options(SPIRV-Tools-opt)
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index 67385e9..d83d21e 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -30,7 +30,7 @@
 namespace spvtools {
 namespace ir {
 
-std::vector<Instruction*> Module::types() {
+std::vector<Instruction*> Module::GetTypes() {
   std::vector<Instruction*> insts;
   for (uint32_t i = 0; i < types_values_.size(); ++i) {
     if (IsTypeInst(types_values_[i]->opcode()))
@@ -39,6 +39,15 @@
   return insts;
 };
 
+std::vector<const Instruction*> Module::GetTypes() const {
+  std::vector<const Instruction*> insts;
+  for (uint32_t i = 0; i < types_values_.size(); ++i) {
+    if (IsTypeInst(types_values_[i]->opcode()))
+      insts.push_back(types_values_[i].get());
+  }
+  return insts;
+};
+
 std::vector<Instruction*> Module::GetConstants() {
   std::vector<Instruction*> insts;
   for (uint32_t i = 0; i < types_values_.size(); ++i) {
diff --git a/source/opt/module.h b/source/opt/module.h
index 8eca515..a463296 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -83,7 +83,8 @@
 
   // Returns a vector of pointers to type-declaration instructions in this
   // module.
-  std::vector<Instruction*> types();
+  std::vector<Instruction*> GetTypes();
+  std::vector<const Instruction*> GetTypes() const;
   // Returns the constant-defining instructions.
   std::vector<Instruction*> GetConstants();
   const std::vector<std::unique_ptr<Instruction>>& debugs() const {
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
new file mode 100644
index 0000000..c01ae4a
--- /dev/null
+++ b/source/opt/type_manager.cpp
@@ -0,0 +1,226 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include <algorithm>
+#include <cassert>
+
+#include "reflect.h"
+#include "type_manager.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+
+Type* TypeManager::GetType(uint32_t id) const {
+  if (id_to_type_.count(id) != 0) return id_to_type_.at(id).get();
+  return nullptr;
+}
+
+ForwardPointer* TypeManager::GetForwardPointer(uint32_t index) const {
+  if (index >= forward_pointers_.size()) return nullptr;
+  return forward_pointers_.at(index).get();
+}
+
+void TypeManager::AnalyzeType(const spvtools::ir::Module& module) {
+  for (const auto* inst : module.GetTypes()) RecordIfTypeDefinition(*inst);
+  for (const auto& inst : module.annotations()) AttachIfTypeDecoration(*inst);
+}
+
+Type* TypeManager::RecordIfTypeDefinition(const spvtools::ir::Instruction& inst) {
+  if (!spvtools::ir::IsTypeInst(inst.opcode())) return nullptr;
+
+  Type* type = nullptr;
+  switch (inst.opcode()) {
+    case SpvOpTypeVoid:
+      type = new Void();
+      break;
+    case SpvOpTypeBool:
+      type = new Bool();
+      break;
+    case SpvOpTypeInt:
+      type = new Integer(inst.GetSingleWordInOperand(0),
+                         inst.GetSingleWordInOperand(1));
+      break;
+    case SpvOpTypeFloat:
+      type = new Float(inst.GetSingleWordInOperand(0));
+      break;
+    case SpvOpTypeVector:
+      type = new Vector(GetType(inst.GetSingleWordInOperand(0)),
+                        inst.GetSingleWordInOperand(1));
+      break;
+    case SpvOpTypeMatrix:
+      type = new Matrix(GetType(inst.GetSingleWordInOperand(0)),
+                        inst.GetSingleWordInOperand(1));
+      break;
+    case SpvOpTypeImage: {
+      const SpvAccessQualifier access =
+          inst.NumInOperands() < 8
+              ? SpvAccessQualifierReadOnly
+              : static_cast<SpvAccessQualifier>(inst.GetSingleWordInOperand(7));
+      type = new Image(
+          GetType(inst.GetSingleWordInOperand(0)),
+          static_cast<SpvDim>(inst.GetSingleWordInOperand(1)),
+          inst.GetSingleWordInOperand(2), inst.GetSingleWordInOperand(3),
+          inst.GetSingleWordInOperand(4), inst.GetSingleWordInOperand(5),
+          static_cast<SpvImageFormat>(inst.GetSingleWordInOperand(6)), access);
+    } break;
+    case SpvOpTypeSampler:
+      type = new Sampler();
+      break;
+    case SpvOpTypeSampledImage:
+      type = new SampledImage(GetType(inst.GetSingleWordInOperand(0)));
+      break;
+    case SpvOpTypeArray:
+      type = new Array(GetType(inst.GetSingleWordInOperand(0)),
+                       inst.GetSingleWordInOperand(1));
+      break;
+    case SpvOpTypeRuntimeArray:
+      type = new RuntimeArray(GetType(inst.GetSingleWordInOperand(0)));
+      break;
+    case SpvOpTypeStruct: {
+      std::vector<Type*> element_types;
+      for (uint32_t i = 0; i < inst.NumInOperands(); ++i) {
+        element_types.push_back(GetType(inst.GetSingleWordInOperand(i)));
+      }
+      type = new Struct(element_types);
+    } break;
+    case SpvOpTypeOpaque: {
+      const uint32_t* data = inst.GetInOperand(0).words.data();
+      type = new Opaque(reinterpret_cast<const char*>(data));
+    } break;
+    case SpvOpTypePointer: {
+      auto* ptr = new Pointer(
+          GetType(inst.GetSingleWordInOperand(1)),
+          static_cast<SpvStorageClass>(inst.GetSingleWordInOperand(0)));
+      // Let's see if somebody forward references this pointer.
+      for (auto* fp : unresolved_forward_pointers_) {
+        if (fp->target_id() == inst.result_id()) {
+          fp->SetTargetPointer(ptr);
+          unresolved_forward_pointers_.erase(fp);
+          break;
+        }
+      }
+      type = ptr;
+    } break;
+    case SpvOpTypeFunction: {
+      Type* return_type = GetType(inst.GetSingleWordInOperand(0));
+      std::vector<Type*> param_types;
+      for (uint32_t i = 1; i < inst.NumInOperands(); ++i) {
+        param_types.push_back(GetType(inst.GetSingleWordInOperand(i)));
+      }
+      type = new Function(return_type, param_types);
+    } break;
+    case SpvOpTypeEvent:
+      type = new Event();
+      break;
+    case SpvOpTypeDeviceEvent:
+      type = new DeviceEvent();
+      break;
+    case SpvOpTypeReserveId:
+      type = new ReserveId();
+      break;
+    case SpvOpTypeQueue:
+      type = new Queue();
+      break;
+    case SpvOpTypePipe:
+      type = new Pipe(
+          static_cast<SpvAccessQualifier>(inst.GetSingleWordInOperand(0)));
+      break;
+    case SpvOpTypeForwardPointer: {
+      // Handling of forward pointers is different from the other types.
+      auto* fp = new ForwardPointer(
+          inst.GetSingleWordInOperand(0),
+          static_cast<SpvStorageClass>(inst.GetSingleWordInOperand(1)));
+      forward_pointers_.emplace_back(fp);
+      unresolved_forward_pointers_.insert(fp);
+      return fp;
+    }
+    case SpvOpTypePipeStorage:
+      type = new PipeStorage();
+      break;
+    case SpvOpTypeNamedBarrier:
+      type = new NamedBarrier();
+      break;
+    default:
+      assert(0 && "unhandled type found");
+      break;
+  }
+
+  uint32_t id = inst.result_id();
+  if (id == 0) {
+    assert(inst.opcode() == SpvOpTypeForwardPointer &&
+           "instruction without result id found");
+  } else {
+    assert(type != nullptr && "type should not be nullptr at this point");
+    id_to_type_[id].reset(type);
+  }
+  return type;
+}
+
+void TypeManager::AttachIfTypeDecoration(const ir::Instruction& inst) {
+  const SpvOp opcode = inst.opcode();
+  if (!ir::IsAnnotationInst(opcode)) return;
+  const uint32_t id = inst.GetSingleWordOperand(0);
+  // Do nothing if the id to be decorated is not for a known type.
+  if (!id_to_type_.count(id)) return;
+
+  Type* target_type = id_to_type_[id].get();
+  switch (opcode) {
+    case SpvOpDecorate: {
+      const auto count = inst.NumOperands();
+      std::vector<uint32_t> data;
+      for (uint32_t i = 1; i < count; ++i) {
+        data.push_back(inst.GetSingleWordOperand(i));
+      }
+      target_type->AddDecoration(std::move(data));
+    } break;
+    case SpvOpMemberDecorate: {
+      const auto count = inst.NumOperands();
+      const uint32_t index = inst.GetSingleWordOperand(1);
+      std::vector<uint32_t> data;
+      for (uint32_t i = 2; i < count; ++i) {
+        data.push_back(inst.GetSingleWordOperand(i));
+      }
+      if (Struct* st = target_type->AsStruct()) {
+        st->AddMemeberDecoration(index, std::move(data));
+      } else {
+        assert(0 && "OpMemberDecorate on non-struct type");
+      }
+    } break;
+    case SpvOpDecorationGroup:
+    case SpvOpGroupDecorate:
+    case SpvOpGroupMemberDecorate:
+      assert(0 && "unhandled decoration");
+      break;
+    default:
+      assert(0 && "unreachable");
+      break;
+  }
+}
+
+}  // namespace analysis
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/type_manager.h b/source/opt/type_manager.h
new file mode 100644
index 0000000..9fb3330
--- /dev/null
+++ b/source/opt/type_manager.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#ifndef LIBSPIRV_OPT_TYPE_MANAGER_H_
+#define LIBSPIRV_OPT_TYPE_MANAGER_H_
+
+#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "module.h"
+#include "types.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+
+// A class for managing the SPIR-V type hierarchy.
+class TypeManager {
+ public:
+  using IdToTypeMap = std::unordered_map<uint32_t, std::unique_ptr<Type>>;
+  using ForwardPointerVector = std::vector<std::unique_ptr<ForwardPointer>>;
+
+  TypeManager() = default;
+  TypeManager(const TypeManager&) = delete;
+  TypeManager(TypeManager&&) = delete;
+  TypeManager& operator=(const TypeManager&) = delete;
+  TypeManager& operator=(TypeManager&&) = delete;
+
+  // Returns the type for the given type |id|. Returns nullptr if the given |id|
+  // does not define a type.
+  Type* GetType(uint32_t id) const;
+  // Returns the number of types hold in this manager.
+  size_t NumTypes() const { return id_to_type_.size(); }
+
+  // Returns the forward pointer type at the given |index|.
+  ForwardPointer* GetForwardPointer(uint32_t index) const;
+  // Returns the number of forward pointer types hold in this manager.
+  size_t NumForwardPointers() const { return forward_pointers_.size(); }
+
+  // Analyzes the types and decorations on types in the given |module|.
+  void AnalyzeType(const spvtools::ir::Module& module);
+
+ private:
+  // Creates and returns a type from the given SPIR-V |inst|. Returns nullptr if
+  // the given instruction is not for defining a type.
+  Type* RecordIfTypeDefinition(const spvtools::ir::Instruction& inst);
+  // Attaches the decoration encoded in |inst| to a type. Does nothing if the
+  // given instruction is not a decoration instruction or not decorating a type.
+  void AttachIfTypeDecoration(const spvtools::ir::Instruction& inst);
+
+  IdToTypeMap id_to_type_;  // Mapping from ids to their type representations.
+  ForwardPointerVector forward_pointers_;  // All forward pointer declarations.
+  // All unresolved forward pointer declarations.
+  // Refers the contents in the above vector.
+  std::unordered_set<ForwardPointer*> unresolved_forward_pointers_;
+};
+
+}  // namespace analysis
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_TYPE_MANAGER_H_
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index f2fa17e..1167e0d 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -68,3 +68,8 @@
   SRCS test_types.cpp
   LIBS SPIRV-Tools-opt
 )
+
+add_spvtools_unittest(TARGET type_manager
+  SRCS test_type_manager.cpp
+  LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
+)
diff --git a/test/opt/test_type_manager.cpp b/test/opt/test_type_manager.cpp
new file mode 100644
index 0000000..b5f460c
--- /dev/null
+++ b/test/opt/test_type_manager.cpp
@@ -0,0 +1,212 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+//    https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "opt/instruction.h"
+#include "opt/libspirv.hpp"
+#include "opt/type_manager.h"
+
+namespace {
+
+using namespace spvtools;
+
+TEST(TypeManager, TypeStrings) {
+  const std::string text = R"(
+    OpTypeForwardPointer !20 !2 ; id for %p is 20, Uniform is 2
+    OpTypeForwardPointer !10000 !1
+    %void    = OpTypeVoid
+    %bool    = OpTypeBool
+    %u32     = OpTypeInt 32 0
+    %id4     = OpConstant %u32 4
+    %s32     = OpTypeInt 32 1
+    %f64     = OpTypeFloat 64
+    %v3u32   = OpTypeVector %u32 3
+    %m3x3    = OpTypeMatrix %v3u32 3
+    %img1    = OpTypeImage %s32 Cube 0 1 1 0 R32f ReadWrite
+    %img2    = OpTypeImage %s32 Cube 0 1 1 0 R32f
+    %sampler = OpTypeSampler
+    %si1     = OpTypeSampledImage %img1
+    %si2     = OpTypeSampledImage %img2
+    %a5u32   = OpTypeArray %u32 %id4
+    %af64    = OpTypeRuntimeArray %f64
+    %st1     = OpTypeStruct %u32
+    %st2     = OpTypeStruct %f64 %s32 %v3u32
+    %opaque1 = OpTypeOpaque ""
+    %opaque2 = OpTypeOpaque "opaque"
+    %p       = OpTypePointer Uniform %st1
+    %f       = OpTypeFunction %void %u32 %u32
+    %event   = OpTypeEvent
+    %de      = OpTypeDeviceEvent
+    %ri      = OpTypeReserveId
+    %queue   = OpTypeQueue
+    %pipe    = OpTypePipe ReadOnly
+    %ps      = OpTypePipeStorage
+    %nb      = OpTypeNamedBarrier
+  )";
+
+  std::vector<std::pair<uint32_t, std::string>> type_id_strs = {
+      {1, "void"},
+      {2, "bool"},
+      {3, "uint32"},
+      // Id 4 is used by the constant.
+      {5, "sint32"},
+      {6, "float64"},
+      {7, "<uint32, 3>"},
+      {8, "<<uint32, 3>, 3>"},
+      {9, "image(sint32, 3, 0, 1, 1, 0, 3, 2)"},
+      {10, "image(sint32, 3, 0, 1, 1, 0, 3, 0)"},
+      {11, "sampler"},
+      {12, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 2))"},
+      {13, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 0))"},
+      {14, "[uint32, id(4)]"},
+      {15, "[float64]"},
+      {16, "{uint32}"},
+      {17, "{float64, sint32, <uint32, 3>}"},
+      {18, "opaque('')"},
+      {19, "opaque('opaque')"},
+      {20, "{uint32}*"},
+      {21, "(uint32, uint32) -> void"},
+      {22, "event"},
+      {23, "device_event"},
+      {24, "reserve_id"},
+      {25, "queue"},
+      {26, "pipe(0)"},
+      {27, "pipe_storage"},
+      {28, "named_barrier"},
+  };
+
+  opt::analysis::TypeManager manager;
+  std::unique_ptr<ir::Module> module =
+      SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(text);
+  manager.AnalyzeType(*module);
+
+  EXPECT_EQ(type_id_strs.size(), manager.NumTypes());
+  EXPECT_EQ(2u, manager.NumForwardPointers());
+
+  for (const auto& p : type_id_strs) {
+    EXPECT_EQ(p.second, manager.GetType(p.first)->str());
+  }
+  EXPECT_EQ("forward_pointer({uint32}*)", manager.GetForwardPointer(0)->str());
+  EXPECT_EQ("forward_pointer(10000)", manager.GetForwardPointer(1)->str());
+}
+
+TEST(Struct, DecorationOnStruct) {
+  const std::string text = R"(
+    OpDecorate %struct1 Block
+    OpDecorate %struct2 Block
+    OpDecorate %struct3 Block
+    OpDecorate %struct4 Block
+
+    %u32 = OpTypeInt 32 0             ; id: 5
+    %f32 = OpTypeFloat 32             ; id: 6
+    %struct1 = OpTypeStruct %u32 %f32 ; base
+    %struct2 = OpTypeStruct %f32 %u32 ; different member order
+    %struct3 = OpTypeStruct %f32      ; different member list
+    %struct4 = OpTypeStruct %u32 %f32 ; the same
+    %struct7 = OpTypeStruct %f32      ; no decoration
+  )";
+  opt::analysis::TypeManager manager;
+  std::unique_ptr<ir::Module> module =
+      SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(text);
+  manager.AnalyzeType(*module);
+
+  ASSERT_EQ(7u, manager.NumTypes());
+  ASSERT_EQ(0u, manager.NumForwardPointers());
+  // Make sure we get ids correct.
+  ASSERT_EQ("uint32", manager.GetType(5)->str());
+  ASSERT_EQ("float32", manager.GetType(6)->str());
+
+  // Try all combinations of pairs. Expect to be the same type only when the
+  // same id or (1, 4).
+  for (const auto id1 : {1, 2, 3, 4, 7}) {
+    for (const auto id2 : {1, 2, 3, 4, 7}) {
+      if (id1 == id2 || (id1 == 1 && id2 == 4) || (id1 == 4 && id2 == 1)) {
+        EXPECT_TRUE(manager.GetType(id1)->IsSame(manager.GetType(id2)))
+            << "%struct" << id1 << " is expected to be the same as %struct"
+            << id2;
+      } else {
+        EXPECT_FALSE(manager.GetType(id1)->IsSame(manager.GetType(id2)))
+            << "%struct" << id1 << " is expected to be different with %struct"
+            << id2;
+      }
+    }
+  }
+}
+
+TEST(Struct, DecorationOnMember) {
+  const std::string text = R"(
+    OpMemberDecorate %struct1  0 Offset 0
+    OpMemberDecorate %struct2  0 Offset 0
+    OpMemberDecorate %struct3  0 Offset 0
+    OpMemberDecorate %struct4  0 Offset 0
+    OpMemberDecorate %struct5  1 Offset 0
+    OpMemberDecorate %struct6  0 Offset 4
+
+    OpDecorate %struct7 Block
+    OpMemberDecorate %struct7  0 Offset 0
+
+    %u32 = OpTypeInt 32 0              ; id: 8
+    %f32 = OpTypeFloat 32              ; id: 9
+    %struct1  = OpTypeStruct %u32 %f32 ; base
+    %struct2  = OpTypeStruct %f32 %u32 ; different member order
+    %struct3  = OpTypeStruct %f32      ; different member list
+    %struct4  = OpTypeStruct %u32 %f32 ; the same
+    %struct5  = OpTypeStruct %u32 %f32 ; member decorate different field
+    %struct6  = OpTypeStruct %u32 %f32 ; different member decoration parameter
+    %struct7  = OpTypeStruct %u32 %f32 ; extra decoration on the struct
+    %struct10 = OpTypeStruct %u32 %f32 ; no member decoration
+  )";
+  opt::analysis::TypeManager manager;
+  std::unique_ptr<ir::Module> module =
+      SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(text);
+  manager.AnalyzeType(*module);
+
+  ASSERT_EQ(10u, manager.NumTypes());
+  ASSERT_EQ(0u, manager.NumForwardPointers());
+  // Make sure we get ids correct.
+  ASSERT_EQ("uint32", manager.GetType(8)->str());
+  ASSERT_EQ("float32", manager.GetType(9)->str());
+
+  // Try all combinations of pairs. Expect to be the same type only when the
+  // same id or (1, 4).
+  for (const auto id1 : {1, 2, 3, 4, 5, 6, 7, 10}) {
+    for (const auto id2 : {1, 2, 3, 4, 5, 6, 7, 10}) {
+      if (id1 == id2 || (id1 == 1 && id2 == 4) || (id1 == 4 && id2 == 1)) {
+        EXPECT_TRUE(manager.GetType(id1)->IsSame(manager.GetType(id2)))
+            << "%struct" << id1 << " is expected to be the same as %struct"
+            << id2;
+      } else {
+        EXPECT_FALSE(manager.GetType(id1)->IsSame(manager.GetType(id2)))
+            << "%struct" << id1 << " is expected to be different with %struct"
+            << id2;
+      }
+    }
+  }
+}
+
+}  // anonymous namespace