tint: Extract intrinsic-table common type to helper

• Add sem::Type::Common() which returns the 'common' type for the list of
  types.
• Migrate intrisnic table to use this.
• Add a whole-lotta-tests.
• Deduplicate and improve the EXPECT_TEST() macro. Move it to a common home.

Bug: tint:1504
Change-Id: I1564f67ecf87fc594f3f54274da906ff0d822795
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/91020
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index 0f20bcd..6c19f7c 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -124,16 +124,11 @@
         if (existing == ty) {
             return ty;
         }
-        if (sem::Type::ConversionRank(ty, existing) != sem::Type::kNoConversion) {
-            // ty can be converted to the existing type. Keep the existing type.
-            return existing;
+        ty = sem::Type::Common({existing, ty});
+        if (ty) {
+            res.first->second = ty;
         }
-        if (sem::Type::ConversionRank(existing, ty) != sem::Type::kNoConversion) {
-            // template type can be converted to ty. Constrain the existing type.
-            types_[idx] = ty;
-            return ty;
-        }
-        return nullptr;
+        return ty;
     }
 
     /// If the number with index `idx` is undefined, then it is defined with the number `number` and
diff --git a/src/tint/resolver/intrinsic_table_test.cc b/src/tint/resolver/intrinsic_table_test.cc
index a72eb00..854be5f 100644
--- a/src/tint/resolver/intrinsic_table_test.cc
+++ b/src/tint/resolver/intrinsic_table_test.cc
@@ -27,21 +27,13 @@
 #include "src/tint/sem/reference.h"
 #include "src/tint/sem/sampled_texture.h"
 #include "src/tint/sem/storage_texture.h"
+#include "src/tint/sem/test_helper.h"
 #include "src/tint/sem/type_constructor.h"
 #include "src/tint/sem/type_conversion.h"
 
 namespace tint::resolver {
 namespace {
 
-#define EXPECT_TYPE(GOT, EXPECT)                        \
-    if ((GOT) != (EXPECT)) {                            \
-        FAIL() << #GOT " != " #EXPECT "\n"              \
-               << "  " #GOT ": " << NameOf(GOT) << "\n" \
-               << "  " #EXPECT ": " << NameOf(EXPECT);  \
-    }                                                   \
-    do {                                                \
-    } while (false)
-
 using ::testing::HasSubstr;
 
 using BuiltinType = sem::BuiltinType;
@@ -830,13 +822,8 @@
     builder::sem_type_func_ptr arg_rhs;
 };
 
-class IntrinsicTableAbstractBinaryTest : public testing::TestWithParam<Case>,
-                                         public ProgramBuilder {
-  public:
-    std::string NameOf(const sem::Type* type) {
-        return type ? type->FriendlyName(Symbols()) : "<null>";
-    }
-
+struct IntrinsicTableAbstractBinaryTest : public testing::TestWithParam<Case>,
+                                          public ProgramBuilder {
     std::unique_ptr<IntrinsicTable> table = IntrinsicTable::Create(*this);
 };
 
@@ -1017,13 +1004,8 @@
     builder::sem_type_func_ptr arg_c;
 };
 
-class IntrinsicTableAbstractTernaryTest : public testing::TestWithParam<Case>,
-                                          public ProgramBuilder {
-  public:
-    std::string NameOf(const sem::Type* type) {
-        return type ? type->FriendlyName(Symbols()) : "<null>";
-    }
-
+struct IntrinsicTableAbstractTernaryTest : public testing::TestWithParam<Case>,
+                                           public ProgramBuilder {
     std::unique_ptr<IntrinsicTable> table = IntrinsicTable::Create(*this);
 };
 
diff --git a/src/tint/sem/test_helper.h b/src/tint/sem/test_helper.h
index 94f66e4..e1b4eb3 100644
--- a/src/tint/sem/test_helper.h
+++ b/src/tint/sem/test_helper.h
@@ -44,4 +44,16 @@
 
 }  // namespace tint::sem
 
+/// Helper macro for testing that a semantic type was as expected
+#define EXPECT_TYPE(GOT, EXPECT)                                         \
+    do {                                                                 \
+        const sem::Type* got = GOT;                                      \
+        const sem::Type* expect = EXPECT;                                \
+        if (got != expect) {                                             \
+            ADD_FAILURE() << #GOT " != " #EXPECT "\n"                    \
+                          << "  " #GOT ": " << FriendlyName(got) << "\n" \
+                          << "  " #EXPECT ": " << FriendlyName(expect);  \
+        }                                                                \
+    } while (false)
+
 #endif  // SRC_TINT_SEM_TEST_HELPER_H_
diff --git a/src/tint/sem/type.cc b/src/tint/sem/type.cc
index df38a2b..40666e3 100644
--- a/src/tint/sem/type.cc
+++ b/src/tint/sem/type.cc
@@ -232,4 +232,26 @@
         });
 }
 
