#include "ir/subtypes.h"
#include "type-test.h"
#include "wasm-builder.h"
#include "wasm-type-printing.h"
#include "wasm-type.h"
#include "gtest/gtest.h"

using namespace wasm;

TEST_F(TypeTest, TypeBuilderGrowth) {
  TypeBuilder builder;
  EXPECT_EQ(builder.size(), 0u);
  builder.grow(3);
  EXPECT_EQ(builder.size(), 3u);
  builder.grow(0);
  EXPECT_EQ(builder.size(), 3u);
}

TEST_F(TypeTest, TypeIterator) {
  Type none = Type::none;
  Type i32 = Type::i32;
  Type i64 = Type::i64;
  Type f32 = Type::f32;
  Type f64 = Type::f64;
  Type tuple = Tuple{i32, i64, f32, f64};

  EXPECT_EQ(none.size(), 0u);
  EXPECT_EQ(none.begin(), none.end());
  EXPECT_EQ(none.end() - none.begin(), 0);
  EXPECT_EQ(none.begin() + 0, none.end());

  EXPECT_EQ(i32.size(), 1u);
  EXPECT_NE(i32.begin(), i32.end());
  EXPECT_EQ(i32.end() - i32.begin(), 1);

  EXPECT_EQ(*i32.begin(), i32);
  EXPECT_EQ(i32[0], i32);

  EXPECT_EQ(i32.begin() + 1, i32.end());
  EXPECT_EQ(i32.end() - 1, i32.begin());

  auto preInc = i32.begin();
  EXPECT_EQ(++preInc, i32.end());
  EXPECT_EQ(preInc, i32.end());

  auto postInc = i32.begin();
  EXPECT_EQ(postInc++, i32.begin());
  EXPECT_EQ(postInc, i32.end());

  auto preDec = i32.end();
  EXPECT_EQ(--preDec, i32.begin());
  EXPECT_EQ(preDec, i32.begin());

  auto postDec = i32.end();
  EXPECT_EQ(postDec--, i32.end());
  EXPECT_EQ(postDec, i32.begin());

  EXPECT_EQ(tuple.size(), 4u);
  EXPECT_NE(tuple.begin(), tuple.end());
  EXPECT_EQ(tuple.end() - tuple.begin(), 4);

  EXPECT_EQ(*tuple.begin(), i32);
  EXPECT_EQ(*(tuple.begin() + 1), i64);
  EXPECT_EQ(*(tuple.begin() + 2), f32);
  EXPECT_EQ(*(tuple.begin() + 3), f64);
  EXPECT_EQ(tuple[0], i32);
  EXPECT_EQ(tuple[1], i64);
  EXPECT_EQ(tuple[2], f32);
  EXPECT_EQ(tuple[3], f64);

  auto reverse = tuple.rbegin();
  EXPECT_EQ(*reverse++, f64);
  EXPECT_EQ(*reverse++, f32);
  EXPECT_EQ(*reverse++, i64);
  EXPECT_EQ(*reverse++, i32);
  EXPECT_EQ(reverse, tuple.rend());
}

TEST_F(TypeTest, IndexedTypePrinter) {
  TypeBuilder builder(4);
  builder.createRecGroup(0, 4);

  Type refStructA = builder.getTempRefType(builder[0], Nullable);
  Type refStructB = builder.getTempRefType(builder[1], Nullable);
  Type refArrayA = builder.getTempRefType(builder[2], Nullable);
  Type refArrayB = builder.getTempRefType(builder[3], Nullable);
  builder[0] = Struct({Field(refArrayB, Immutable)});
  builder[1] = Struct({Field(refStructA, Immutable)});
  builder[2] = Array(Field(refStructB, Immutable));
  builder[3] = Array(Field(refArrayA, Immutable));

  auto result = builder.build();
  ASSERT_TRUE(result);
  auto built = *result;

  std::vector<HeapType> structs{built[0], built[1]};
  std::vector<HeapType> arrays{built[2], built[3]};

  // Check that IndexedTypePrinters configured with fallbacks work correctly.
  using ArrayPrinter = IndexedTypeNameGenerator<DefaultTypeNameGenerator>;
  ArrayPrinter printArrays(arrays, "array");
  using StructPrinter = IndexedTypeNameGenerator<ArrayPrinter>;
  StructPrinter print(structs, printArrays, "struct");

  std::stringstream stream;
  stream << print(built[0]);
  EXPECT_EQ(stream.str(),
            "(type $struct0 (struct (field (ref null $array1))))");

  stream.str("");
  stream << print(built[1]);
  EXPECT_EQ(stream.str(),
            "(type $struct1 (struct (field (ref null $struct0))))");

  stream.str("");
  stream << print(built[2]);
  EXPECT_EQ(stream.str(), "(type $array0 (array (ref null $struct1)))");

  stream.str("");
  stream << print(built[3]);
  EXPECT_EQ(stream.str(), "(type $array1 (array (ref null $array0)))");
}

TEST_F(TypeTest, ModuleTypePrinter) {
  TypeBuilder builder(2);
  builder.createRecGroup(0, 2);
  builder[0] = Struct({Field(Type::i32, Immutable)});
  builder[1] = Struct({Field(Type::i32, Immutable)});

  auto result = builder.build();
  ASSERT_TRUE(result);
  auto built = *result;

  Module module;
  module.typeNames[built[0]] = {"A", {}};

  ModuleTypeNameGenerator printDefault(module);

  std::stringstream stream;
  stream << printDefault(built[0]);
  EXPECT_EQ(stream.str(), "(type $A (struct (field i32)))");

  stream.str("");
  stream << printDefault(built[1]);
  EXPECT_EQ(stream.str(), "(type $struct.0 (struct (field i32)))");

  using IndexedFallback = IndexedTypeNameGenerator<DefaultTypeNameGenerator>;
  IndexedTypeNameGenerator fallback(built);
  ModuleTypeNameGenerator<IndexedFallback> printIndexed(module, fallback);

  stream.str("");
  stream << printIndexed(built[0]);
  EXPECT_EQ(stream.str(), "(type $A (struct (field i32)))");

  stream.str("");
  stream << printIndexed(built[1]);
  EXPECT_EQ(stream.str(), "(type $1 (struct (field i32)))");
}

TEST_F(TypeTest, Basics) {
  // (type $sig (func (param (ref $struct)) (result (ref $array) i32)))
  // (type $struct (struct (field (ref null $array))))
  // (type $array (array (mut anyref)))
  TypeBuilder builder(3);
  ASSERT_EQ(builder.size(), size_t{3});

  Type refSig = builder.getTempRefType(builder[0], NonNullable);
  Type refStruct = builder.getTempRefType(builder[1], NonNullable);
  Type refArray = builder.getTempRefType(builder[2], NonNullable);
  Type refNullArray = builder.getTempRefType(builder[2], Nullable);
  Type refNullAny(HeapType::any, Nullable);

  Signature sig(refStruct, builder.getTempTupleType({refArray, Type::i32}));
  Struct struct_({Field(refNullArray, Immutable)});
  Array array(Field(refNullAny, Mutable));

  builder[0] = sig;
  builder[1] = struct_;
  builder[2] = array;

  builder.createRecGroup(0, 3);

  auto result = builder.build();
  ASSERT_TRUE(result);
  std::vector<HeapType> built = *result;
  ASSERT_EQ(built.size(), size_t{3});

  // The built types should have the correct kinds.
  ASSERT_TRUE(built[0].isSignature());
  ASSERT_TRUE(built[1].isStruct());
  ASSERT_TRUE(built[2].isArray());

  // The built types should have the correct structure.
  Type newRefSig = Type(built[0], NonNullable);
  Type newRefStruct = Type(built[1], NonNullable);
  Type newRefArray = Type(built[2], NonNullable);
  Type newRefNullArray = Type(built[2], Nullable);

  EXPECT_EQ(built[0].getSignature(),
            Signature(newRefStruct, {newRefArray, Type::i32}));
  EXPECT_EQ(built[1].getStruct(), Struct({Field(newRefNullArray, Immutable)}));
  EXPECT_EQ(built[2].getArray(), Array(Field(refNullAny, Mutable)));

  // The built types should be different from the temporary types.
  EXPECT_NE(newRefSig, refSig);
  EXPECT_NE(newRefStruct, refStruct);
  EXPECT_NE(newRefArray, refArray);
  EXPECT_NE(newRefNullArray, refNullArray);
}

