traits: Add SignatureOf helper

This a more powerful version of the ParamType class.
It provide all the same information as before, plus function return type
and number of parameters.

Change-Id: If03feed0c1b94434fa95340b6b6277621b4f81b4
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/69100
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d1a41c1..021c606 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -675,10 +675,10 @@
     resolver/intrinsic_validation_test.cc
     resolver/is_host_shareable_test.cc
     resolver/is_storeable_test.cc
+    resolver/pipeline_overridable_constant_test.cc
     resolver/ptr_ref_test.cc
     resolver/ptr_ref_validation_test.cc
     resolver/resolver_constants_test.cc
-    resolver/pipeline_overridable_constant_test.cc
     resolver/resolver_test_helper.cc
     resolver/resolver_test_helper.h
     resolver/resolver_test.cc
@@ -693,11 +693,6 @@
     resolver/var_let_test.cc
     resolver/var_let_validation_test.cc
     scope_stack_test.cc
-    sem/intrinsic_test.cc
-    symbol_table_test.cc
-    symbol_test.cc
-    transform/transform_test.cc
-    test_main.cc
     sem/atomic_type_test.cc
     sem/bool_type_test.cc
     sem/depth_multisampled_texture_type_test.cc
@@ -705,6 +700,7 @@
     sem/external_texture_type_test.cc
     sem/f32_type_test.cc
     sem/i32_type_test.cc
+    sem/intrinsic_test.cc
     sem/matrix_type_test.cc
     sem/multisampled_texture_type_test.cc
     sem/pointer_type_test.cc
@@ -718,6 +714,11 @@
     sem/type_manager_test.cc
     sem/u32_type_test.cc
     sem/vector_type_test.cc
+    symbol_table_test.cc
+    symbol_test.cc
+    test_main.cc
+    traits_test.cc
+    transform/transform_test.cc
     utils/defer_test.cc
     utils/enum_set_test.cc
     utils/get_or_create_test.cc
diff --git a/src/ast/traverse_expressions.h b/src/ast/traverse_expressions.h
index 0281906..2792743 100644
--- a/src/ast/traverse_expressions.h
+++ b/src/ast/traverse_expressions.h
@@ -62,7 +62,7 @@
 bool TraverseExpressions(const ast::Expression* root,
                          diag::List& diags,
                          CALLBACK&& callback) {
-  using EXPR_TYPE = std::remove_pointer_t<traits::ParamTypeT<CALLBACK, 0>>;
+  using EXPR_TYPE = std::remove_pointer_t<traits::ParameterType<CALLBACK, 0>>;
   std::vector<const ast::Expression*> to_visit{root};
 
   auto push_pair = [&](const ast::Expression* left,
diff --git a/src/castable.h b/src/castable.h
index 67f1dc2..1b30955 100644
--- a/src/castable.h
+++ b/src/castable.h
@@ -303,7 +303,8 @@
   /// object is of, or derives from the class `TO`.
   template <int FLAGS = 0, typename Pred = detail::Infer>
   inline bool Is(Pred&& pred) const {
-    using TO = typename std::remove_pointer<traits::ParamTypeT<Pred, 0>>::type;
+    using TO =
+        typename std::remove_pointer<traits::ParameterType<Pred, 0>>::type;
     return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this),
                                std::forward<Pred>(pred));
   }
diff --git a/src/clone_context.h b/src/clone_context.h
index e399deb..fd47a2f 100644
--- a/src/clone_context.h
+++ b/src/clone_context.h
@@ -62,7 +62,7 @@
   /// F is a pointer of (or derives from) type T.
   template <typename F, typename T>
   using ParamTypeIsPtrOf = traits::IsTypeOrDerived<
-      typename std::remove_pointer<traits::ParamTypeT<F, 0>>::type,
+      typename std::remove_pointer<traits::ParameterType<F, 0>>::type,
       T>;
 
  public:
@@ -274,7 +274,7 @@
   template <typename F>
   traits::EnableIf<ParamTypeIsPtrOf<F, Cloneable>::value, CloneContext>&
   ReplaceAll(F&& replacer) {
-    using TPtr = traits::ParamTypeT<F, 0>;
+    using TPtr = traits::ParameterType<F, 0>;
     using T = typename std::remove_pointer<TPtr>::type;
     for (auto& transform : transforms_) {
       if (transform.typeinfo->Is(TypeInfo::Of<T>()) ||
diff --git a/src/traits.h b/src/traits.h
index 1fda4f0..5b933b9 100644
--- a/src/traits.h
+++ b/src/traits.h
@@ -28,44 +28,63 @@
 template <int N, typename... Types>
 using NthTypeOf = typename std::tuple_element<N, std::tuple<Types...>>::type;
 
-/// ParamType is a traits helper that infers the type of the `N`th parameter
-/// of the function, method, static method, lambda, or function-like object `F`.
-template <typename F, int N>
-struct ParamType {
-  /// The type of the `N`th parameter of the function-like object `F`
-  using type = typename ParamType<decltype(&F::operator()), N>::type;
+/// Signature describes the signature of a function.
+template <typename RETURN, typename... PARAMETERS>
+struct Signature {
+  /// The return type of the function signature
+  using ret = RETURN;
+  /// The parameters of the function signature held in a std::tuple
+  using parameters = std::tuple<PARAMETERS...>;
+  /// The type of the Nth parameter of function signature
+  template <std::size_t N>
+  using parameter = NthTypeOf<N, PARAMETERS...>;
+  /// The total number of parameters
+  static constexpr std::size_t parameter_count = sizeof...(PARAMETERS);
 };
 
-/// ParamType specialization for a regular function or static method.
-template <typename R, int N, typename... Args>
-struct ParamType<R (*)(Args...), N> {
-  /// Arg is the raw type of the `N`th parameter of the function
-  using Arg = NthTypeOf<N, Args...>;
-  /// The type of the `N`th parameter of the function
-  using type = Decay<Arg>;
+/// SignatureOf is a traits helper that infers the signature of the function,
+/// method, static method, lambda, or function-like object `F`.
+template <typename F>
+struct SignatureOf {
+  /// The signature of the function-like object `F`
+  using type = typename SignatureOf<decltype(&F::operator())>::type;
 };
 
-/// ParamType specialization for a non-static method.
-template <typename R, typename C, int N, typename... Args>
-struct ParamType<R (C::*)(Args...), N> {
-  /// Arg is the raw type of the `N`th parameter of the function
-  using Arg = NthTypeOf<N, Args...>;
-  /// The type of the `N`th parameter of the function
-  using type = Decay<Arg>;
+/// SignatureOf specialization for a regular function or static method.
+template <typename R, typename... ARGS>
+struct SignatureOf<R (*)(ARGS...)> {
+  /// The signature of the function-like object `F`
+  using type = Signature<typename std::decay<R>::type,
+                         typename std::decay<ARGS>::type...>;
 };
 
-/// ParamType specialization for a non-static, const method.
-template <typename R, typename C, int N, typename... Args>
-struct ParamType<R (C::*)(Args...) const, N> {
-  /// Arg is the raw type of the `N`th parameter of the function
-  using Arg = NthTypeOf<N, Args...>;
-  /// The type of the `N`th parameter of the function
-  using type = Decay<Arg>;
+/// SignatureOf specialization for a non-static method.
+template <typename R, typename C, typename... ARGS>
+struct SignatureOf<R (C::*)(ARGS...)> {
+  /// The signature of the function-like object `F`
+  using type = Signature<typename std::decay<R>::type,
+                         typename std::decay<ARGS>::type...>;
 };
 
-/// ParamTypeT is an alias to `typename ParamType<F, N>::type`.
-template <typename F, int N>
-using ParamTypeT = typename ParamType<F, N>::type;
+/// SignatureOf specialization for a non-static, const method.
+template <typename R, typename C, typename... ARGS>
+struct SignatureOf<R (C::*)(ARGS...) const> {
+  /// The signature of the function-like object `F`
+  using type = Signature<typename std::decay<R>::type,
+                         typename std::decay<ARGS>::type...>;
+};
+
+/// SignatureOfT is an alias to `typename SignatureOf<F>::type`.
+template <typename F>
+using SignatureOfT = typename SignatureOf<F>::type;
+
+/// ParameterType is an alias to `typename SignatureOf<F>::type::parameter<N>`.
+template <typename F, std::size_t N>
+using ParameterType = typename SignatureOfT<F>::template parameter<N>;
+
+/// ReturnType is an alias to `typename SignatureOf<F>::type::ret`.
+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`.
diff --git a/src/traits_test.cc b/src/traits_test.cc
index b598298..43cd209 100644
--- a/src/traits_test.cc
+++ b/src/traits_test.cc
@@ -28,10 +28,15 @@
 TEST(ParamType, Function) {
   F1({});        // Avoid unused method warning
   F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same<ParamTypeT<decltype(&F1), 0>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&F3), 0>, int>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&F3), 1>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&F3), 2>, float>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&F1), 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&F3), 0>, int>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&F3), 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&F3), 2>, float>::value,
+                "");
+  static_assert(std::is_same<ReturnType<decltype(&F1)>, void>::value, "");
+  static_assert(std::is_same<ReturnType<decltype(&F3)>, void>::value, "");
+  static_assert(SignatureOfT<decltype(&F1)>::parameter_count == 1, "");
+  static_assert(SignatureOfT<decltype(&F3)>::parameter_count == 3, "");
 }
 
 TEST(ParamType, Method) {
@@ -42,11 +47,16 @@
   };
   C().F1({});        // Avoid unused method warning
   C().F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F1), 0>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F3), 0>, int>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F3), 1>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F3), 2>, float>::value,
+  static_assert(std::is_same<ParameterType<decltype(&C::F1), 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 0>, int>::value,
                 "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 2>, float>::value,
+                "");
+  static_assert(std::is_same<ReturnType<decltype(&C::F1)>, void>::value, "");
+  static_assert(std::is_same<ReturnType<decltype(&C::F3)>, void>::value, "");
+  static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1, "");
+  static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3, "");
 }
 
 TEST(ParamType, ConstMethod) {
@@ -57,11 +67,16 @@
   };
   C().F1({});        // Avoid unused method warning
   C().F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F1), 0>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F3), 0>, int>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F3), 1>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F3), 2>, float>::value,
+  static_assert(std::is_same<ParameterType<decltype(&C::F1), 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 0>, int>::value,
                 "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 2>, float>::value,
+                "");
+  static_assert(std::is_same<ReturnType<decltype(&C::F1)>, void>::value, "");
+  static_assert(std::is_same<ReturnType<decltype(&C::F3)>, void>::value, "");
+  static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1, "");
+  static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3, "");
 }
 
 TEST(ParamType, StaticMethod) {
@@ -72,29 +87,42 @@
   };
   C::F1({});        // Avoid unused method warning
   C::F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F1), 0>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F3), 0>, int>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F3), 1>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(&C::F3), 2>, float>::value,
+  static_assert(std::is_same<ParameterType<decltype(&C::F1), 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 0>, int>::value,
                 "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 2>, float>::value,
+                "");
+  static_assert(std::is_same<ReturnType<decltype(&C::F1)>, void>::value, "");
+  static_assert(std::is_same<ReturnType<decltype(&C::F3)>, void>::value, "");
+  static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1, "");
+  static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3, "");
 }
 
 TEST(ParamType, FunctionLike) {
   using F1 = std::function<void(S)>;
   using F3 = std::function<void(int, S, float)>;
-  static_assert(std::is_same<ParamTypeT<F1, 0>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<F3, 0>, int>::value, "");
-  static_assert(std::is_same<ParamTypeT<F3, 1>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<F3, 2>, float>::value, "");
+  static_assert(std::is_same<ParameterType<F1, 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<F3, 0>, int>::value, "");
+  static_assert(std::is_same<ParameterType<F3, 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<F3, 2>, float>::value, "");
+  static_assert(std::is_same<ReturnType<F1>, void>::value, "");
+  static_assert(std::is_same<ReturnType<F3>, void>::value, "");
+  static_assert(SignatureOfT<F1>::parameter_count == 1, "");
+  static_assert(SignatureOfT<F3>::parameter_count == 3, "");
 }
 
 TEST(ParamType, Lambda) {
   auto l1 = [](S) {};
   auto l3 = [](int, S, float) {};
-  static_assert(std::is_same<ParamTypeT<decltype(l1), 0>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(l3), 0>, int>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(l3), 1>, S>::value, "");
-  static_assert(std::is_same<ParamTypeT<decltype(l3), 2>, float>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(l1), 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(l3), 0>, int>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(l3), 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(l3), 2>, float>::value, "");
+  static_assert(std::is_same<ReturnType<decltype(l1)>, void>::value, "");
+  static_assert(std::is_same<ReturnType<decltype(l3)>, void>::value, "");
+  static_assert(SignatureOfT<decltype(l1)>::parameter_count == 1, "");
+  static_assert(SignatureOfT<decltype(l3)>::parameter_count == 3, "");
 }
 
 }  // namespace traits