Add Castable template class

Implements `As<Blah>()` and `Is<Blah>()` automatically.

There are several benefits to using this over the pattern of hand-rolled `IsBlah()`, `AsBlah()` methods:
(1) We don't have to maintain a whole lot of hand written code.
(2) These allow us to cast from the base type to _any_ derived type in a single cast. The existing hand-rolled methods usually require a couple of intermediary casts to go from the base type to the leaf type.
(3) The use of a template parameter means these casts can be called from other template logic.

Note: Unlike the hand-rolled `AsBlah()` methods, it is safe to call `As<T>()` even if the type does not derive from `T`. If the object does not derive from `T` then  `As` will simply return `nullptr`. This allows the calling logic to replace the common pattern of:

```
if (obj.IsBlah()) {
   auto* b = obj.AsBlah();
   ...
}
```

with:

```
if (auto* b = obj.As<Blah>()) {
   ...
}
```

This halves the number of virtual method calls, and is one line shorter.

Change-Id: I4312e9831d7de6703a97184640864b8050a34177
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/34260
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index cd9c72a..af036e5 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -387,6 +387,8 @@
     "src/ast/variable_decoration.h",
     "src/ast/workgroup_decoration.cc",
     "src/ast/workgroup_decoration.h",
+    "src/castable.cc",
+    "src/castable.h",
     "src/context.cc",
     "src/context.h",
     "src/diagnostic/diagnostic.cc",
@@ -805,6 +807,7 @@
     "src/ast/variable_decl_statement_test.cc",
     "src/ast/variable_test.cc",
     "src/ast/workgroup_decoration_test.cc",
+    "src/castable_test.cc",
     "src/diagnostic/formatter_test.cc",
     "src/diagnostic/printer_test.cc",
     "src/inspector/inspector_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 45b2c88..b5c4477 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -208,6 +208,8 @@
   ast/variable_decl_statement.h
   ast/workgroup_decoration.cc
   ast/workgroup_decoration.h
+  castable.cc
+  castable.h
   context.cc
   context.h
   diagnostic/diagnostic.cc
@@ -415,6 +417,7 @@
   ast/variable_decl_statement_test.cc
   ast/variable_test.cc
   ast/workgroup_decoration_test.cc
+  castable_test.cc
   diagnostic/formatter_test.cc
   diagnostic/printer_test.cc
   inspector/inspector_test.cc