TEST_F(TypeTest, DirectSelfSupertype) {
  // Type is directly a supertype of itself.
  TypeBuilder builder(1);
  builder[0] = Struct{};
  builder[0].subTypeOf(builder[0]);

  auto result = builder.build();
  EXPECT_FALSE(result);

  const auto* error = result.getError();
  ASSERT_TRUE(error);
  EXPECT_EQ(error->reason, TypeBuilder::ErrorReason::ForwardSupertypeReference);
  EXPECT_EQ(error->index, 0u);
}

TEST_F(TypeTest, IndirectSelfSupertype) {
  // Type is indirectly a supertype of itself.
  TypeBuilder builder(2);
  builder.createRecGroup(0, 2);
  builder[0] = Struct{};
  builder[1] = Struct{};
  builder[0].subTypeOf(builder[1]);
  builder[1].subTypeOf(builder[0]);

  auto result = builder.build();
  EXPECT_FALSE(result);

  const auto* error = result.getError();
  ASSERT_TRUE(error);
  EXPECT_EQ(error->reason, TypeBuilder::ErrorReason::ForwardSupertypeReference);
  EXPECT_EQ(error->index, 0u);
}

TEST_F(TypeTest, InvalidSupertype) {
  TypeBuilder builder(2);
  builder.createRecGroup(0, 2);
  builder[0] = Struct({Field(Type::i32, Immutable)});
  builder[1] = Struct{};
  builder[1].subTypeOf(builder[0]);

  auto result = builder.build();
  EXPECT_FALSE(result);

  const auto* error = result.getError();
  ASSERT_TRUE(error);
  EXPECT_EQ(error->reason, TypeBuilder::ErrorReason::InvalidSupertype);
  EXPECT_EQ(error->index, 1u);
}

TEST_F(TypeTest, InvalidFinalSupertype) {
  TypeBuilder builder(2);
  builder[0] = Struct{};
  builder[1] = Struct{};
  builder[0].setOpen(false);
  builder[1].subTypeOf(builder[0]);

  auto result = builder.build();
  EXPECT_FALSE(result);

  const auto* error = result.getError();
  ASSERT_TRUE(error);
  EXPECT_EQ(error->reason, TypeBuilder::ErrorReason::InvalidSupertype);
  EXPECT_EQ(error->index, 1u);
}

TEST_F(TypeTest, InvalidSharedSupertype) {
  TypeBuilder builder(2);
  builder[0] = Struct{};
  builder[1] = Struct{};
  builder[0].setShared();
  builder[1].setShared();
  builder[1].subTypeOf(builder[0]);

  auto result = builder.build();
  EXPECT_FALSE(result);

  const auto* error = result.getError();
  ASSERT_TRUE(error);
  EXPECT_EQ(error->reason, TypeBuilder::ErrorReason::InvalidSupertype);
  EXPECT_EQ(error->index, 1u);
}

TEST_F(TypeTest, InvalidUnsharedSupertype) {
  TypeBuilder builder(2);
  builder[0] = Struct{};
  builder[1] = Struct{};
  builder[0].setShared(Unshared);
  builder[1].setShared(Shared);
  builder[1].subTypeOf(builder[0]);

  auto result = builder.build();
  EXPECT_FALSE(result);

  const auto* error = result.getError();
  ASSERT_TRUE(error);
  EXPECT_EQ(error->reason, TypeBuilder::ErrorReason::InvalidSupertype);
  EXPECT_EQ(error->index, 1u);
}

TEST_F(TypeTest, ForwardReferencedChild) {
  TypeBuilder builder(3);
  builder.createRecGroup(0, 2);
  Type refA1 = builder.getTempRefType(builder[1], Nullable);
  Type refB0 = builder.getTempRefType(builder[2], Nullable);
  // Forward reference to same group is ok.
  builder[0] = Struct({Field(refA1, Mutable)});
  // Forward reference to different group is not ok.
  builder[1] = Struct({Field(refB0, Mutable)});
  builder[2] = Struct{};

  auto result = builder.build();
  EXPECT_FALSE(result);

  const auto* error = result.getError();
  ASSERT_TRUE(error);
  EXPECT_EQ(error->reason, TypeBuilder::ErrorReason::ForwardChildReference);
  EXPECT_EQ(error->index, 1u);
}

TEST_F(TypeTest, RecGroupIndices) {
  TypeBuilder builder(5);

  builder.createRecGroup(0, 2);
  builder[0] = Struct{};
  builder[1] = Struct{};

  builder.createRecGroup(2, 3);
  builder[2] = Struct{};
  builder[3] = Struct{};
  builder[4] = Struct{};

  auto result = builder.build();
  ASSERT_TRUE(result);
  auto built = *result;

  EXPECT_EQ(built[0].getRecGroup(), built[1].getRecGroup());
  EXPECT_EQ(built[0].getRecGroupIndex(), 0u);
  EXPECT_EQ(built[1].getRecGroupIndex(), 1u);

  EXPECT_EQ(built[2].getRecGroup(), built[3].getRecGroup());
  EXPECT_EQ(built[3].getRecGroup(), built[4].getRecGroup());
  EXPECT_EQ(built[2].getRecGroupIndex(), 0u);
  EXPECT_EQ(built[3].getRecGroupIndex(), 1u);
  EXPECT_EQ(built[4].getRecGroupIndex(), 2u);
}

TEST_F(TypeTest, CanonicalizeGroups) {
  // Trivial types in the same group are not equivalent.
  TypeBuilder builderA(2);
  builderA.createRecGroup(0, 2);
  builderA[0] = Struct{};
  builderA[1] = Struct{};
  auto resultA = builderA.build();
  ASSERT_TRUE(resultA);
  auto builtA = *resultA;

  EXPECT_NE(builtA[0], builtA[1]);

  // But if they are in their own separate groups, they are equivalent.
  TypeBuilder builderB(2);
  builderB[0] = Struct{};
  builderB[1] = Struct{};
  auto resultB = builderB.build();
  ASSERT_TRUE(resultB);
  auto builtB = *resultB;

  EXPECT_EQ(builtB[0], builtB[1]);
  EXPECT_NE(builtB[0], builtA[0]);
  EXPECT_NE(builtB[0], builtA[1]);

  // If we build the same groups again, we should get the same results.
  TypeBuilder builderA2(4);
  builderA2.createRecGroup(0, 2);
  builderA2.createRecGroup(2, 2);
  builderA2[0] = Struct{};
  builderA2[1] = Struct{};
  builderA2[2] = Struct{};
  builderA2[3] = Struct{};
  auto resultA2 = builderA2.build();
  ASSERT_TRUE(resultA2);
  auto builtA2 = *resultA2;

  EXPECT_EQ(builtA2[0], builtA[0]);
  EXPECT_EQ(builtA2[1], builtA[1]);
  EXPECT_EQ(builtA2[2], builtA[0]);
  EXPECT_EQ(builtA2[3], builtA[1]);

  TypeBuilder builderB2(1);
  builderB2[0] = Struct{};
  auto resultB2 = builderB2.build();
  ASSERT_TRUE(resultB2);
  auto builtB2 = *resultB2;

  EXPECT_EQ(builtB2[0], builtB[0]);
}