+const sem::Type* Type::Common(Type const* const* types, size_t count) {
+    if (count == 0) {
+        return nullptr;
+    }
+    const auto* common = types[0];
+    for (size_t i = 1; i < count; i++) {
+        auto* ty = types[i];
+        if (ty == common) {
+            continue;  // ty == common
+        }
+        if (sem::Type::ConversionRank(ty, common) != sem::Type::kNoConversion) {
+            continue;  // ty can be converted to common.
+        }
+        if (sem::Type::ConversionRank(common, ty) != sem::Type::kNoConversion) {
+            common = ty;  // common can be converted to ty.
+            continue;
+        }
+        return nullptr;  // Conversion is not valid.
+    }
+    return common;
+}
+
 }  // namespace tint::sem
diff --git a/src/tint/sem/type.h b/src/tint/sem/type.h
index 2a7ad96..9987637 100644
--- a/src/tint/sem/type.h
+++ b/src/tint/sem/type.h
@@ -135,6 +135,21 @@
     /// or array, otherwise nullptr.
     static const Type* ElementOf(const Type* ty, uint32_t* count = nullptr);
 
+    /// @param types a pointer to a list of `const Type*`.
+    /// @param count the number of types in `types`.
+    /// @returns the lowest-ranking type that all types in `types` can be implicitly converted to,
+    /// or nullptr if there is no consistent common type across all types in `types`.
+    /// @see https://www.w3.org/TR/WGSL/#conversion-rank
+    static const sem::Type* Common(Type const* const* types, size_t count);
+
+    /// @param types an initializer_list of `const Type*`.
+    /// @returns the lowest-ranking type that all types in `types` can be implicitly converted to,
+    /// or nullptr if there is no consistent common type across all types in `types`.
+    /// @see https://www.w3.org/TR/WGSL/#conversion-rank
+    static const sem::Type* Common(std::initializer_list<const Type*> types) {
+        return Common(types.begin(), types.size());
+    }
+
   protected:
     Type();
 };
diff --git a/src/tint/sem/type_test.cc b/src/tint/sem/type_test.cc
index 62fc82f..c11efea 100644
--- a/src/tint/sem/type_test.cc
+++ b/src/tint/sem/type_test.cc
@@ -91,16 +91,6 @@
     EXPECT_EQ(Type::ConversionRank(f16, ai), Type::kNoConversion);
 }
 
-/// Helper macro for testing that a semantic type was as expected
-#define EXPECT_TYPE(GOT, EXPECT)                              \
-    if ((GOT) != (EXPECT)) {                                  \
-        FAIL() << #GOT " != " #EXPECT "\n"                    \
-               << "  " #GOT ": " << FriendlyName(GOT) << "\n" \
-               << "  " #EXPECT ": " << FriendlyName(EXPECT);  \
-    }                                                         \
-    do {                                                      \
-    } while (false)
-
 TEST_F(TypeTest, ElementOf) {
     auto* f32 = create<F32>();
     auto* f16 = create<F16>();
@@ -179,5 +169,225 @@
     EXPECT_EQ(count, 5u);
 }
 