diff --git a/src/castable.cc b/src/castable.cc
new file mode 100644
index 0000000..0ddfdb2
--- /dev/null
+++ b/src/castable.cc
@@ -0,0 +1,23 @@
+// 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/castable.h"
+
+namespace tint {
+
+bool CastableBase::Is(ClassID id) const {
+  return ClassID::Of<CastableBase>() == id;
+}
+
+}  // namespace tint
diff --git a/src/castable.h b/src/castable.h
new file mode 100644
index 0000000..165b6fc
--- /dev/null
+++ b/src/castable.h
@@ -0,0 +1,174 @@
+// 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_CASTABLE_H_
+#define SRC_CASTABLE_H_
+
+#include <cstdint>
+#include <type_traits>
+#include <utility>
+
+namespace tint {
+
+/// ClassID represents a unique, comparable identifier for a C++ type.
+class ClassID {
+ public:
+  /// @returns the unique ClassID for the type T.
+  template <typename T>
+  static ClassID Of() {
+    static char _;
+    return ClassID{reinterpret_cast<uintptr_t>(&_)};
+  }
+
+  /// Equality operator
+  /// @param rhs the ClassID to compare against
+  /// @returns true if this ClassID is equal to @p rhs
+  inline bool operator==(ClassID& rhs) const { return id == rhs.id; }
+
+ private:
+  inline explicit ClassID(uintptr_t v) : id(v) {}
+
+  const uintptr_t id;
+};
+
+/// CastableBase is the base class for all Castable objects.
+/// It is not encouraged to directly derive from CastableBase without using the
+/// Castable helper template.
+/// @see Castable
+class CastableBase {
+ public:
+  virtual ~CastableBase() = default;
+
+  /// @returns true if this object is of, or derives from a class with the
+  /// ClassID @p id.
+  /// @param id the ClassID to test for
+  virtual bool Is(ClassID id) const;
+
+  /// @returns true if this object is of, or derives from the class `TO`
+  template <typename TO>
+  bool Is() const {
+    using FROM = CastableBase;
+    constexpr const bool downcast = std::is_base_of<FROM, TO>::value;
+    constexpr const bool upcast = std::is_base_of<TO, FROM>::value;
+    constexpr const bool nocast = std::is_same<FROM, TO>::value;
+    static_assert(upcast || downcast || nocast, "impossible cast");
+
+    if (upcast || nocast) {
+      return true;
+    }
+
+    return Is(ClassID::Of<TO>());
+  }
+
+  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+  /// this object does not derive from `TO`.
+  template <typename TO>
+  inline TO* As() {
+    return Is<TO>() ? static_cast<TO*>(this) : nullptr;
+  }
+
+  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+  /// this object does not derive from `TO`.
+  template <typename TO>
+  inline const TO* As() const {
+    return Is<TO>() ? static_cast<const TO*>(this) : nullptr;
+  }
+
+ protected:
+  CastableBase() = default;
+};
+
+/// Castable is a helper to derive `CLASS` from `BASE`, automatically
+/// implementing the Is() and As() methods, along with a #Base type alias.
+///
+/// Example usage:
+///
+/// ```
+/// class Animal : public Castable<Animal> {};
+///
+/// class Sheep : public Castable<Sheep, Animal> {};
+///
+/// Sheep* cast_to_sheep(Animal* animal) {
+///    // You can query whether a Castable is of the given type with Is<T>():
+///    printf("animal is a sheep? %s", animal->Is<Sheep>() ? "yes" : "no");
+///
+///    // You can always just try the cast with As<T>().
+///    // If the object is not of the correct type, As<T>() will return nullptr:
+///    return animal->As<Sheep>();
+/// }
+/// ```
+template <typename CLASS, typename BASE = CastableBase>
+class Castable : public BASE {
+ public:
+  // Inherit the `BASE` class constructors.
+  using BASE::BASE;
+
+  /// A type alias for `CLASS` to easily access the `BASE` class members.
+  /// Base actually aliases to the Castable instead of `BASE` so that you can
+  /// use Base in the `CLASS` constructor.
+  using Base = Castable;
+
+  /// @returns true if this object is of, or derives from a class with the
+  /// ClassID @p id.
+  /// @param id the ClassID to test for
+  bool Is(ClassID id) const override {
+    return ClassID::Of<CLASS>() == id || BASE::Is(id);
+  }
+
+  /// @returns true if this object is of, or derives from the class `TO`
+  template <typename TO>
+  bool Is() const {
+    using FROM = Castable;
+    constexpr const bool downcast = std::is_base_of<FROM, TO>::value;
+    constexpr const bool upcast = std::is_base_of<TO, FROM>::value;
+    constexpr const bool nocast = std::is_same<FROM, TO>::value;
+    static_assert(upcast || downcast || nocast, "impossible cast");
+
+    if (upcast || nocast) {
+      return true;
+    }
+
+    return Is(ClassID::Of<TO>());
+  }
+
+  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+  /// this object does not derive from `TO`.
+  template <typename TO>
+  inline TO* As() {
+    return Is<TO>() ? static_cast<TO*>(this) : nullptr;
+  }
+
+  /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+  /// this object does not derive from `TO`.
+  template <typename TO>
+  inline const TO* As() const {
+    return Is<TO>() ? static_cast<const TO*>(this) : nullptr;
+  }
+};
+
+/// As() dynamically casts @p obj to the target type `TO`.
+/// @returns the cast object, or nullptr if @p obj is `nullptr` or not of the
+/// type `TO`.
+/// @param obj the object to cast
+template <typename TO, typename FROM>
+inline TO* As(FROM* obj) {
+  if (obj == nullptr) {
+    return nullptr;
+  }
+  return obj->template As<TO>();
+}
+
+}  // namespace tint
+
+#endif  // SRC_CASTABLE_H_
diff --git a/src/castable_test.cc b/src/castable_test.cc
new file mode 100644
index 0000000..5c7a9bd
--- /dev/null
+++ b/src/castable_test.cc
@@ -0,0 +1,144 @@
+// 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/castable.h"
+
+#include <memory>
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+struct Animal : public Castable<Animal> {
+  explicit Animal(std::string n) : name(n) {}
+  const std::string name;
+};
+
+struct Amphibian : public Castable<Amphibian, Animal> {
+  explicit Amphibian(std::string n) : Base(n) {}
+};
+
+struct Mammal : public Castable<Mammal, Animal> {
+  explicit Mammal(std::string n) : Base(n) {}
+};
+
+struct Reptile : public Castable<Reptile, Animal> {
+  explicit Reptile(std::string n) : Base(n) {}
+};
+
+struct Frog : public Castable<Frog, Amphibian> {
+  Frog() : Base("Frog") {}
+};
+
+struct Bear : public Castable<Bear, Mammal> {
+  Bear() : Base("Bear") {}
+};
+
+struct Gecko : public Castable<Gecko, Reptile> {
+  Gecko() : Base("Gecko") {}
+};
+
+}  // namespace
+
+TEST(CastableBase, Is) {
+  std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+  std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
+  std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
+
+  ASSERT_TRUE(frog->Is<Animal>());
+  ASSERT_TRUE(bear->Is<Animal>());
+  ASSERT_TRUE(gecko->Is<Animal>());
+
+  ASSERT_TRUE(frog->Is<Amphibian>());
+  ASSERT_FALSE(bear->Is<Amphibian>());
+  ASSERT_FALSE(gecko->Is<Amphibian>());
+
+  ASSERT_FALSE(frog->Is<Mammal>());
+  ASSERT_TRUE(bear->Is<Mammal>());
+  ASSERT_FALSE(gecko->Is<Mammal>());
+
+  ASSERT_FALSE(frog->Is<Reptile>());
+  ASSERT_FALSE(bear->Is<Reptile>());
+  ASSERT_TRUE(gecko->Is<Reptile>());
+}
+
+TEST(CastableBase, As) {
+  std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+  std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
+  std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
+
+  ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get()));
+  ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get()));
+  ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get()));
+
+  ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get()));
+  ASSERT_EQ(bear->As<Amphibian>(), nullptr);
+  ASSERT_EQ(gecko->As<Amphibian>(), nullptr);
+
+  ASSERT_EQ(frog->As<Mammal>(), nullptr);
+  ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get()));
+  ASSERT_EQ(gecko->As<Mammal>(), nullptr);
+
+  ASSERT_EQ(frog->As<Reptile>(), nullptr);
+  ASSERT_EQ(bear->As<Reptile>(), nullptr);
+  ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
+}
+
+TEST(Castable, Is) {
+  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+
+  ASSERT_TRUE(frog->Is<Animal>());
+  ASSERT_TRUE(bear->Is<Animal>());
+  ASSERT_TRUE(gecko->Is<Animal>());
+
+  ASSERT_TRUE(frog->Is<Amphibian>());
+  ASSERT_FALSE(bear->Is<Amphibian>());
+  ASSERT_FALSE(gecko->Is<Amphibian>());
+
+  ASSERT_FALSE(frog->Is<Mammal>());
+  ASSERT_TRUE(bear->Is<Mammal>());
+  ASSERT_FALSE(gecko->Is<Mammal>());
+
+  ASSERT_FALSE(frog->Is<Reptile>());
+  ASSERT_FALSE(bear->Is<Reptile>());
+  ASSERT_TRUE(gecko->Is<Reptile>());
+}
+
+TEST(Castable, As) {
+  std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+  std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+  std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+
+  ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get()));
+  ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get()));
+  ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get()));
+
+  ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get()));
+  ASSERT_EQ(bear->As<Amphibian>(), nullptr);
+  ASSERT_EQ(gecko->As<Amphibian>(), nullptr);
+
+  ASSERT_EQ(frog->As<Mammal>(), nullptr);
+  ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get()));
+  ASSERT_EQ(gecko->As<Mammal>(), nullptr);
+
+  ASSERT_EQ(frog->As<Reptile>(), nullptr);
+  ASSERT_EQ(bear->As<Reptile>(), nullptr);
+  ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
+}
+
+}  // namespace tint