TEST_F(TypeTest, CanonicalizeUses) {
  TypeBuilder builder(8);
  builder[0] = makeStruct(builder, {});
  builder[1] = makeStruct(builder, {});
  builder[2] = makeStruct(builder, {0});
  builder[3] = makeStruct(builder, {1});
  builder[4] = makeStruct(builder, {0, 2});
  builder[5] = makeStruct(builder, {1, 3});
  builder[6] = makeStruct(builder, {2, 4});
  builder[7] = makeStruct(builder, {3, 5});

  auto result = builder.build();
  ASSERT_TRUE(result);
  auto built = *result;

  EXPECT_EQ(built[0], built[1]);
  EXPECT_EQ(built[2], built[3]);
  EXPECT_EQ(built[4], built[5]);
  EXPECT_EQ(built[6], built[7]);

  EXPECT_NE(built[0], built[2]);
  EXPECT_NE(built[0], built[4]);
  EXPECT_NE(built[0], built[6]);
  EXPECT_NE(built[2], built[4]);
  EXPECT_NE(built[2], built[6]);
  EXPECT_NE(built[4], built[6]);
}

TEST_F(TypeTest, CanonicalizeSelfReferences) {
  TypeBuilder builder(5);
  // Single self-reference
  builder[0] = makeStruct(builder, {0});
  builder[1] = makeStruct(builder, {1});
  // Single other reference
  builder[2] = makeStruct(builder, {0});
  // Other reference followed by self-reference
  builder[3] = makeStruct(builder, {2, 3});
  // Self-reference followed by other reference
  builder[4] = makeStruct(builder, {4, 2});

  auto result = builder.build();
  ASSERT_TRUE(result);
  auto built = *result;

  EXPECT_EQ(built[0], built[1]);

  EXPECT_NE(built[0], built[2]);
  EXPECT_NE(built[0], built[3]);
  EXPECT_NE(built[0], built[4]);
  EXPECT_NE(built[2], built[3]);
  EXPECT_NE(built[2], built[4]);
  EXPECT_NE(built[3], built[4]);
}

TEST_F(TypeTest, CanonicalizeSupertypes) {
  TypeBuilder builder(6);
  builder[0].setOpen() = Struct{};
  builder[1].setOpen() = Struct{};
  // Type with a supertype
  builder[2].setOpen().subTypeOf(builder[0]) = Struct{};
  // Type with the same supertype after canonicalization.
  builder[3].setOpen().subTypeOf(builder[1]) = Struct{};
  // Type with a different supertype
  builder[4].setOpen().subTypeOf(builder[2]) = Struct{};
  // Type with no supertype
  builder[5].setOpen() = Struct{};

  auto result = builder.build();
  ASSERT_TRUE(result);
  auto built = *result;

  EXPECT_EQ(built[2], built[3]);

  EXPECT_NE(built[3], built[4]);
  EXPECT_NE(built[3], built[5]);
  EXPECT_NE(built[4], built[5]);
}

TEST_F(TypeTest, CanonicalizeFinal) {
  // Types are different if their finality flag is different.
  TypeBuilder builder(2);
  builder[0] = Struct{};
  builder[1].setOpen() = Struct{};

  auto result = builder.build();
  ASSERT_TRUE(result);
  auto built = *result;

  EXPECT_NE(built[0], built[1]);
  EXPECT_TRUE(!built[0].isOpen());
  EXPECT_FALSE(!built[1].isOpen());
}

TEST_F(TypeTest, HeapTypeConstructors) {
  HeapType sig(Signature(Type::i32, Type::i32));
  HeapType struct_(Struct({Field(Type(sig, Nullable), Mutable)}));
  HeapType array(Field(Type(struct_, Nullable), Mutable));

  TypeBuilder builder(3);
  builder[0] = Signature(Type::i32, Type::i32);
  Type sigRef = builder.getTempRefType(builder[0], Nullable);
  builder[1] = Struct({Field(sigRef, Mutable)});
  Type structRef = builder.getTempRefType(builder[1], Nullable);
  builder[2] = Array(Field(structRef, Mutable));

  auto result = builder.build();
  ASSERT_TRUE(result);
  auto built = *result;

  EXPECT_EQ(built[0], sig);
  EXPECT_EQ(built[1], struct_);
  EXPECT_EQ(built[2], array);

  HeapType sig2(Signature(Type::i32, Type::i32));
  HeapType struct2(Struct({Field(Type(sig, Nullable), Mutable)}));
  HeapType array2(Field(Type(struct_, Nullable), Mutable));

  EXPECT_EQ(sig, sig2);
  EXPECT_EQ(struct_, struct2);
  EXPECT_EQ(array, array2);
}

TEST_F(TypeTest, CanonicalizeTypesBeforeSubtyping) {
  TypeBuilder builder(6);
  // A rec group
  builder.createRecGroup(0, 2);
  builder[0].setOpen() = Struct{};
  builder[1].setOpen() = Struct{};
  builder[1].subTypeOf(builder[0]);

  // The same rec group again
  builder.createRecGroup(2, 2);
  builder[2].setOpen() = Struct{};
  builder[3].setOpen() = Struct{};
  builder[3].subTypeOf(builder[2]);

  // This subtyping only validates if the previous two groups are deduplicated
  // before checking subtype validity.
  builder[4].setOpen() =
    Struct({Field(builder.getTempRefType(builder[0], Nullable), Immutable)});
  builder[5].setOpen() =
    Struct({Field(builder.getTempRefType(builder[3], Nullable), Immutable)});
  builder[5].subTypeOf(builder[4]);

  auto result = builder.build();
  EXPECT_TRUE(result);
}

