ast: Have all decorations derive from base class

This is the first step in unifying the way decorations are parsed - i.e. instead of parsing decorations in different ways based on the predicted grammar that follows, we can parse decorations blocks in a unified way, then later verify what we have is as expected.

`StructDecoration` has been transformed from an `enum class` to a proper class so it can derive from `Decoration`.

Bug: tint:282
Bug: tint:291
Change-Id: Iaf12d266068d03edf695acdf2cd21e6cc3ea8eb3
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/31663
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 2165569..1a1aae1 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -255,6 +255,8 @@
     "src/ast/continue_statement.h",
     "src/ast/decorated_variable.cc",
     "src/ast/decorated_variable.h",
+    "src/ast/decoration.cc",
+    "src/ast/decoration.h",
     "src/ast/discard_statement.cc",
     "src/ast/discard_statement.h",
     "src/ast/else_statement.cc",
@@ -311,6 +313,8 @@
     "src/ast/stride_decoration.h",
     "src/ast/struct.cc",
     "src/ast/struct.h",
+    "src/ast/struct_block_decoration.cc",
+    "src/ast/struct_block_decoration.h",
     "src/ast/struct_decoration.cc",
     "src/ast/struct_decoration.h",
     "src/ast/struct_member.cc",
@@ -734,6 +738,7 @@
     "src/ast/case_statement_test.cc",
     "src/ast/constant_id_decoration_test.cc",
     "src/ast/continue_statement_test.cc",
+    "src/ast/decoration_test.cc",
     "src/ast/decorated_variable_test.cc",
     "src/ast/discard_statement_test.cc",
     "src/ast/else_statement_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0eff8bd..f5e1945 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -76,6 +76,8 @@
   ast/continue_statement.h
   ast/decorated_variable.cc
   ast/decorated_variable.h
+  ast/decoration.cc
+  ast/decoration.h
   ast/discard_statement.cc
   ast/discard_statement.h
   ast/else_statement.cc
@@ -132,6 +134,8 @@
   ast/stride_decoration.h
   ast/struct.cc
   ast/struct.h
+  ast/struct_block_decoration.cc
+  ast/struct_block_decoration.h
   ast/struct_decoration.cc
   ast/struct_decoration.h
   ast/struct_member.cc
@@ -344,6 +348,7 @@
   ast/constant_id_decoration_test.cc
   ast/continue_statement_test.cc
   ast/discard_statement_test.cc
+  ast/decoration_test.cc
   ast/decorated_variable_test.cc
   ast/else_statement_test.cc
   ast/expression_test.cc
