Simplify traits, add CastableCommonBase & IsCastable

Use `static constexpr bool` instead of `std::integral_constant` for `IsTypeOrDerived`. This is often cleaner to use.

Add `IsCastable`, a helper for determining if all the template types derive from `CastableBase`.

Add `CastableCommonBase`, some template magic for determinine the most derived, common base type for all castable types.

Change-Id: Ia3d33548424750f8260f518ecd63d39949e4a826
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/81105
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/castable.h b/src/castable.h
index 8977467..2906563 100644
--- a/src/castable.h
+++ b/src/castable.h
@@ -48,12 +48,23 @@
 // Forward declaration
 class CastableBase;
 
+/// Ignore is used as a special type used for skipping over types for trait
+/// helper functions.
+class Ignore {};
+
 namespace detail {
 template <typename T>
 struct TypeInfoOf;
 
 }  // namespace detail
 
+/// True if all template types that are not Ignore derive from CastableBase
+template <typename... TYPES>
+static constexpr bool IsCastable =
+    ((traits::IsTypeOrDerived<TYPES, CastableBase> ||
+      std::is_same_v<TYPES, Ignore>)&&...) &&
+    !(std::is_same_v<TYPES, Ignore> && ...);
+
 /// Helper macro to instantiate the TypeInfo<T> template for `CLASS`.
 #define TINT_INSTANTIATE_TYPEINFO(CLASS)                      \
   TINT_CASTABLE_PUSH_DISABLE_WARNINGS();                      \
@@ -145,8 +156,7 @@
   /// multiple hashcodes are bitwise-or'd together.
   template <typename T>
   static constexpr HashCode HashCodeOf() {
-    static_assert(traits::IsTypeOrDerived<T, CastableBase>::value,
-                  "T is not Castable");
+    static_assert(IsCastable<T>, "T is not Castable");
     static_assert(
         std::is_same_v<T, std::remove_cv_t<T>>,
         "Strip const / volatile decorations before calling HashCodeOf");
@@ -455,6 +465,68 @@
   }
 };
 
+namespace detail {
+/// <code>typename CastableCommonBaseImpl<TYPES>::type</code> resolves to the
+/// common base class for all of TYPES.
+template <typename... TYPES>
+struct CastableCommonBaseImpl {};
+
+/// Alias to typename CastableCommonBaseImpl<TYPES>::type
+template <typename... TYPES>
+using CastableCommonBase =
+    typename detail::CastableCommonBaseImpl<TYPES...>::type;
+
+/// CastableCommonBaseImpl template specialization for a single type
+template <typename T>
+struct CastableCommonBaseImpl<T> {
+  /// Common base class of a single type is itself
+  using type = T;
+};
+
+/// CastableCommonBaseImpl A <-> CastableBase specialization
+template <typename A>
+struct CastableCommonBaseImpl<A, CastableBase> {
+  /// Common base class for A and CastableBase is CastableBase
+  using type = CastableBase;
+};
+
+/// CastableCommonBaseImpl T <-> Ignore specialization
+template <typename T>
+struct CastableCommonBaseImpl<T, Ignore> {
+  /// Resolves to T as the other type is ignored
+  using type = T;
+};
+
+/// CastableCommonBaseImpl Ignore <-> T specialization
+template <typename T>
+struct CastableCommonBaseImpl<Ignore, T> {
+  /// Resolves to T as the other type is ignored
+  using type = T;
+};
+
+/// CastableCommonBaseImpl A <-> B specialization
+template <typename A, typename B>
+struct CastableCommonBaseImpl<A, B> {
+  /// The common base class for A, B and OTHERS
+  using type = std::conditional_t<traits::IsTypeOrDerived<A, B>,
+                                  B,  // A derives from B
+                                  CastableCommonBase<A, typename B::TrueBase>>;
+};
+
+/// CastableCommonBaseImpl 3+ types specialization
+template <typename A, typename B, typename... OTHERS>
+struct CastableCommonBaseImpl<A, B, OTHERS...> {
+  /// The common base class for A, B and OTHERS
+  using type = CastableCommonBase<CastableCommonBase<A, B>, OTHERS...>;
+};
+
+}  // namespace detail
+
+/// Resolves to the common most derived type that each of the types in `TYPES`
+/// derives from.
+template <typename... TYPES>
+using CastableCommonBase = detail::CastableCommonBase<TYPES...>;
+
 /// Default can be used as the default case for a Switch(), when all previous
 /// cases failed to match.
 ///