TEST_F(TypeTest, TestHeapTypeRelations) {
  HeapType ext = HeapType::ext;
  HeapType func = HeapType::func;
  HeapType cont = HeapType::cont;
  HeapType any = HeapType::any;
  HeapType eq = HeapType::eq;
  HeapType i31 = HeapType::i31;
  HeapType struct_ = HeapType::struct_;
  HeapType array = HeapType::array;
  HeapType string = HeapType::string;
  HeapType none = HeapType::none;
  HeapType noext = HeapType::noext;
  HeapType nofunc = HeapType::nofunc;
  HeapType nocont = HeapType::nocont;
  HeapType defFunc = Signature();
  HeapType defCont = Continuation(defFunc);
  HeapType defStruct = Struct();
  HeapType defArray = Array(Field(Type::i32, Immutable));
  HeapType sharedAny = any.getBasic(Shared);
  HeapType sharedEq = eq.getBasic(Shared);
  HeapType sharedI31 = i31.getBasic(Shared);
  HeapType sharedStruct = struct_.getBasic(Shared);
  HeapType sharedNone = none.getBasic(Shared);
  HeapType sharedFunc = func.getBasic(Shared);

  HeapType sharedDefStruct;
  HeapType sharedDefFunc;
  {
    TypeBuilder builder(2);
    builder[0] = Struct{};
    builder[1] = Signature();
    builder[0].setShared();
    builder[1].setShared();
    auto results = builder.build();
    ASSERT_TRUE(results);
    auto built = *results;
    sharedDefStruct = built[0];
    sharedDefFunc = built[1];
  }

  auto assertLUB = [](HeapType a, HeapType b, std::optional<HeapType> lub) {
    auto lub1 = HeapType::getLeastUpperBound(a, b);
    auto lub2 = HeapType::getLeastUpperBound(b, a);
    EXPECT_EQ(lub, lub1);
    EXPECT_EQ(lub1, lub2);
    if (a == b) {
      EXPECT_TRUE(HeapType::isSubType(a, b));
      EXPECT_TRUE(HeapType::isSubType(b, a));
      EXPECT_EQ(a.getTop(), b.getTop());
      EXPECT_EQ(a.getBottom(), b.getBottom());
    } else if (lub && *lub == b) {
      EXPECT_TRUE(HeapType::isSubType(a, b));
      EXPECT_FALSE(HeapType::isSubType(b, a));
      EXPECT_EQ(a.getTop(), b.getTop());
      EXPECT_EQ(a.getBottom(), b.getBottom());
    } else if (lub && *lub == a) {
      EXPECT_FALSE(HeapType::isSubType(a, b));
      EXPECT_TRUE(HeapType::isSubType(b, a));
      EXPECT_EQ(a.getTop(), b.getTop());
      EXPECT_EQ(a.getBottom(), b.getBottom());
    } else if (lub) {
      EXPECT_FALSE(HeapType::isSubType(a, b));
      EXPECT_FALSE(HeapType::isSubType(b, a));
      EXPECT_EQ(a.getTop(), b.getTop());
      EXPECT_EQ(a.getBottom(), b.getBottom());
    } else {
      EXPECT_FALSE(HeapType::isSubType(a, b));
      EXPECT_FALSE(HeapType::isSubType(b, a));
      EXPECT_NE(a.getTop(), b.getTop());
      EXPECT_NE(a.getBottom(), b.getBottom());
    }
  };

  assertLUB(ext, ext, ext);
  assertLUB(ext, func, {});
  assertLUB(ext, cont, {});
  assertLUB(ext, any, {});
  assertLUB(ext, eq, {});
  assertLUB(ext, i31, {});
  assertLUB(ext, struct_, {});
  assertLUB(ext, array, {});
  assertLUB(ext, string, {});
  assertLUB(ext, none, {});
  assertLUB(ext, noext, ext);
  assertLUB(ext, nofunc, {});
  assertLUB(ext, nocont, {});
  assertLUB(ext, defFunc, {});
  assertLUB(ext, defStruct, {});
  assertLUB(ext, defArray, {});
  assertLUB(ext, sharedAny, {});
  assertLUB(ext, sharedEq, {});
  assertLUB(ext, sharedI31, {});
  assertLUB(ext, sharedStruct, {});
  assertLUB(ext, sharedNone, {});
  assertLUB(ext, sharedFunc, {});
  assertLUB(ext, sharedDefStruct, {});
  assertLUB(ext, sharedDefFunc, {});

  assertLUB(func, func, func);
  assertLUB(func, cont, {});
  assertLUB(func, any, {});
  assertLUB(func, eq, {});
  assertLUB(func, i31, {});
  assertLUB(func, struct_, {});
  assertLUB(func, array, {});
  assertLUB(func, string, {});
  assertLUB(func, none, {});
  assertLUB(func, noext, {});
  assertLUB(func, nofunc, func);
  assertLUB(func, nocont, {});
  assertLUB(func, defFunc, func);
  assertLUB(func, defCont, {});
  assertLUB(func, defStruct, {});
  assertLUB(func, defArray, {});
  assertLUB(func, sharedAny, {});
  assertLUB(func, sharedEq, {});
  assertLUB(func, sharedI31, {});
  assertLUB(func, sharedStruct, {});
  assertLUB(func, sharedNone, {});
  assertLUB(func, sharedFunc, {});
  assertLUB(func, sharedDefStruct, {});
  assertLUB(func, sharedDefFunc, {});

  assertLUB(cont, cont, cont);
  assertLUB(cont, func, {});
  assertLUB(cont, any, {});
  assertLUB(cont, eq, {});
  assertLUB(cont, i31, {});
  assertLUB(cont, struct_, {});
  assertLUB(cont, array, {});
  assertLUB(cont, string, {});
  assertLUB(cont, none, {});
  assertLUB(cont, noext, {});
  assertLUB(cont, nofunc, {});
  assertLUB(cont, nocont, cont);
  assertLUB(cont, defFunc, {});
  assertLUB(cont, defCont, cont);
  assertLUB(cont, defStruct, {});
  assertLUB(cont, defArray, {});
  assertLUB(cont, sharedAny, {});
  assertLUB(cont, sharedEq, {});
  assertLUB(cont, sharedI31, {});
  assertLUB(cont, sharedStruct, {});
  assertLUB(cont, sharedNone, {});
  assertLUB(cont, sharedFunc, {});
  assertLUB(cont, sharedDefStruct, {});
  assertLUB(cont, sharedDefFunc, {});

  assertLUB(any, any, any);
  assertLUB(any, cont, {});
  assertLUB(any, eq, any);
  assertLUB(any, i31, any);
  assertLUB(any, struct_, any);
  assertLUB(any, array, any);
  assertLUB(any, string, any);
  assertLUB(any, none, any);
  assertLUB(any, noext, {});
  assertLUB(any, nofunc, {});
  assertLUB(any, nocont, {});
  assertLUB(any, defFunc, {});
  assertLUB(any, defCont, {});
  assertLUB(any, defStruct, any);
  assertLUB(any, defArray, any);
  assertLUB(any, sharedAny, {});
  assertLUB(any, sharedEq, {});
  assertLUB(any, sharedI31, {});
  assertLUB(any, sharedStruct, {});
  assertLUB(any, sharedNone, {});
  assertLUB(any, sharedFunc, {});
  assertLUB(any, sharedDefStruct, {});
  assertLUB(any, sharedDefFunc, {});

  assertLUB(eq, eq, eq);
  assertLUB(eq, cont, {});
  assertLUB(eq, i31, eq);
  assertLUB(eq, struct_, eq);
  assertLUB(eq, array, eq);
  assertLUB(eq, string, any);
  assertLUB(eq, none, eq);
  assertLUB(eq, noext, {});
  assertLUB(eq, nofunc, {});
  assertLUB(eq, nocont, {});
  assertLUB(eq, defFunc, {});
  assertLUB(eq, defCont, {});
  assertLUB(eq, defStruct, eq);
  assertLUB(eq, defArray, eq);
  assertLUB(eq, sharedAny, {});
  assertLUB(eq, sharedEq, {});
  assertLUB(eq, sharedI31, {});
  assertLUB(eq, sharedStruct, {});
  assertLUB(eq, sharedNone, {});
  assertLUB(eq, sharedFunc, {});
  assertLUB(eq, sharedDefStruct, {});
  assertLUB(eq, sharedDefFunc, {});

  assertLUB(i31, i31, i31);
  assertLUB(i31, cont, {});
  assertLUB(i31, struct_, eq);
  assertLUB(i31, array, eq);
  assertLUB(i31, string, any);
  assertLUB(i31, none, i31);
  assertLUB(i31, noext, {});
  assertLUB(i31, nofunc, {});
  assertLUB(i31, nocont, {});
  assertLUB(i31, defFunc, {});
  assertLUB(i31, defCont, {});
  assertLUB(i31, defStruct, eq);
  assertLUB(i31, defArray, eq);
  assertLUB(i31, sharedAny, {});
  assertLUB(i31, sharedEq, {});
  assertLUB(i31, sharedI31, {});
  assertLUB(i31, sharedStruct, {});
  assertLUB(i31, sharedNone, {});
  assertLUB(i31, sharedFunc, {});
  assertLUB(i31, sharedDefStruct, {});
  assertLUB(i31, sharedDefFunc, {});

  assertLUB(struct_, struct_, struct_);
  assertLUB(struct_, cont, {});
  assertLUB(struct_, array, eq);
  assertLUB(struct_, string, any);
  assertLUB(struct_, none, struct_);
  assertLUB(struct_, noext, {});
  assertLUB(struct_, nofunc, {});
  assertLUB(struct_, nocont, {});
  assertLUB(struct_, defFunc, {});
  assertLUB(struct_, defCont, {});
  assertLUB(struct_, defStruct, struct_);
  assertLUB(struct_, defArray, eq);
  assertLUB(struct_, sharedAny, {});
  assertLUB(struct_, sharedEq, {});
  assertLUB(struct_, sharedI31, {});
  assertLUB(struct_, sharedStruct, {});
  assertLUB(struct_, sharedNone, {});
  assertLUB(struct_, sharedFunc, {});
  assertLUB(struct_, sharedDefStruct, {});
  assertLUB(struct_, sharedDefFunc, {});

  assertLUB(array, array, array);
  assertLUB(array, cont, {});
  assertLUB(array, string, any);
  assertLUB(array, none, array);
  assertLUB(array, noext, {});
  assertLUB(array, nofunc, {});
  assertLUB(array, nocont, {});
  assertLUB(array, defFunc, {});
  assertLUB(array, defCont, {});
  assertLUB(array, defStruct, eq);
  assertLUB(array, defArray, array);
  assertLUB(array, sharedAny, {});
  assertLUB(array, sharedEq, {});
  assertLUB(array, sharedI31, {});
  assertLUB(array, sharedStruct, {});
  assertLUB(array, sharedNone, {});
  assertLUB(array, sharedFunc, {});
  assertLUB(array, sharedDefStruct, {});
  assertLUB(array, sharedDefFunc, {});

  assertLUB(string, string, string);
  assertLUB(string, cont, {});
  assertLUB(string, none, string);
  assertLUB(string, noext, {});
  assertLUB(string, nofunc, {});
  assertLUB(string, nocont, {});
  assertLUB(string, defFunc, {});
  assertLUB(string, defCont, {});
  assertLUB(string, defStruct, any);
  assertLUB(string, defArray, any);
  assertLUB(string, sharedAny, {});
  assertLUB(string, sharedEq, {});
  assertLUB(string, sharedI31, {});
  assertLUB(string, sharedStruct, {});
  assertLUB(string, sharedNone, {});
  assertLUB(string, sharedFunc, {});
  assertLUB(string, sharedDefStruct, {});
  assertLUB(string, sharedDefFunc, {});

  assertLUB(none, none, none);
  assertLUB(none, noext, {});
  assertLUB(none, nofunc, {});
  assertLUB(none, nocont, {});
  assertLUB(none, defFunc, {});
  assertLUB(none, defCont, {});
  assertLUB(none, defStruct, defStruct);
  assertLUB(none, defArray, defArray);
  assertLUB(none, sharedAny, {});
  assertLUB(none, sharedEq, {});
  assertLUB(none, sharedI31, {});
  assertLUB(none, sharedStruct, {});
  assertLUB(none, sharedNone, {});
  assertLUB(none, sharedFunc, {});
  assertLUB(none, sharedDefStruct, {});
  assertLUB(none, sharedDefFunc, {});

  assertLUB(noext, noext, noext);
  assertLUB(noext, nofunc, {});
  assertLUB(noext, nocont, {});
  assertLUB(noext, defFunc, {});
  assertLUB(noext, defCont, {});
  assertLUB(noext, defStruct, {});
  assertLUB(noext, defArray, {});
  assertLUB(noext, sharedAny, {});
  assertLUB(noext, sharedEq, {});
  assertLUB(noext, sharedI31, {});
  assertLUB(noext, sharedStruct, {});
  assertLUB(noext, sharedNone, {});
  assertLUB(noext, sharedFunc, {});
  assertLUB(noext, sharedDefStruct, {});
  assertLUB(noext, sharedDefFunc, {});

  assertLUB(nofunc, nofunc, nofunc);
  assertLUB(nofunc, nocont, {});
  assertLUB(nofunc, defFunc, defFunc);
  assertLUB(nofunc, defCont, {});
  assertLUB(nofunc, defStruct, {});
  assertLUB(nofunc, defArray, {});
  assertLUB(nofunc, sharedAny, {});
  assertLUB(nofunc, sharedEq, {});
  assertLUB(nofunc, sharedI31, {});
  assertLUB(nofunc, sharedStruct, {});
  assertLUB(nofunc, sharedNone, {});
  assertLUB(nofunc, sharedFunc, {});
  assertLUB(nofunc, sharedDefStruct, {});
  assertLUB(nofunc, sharedDefFunc, {});

  assertLUB(nocont, nocont, nocont);
  assertLUB(nocont, func, {});
  assertLUB(nocont, cont, cont);
  assertLUB(nocont, nofunc, {});
  assertLUB(nocont, defFunc, {});
  assertLUB(nocont, defCont, defCont);
  assertLUB(nocont, defStruct, {});
  assertLUB(nocont, defArray, {});
  assertLUB(nocont, sharedAny, {});
  assertLUB(nocont, sharedEq, {});
  assertLUB(nocont, sharedI31, {});
  assertLUB(nocont, sharedStruct, {});
  assertLUB(nocont, sharedNone, {});
  assertLUB(nocont, sharedFunc, {});
  assertLUB(nocont, sharedDefStruct, {});
  assertLUB(nocont, sharedDefFunc, {});

  assertLUB(defFunc, defFunc, defFunc);
  assertLUB(defFunc, defCont, {});
  assertLUB(defFunc, defStruct, {});
  assertLUB(defFunc, defArray, {});
  assertLUB(defFunc, sharedAny, {});
  assertLUB(defFunc, sharedEq, {});
  assertLUB(defFunc, sharedI31, {});
  assertLUB(defFunc, sharedStruct, {});
  assertLUB(defFunc, sharedNone, {});
  assertLUB(defFunc, sharedFunc, {});
  assertLUB(defFunc, sharedDefStruct, {});
  assertLUB(defFunc, sharedDefFunc, {});

  assertLUB(defCont, defCont, defCont);
  assertLUB(defCont, defFunc, {});
  assertLUB(defCont, defStruct, {});
  assertLUB(defCont, defArray, {});
  assertLUB(defCont, sharedAny, {});
  assertLUB(defCont, sharedEq, {});
  assertLUB(defCont, sharedI31, {});
  assertLUB(defCont, sharedStruct, {});
  assertLUB(defCont, sharedNone, {});
  assertLUB(defCont, sharedFunc, {});
  assertLUB(defCont, sharedDefStruct, {});
  assertLUB(defCont, sharedDefFunc, {});

  assertLUB(defStruct, defStruct, defStruct);
  assertLUB(defStruct, defArray, eq);
  assertLUB(defStruct, sharedAny, {});
  assertLUB(defStruct, sharedEq, {});
  assertLUB(defStruct, sharedI31, {});
  assertLUB(defStruct, sharedStruct, {});
  assertLUB(defStruct, sharedNone, {});
  assertLUB(defStruct, sharedFunc, {});
  assertLUB(defStruct, sharedDefStruct, {});
  assertLUB(defStruct, sharedDefFunc, {});

  assertLUB(defArray, defArray, defArray);
  assertLUB(defArray, sharedAny, {});
  assertLUB(defArray, sharedEq, {});
  assertLUB(defArray, sharedI31, {});
  assertLUB(defArray, sharedStruct, {});
  assertLUB(defArray, sharedNone, {});
  assertLUB(defArray, sharedFunc, {});
  assertLUB(defArray, sharedDefStruct, {});
  assertLUB(defArray, sharedDefFunc, {});

  assertLUB(sharedAny, sharedAny, sharedAny);
  assertLUB(sharedAny, sharedEq, sharedAny);
  assertLUB(sharedAny, sharedI31, sharedAny);
  assertLUB(sharedAny, sharedStruct, sharedAny);
  assertLUB(sharedAny, sharedNone, sharedAny);
  assertLUB(sharedAny, sharedFunc, {});
  assertLUB(sharedAny, sharedDefStruct, sharedAny);
  assertLUB(sharedAny, sharedDefFunc, {});

  assertLUB(sharedEq, sharedEq, sharedEq);
  assertLUB(sharedEq, sharedI31, sharedEq);
  assertLUB(sharedEq, sharedStruct, sharedEq);
  assertLUB(sharedEq, sharedNone, sharedEq);
  assertLUB(sharedEq, sharedFunc, {});
  assertLUB(sharedEq, sharedDefStruct, sharedEq);
  assertLUB(sharedEq, sharedDefFunc, {});

  assertLUB(sharedI31, sharedI31, sharedI31);
  assertLUB(sharedI31, sharedStruct, sharedEq);
  assertLUB(sharedI31, sharedNone, sharedI31);
  assertLUB(sharedI31, sharedFunc, {});
  assertLUB(sharedI31, sharedDefStruct, sharedEq);
  assertLUB(sharedI31, sharedDefFunc, {});

  assertLUB(sharedStruct, sharedStruct, sharedStruct);
  assertLUB(sharedStruct, sharedNone, sharedStruct);
  assertLUB(sharedStruct, sharedFunc, {});
  assertLUB(sharedStruct, sharedDefStruct, sharedStruct);
  assertLUB(sharedStruct, sharedDefFunc, {});

  assertLUB(sharedNone, sharedNone, sharedNone);
  assertLUB(sharedNone, sharedFunc, {});
  assertLUB(sharedNone, sharedDefStruct, sharedDefStruct);
  assertLUB(sharedNone, sharedDefFunc, {});

  assertLUB(sharedFunc, sharedFunc, sharedFunc);
  assertLUB(sharedFunc, sharedDefStruct, {});
  assertLUB(sharedFunc, sharedDefFunc, sharedFunc);

  assertLUB(sharedDefStruct, sharedDefStruct, sharedDefStruct);
  assertLUB(sharedDefStruct, sharedDefFunc, {});

  assertLUB(sharedDefFunc, sharedDefFunc, sharedDefFunc);

  Type anyref = Type(any, Nullable);
  Type eqref = Type(eq, Nullable);

  {
    // Nullable and non-nullable references.
    Type nonNullable(any, NonNullable);
    EXPECT_TRUE(Type::isSubType(nonNullable, anyref));
    EXPECT_FALSE(Type::isSubType(anyref, nonNullable));
    EXPECT_TRUE(Type::hasLeastUpperBound(anyref, nonNullable));
    EXPECT_EQ(Type::getLeastUpperBound(anyref, nonNullable), anyref);
  }

  {
    // Immutable array fields are covariant.
    TypeBuilder builder(2);
    builder[0].setOpen() = Array(Field(anyref, Immutable));
    builder[1].setOpen().subTypeOf(builder[0]) = Array(Field(eqref, Immutable));
    auto results = builder.build();
    ASSERT_TRUE(results);
    auto built = *results;
    EXPECT_TRUE(HeapType::isSubType(built[1], built[0]));
  }

  {
    // Depth subtyping
    TypeBuilder builder(2);
    builder[0].setOpen() = Struct({Field(anyref, Immutable)});
    builder[1].setOpen().subTypeOf(builder[0]) =
      Struct({Field(eqref, Immutable)});
    auto results = builder.build();
    ASSERT_TRUE(results);
    auto built = *results;
    EXPECT_TRUE(HeapType::isSubType(built[1], built[0]));
  }

  {
    // Width subtyping
    TypeBuilder builder(2);
    builder[0].setOpen() = Struct({Field(anyref, Immutable)});
    builder[1].setOpen().subTypeOf(builder[0]) =
      Struct({Field(anyref, Immutable), Field(anyref, Immutable)});
    auto results = builder.build();
    ASSERT_TRUE(results);
    auto built = *results;
    EXPECT_TRUE(HeapType::isSubType(built[1], built[0]));
  }

  {
    // Nested structs
    TypeBuilder builder(4);
    auto ref0 = builder.getTempRefType(builder[0], Nullable);
    auto ref1 = builder.getTempRefType(builder[1], Nullable);
    builder[0].setOpen() = Struct({Field(anyref, Immutable)});
    builder[1].setOpen().subTypeOf(builder[0]) =
      Struct({Field(eqref, Immutable)});
    builder[2].setOpen() = Struct({Field(ref0, Immutable)});
    builder[3].setOpen().subTypeOf(builder[2]) =
      Struct({Field(ref1, Immutable)});
    auto results = builder.build();
    ASSERT_TRUE(results);
    auto built = *results;
    EXPECT_TRUE(HeapType::isSubType(built[3], built[2]));
  }

  {
    // Recursive structs
    TypeBuilder builder(2);
    auto ref0 = builder.getTempRefType(builder[0], Nullable);
    auto ref1 = builder.getTempRefType(builder[1], Nullable);
    builder[0].setOpen() = Struct({Field(ref0, Immutable)});
    builder[1].setOpen().subTypeOf(builder[0]) =
      Struct({Field(ref1, Immutable)});
    auto results = builder.build();
    ASSERT_TRUE(results);
    auto built = *results;
    EXPECT_TRUE(HeapType::isSubType(built[1], built[0]));
  }
}