+TEST_F(TypeTest, Common2) {
+    auto* ai = create<AbstractInt>();
+    auto* af = create<AbstractFloat>();
+    auto* f32 = create<F32>();
+    auto* f16 = create<F16>();
+    auto* i32 = create<I32>();
+    auto* u32 = create<U32>();
+
+    EXPECT_TYPE(Type::Common({ai, ai}), ai);
+    EXPECT_TYPE(Type::Common({af, af}), af);
+    EXPECT_TYPE(Type::Common({f32, f32}), f32);
+    EXPECT_TYPE(Type::Common({f16, f16}), f16);
+    EXPECT_TYPE(Type::Common({i32, i32}), i32);
+    EXPECT_TYPE(Type::Common({u32, u32}), u32);
+
+    EXPECT_TYPE(Type::Common({i32, u32}), nullptr);
+    EXPECT_TYPE(Type::Common({u32, f32}), nullptr);
+    EXPECT_TYPE(Type::Common({f32, f16}), nullptr);
+    EXPECT_TYPE(Type::Common({f16, i32}), nullptr);
+
+    EXPECT_TYPE(Type::Common({ai, af}), af);
+    EXPECT_TYPE(Type::Common({ai, f32}), f32);
+    EXPECT_TYPE(Type::Common({ai, f16}), f16);
+    EXPECT_TYPE(Type::Common({ai, i32}), i32);
+    EXPECT_TYPE(Type::Common({ai, u32}), u32);
+
+    EXPECT_TYPE(Type::Common({af, ai}), af);
+    EXPECT_TYPE(Type::Common({f32, ai}), f32);
+    EXPECT_TYPE(Type::Common({f16, ai}), f16);
+    EXPECT_TYPE(Type::Common({i32, ai}), i32);
+    EXPECT_TYPE(Type::Common({u32, ai}), u32);
+
+    EXPECT_TYPE(Type::Common({ai, af}), af);
+    EXPECT_TYPE(Type::Common({f32, af}), f32);
+    EXPECT_TYPE(Type::Common({f16, af}), f16);
+    EXPECT_TYPE(Type::Common({i32, af}), nullptr);
+    EXPECT_TYPE(Type::Common({u32, af}), nullptr);
+
+    EXPECT_TYPE(Type::Common({af, ai}), af);
+    EXPECT_TYPE(Type::Common({af, f32}), f32);
+    EXPECT_TYPE(Type::Common({af, f16}), f16);
+    EXPECT_TYPE(Type::Common({af, i32}), nullptr);
+    EXPECT_TYPE(Type::Common({af, u32}), nullptr);
+
+    auto* vec3_ai = create<Vector>(ai, 3u);
+    auto* vec3_af = create<Vector>(af, 3u);
+    auto* vec3_f32 = create<Vector>(f32, 3u);
+    auto* vec3_f16 = create<Vector>(f16, 3u);
+    auto* vec4_f32 = create<Vector>(f32, 4u);
+    auto* vec3_u32 = create<Vector>(u32, 3u);
+    auto* vec3_i32 = create<Vector>(i32, 3u);
+
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_ai}), vec3_ai);
+    EXPECT_TYPE(Type::Common({vec3_af, vec3_af}), vec3_af);
+    EXPECT_TYPE(Type::Common({vec3_f32, vec3_f32}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_f16, vec3_f16}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec4_f32, vec4_f32}), vec4_f32);
+    EXPECT_TYPE(Type::Common({vec3_u32, vec3_u32}), vec3_u32);
+    EXPECT_TYPE(Type::Common({vec3_i32, vec3_i32}), vec3_i32);
+
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_f32}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_f16}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec4_f32}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_u32}), vec3_u32);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_i32}), vec3_i32);
+
+    EXPECT_TYPE(Type::Common({vec3_f32, vec3_ai}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_f16, vec3_ai}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec4_f32, vec3_ai}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_u32, vec3_ai}), vec3_u32);
+    EXPECT_TYPE(Type::Common({vec3_i32, vec3_ai}), vec3_i32);
+
+    EXPECT_TYPE(Type::Common({vec3_af, vec3_f32}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_af, vec3_f16}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec3_af, vec4_f32}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_af, vec3_u32}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_af, vec3_i32}), nullptr);
+
+    EXPECT_TYPE(Type::Common({vec3_f32, vec3_af}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_f16, vec3_af}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec4_f32, vec3_af}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_u32, vec3_af}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_i32, vec3_af}), nullptr);
+
+    auto* mat4x3_af = create<Matrix>(vec3_af, 4u);
+    auto* mat3x4_f32 = create<Matrix>(vec4_f32, 3u);
+    auto* mat4x3_f32 = create<Matrix>(vec3_f32, 4u);
+    auto* mat4x3_f16 = create<Matrix>(vec3_f16, 4u);
+
+    EXPECT_TYPE(Type::Common({mat4x3_af, mat4x3_af}), mat4x3_af);
+    EXPECT_TYPE(Type::Common({mat3x4_f32, mat3x4_f32}), mat3x4_f32);
+    EXPECT_TYPE(Type::Common({mat4x3_f32, mat4x3_f32}), mat4x3_f32);
+    EXPECT_TYPE(Type::Common({mat4x3_f16, mat4x3_f16}), mat4x3_f16);
+
+    EXPECT_TYPE(Type::Common({mat4x3_af, mat3x4_f32}), nullptr);
+    EXPECT_TYPE(Type::Common({mat4x3_af, mat4x3_f32}), mat4x3_f32);
+    EXPECT_TYPE(Type::Common({mat4x3_af, mat4x3_f16}), mat4x3_f16);
+
+    EXPECT_TYPE(Type::Common({mat3x4_f32, mat4x3_af}), nullptr);
+    EXPECT_TYPE(Type::Common({mat4x3_f32, mat4x3_af}), mat4x3_f32);
+    EXPECT_TYPE(Type::Common({mat4x3_f16, mat4x3_af}), mat4x3_f16);
+}
+
+TEST_F(TypeTest, Common3) {
+    auto* ai = create<AbstractInt>();
+    auto* af = create<AbstractFloat>();
+    auto* f32 = create<F32>();
+    auto* f16 = create<F16>();
+    auto* i32 = create<I32>();
+    auto* u32 = create<U32>();
+
+    EXPECT_TYPE(Type::Common({ai, ai, ai}), ai);
+    EXPECT_TYPE(Type::Common({af, af, af}), af);
+    EXPECT_TYPE(Type::Common({f32, f32, f32}), f32);
+    EXPECT_TYPE(Type::Common({f16, f16, f16}), f16);
+    EXPECT_TYPE(Type::Common({i32, i32, i32}), i32);
+    EXPECT_TYPE(Type::Common({u32, u32, u32}), u32);
+
+    EXPECT_TYPE(Type::Common({ai, af, ai}), af);
+    EXPECT_TYPE(Type::Common({ai, f32, ai}), f32);
+    EXPECT_TYPE(Type::Common({ai, f16, ai}), f16);
+    EXPECT_TYPE(Type::Common({ai, i32, ai}), i32);
+    EXPECT_TYPE(Type::Common({ai, u32, ai}), u32);
+
+    EXPECT_TYPE(Type::Common({af, ai, af}), af);
+    EXPECT_TYPE(Type::Common({f32, ai, f32}), f32);
+    EXPECT_TYPE(Type::Common({f16, ai, f16}), f16);
+    EXPECT_TYPE(Type::Common({i32, ai, i32}), i32);
+    EXPECT_TYPE(Type::Common({u32, ai, u32}), u32);
+
+    EXPECT_TYPE(Type::Common({ai, f32, ai}), f32);
+    EXPECT_TYPE(Type::Common({ai, f16, ai}), f16);
+    EXPECT_TYPE(Type::Common({ai, i32, ai}), i32);
+    EXPECT_TYPE(Type::Common({ai, u32, ai}), u32);
+
+    EXPECT_TYPE(Type::Common({f32, ai, f32}), f32);
+    EXPECT_TYPE(Type::Common({f16, ai, f16}), f16);
+    EXPECT_TYPE(Type::Common({i32, ai, i32}), i32);
+    EXPECT_TYPE(Type::Common({u32, ai, u32}), u32);
+
+    EXPECT_TYPE(Type::Common({af, f32, af}), f32);
+    EXPECT_TYPE(Type::Common({af, f16, af}), f16);
+    EXPECT_TYPE(Type::Common({af, i32, af}), nullptr);
+    EXPECT_TYPE(Type::Common({af, u32, af}), nullptr);
+
+    EXPECT_TYPE(Type::Common({f32, af, f32}), f32);
+    EXPECT_TYPE(Type::Common({f16, af, f16}), f16);
+    EXPECT_TYPE(Type::Common({i32, af, i32}), nullptr);
+    EXPECT_TYPE(Type::Common({u32, af, u32}), nullptr);
+
+    EXPECT_TYPE(Type::Common({ai, af, f32}), f32);
+    EXPECT_TYPE(Type::Common({ai, af, f16}), f16);
+    EXPECT_TYPE(Type::Common({ai, af, i32}), nullptr);
+    EXPECT_TYPE(Type::Common({ai, af, u32}), nullptr);
+
+    auto* vec3_ai = create<Vector>(ai, 3u);
+    auto* vec3_af = create<Vector>(af, 3u);
+    auto* vec3_f32 = create<Vector>(f32, 3u);
+    auto* vec3_f16 = create<Vector>(f16, 3u);
+    auto* vec4_f32 = create<Vector>(f32, 4u);
+    auto* vec3_u32 = create<Vector>(u32, 3u);
+    auto* vec3_i32 = create<Vector>(i32, 3u);
+
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_ai, vec3_ai}), vec3_ai);
+    EXPECT_TYPE(Type::Common({vec3_af, vec3_af, vec3_af}), vec3_af);
+    EXPECT_TYPE(Type::Common({vec3_f32, vec3_f32, vec3_f32}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_f16, vec3_f16, vec3_f16}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec4_f32, vec4_f32, vec4_f32}), vec4_f32);
+    EXPECT_TYPE(Type::Common({vec3_u32, vec3_u32, vec3_u32}), vec3_u32);
+    EXPECT_TYPE(Type::Common({vec3_i32, vec3_i32, vec3_i32}), vec3_i32);
+
+    EXPECT_TYPE(Type::Common({vec3_f32, vec3_ai, vec3_f32}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_f16, vec3_ai, vec3_f16}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec4_f32, vec3_ai, vec4_f32}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_u32, vec3_ai, vec3_u32}), vec3_u32);
+    EXPECT_TYPE(Type::Common({vec3_i32, vec3_ai, vec3_i32}), vec3_i32);
+
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_f32, vec3_ai}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_f16, vec3_ai}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec4_f32, vec3_ai}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_u32, vec3_ai}), vec3_u32);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_i32, vec3_ai}), vec3_i32);
+
+    EXPECT_TYPE(Type::Common({vec3_f32, vec3_af, vec3_f32}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_f16, vec3_af, vec3_f16}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec4_f32, vec3_af, vec4_f32}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_u32, vec3_af, vec3_u32}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_i32, vec3_af, vec3_i32}), nullptr);
+
+    EXPECT_TYPE(Type::Common({vec3_af, vec3_f32, vec3_af}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_af, vec3_f16, vec3_af}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec3_af, vec4_f32, vec3_af}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_af, vec3_u32, vec3_af}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_af, vec3_i32, vec3_af}), nullptr);
+
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_af, vec3_f32}), vec3_f32);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_af, vec3_f16}), vec3_f16);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_af, vec4_f32}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_af, vec3_u32}), nullptr);
+    EXPECT_TYPE(Type::Common({vec3_ai, vec3_af, vec3_i32}), nullptr);
+
+    auto* mat4x3_af = create<Matrix>(vec3_af, 4u);
+    auto* mat3x4_f32 = create<Matrix>(vec4_f32, 3u);
+    auto* mat4x3_f32 = create<Matrix>(vec3_f32, 4u);
+    auto* mat4x3_f16 = create<Matrix>(vec3_f16, 4u);
+
+    EXPECT_TYPE(Type::Common({mat4x3_af, mat4x3_af, mat4x3_af}), mat4x3_af);
+    EXPECT_TYPE(Type::Common({mat3x4_f32, mat3x4_f32, mat3x4_f32}), mat3x4_f32);
+    EXPECT_TYPE(Type::Common({mat4x3_f32, mat4x3_f32, mat4x3_f32}), mat4x3_f32);
+    EXPECT_TYPE(Type::Common({mat4x3_f16, mat4x3_f16, mat4x3_f16}), mat4x3_f16);
+
+    EXPECT_TYPE(Type::Common({mat3x4_f32, mat4x3_af, mat3x4_f32}), nullptr);
+    EXPECT_TYPE(Type::Common({mat4x3_f32, mat4x3_af, mat4x3_f32}), mat4x3_f32);
+    EXPECT_TYPE(Type::Common({mat4x3_f16, mat4x3_af, mat4x3_f16}), mat4x3_f16);
+
+    EXPECT_TYPE(Type::Common({mat4x3_af, mat3x4_f32, mat4x3_af}), nullptr);
+    EXPECT_TYPE(Type::Common({mat4x3_af, mat4x3_f32, mat4x3_af}), mat4x3_f32);
+    EXPECT_TYPE(Type::Common({mat4x3_af, mat4x3_f16, mat4x3_af}), mat4x3_f16);
+}
+
 }  // namespace
 }  // namespace tint::sem