diff --git a/src/castable_test.cc b/src/castable_test.cc
index a15b3d6..4975bef 100644
--- a/src/castable_test.cc
+++ b/src/castable_test.cc
@@ -22,34 +22,15 @@
 namespace tint {
 namespace {
 
-struct Animal : public tint::Castable<Animal> {
-  explicit Animal(std::string n) : name(n) {}
-  const std::string name;
-};
-
-struct Amphibian : public tint::Castable<Amphibian, Animal> {
-  explicit Amphibian(std::string n) : Base(n) {}
-};
-
-struct Mammal : public tint::Castable<Mammal, Animal> {
-  explicit Mammal(std::string n) : Base(n) {}
-};
-
-struct Reptile : public tint::Castable<Reptile, Animal> {
-  explicit Reptile(std::string n) : Base(n) {}
-};
-
-struct Frog : public tint::Castable<Frog, Amphibian> {
-  Frog() : Base("Frog") {}
-};
-
-struct Bear : public tint::Castable<Bear, Mammal> {
-  Bear() : Base("Bear") {}
-};
-
-struct Gecko : public tint::Castable<Gecko, Reptile> {
-  Gecko() : Base("Gecko") {}
-};
+struct Animal : public tint::Castable<Animal> {};
+struct Amphibian : public tint::Castable<Amphibian, Animal> {};
+struct Mammal : public tint::Castable<Mammal, Animal> {};
+struct Reptile : public tint::Castable<Reptile, Animal> {};
+struct Frog : public tint::Castable<Frog, Amphibian> {};
+struct Bear : public tint::Castable<Bear, Mammal> {};
+struct Lizard : public tint::Castable<Lizard, Reptile> {};
+struct Gecko : public tint::Castable<Gecko, Lizard> {};
+struct Iguana : public tint::Castable<Iguana, Lizard> {};
 
 TEST(CastableBase, Is) {
   std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
@@ -417,6 +398,65 @@
   EXPECT_TRUE(default_called);
 }
 
+// IsCastable static tests
+static_assert(IsCastable<CastableBase>);
+static_assert(IsCastable<Animal>);
+static_assert(IsCastable<Ignore, Frog, Bear>);
+static_assert(IsCastable<Mammal, Ignore, Amphibian, Gecko>);
+static_assert(!IsCastable<Mammal, int, Amphibian, Ignore, Gecko>);
+static_assert(!IsCastable<bool>);
+static_assert(!IsCastable<int, float>);
+static_assert(!IsCastable<Ignore>);
+
+// CastableCommonBase static tests
+static_assert(std::is_same_v<Animal, CastableCommonBase<Animal>>);
+static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian>>);
+static_assert(std::is_same_v<Mammal, CastableCommonBase<Mammal>>);
+static_assert(std::is_same_v<Reptile, CastableCommonBase<Reptile>>);
+static_assert(std::is_same_v<Frog, CastableCommonBase<Frog>>);
+static_assert(std::is_same_v<Bear, CastableCommonBase<Bear>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard>>);
+static_assert(std::is_same_v<Gecko, CastableCommonBase<Gecko>>);
+static_assert(std::is_same_v<Iguana, CastableCommonBase<Iguana>>);
+
+static_assert(std::is_same_v<Animal, CastableCommonBase<Animal, Animal>>);
+static_assert(
+    std::is_same_v<Amphibian, CastableCommonBase<Amphibian, Amphibian>>);
+static_assert(std::is_same_v<Mammal, CastableCommonBase<Mammal, Mammal>>);
+static_assert(std::is_same_v<Reptile, CastableCommonBase<Reptile, Reptile>>);
+static_assert(std::is_same_v<Frog, CastableCommonBase<Frog, Frog>>);
+static_assert(std::is_same_v<Bear, CastableCommonBase<Bear, Bear>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard, Lizard>>);
+static_assert(std::is_same_v<Gecko, CastableCommonBase<Gecko, Gecko>>);
+static_assert(std::is_same_v<Iguana, CastableCommonBase<Iguana, Iguana>>);
+
+static_assert(
+    std::is_same_v<CastableBase, CastableCommonBase<CastableBase, Animal>>);
+static_assert(
+    std::is_same_v<CastableBase, CastableCommonBase<Animal, CastableBase>>);
+static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian, Frog>>);
+static_assert(std::is_same_v<Amphibian, CastableCommonBase<Frog, Amphibian>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Reptile, Frog>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Reptile>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Bear, Frog>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Bear>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Gecko, Iguana>>);
+
+static_assert(std::is_same_v<Animal, CastableCommonBase<Bear, Frog, Iguana>>);
+static_assert(
+    std::is_same_v<Lizard, CastableCommonBase<Lizard, Gecko, Iguana>>);
+static_assert(
+    std::is_same_v<Lizard, CastableCommonBase<Gecko, Iguana, Lizard>>);
+static_assert(
+    std::is_same_v<Lizard, CastableCommonBase<Gecko, Lizard, Iguana>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Gecko, Iguana>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Gecko, Iguana, Frog>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Gecko, Frog, Iguana>>);
+
+static_assert(
+    std::is_same_v<CastableBase,
+                   CastableCommonBase<Bear, Frog, Iguana, CastableBase>>);
+
 }  // namespace
 
 TINT_INSTANTIATE_TYPEINFO(Animal);
@@ -425,6 +465,7 @@
 TINT_INSTANTIATE_TYPEINFO(Reptile);
 TINT_INSTANTIATE_TYPEINFO(Frog);
 TINT_INSTANTIATE_TYPEINFO(Bear);