TEST_F(TypeTest, TestSubtypeErrors) {
  Type anyref = Type(HeapType::any, Nullable);
  Type eqref = Type(HeapType::eq, Nullable);
  Type funcref = Type(HeapType::func, Nullable);

  {
    // Incompatible signatures.
    TypeBuilder builder(2);
    builder[0] = Signature(Type::none, anyref);
    builder[1] = Signature(anyref, Type::none);
    builder[1].subTypeOf(builder[0]);
    EXPECT_FALSE(builder.build());
  }

  {
    // Signatures incompatible in tuple size.
    TypeBuilder builder(2);
    builder[0] = Signature(Type::none, {anyref, anyref});
    builder[1] = Signature(Type::none, {anyref, anyref, anyref});
    builder[1].subTypeOf(builder[0]);
    EXPECT_FALSE(builder.build());
  }

  {
    // Mutable array fields are invariant.
    TypeBuilder builder(2);
    builder[0] = Array(Field(anyref, Mutable));
    builder[1] = Array(Field(eqref, Mutable));
    builder[1].subTypeOf(builder[0]);
    EXPECT_FALSE(builder.build());
  }

  {
    // Incompatible struct prefixes
    TypeBuilder builder(2);
    builder[0] = Struct({Field(anyref, Immutable), Field(anyref, Immutable)});
    builder[1] = Struct({Field(funcref, Immutable), Field(anyref, Immutable)});
    builder[1].subTypeOf(builder[0]);
    EXPECT_FALSE(builder.build());
  }
}

