Castable: Add FLAGS template argument for Is,As

[Is,As] contain a static_assert() that checks a cast is actually possible (TO -> FROM share a common base class).
This has been extremely valuable - it's caught numerious impossible casts due to stupid mistakes - however it makes certain generic templates impossible to write.

Add a compile-time FLAGS argument to Is() and As(), which accepts a new kDontErrorOnImpossibleCast flag.
When specified, this static_assert will always pass, allowing impossible casts to be attempted.

Change-Id: I5ff434b329c04d007f4e6976301bf30c73ab3f8d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/47775
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/castable.h b/src/castable.h
index 460e509..884ba5f 100644
--- a/src/castable.h
+++ b/src/castable.h
@@ -44,6 +44,9 @@
 struct TypeInfoOf;
 }  // namespace detail
 
+// Forward declaration
+class CastableBase;
+
 /// Helper macro to instantiate the TypeInfo<T> template for `CLASS`.
 #define TINT_INSTANTIATE_TYPEINFO(CLASS)                      \
   TINT_CASTABLE_PUSH_DISABLE_WARNINGS();                      \
@@ -88,17 +91,36 @@
 template <typename TO_FIRST, typename... TO_REST>
 struct IsAnyOf;
 
+/// A placeholder structure used for template parameters that need a default
+/// type, but can always be automatically inferred.
+struct Infer;
+
 }  // namespace detail
 
+/// Bit flags that can be passed to the template parameter `FLAGS` of Is() and
+/// As().
+enum CastFlags {
+  /// Disables the static_assert() inside Is(), that compile-time-verifies that
+  /// the cast is possible. This flag may be useful for highly-generic template
+  /// code that needs to compile for template permutations that generate
+  /// impossible casts.
+  kDontErrorOnImpossibleCast = 1,
+};
+
 /// @returns true if `obj` is a valid pointer, and is of, or derives from the
 /// class `TO`
 /// @param obj the object to test from
-template <typename TO, typename FROM>
+/// @see CastFlags
+template <typename TO, int FLAGS = 0, typename FROM = detail::Infer>
 inline bool Is(FROM* obj) {
   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");
+  constexpr const bool assert_is_castable =
+      (FLAGS & kDontErrorOnImpossibleCast) == 0;
+
+  static_assert(upcast || downcast || nocast || !assert_is_castable,
+                "impossible cast");
 
   if (obj == nullptr) {
     return false;
@@ -116,7 +138,11 @@
 /// @param obj the object to test from
 /// @param pred predicate function with signature `bool(const TO*)` called iff
 /// object is of, or derives from the class `TO`.
-template <typename TO, typename FROM, typename Pred>
+/// @see CastFlags
+template <typename TO,
+          int FLAGS = 0,
+          typename FROM = detail::Infer,
+          typename Pred = detail::Infer>
 inline bool Is(FROM* obj, Pred&& pred) {
   constexpr const bool downcast = std::is_base_of<FROM, TO>::value;
   constexpr const bool upcast = std::is_base_of<TO, FROM>::value;
@@ -144,9 +170,14 @@
 /// @returns obj dynamically cast to the type `TO` or `nullptr` if
 /// this object does not derive from `TO`.
 /// @param obj the object to cast from
-template <typename TO, typename FROM>
+/// @see CastFlags
+template <typename TO, int FLAGS = 0, typename FROM = detail::Infer>
 inline TO* As(FROM* obj) {
-  return Is<TO>(obj) ? static_cast<TO*>(obj) : nullptr;
+  using castable =
+      typename std::conditional<std::is_const<FROM>::value, const CastableBase,
+                                CastableBase>::type;
+  auto* as_castable = static_cast<castable*>(obj);
+  return Is<TO, FLAGS>(obj) ? static_cast<TO*>(as_castable) : nullptr;
 }
 
 /// CastableBase is the base class for all Castable objects.
@@ -173,9 +204,9 @@
   /// pred(const TO*) returns true
   /// @param pred predicate function with signature `bool(const TO*)` called iff
   /// object is of, or derives from the class `TO`.
-  template <typename TO, typename Pred>
+  template <typename TO, int FLAGS = 0, typename Pred = detail::Infer>
   inline bool Is(Pred&& pred) const {
-    return tint::Is<TO>(this, std::forward<Pred>(pred));
+    return tint::Is<TO, FLAGS>(this, std::forward<Pred>(pred));
   }
 
   /// @returns true if this object is of, or derives from any of the `TO`
@@ -187,16 +218,18 @@
 
   /// @returns this object dynamically cast to the type `TO` or `nullptr` if
   /// this object does not derive from `TO`.
-  template <typename TO>
+  /// @see CastFlags
+  template <typename TO, int FLAGS = 0>
   inline TO* As() {
-    return tint::As<TO>(this);
+    return tint::As<TO, FLAGS>(this);
   }
 
   /// @returns this object dynamically cast to the type `TO` or `nullptr` if
   /// this object does not derive from `TO`.
-  template <typename TO>
+  /// @see CastFlags
+  template <typename TO, int FLAGS = 0>
   inline const TO* As() const {
-    return tint::As<const TO>(this);
+    return tint::As<const TO, FLAGS>(this);
   }
 
  protected:
@@ -242,19 +275,20 @@
   }
 
   /// @returns true if this object is of, or derives from the class `TO`
-  template <typename TO>
+  /// @see CastFlags
+  template <typename TO, int FLAGS = 0>
   inline bool Is() const {
-    return tint::Is<TO>(static_cast<const CLASS*>(this));
+    return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this));
   }
 
   /// @returns true if this object is of, or derives from the class `TO` and
   /// pred(const TO*) returns true
   /// @param pred predicate function with signature `bool(const TO*)` called iff
   /// object is of, or derives from the class `TO`.