+TINT_INSTANTIATE_TYPEINFO(Lizard);
 TINT_INSTANTIATE_TYPEINFO(Gecko);
 
 }  // namespace tint
diff --git a/src/clone_context.h b/src/clone_context.h
index 974ee90..4037e80 100644
--- a/src/clone_context.h
+++ b/src/clone_context.h
@@ -58,10 +58,10 @@
 
 /// CloneContext holds the state used while cloning AST nodes.
 class CloneContext {
-  /// ParamTypeIsPtrOf<F, T>::value is true iff the first parameter of
+  /// ParamTypeIsPtrOf<F, T> is true iff the first parameter of
   /// F is a pointer of (or derives from) type T.
   template <typename F, typename T>
-  using ParamTypeIsPtrOf = traits::IsTypeOrDerived<
+  static constexpr bool ParamTypeIsPtrOf = traits::IsTypeOrDerived<
       typename std::remove_pointer<traits::ParameterType<F, 0>>::type,
       T>;
 
@@ -295,8 +295,8 @@
   ///        `T* (T*)`, where `T` derives from Cloneable
   /// @returns this CloneContext so calls can be chained
   template <typename F>
-  traits::EnableIf<ParamTypeIsPtrOf<F, Cloneable>::value, CloneContext>&
-  ReplaceAll(F&& replacer) {
+  traits::EnableIf<ParamTypeIsPtrOf<F, Cloneable>, CloneContext>& ReplaceAll(
+      F&& replacer) {
     using TPtr = traits::ParameterType<F, 0>;
     using T = typename std::remove_pointer<TPtr>::type;
     for (auto& transform : transforms_) {
diff --git a/src/program_builder.h b/src/program_builder.h
index 0db8eea..787b64e 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -331,8 +331,8 @@
   /// @returns the node pointer
   template <typename T, typename ARG0, typename... ARGS>
   traits::EnableIf</* T is ast::Node and ARG0 is not Source */
-                   traits::IsTypeOrDerived<T, ast::Node>::value &&
-                       !traits::IsTypeOrDerived<ARG0, Source>::value,
+                   traits::IsTypeOrDerived<T, ast::Node> &&
+                       !traits::IsTypeOrDerived<ARG0, Source>,
                    T>*
   create(ARG0&& arg0, ARGS&&... args) {
     AssertNotMoved();
@@ -346,8 +346,8 @@
   /// @param args the arguments to pass to the type constructor
   /// @returns the node pointer
   template <typename T, typename... ARGS>
-  traits::EnableIf<traits::IsTypeOrDerived<T, sem::Node>::value &&
-                       !traits::IsTypeOrDerived<T, sem::Type>::value,
+  traits::EnableIf<traits::IsTypeOrDerived<T, sem::Node> &&
+                       !traits::IsTypeOrDerived<T, sem::Type>,
                    T>*
   create(ARGS&&... args) {
     AssertNotMoved();
diff --git a/src/traits.h b/src/traits.h
index f8e52a6..ef702d6 100644
--- a/src/traits.h
+++ b/src/traits.h
@@ -86,13 +86,12 @@
 template <typename F>
 using ReturnType = typename SignatureOfT<F>::ret;
 
-/// `IsTypeOrDerived<T, BASE>::value` is true iff `T` is of type `BASE`, or
-/// derives from `BASE`.
+/// IsTypeOrDerived<T, BASE> is true iff `T` is of type `BASE`, or derives from
+/// `BASE`.
 template <typename T, typename BASE>
-using IsTypeOrDerived =
-    std::integral_constant<bool,
-                           std::is_base_of<BASE, Decay<T>>::value ||
-                               std::is_same<BASE, Decay<T>>::value>;
+static constexpr bool IsTypeOrDerived =
+    std::is_base_of<BASE, Decay<T>>::value ||
+    std::is_same<BASE, Decay<T>>::value;
 
 /// If `CONDITION` is true then EnableIf resolves to type T, otherwise an
 /// invalid type.
@@ -102,12 +101,12 @@
 /// If `T` is of type `BASE`, or derives from `BASE`, then EnableIfIsType
 /// resolves to type `T`, otherwise an invalid type.
 template <typename T, typename BASE>
-using EnableIfIsType = EnableIf<IsTypeOrDerived<T, BASE>::value, T>;
+using EnableIfIsType = EnableIf<IsTypeOrDerived<T, BASE>, T>;
 
 /// If `T` is not of type `BASE`, or does not derive from `BASE`, then
 /// EnableIfIsNotType resolves to type `T`, otherwise an invalid type.
 template <typename T, typename BASE>
-using EnableIfIsNotType = EnableIf<!IsTypeOrDerived<T, BASE>::value, T>;
+using EnableIfIsNotType = EnableIf<!IsTypeOrDerived<T, BASE>, T>;
 
 /// @returns the std::index_sequence with all the indices shifted by OFFSET.
 template <std::size_t OFFSET, std::size_t... INDICES>