// Test SubTypes utility code.
TEST_F(TypeTest, TestSubTypes) {
  Type anyref = Type(HeapType::any, Nullable);
  Type eqref = Type(HeapType::eq, Nullable);
  Type sharedAnyref = Type(HeapTypes::any.getBasic(Shared), Nullable);
  Type sharedEqref = Type(HeapTypes::eq.getBasic(Shared), Nullable);

  // Build type types, the second of which is a subtype.
  TypeBuilder builder(4);
  builder[0].setOpen() = Struct({Field(anyref, Immutable)});
  builder[1].setOpen() = Struct({Field(eqref, Immutable)});
  // Make shared versions, too.
  builder[2].setOpen().setShared() = Array(Field(sharedAnyref, Immutable));
  builder[3].setOpen().setShared() = Array(Field(sharedEqref, Immutable));
  builder[1].subTypeOf(builder[0]);
  builder[3].subTypeOf(builder[2]);

  auto result = builder.build();
  ASSERT_TRUE(result);
  auto built = *result;

  // Build a tiny wasm module that uses the types, so that we can test the
  // SubTypes utility code.
  Module wasm;
  Builder wasmBuilder(wasm);
  wasm.addFunction(wasmBuilder.makeFunction("func",
                                            Signature(Type::none, Type::none),
                                            {Type(built[0], Nullable),
                                             Type(built[1], Nullable),
                                             Type(built[2], Nullable),
                                             Type(built[3], Nullable)},
                                            wasmBuilder.makeNop()));
  SubTypes subTypes(wasm);
  auto immSubTypes0 = subTypes.getImmediateSubTypes(built[0]);
  ASSERT_EQ(immSubTypes0.size(), 1u);
  EXPECT_EQ(immSubTypes0[0], built[1]);
  auto subTypes0 = subTypes.getSubTypes(built[0]);
  ASSERT_EQ(subTypes0.size(), 2u);
  EXPECT_EQ(subTypes0[0], built[1]);
  EXPECT_EQ(subTypes0[1], built[0]);
  auto immSubTypes1 = subTypes.getImmediateSubTypes(built[1]);
  EXPECT_EQ(immSubTypes1.size(), 0u);

  auto depths = subTypes.getMaxDepths();
  EXPECT_EQ(depths[HeapTypes::any.getBasic(Unshared)], 4u);
  EXPECT_EQ(depths[HeapTypes::any.getBasic(Shared)], 4u);
  EXPECT_EQ(depths[HeapTypes::eq.getBasic(Unshared)], 3u);
  EXPECT_EQ(depths[HeapTypes::eq.getBasic(Shared)], 3u);
  EXPECT_EQ(depths[HeapTypes::struct_.getBasic(Unshared)], 2u);
  EXPECT_EQ(depths[HeapTypes::struct_.getBasic(Shared)], 0u);
  EXPECT_EQ(depths[HeapTypes::array.getBasic(Unshared)], 0u);
  EXPECT_EQ(depths[HeapTypes::array.getBasic(Shared)], 2u);
  EXPECT_EQ(depths[built[0]], 1u);
  EXPECT_EQ(depths[built[1]], 0u);
  EXPECT_EQ(depths[built[2]], 1u);
  EXPECT_EQ(depths[built[3]], 0u);
}