-  template <typename TO, typename Pred>
+  template <typename TO, int FLAGS = 0, typename Pred = detail::Infer>
   inline bool Is(Pred&& pred) const {
-    return tint::Is<TO>(static_cast<const CLASS*>(this),
-                        std::forward<Pred>(pred));
+    return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this),
+                               std::forward<Pred>(pred));
   }
 
   /// @returns true if this object is of, or derives from any of the `TO`
@@ -266,16 +300,18 @@
 
   /// @returns this object dynamically cast to the type `TO` or `nullptr` if
   /// this object does not derive from `TO`.
-  template <typename TO>
+  /// @see CastFlags
+  template <typename TO, int FLAGS = 0>
   inline TO* As() {
-    return tint::As<TO>(this);
+    return tint::As<TO, FLAGS>(this);
   }
 
   /// @returns this object dynamically cast to the type `TO` or `nullptr` if
   /// this object does not derive from `TO`.
-  template <typename TO>
+  /// @see CastFlags
+  template <typename TO, int FLAGS = 0>
   inline const TO* As() const {
-    return tint::As<const TO>(this);
+    return tint::As<const TO, FLAGS>(this);
   }
 };
 
diff --git a/src/castable_test.cc b/src/castable_test.cc
index a62bc3d..0d10aac 100644
--- a/src/castable_test.cc
+++ b/src/castable_test.cc
@@ -73,6 +73,30 @@
   ASSERT_TRUE(gecko->Is<Reptile>());
 }
 
+TEST(CastableBase, Is_kDontErrorOnImpossibleCast) {
+  // Unlike TEST(CastableBase, Is), we're dynamically querying [A -> B] without
+  // going via CastableBase.
+  auto frog = std::make_unique<Frog>();
+  auto bear = std::make_unique<Bear>();
+  auto gecko = std::make_unique<Gecko>();
+
+  ASSERT_TRUE((frog->Is<Animal, kDontErrorOnImpossibleCast>()));
+  ASSERT_TRUE((bear->Is<Animal, kDontErrorOnImpossibleCast>()));
+  ASSERT_TRUE((gecko->Is<Animal, kDontErrorOnImpossibleCast>()));
+
+  ASSERT_TRUE((frog->Is<Amphibian, kDontErrorOnImpossibleCast>()));
+  ASSERT_FALSE((bear->Is<Amphibian, kDontErrorOnImpossibleCast>()));
+  ASSERT_FALSE((gecko->Is<Amphibian, kDontErrorOnImpossibleCast>()));
+
+  ASSERT_FALSE((frog->Is<Mammal, kDontErrorOnImpossibleCast>()));
+  ASSERT_TRUE((bear->Is<Mammal, kDontErrorOnImpossibleCast>()));
+  ASSERT_FALSE((gecko->Is<Mammal, kDontErrorOnImpossibleCast>()));
+
+  ASSERT_FALSE((frog->Is<Reptile, kDontErrorOnImpossibleCast>()));
+  ASSERT_FALSE((bear->Is<Reptile, kDontErrorOnImpossibleCast>()));
+  ASSERT_TRUE((gecko->Is<Reptile, kDontErrorOnImpossibleCast>()));
+}
+
 TEST(CastableBase, IsWithPredicate) {
   std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
 
@@ -135,6 +159,36 @@
   ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
 }
 
+TEST(CastableBase, As_kDontErrorOnImpossibleCast) {
+  // Unlike TEST(CastableBase, As), we're dynamically casting [A -> B] without
+  // going via CastableBase.
+  auto frog = std::make_unique<Frog>();
+  auto bear = std::make_unique<Bear>();
+  auto gecko = std::make_unique<Gecko>();
+
+  ASSERT_EQ((frog->As<Animal, kDontErrorOnImpossibleCast>()),
+            static_cast<Animal*>(frog.get()));
+  ASSERT_EQ((bear->As<Animal, kDontErrorOnImpossibleCast>()),
+            static_cast<Animal*>(bear.get()));
+  ASSERT_EQ((gecko->As<Animal, kDontErrorOnImpossibleCast>()),
+            static_cast<Animal*>(gecko.get()));
+
+  ASSERT_EQ((frog->As<Amphibian, kDontErrorOnImpossibleCast>()),
+            static_cast<Amphibian*>(frog.get()));
+  ASSERT_EQ((bear->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr);
+  ASSERT_EQ((gecko->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr);
+
+  ASSERT_EQ((frog->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr);
+  ASSERT_EQ((bear->As<Mammal, kDontErrorOnImpossibleCast>()),
+            static_cast<Mammal*>(bear.get()));
+  ASSERT_EQ((gecko->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr);
+
+  ASSERT_EQ((frog->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr);
+  ASSERT_EQ((bear->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr);
+  ASSERT_EQ((gecko->As<Reptile, kDontErrorOnImpossibleCast>()),
+            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>();