diff --git a/src/ast/array_decoration.cc b/src/ast/array_decoration.cc
index 8402439..96da686 100644
--- a/src/ast/array_decoration.cc
+++ b/src/ast/array_decoration.cc
@@ -21,7 +21,7 @@
 namespace tint {
 namespace ast {
 
-ArrayDecoration::ArrayDecoration() = default;
+ArrayDecoration::ArrayDecoration() : Decoration(Kind) {}
 
 ArrayDecoration::~ArrayDecoration() = default;
 
diff --git a/src/ast/array_decoration.h b/src/ast/array_decoration.h
index 579668b..a86a4df 100644
--- a/src/ast/array_decoration.h
+++ b/src/ast/array_decoration.h
@@ -19,15 +19,20 @@
 #include <string>
 #include <vector>
 
+#include "src/ast/decoration.h"
+
 namespace tint {
 namespace ast {
 
 class StrideDecoration;
 
 /// A decoration attached to an array
-class ArrayDecoration {
+class ArrayDecoration : public Decoration {
  public:
-  virtual ~ArrayDecoration();
+  /// The kind of decoration that this type represents
+  static constexpr DecorationKind Kind = DecorationKind::kArray;
+
+  ~ArrayDecoration() override;
 
   /// @returns true if this is a stride decoration
   virtual bool IsStride() const;
@@ -39,6 +44,7 @@
   virtual std::string to_str() const = 0;
 
  protected:
+  /// Constructor
   ArrayDecoration();
 };
 
diff --git a/src/ast/decoration.cc b/src/ast/decoration.cc
new file mode 100644
index 0000000..c4abc90
--- /dev/null
+++ b/src/ast/decoration.cc
@@ -0,0 +1,39 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/ast/decoration.h"
+
+namespace tint {
+namespace ast {
+
+Decoration::~Decoration() = default;
+
+std::ostream& operator<<(std::ostream& out, DecorationKind data) {
+  switch (data) {
+    case DecorationKind::kArray:
+      return out << "array";
+    case DecorationKind::kFunction:
+      return out << "function";
+    case DecorationKind::kStruct:
+      return out << "struct";
+    case DecorationKind::kStructMember:
+      return out << "struct member";
+    case DecorationKind::kVariable:
+      return out << "variable";
+  }
+  return out << "<unknown>";
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/decoration.h b/src/ast/decoration.h
new file mode 100644
index 0000000..9c58cca
--- /dev/null
+++ b/src/ast/decoration.h
@@ -0,0 +1,82 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_AST_DECORATION_H_
+#define SRC_AST_DECORATION_H_
+
+#include <memory>
+#include <ostream>
+#include <vector>
+
+#include "src/source.h"
+
+namespace tint {
+namespace ast {
+
+/// The decoration kind enumerator
+enum class DecorationKind {
+  kArray,
+  kFunction,
+  kStruct,
+  kStructMember,
+  kVariable
+};
+
+std::ostream& operator<<(std::ostream& out, DecorationKind data);
+
+/// The base class for all decorations
+class Decoration {
+ public:
+  virtual ~Decoration();
+
+  /// @return the decoration kind
+  DecorationKind GetKind() const { return kind_; }
+
+  /// @return true if this decoration is of (or derives from) type |TO|
+  template <typename TO>
+  bool Is() const {
+    return GetKind() == TO::Kind;
+  }
+
+ protected:
+  /// Constructor
+  /// @param kind represents the derived type
+  explicit Decoration(DecorationKind kind) : kind_(kind) {}
+
+ private:
+  DecorationKind const kind_;
+};
+
+/// As dynamically casts |deco| to the target type |TO|.
+/// @return the dynamically cast decoration, or nullptr if |deco| is not of the
+/// type |TO|.
+template <typename TO>
+std::unique_ptr<TO> As(std::unique_ptr<Decoration>&& deco) {
+  if (deco == nullptr) {
+    return nullptr;
+  }
+  if (deco->Is<TO>()) {
+    auto ptr = static_cast<TO*>(deco.release());
+    return std::unique_ptr<TO>(ptr);
+  }
+  return nullptr;
+}
+
+/// A list of unique decorations
+using DecorationList = std::vector<std::unique_ptr<Decoration>>;
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_DECORATION_H_
diff --git a/src/ast/decoration_test.cc b/src/ast/decoration_test.cc
new file mode 100644
index 0000000..a800494
--- /dev/null
+++ b/src/ast/decoration_test.cc
@@ -0,0 +1,53 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/ast/decoration.h"
+
+#include <sstream>
+#include <utility>
+
+#include "gtest/gtest.h"
+
+#include "src/ast/array_decoration.h"
+#include "src/ast/constant_id_decoration.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using DecorationTest = testing::Test;
+
+TEST_F(DecorationTest, AsCorrectType) {
+  auto* decoration = new ConstantIdDecoration(1);
+  auto upcast = std::unique_ptr<Decoration>(decoration);
+  auto downcast = As<VariableDecoration>(std::move(upcast));
+  EXPECT_EQ(decoration, downcast.get());
+}
+
+TEST_F(DecorationTest, AsIncorrectType) {
+  auto* decoration = new ConstantIdDecoration(1);
+  auto upcast = std::unique_ptr<Decoration>(decoration);
+  auto downcast = As<ArrayDecoration>(std::move(upcast));
+  EXPECT_EQ(nullptr, downcast.get());
+}
+
+TEST_F(DecorationTest, Is) {
+  auto decoration = std::make_unique<ConstantIdDecoration>(1);
+  EXPECT_TRUE(decoration->Is<VariableDecoration>());
+  EXPECT_FALSE(decoration->Is<ArrayDecoration>());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/function_decoration.cc b/src/ast/function_decoration.cc
index d94c2c1..15d7eaa 100644
--- a/src/ast/function_decoration.cc
+++ b/src/ast/function_decoration.cc
@@ -22,7 +22,7 @@
 namespace tint {
 namespace ast {
 
-FunctionDecoration::FunctionDecoration() = default;
+FunctionDecoration::FunctionDecoration() : Decoration(Kind) {}
 
 FunctionDecoration::~FunctionDecoration() = default;
 
diff --git a/src/ast/function_decoration.h b/src/ast/function_decoration.h
index d767138..101f3a9 100644
--- a/src/ast/function_decoration.h
+++ b/src/ast/function_decoration.h
@@ -19,6 +19,8 @@
 #include <ostream>
 #include <vector>
 
+#include "src/ast/decoration.h"
+
 namespace tint {
 namespace ast {
 
@@ -26,9 +28,12 @@
 class WorkgroupDecoration;
 
 /// A decoration attached to a function
-class FunctionDecoration {
+class FunctionDecoration : public Decoration {
  public:
-  virtual ~FunctionDecoration();
+  /// The kind of decoration that this type represents
+  static constexpr DecorationKind Kind = DecorationKind::kFunction;
+
+  ~FunctionDecoration() override;
 
   /// @returns true if this is a stage decoration
   virtual bool IsStage() const;
@@ -42,11 +47,10 @@
 
   /// Outputs the function decoration to the given stream
   /// @param out the stream to output too
-  //! @cond Doxygen_Suppress
   virtual void to_str(std::ostream& out) const = 0;
-  //! @endcond
 
  protected:
+  /// Constructor
   FunctionDecoration();
 };
 
diff --git a/src/ast/struct.cc b/src/ast/struct.cc
index cf3a02c..8f1e23b 100644
--- a/src/ast/struct.cc
+++ b/src/ast/struct.cc
@@ -23,7 +23,9 @@
     : Node(), members_(std::move(members)) {}
 
 Struct::Struct(StructDecorationList decorations, StructMemberList members)
-    : Node(), decorations_(decorations), members_(std::move(members)) {}
+    : Node(),
+      decorations_(std::move(decorations)),
+      members_(std::move(members)) {}
 
 Struct::Struct(const Source& source, StructMemberList members)
     : Node(source), members_(std::move(members)) {}
@@ -31,7 +33,9 @@
 Struct::Struct(const Source& source,
                StructDecorationList decorations,
                StructMemberList members)
-    : Node(source), decorations_(decorations), members_(std::move(members)) {}
+    : Node(source),
+      decorations_(std::move(decorations)),
+      members_(std::move(members)) {}
 
 Struct::Struct(Struct&&) = default;
 
@@ -47,8 +51,8 @@
 }
 
 bool Struct::IsBlockDecorated() const {
-  for (auto deco : decorations_) {
-    if (deco == StructDecoration::kBlock) {
+  for (auto& deco : decorations_) {
+    if (deco->IsBlock()) {
       return true;
     }
   }
@@ -56,11 +60,6 @@
 }
 
 bool Struct::IsValid() const {
-  for (auto deco : decorations_) {
-    if (deco == StructDecoration::kNone) {
-      return false;
-    }
-  }
   for (const auto& mem : members_) {
     if (mem == nullptr || !mem->IsValid()) {
       return false;
@@ -71,9 +70,11 @@
 
 void Struct::to_str(std::ostream& out, size_t indent) const {
   out << "Struct{" << std::endl;
-  for (auto deco : decorations_) {
+  for (auto& deco : decorations_) {
     make_indent(out, indent + 2);
-    out << "[[" << deco << "]]" << std::endl;
+    out << "[[";
+    deco->to_str(out);
+    out << "]]" << std::endl;
   }
   for (const auto& member : members_) {
     member->to_str(out, indent + 2);
diff --git a/src/ast/struct_block_decoration.cc b/src/ast/struct_block_decoration.cc
new file mode 100644
index 0000000..aac4c21
--- /dev/null
+++ b/src/ast/struct_block_decoration.cc
@@ -0,0 +1,33 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/ast/struct_block_decoration.h"
+
+namespace tint {
+namespace ast {
+
+StructBlockDecoration::StructBlockDecoration() = default;
+
+StructBlockDecoration::~StructBlockDecoration() = default;
+
+bool StructBlockDecoration::IsBlock() const {
+  return true;
+}
+
+void StructBlockDecoration::to_str(std::ostream& out) const {
+  out << "block";
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/struct_block_decoration.h b/src/ast/struct_block_decoration.h
new file mode 100644
index 0000000..e5f4eaf
--- /dev/null
+++ b/src/ast/struct_block_decoration.h
@@ -0,0 +1,48 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_AST_STRUCT_BLOCK_DECORATION_H_
+#define SRC_AST_STRUCT_BLOCK_DECORATION_H_
+
+#include <memory>
+#include <ostream>
+#include <vector>
+
+#include "src/ast/struct_decoration.h"
+
+namespace tint {
+namespace ast {
+
+/// The struct decorations
+class StructBlockDecoration : public StructDecoration {
+ public:
+  /// constructor
+  StructBlockDecoration();
+  ~StructBlockDecoration() override;
+
+  /// @returns true if this is a block struct
+  bool IsBlock() const override;
+
+  /// Outputs the decoration to the given stream
+  /// @param out the stream to output too
+  void to_str(std::ostream& out) const override;
+};
+
+/// List of struct decorations
+using StructDecorationList = std::vector<std::unique_ptr<StructDecoration>>;
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_STRUCT_BLOCK_DECORATION_H_
diff --git a/src/ast/struct_decoration.cc b/src/ast/struct_decoration.cc
index 10ee485..f75e16b 100644
--- a/src/ast/struct_decoration.cc
+++ b/src/ast/struct_decoration.cc
@@ -17,19 +17,9 @@
 namespace tint {
 namespace ast {
 
-std::ostream& operator<<(std::ostream& out, StructDecoration stage) {
-  switch (stage) {
-    case StructDecoration::kNone: {
-      out << "none";
-      break;
-    }
-    case StructDecoration::kBlock: {
-      out << "block";
-      break;
-    }
-  }
-  return out;
-}
+StructDecoration::StructDecoration() : Decoration(Kind) {}
+
+StructDecoration::~StructDecoration() = default;
 
 }  // namespace ast
 }  // namespace tint
diff --git a/src/ast/struct_decoration.h b/src/ast/struct_decoration.h
index 20139ac..9386e42 100644
--- a/src/ast/struct_decoration.h
+++ b/src/ast/struct_decoration.h
@@ -15,19 +15,37 @@
 #ifndef SRC_AST_STRUCT_DECORATION_H_
 #define SRC_AST_STRUCT_DECORATION_H_
 
+#include <memory>
 #include <ostream>
 #include <vector>
 
+#include "src/ast/decoration.h"
+
 namespace tint {
 namespace ast {
 
 /// The struct decorations
-enum class StructDecoration { kNone = -1, kBlock };
+class StructDecoration : public Decoration {
+ public:
+  /// The kind of decoration that this type represents
+  static constexpr DecorationKind Kind = DecorationKind::kStruct;
 
-std::ostream& operator<<(std::ostream& out, StructDecoration stage);
+  ~StructDecoration() override;
+
+  /// @returns true if this is a block struct
+  virtual bool IsBlock() const = 0;
+
+  /// Outputs the decoration to the given stream
+  /// @param out the stream to output too
+  virtual void to_str(std::ostream& out) const = 0;
+
+ protected:
+  /// Constructor
+  StructDecoration();
+};
 
 /// List of struct decorations
-using StructDecorationList = std::vector<StructDecoration>;
+using StructDecorationList = std::vector<std::unique_ptr<StructDecoration>>;
 
 }  // namespace ast
 }  // namespace tint
diff --git a/src/ast/struct_member_decoration.cc b/src/ast/struct_member_decoration.cc
index 1c4d154..1e35fd4 100644
--- a/src/ast/struct_member_decoration.cc
+++ b/src/ast/struct_member_decoration.cc
@@ -21,7 +21,8 @@
 namespace tint {
 namespace ast {
 
-StructMemberDecoration::StructMemberDecoration() = default;
+StructMemberDecoration::StructMemberDecoration()
+    : Decoration(DecorationKind::kStructMember) {}
 
 StructMemberDecoration::~StructMemberDecoration() = default;
 
diff --git a/src/ast/struct_member_decoration.h b/src/ast/struct_member_decoration.h
index b54f3eb..b8cbb8a 100644
--- a/src/ast/struct_member_decoration.h
+++ b/src/ast/struct_member_decoration.h
@@ -19,15 +19,20 @@
 #include <string>
 #include <vector>
 
+#include "src/ast/decoration.h"
+
 namespace tint {
 namespace ast {
 
 class StructMemberOffsetDecoration;
 
 /// A decoration attached to a struct member
-class StructMemberDecoration {
+class StructMemberDecoration : public Decoration {
  public:
-  virtual ~StructMemberDecoration();
+  /// The kind of decoration that this type represents
+  static constexpr DecorationKind Kind = DecorationKind::kStructMember;
+
+  ~StructMemberDecoration() override;
 
   /// @returns true if this is an offset decoration
   virtual bool IsOffset() const;
@@ -39,6 +44,7 @@
   virtual std::string to_str() const = 0;
 
  protected:
+  /// Constructor
   StructMemberDecoration();
 };
 
diff --git a/src/ast/struct_test.cc b/src/ast/struct_test.cc
index 55abf23..95f8adb 100644
--- a/src/ast/struct_test.cc
+++ b/src/ast/struct_test.cc
@@ -19,7 +19,7 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "src/ast/struct_decoration.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/ast/struct_member.h"
 #include "src/ast/type/i32_type.h"
 
@@ -52,12 +52,12 @@
       std::make_unique<StructMember>("a", &i32, StructMemberDecorationList()));
 
   StructDecorationList decos;
-  decos.push_back(StructDecoration::kBlock);
+  decos.push_back(std::make_unique<StructBlockDecoration>());
 
   Struct s{std::move(decos), std::move(members)};
   EXPECT_EQ(s.members().size(), 1u);
   ASSERT_EQ(s.decorations().size(), 1u);
-  EXPECT_EQ(s.decorations()[0], StructDecoration::kBlock);
+  EXPECT_TRUE(s.decorations()[0]->IsBlock());
   EXPECT_EQ(s.source().range.begin.line, 0u);
   EXPECT_EQ(s.source().range.begin.column, 0u);
   EXPECT_EQ(s.source().range.end.line, 0u);
@@ -72,14 +72,14 @@
       std::make_unique<StructMember>("a", &i32, StructMemberDecorationList()));
 
   StructDecorationList decos;
-  decos.push_back(StructDecoration::kBlock);
+  decos.push_back(std::make_unique<StructBlockDecoration>());
 
   Struct s{
       Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}},
       std::move(decos), std::move(members)};
   EXPECT_EQ(s.members().size(), 1u);
   ASSERT_EQ(s.decorations().size(), 1u);
-  EXPECT_EQ(s.decorations()[0], StructDecoration::kBlock);
+  EXPECT_TRUE(s.decorations()[0]->IsBlock());
   EXPECT_EQ(s.source().range.begin.line, 27u);
   EXPECT_EQ(s.source().range.begin.column, 4u);
   EXPECT_EQ(s.source().range.end.line, 27u);
@@ -114,20 +114,6 @@
   EXPECT_FALSE(s.IsValid());
 }
 
-TEST_F(StructTest, IsValid_NoneDecoration) {
-  type::I32Type i32;
-
-  StructMemberList members;
-  members.push_back(
-      std::make_unique<StructMember>("a", &i32, StructMemberDecorationList()));
-
-  StructDecorationList decos;
-  decos.push_back(StructDecoration::kNone);
-
-  Struct s{std::move(decos), std::move(members)};
-  EXPECT_FALSE(s.IsValid());
-}
-
 TEST_F(StructTest, ToStr) {
   type::I32Type i32;
 
@@ -136,7 +122,7 @@
       std::make_unique<StructMember>("a", &i32, StructMemberDecorationList()));
 
   StructDecorationList decos;
-  decos.push_back(StructDecoration::kBlock);
+  decos.push_back(std::make_unique<StructBlockDecoration>());
 
   Struct s{std::move(decos), std::move(members)};
 
diff --git a/src/ast/type/access_control_type_test.cc b/src/ast/type/access_control_type_test.cc
index cfb3eb0..6dc716e 100644
--- a/src/ast/type/access_control_type_test.cc
+++ b/src/ast/type/access_control_type_test.cc
@@ -14,6 +14,9 @@
 
 #include "src/ast/type/access_control_type.h"
 
+#include <memory>
+#include <utility>
+
 #include "gtest/gtest.h"
 #include "src/ast/storage_class.h"
 #include "src/ast/stride_decoration.h"
diff --git a/src/ast/type/alias_type_test.cc b/src/ast/type/alias_type_test.cc
index 1b5dbc0..6266cfb 100644
--- a/src/ast/type/alias_type_test.cc
+++ b/src/ast/type/alias_type_test.cc
@@ -14,6 +14,9 @@
 
 #include "src/ast/type/alias_type.h"
 
+#include <memory>
+#include <utility>
+
 #include "gtest/gtest.h"
 #include "src/ast/storage_class.h"
 #include "src/ast/stride_decoration.h"
diff --git a/src/ast/variable_decoration.cc b/src/ast/variable_decoration.cc
index fff88be..2087e68 100644
--- a/src/ast/variable_decoration.cc
+++ b/src/ast/variable_decoration.cc
@@ -25,7 +25,7 @@
 namespace tint {
 namespace ast {
 
-VariableDecoration::VariableDecoration() = default;
+VariableDecoration::VariableDecoration() : Decoration(Kind) {}
 
 VariableDecoration::~VariableDecoration() = default;
 
diff --git a/src/ast/variable_decoration.h b/src/ast/variable_decoration.h
index 8cc5f70..3a13b68 100644
--- a/src/ast/variable_decoration.h
+++ b/src/ast/variable_decoration.h
@@ -20,6 +20,8 @@
 #include <string>
 #include <vector>
 
+#include "src/ast/decoration.h"
+
 namespace tint {
 namespace ast {
 
@@ -30,9 +32,12 @@
 class SetDecoration;
 
 /// A decoration attached to a variable
-class VariableDecoration {
+class VariableDecoration : public Decoration {
  public:
-  virtual ~VariableDecoration();
+  /// The kind of decoration that this type represents
+  static constexpr DecorationKind Kind = DecorationKind::kVariable;
+
+  ~VariableDecoration() override;
 
   /// @returns true if this is a binding decoration
   virtual bool IsBinding() const;
@@ -58,11 +63,10 @@
 
   /// Outputs the variable decoration to the given stream
   /// @param out the stream to output too
-  //! @cond Doxygen_Suppress
   virtual void to_str(std::ostream& out) const = 0;
-  //! @endcond
 
  protected:
+  /// Constructor
   VariableDecoration();
 };
 
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index f3665e7..97d04f7 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -34,6 +34,7 @@
 #include "src/ast/sint_literal.h"
 #include "src/ast/stage_decoration.h"
 #include "src/ast/stride_decoration.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/ast/struct_decoration.h"
 #include "src/ast/struct_member.h"
 #include "src/ast/struct_member_decoration.h"
@@ -268,7 +269,7 @@
 
     ast::StructDecorationList decos;
     if (is_block) {
-      decos.push_back(ast::StructDecoration::kBlock);
+      decos.push_back(std::make_unique<ast::StructBlockDecoration>());
     }
 
     auto str =
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index d38e96c..ab77baf 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -48,6 +48,7 @@
 #include "src/ast/sint_literal.h"
 #include "src/ast/stride_decoration.h"
 #include "src/ast/struct.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/ast/struct_decoration.h"
 #include "src/ast/struct_member.h"
 #include "src/ast/struct_member_decoration.h"
@@ -802,9 +803,11 @@
   if (struct_decorations.size() == 1) {
     const auto decoration = struct_decorations[0][0];
     if (decoration == SpvDecorationBlock) {
-      ast_struct_decorations.push_back(ast::StructDecoration::kBlock);
+      ast_struct_decorations.push_back(
+          std::make_unique<ast::StructBlockDecoration>());
     } else if (decoration == SpvDecorationBufferBlock) {
-      ast_struct_decorations.push_back(ast::StructDecoration::kBlock);
+      ast_struct_decorations.push_back(
+          std::make_unique<ast::StructBlockDecoration>());
       remap_buffer_block_type_.insert(type_id);
     } else {
       Fail() << "struct with ID " << type_id
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index eb43221..a938eca 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -42,6 +42,7 @@
 #include "src/ast/sint_literal.h"
 #include "src/ast/stage_decoration.h"
 #include "src/ast/stride_decoration.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/ast/struct_member_offset_decoration.h"
 #include "src/ast/switch_statement.h"
 #include "src/ast/type/alias_type.h"
@@ -1524,10 +1525,10 @@
   if (has_error()) {
     return false;
   }
-  if (deco == ast::StructDecoration::kNone) {
+  if (deco == nullptr) {
     return true;
   }
-  decos.push_back(deco);
+  decos.emplace_back(std::move(deco));
 
   next();  // Consume the peek of [[
   next();  // Consume the peek from the struct_decoration
@@ -1543,11 +1544,11 @@
 
 // struct_decoration
 //  : BLOCK
-ast::StructDecoration ParserImpl::struct_decoration(Token t) {
+std::unique_ptr<ast::StructDecoration> ParserImpl::struct_decoration(Token t) {
   if (t.IsBlock()) {
-    return ast::StructDecoration::kBlock;
+    return std::make_unique<ast::StructBlockDecoration>();
   }
-  return ast::StructDecoration::kNone;
+  return nullptr;
 }
 
 // struct_body_decl
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 467b6ee..4133f4b 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -189,7 +189,7 @@
   /// Parses a `struct_decoration` grammar element
   /// @param t the current token
   /// @returns the struct decoration or StructDecoraton::kNone if none matched
-  ast::StructDecoration struct_decoration(Token t);
+  std::unique_ptr<ast::StructDecoration> struct_decoration(Token t);
   /// Parses a `struct_body_decl` grammar element
   /// @returns the struct members
   ast::StructMemberList struct_body_decl();
diff --git a/src/reader/wgsl/parser_impl_struct_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decl_test.cc
index 3222117..c217341 100644
--- a/src/reader/wgsl/parser_impl_struct_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decl_test.cc
@@ -51,7 +51,7 @@
   EXPECT_EQ(s->impl()->members()[0]->name(), "a");
   EXPECT_EQ(s->impl()->members()[1]->name(), "b");
   ASSERT_EQ(s->impl()->decorations().size(), 1u);
-  EXPECT_EQ(s->impl()->decorations()[0], ast::StructDecoration::kBlock);
+  EXPECT_TRUE(s->impl()->decorations()[0]->IsBlock());
 }
 
 TEST_F(ParserImplTest, StructDecl_ParsesWithMultipleDecoration) {
@@ -69,8 +69,8 @@
   EXPECT_EQ(s->impl()->members()[0]->name(), "a");
   EXPECT_EQ(s->impl()->members()[1]->name(), "b");
   ASSERT_EQ(s->impl()->decorations().size(), 2u);
-  EXPECT_EQ(s->impl()->decorations()[0], ast::StructDecoration::kBlock);
-  EXPECT_EQ(s->impl()->decorations()[1], ast::StructDecoration::kBlock);
+  EXPECT_TRUE(s->impl()->decorations()[0]->IsBlock());
+  EXPECT_TRUE(s->impl()->decorations()[1]->IsBlock());
 }
 
 TEST_F(ParserImplTest, StructDecl_EmptyMembers) {
diff --git a/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
index 5c0ef8e..3ff9674 100644
--- a/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
@@ -27,7 +27,7 @@
   ASSERT_TRUE(p->struct_decoration_decl(decos));
   ASSERT_FALSE(p->has_error());
   EXPECT_EQ(decos.size(), 1u);
-  EXPECT_EQ(decos[0], ast::StructDecoration::kBlock);
+  EXPECT_TRUE(decos[0]->IsBlock());
 }
 
 TEST_F(ParserImplTest, StructDecorationDecl_MissingAttrRight) {
diff --git a/src/reader/wgsl/parser_impl_struct_decoration_test.cc b/src/reader/wgsl/parser_impl_struct_decoration_test.cc
index 4d07af4..a8da5b2 100644
--- a/src/reader/wgsl/parser_impl_struct_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decoration_test.cc
@@ -24,7 +24,7 @@
 
 struct StructDecorationData {
   const char* input;
-  ast::StructDecoration result;
+  bool is_block;
 };
 inline std::ostream& operator<<(std::ostream& out, StructDecorationData data) {
   out << std::string(data.input);
@@ -40,17 +40,16 @@
 
   auto deco = p->struct_decoration(p->peek());
   ASSERT_FALSE(p->has_error());
-  EXPECT_EQ(deco, params.result);
+  EXPECT_EQ(deco->IsBlock(), params.is_block);
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplTest,
                          StructDecorationTest,
-                         testing::Values(StructDecorationData{
-                             "block", ast::StructDecoration::kBlock}));
+                         testing::Values(StructDecorationData{"block", true}));
 
 TEST_F(ParserImplTest, StructDecoration_NoMatch) {
   auto* p = parser("not-a-stage");
   auto deco = p->struct_decoration(p->peek());
-  ASSERT_EQ(deco, ast::StructDecoration::kNone);
+  ASSERT_EQ(deco, nullptr);
 
   auto t = p->next();
   EXPECT_TRUE(t.IsIdentifier());
diff --git a/src/transform/vertex_pulling_transform.cc b/src/transform/vertex_pulling_transform.cc
index 3ab074d..e8390d7 100644
--- a/src/transform/vertex_pulling_transform.cc
+++ b/src/transform/vertex_pulling_transform.cc
@@ -25,6 +25,7 @@
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/stride_decoration.h"
 #include "src/ast/struct.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/ast/struct_decoration.h"
 #include "src/ast/struct_member.h"
 #include "src/ast/struct_member_offset_decoration.h"
@@ -234,7 +235,7 @@
       kStructBufferName, internal_array_type, std::move(member_dec)));
 
   ast::StructDecorationList decos;
-  decos.push_back(ast::StructDecoration::kBlock);
+  decos.push_back(std::make_unique<ast::StructBlockDecoration>());
 
   auto* struct_type =
       ctx_->type_mgr().Get(std::make_unique<ast::type::StructType>(
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index 62c7bae..274a6be 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -14,6 +14,7 @@
 
 #include "src/ast/module.h"
 #include "src/ast/struct.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/ast/struct_decoration.h"
 #include "src/ast/struct_member.h"
 #include "src/ast/struct_member_decoration.h"
@@ -286,7 +287,7 @@
       std::make_unique<ast::StructMember>("b", &f32, std::move(b_deco)));
 
   ast::StructDecorationList decos;
-  decos.push_back(ast::StructDecoration::kBlock);
+  decos.push_back(std::make_unique<ast::StructBlockDecoration>());
 
   auto str =
       std::make_unique<ast::Struct>(std::move(decos), std::move(members));
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index d49714e..c3a1205 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -15,6 +15,7 @@
 #include "gtest/gtest.h"
 #include "src/ast/module.h"
 #include "src/ast/struct.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/ast/struct_decoration.h"
 #include "src/ast/struct_member.h"
 #include "src/ast/struct_member_decoration.h"
@@ -324,7 +325,7 @@
       std::make_unique<ast::StructMember>("b", &f32, std::move(b_deco)));
 
   ast::StructDecorationList decos;
-  decos.push_back(ast::StructDecoration::kBlock);
+  decos.push_back(std::make_unique<ast::StructBlockDecoration>());
   auto str =
       std::make_unique<ast::Struct>(std::move(decos), std::move(members));
 
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index fe8710e..aef87ed 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -18,6 +18,7 @@
 #include "src/ast/identifier_expression.h"
 #include "src/ast/stride_decoration.h"
 #include "src/ast/struct.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/ast/struct_member.h"
 #include "src/ast/struct_member_offset_decoration.h"
 #include "src/ast/type/access_control_type.h"
@@ -361,7 +362,7 @@
       std::make_unique<ast::StructMember>("a", &f32, std::move(decos)));
 
   ast::StructDecorationList struct_decos;
-  struct_decos.push_back(ast::StructDecoration::kBlock);
+  struct_decos.push_back(std::make_unique<ast::StructBlockDecoration>());
 
   auto s = std::make_unique<ast::Struct>(std::move(struct_decos),
                                          std::move(members));
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index 205df74..50c378b 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -552,8 +552,10 @@
 
 bool GeneratorImpl::EmitStructType(const ast::type::StructType* str) {
   auto* impl = str->impl();
-  for (auto deco : impl->decorations()) {
-    out_ << "[[" << deco << "]]" << std::endl;
+  for (auto& deco : impl->decorations()) {
+    out_ << "[[";
+    deco->to_str(out_);
+    out_ << "]]" << std::endl;
   }
   out_ << "struct " << str->name() << " {" << std::endl;
 
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index a8b5070..2fc6542 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -15,6 +15,7 @@
 #include "gtest/gtest.h"
 #include "src/ast/stride_decoration.h"
 #include "src/ast/struct.h"
+#include "src/ast/struct_block_decoration.h"
 #include "src/ast/struct_decoration.h"
 #include "src/ast/struct_member.h"
 #include "src/ast/struct_member_decoration.h"
@@ -204,7 +205,7 @@
       std::make_unique<ast::StructMember>("b", &f32, std::move(b_deco)));
 
   ast::StructDecorationList decos;
-  decos.push_back(ast::StructDecoration::kBlock);
+  decos.push_back(std::make_unique<ast::StructBlockDecoration>());
 
   auto str =
       std::make_unique<ast::Struct>(std::move(decos), std::move(members));