// Test reuse of a previously built type as supertype.
TEST_F(TypeTest, TestExistingSuperType) {
  // Build an initial type A1
  Type A1;
  {
    TypeBuilder builder(1);
    builder[0].setOpen() = Struct();
    auto result = builder.build();
    ASSERT_TRUE(result);
    auto built = *result;
    A1 = Type(built[0], Nullable);
  }

  // Build a separate type A2 identical to A1
  Type A2;
  {
    TypeBuilder builder(1);
    builder[0].setOpen() = Struct();
    auto result = builder.build();
    ASSERT_TRUE(result);
    auto built = *result;
    A2 = Type(built[0], Nullable);
  }

  // Build a type B1 <: A1 using a new builder
  Type B1;
  {
    TypeBuilder builder(1);
    builder[0].setOpen().subTypeOf(A1.getHeapType()) = Struct();
    auto result = builder.build();
    ASSERT_TRUE(result);
    auto built = *result;
    B1 = Type(built[0], Nullable);
  }

  // Build a type B2 <: A2 using a new builder
  Type B2;
  {
    TypeBuilder builder(1);
    builder[0].setOpen().subTypeOf(A2.getHeapType()) = Struct();
    auto result = builder.build();
    ASSERT_TRUE(result);
    auto built = *result;
    B2 = Type(built[0], Nullable);
  }

  // Test that A1 == A2 and B1 == B2
  EXPECT_EQ(A1.getHeapType(), A2.getHeapType());
  EXPECT_EQ(B1.getHeapType(), B2.getHeapType());
}

// Test .getMaxDepths() helper.
TEST_F(TypeTest, TestMaxStructDepths) {
  /*
      A
      |
      B
  */
  HeapType A, B;
  {
    TypeBuilder builder(2);
    builder[0].setOpen() = Struct();
    builder[1].setOpen().subTypeOf(builder[0]) = Struct();
    auto result = builder.build();
    ASSERT_TRUE(result);
    auto built = *result;
    A = built[0];
    B = built[1];
  }

  SubTypes subTypes({A, B});
  auto maxDepths = subTypes.getMaxDepths();

  EXPECT_EQ(maxDepths[B], Index(0));
  EXPECT_EQ(maxDepths[A], Index(1));
  EXPECT_EQ(maxDepths[HeapType::struct_], Index(2));
  EXPECT_EQ(maxDepths[HeapType::eq], Index(3));
  EXPECT_EQ(maxDepths[HeapType::any], Index(4));
}

TEST_F(TypeTest, TestMaxArrayDepths) {
  HeapType A;
  {
    TypeBuilder builder(1);
    builder[0] = Array(Field(Type::i32, Immutable));
    auto result = builder.build();
    ASSERT_TRUE(result);
    auto built = *result;
    A = built[0];
  }

  SubTypes subTypes({A});
  auto maxDepths = subTypes.getMaxDepths();

  EXPECT_EQ(maxDepths[A], Index(0));
  EXPECT_EQ(maxDepths[HeapType::array], Index(1));
  EXPECT_EQ(maxDepths[HeapType::eq], Index(2));
  EXPECT_EQ(maxDepths[HeapType::any], Index(3));
}

// Test .depth() helper.
TEST_F(TypeTest, TestDepth) {
  HeapType A, B, C;
  HeapType sig = HeapType(Signature(Type::none, Type::none));
  {
    TypeBuilder builder(3);
    builder[0].setOpen() = Struct();
    builder[1].setOpen().subTypeOf(builder[0]) = Struct();
    builder[2].setOpen() = Array(Field(Type::i32, Immutable));
    auto result = builder.build();
    ASSERT_TRUE(result);
    auto built = *result;
    A = built[0];
    B = built[1];
    C = built[2];
  }

  // any :> eq :> array :> specific array types
  EXPECT_EQ(HeapTypes::any.getDepth(), 0U);
  EXPECT_EQ(HeapTypes::eq.getDepth(), 1U);
  EXPECT_EQ(HeapTypes::array.getDepth(), 2U);
  EXPECT_EQ(HeapTypes::struct_.getDepth(), 2U);
  EXPECT_EQ(A.getDepth(), 3U);
  EXPECT_EQ(B.getDepth(), 4U);
  EXPECT_EQ(C.getDepth(), 3U);

  // Signature types are subtypes of func.
  EXPECT_EQ(HeapTypes::func.getDepth(), 0U);
  EXPECT_EQ(sig.getDepth(), 1U);

  // Continuation types are subtypes of cont.
  EXPECT_EQ(HeapTypes::cont.getDepth(), 0U);
  EXPECT_EQ(HeapType(Continuation(sig)).getDepth(), 1U);

  EXPECT_EQ(HeapTypes::ext.getDepth(), 0U);

  EXPECT_EQ(HeapTypes::i31.getDepth(), 2U);
  EXPECT_EQ(HeapTypes::string.getDepth(), 2U);

  EXPECT_EQ(HeapTypes::none.getDepth(), size_t(-1));
  EXPECT_EQ(HeapTypes::nofunc.getDepth(), size_t(-1));
  EXPECT_EQ(HeapTypes::noext.getDepth(), size_t(-1));
}

// Test .iterSubTypes() helper.
TEST_F(TypeTest, TestIterSubTypes) {
  /*
        A
       / \
      B   C
           \
            D
  */
  HeapType A, B, C, D;
  {
    TypeBuilder builder(4);
    builder[0].setOpen() = Struct();
    builder[1].setOpen().subTypeOf(builder[0]) = Struct();
    builder[2].setOpen().subTypeOf(builder[0]) = Struct();
    builder[3].setOpen().subTypeOf(builder[2]) = Struct();
    auto result = builder.build();
    ASSERT_TRUE(result);
    auto built = *result;
    A = built[0];
    B = built[1];
    C = built[2];
    D = built[3];
  }

  SubTypes subTypes({A, B, C, D});

  // Helper for comparing sets of types + their corresponding depth.
  using TypeDepths = std::unordered_set<std::pair<HeapType, Index>>;

  auto getSubTypes = [&](HeapType type, Index depth) {
    TypeDepths ret;
    subTypes.iterSubTypes(type, depth, [&](HeapType subType, Index depth) {
      ret.insert({subType, depth});
    });
    return ret;
  };

  EXPECT_EQ(getSubTypes(A, 0), TypeDepths({{A, 0}}));
  EXPECT_EQ(getSubTypes(A, 1), TypeDepths({{A, 0}, {B, 1}, {C, 1}}));
  EXPECT_EQ(getSubTypes(A, 2), TypeDepths({{A, 0}, {B, 1}, {C, 1}, {D, 2}}));
  EXPECT_EQ(getSubTypes(A, 3), TypeDepths({{A, 0}, {B, 1}, {C, 1}, {D, 2}}));

  EXPECT_EQ(getSubTypes(C, 0), TypeDepths({{C, 0}}));
  EXPECT_EQ(getSubTypes(C, 1), TypeDepths({{C, 0}, {D, 1}}));
  EXPECT_EQ(getSubTypes(C, 2), TypeDepths({{C, 0}, {D, 1}}));
}

// Test supertypes
TEST_F(TypeTest, TestSupertypes) {
  // Basic types: getDeclaredSuperType always returns nothing.
  ASSERT_FALSE(HeapTypes::ext.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::func.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::cont.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::any.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::eq.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::i31.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::struct_.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::array.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::string.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::none.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::noext.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::nofunc.getDeclaredSuperType());
  ASSERT_FALSE(HeapTypes::nocont.getDeclaredSuperType());

  // Basic types: getSuperType does return a super, when there is one.
  ASSERT_FALSE(HeapTypes::ext.getSuperType());
  ASSERT_FALSE(HeapTypes::func.getSuperType());
  ASSERT_FALSE(HeapTypes::cont.getSuperType());
  ASSERT_FALSE(HeapTypes::any.getSuperType());
  ASSERT_EQ(HeapTypes::eq.getSuperType(), HeapType::any);
  ASSERT_EQ(HeapTypes::i31.getSuperType(), HeapType::eq);
  ASSERT_EQ(HeapTypes::struct_.getSuperType(), HeapType::eq);
  ASSERT_EQ(HeapTypes::array.getSuperType(), HeapType::eq);
  ASSERT_FALSE(HeapTypes::string.getSuperType());
  ASSERT_FALSE(HeapTypes::none.getSuperType());
  ASSERT_FALSE(HeapTypes::noext.getSuperType());
  ASSERT_FALSE(HeapTypes::nofunc.getSuperType());
  ASSERT_FALSE(HeapTypes::nocont.getSuperType());

  // Non-basic types.
  HeapType struct1, struct2, array1, array2, sig1, sig2;
  {
    TypeBuilder builder(6);
    builder[0].setOpen() = Struct();
    builder[1].setOpen().subTypeOf(builder[0]) = Struct();
    auto array = Array(Field(Type::i32, Immutable));
    builder[2].setOpen() = array;
    builder[3].setOpen().subTypeOf(builder[2]) = array;
    auto sig = Signature(Type::none, Type::none);
    builder[4].setOpen() = sig;
    builder[5].setOpen().subTypeOf(builder[4]) = sig;
    auto result = builder.build();
    ASSERT_TRUE(result);
    auto built = *result;
    struct1 = built[0];
    struct2 = built[1];
    array1 = built[2];
    array2 = built[3];
    sig1 = built[4];
    sig2 = built[5];
  }

  ASSERT_EQ(struct1.getSuperType(), HeapType::struct_);
  ASSERT_EQ(struct2.getSuperType(), struct1);
  ASSERT_EQ(array1.getSuperType(), HeapType::array);
  ASSERT_EQ(array2.getSuperType(), array1);
  ASSERT_EQ(sig1.getSuperType(), HeapType::func);
  ASSERT_EQ(sig2.getSuperType(), sig1);

  // With getDeclaredSuperType we don't get basic supers, only declared ones.
  ASSERT_FALSE(struct1.getDeclaredSuperType());
  ASSERT_EQ(struct2.getDeclaredSuperType(), struct1);
  ASSERT_FALSE(array1.getDeclaredSuperType());
  ASSERT_EQ(array2.getDeclaredSuperType(), array1);
  ASSERT_FALSE(sig1.getDeclaredSuperType());
  ASSERT_EQ(sig2.getDeclaredSuperType(), sig1);
}
