/*
 * Copyright 2023 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "analysis/lattice.h"
#include "analysis/lattices/abstraction.h"
#include "analysis/lattices/array.h"
#include "analysis/lattices/bool.h"
#include "analysis/lattices/conetype.h"
#include "analysis/lattices/flat.h"
#include "analysis/lattices/int.h"
#include "analysis/lattices/inverted.h"
#include "analysis/lattices/lift.h"
#include "analysis/lattices/shared.h"
#include "analysis/lattices/stack.h"
#include "analysis/lattices/tuple.h"
#include "analysis/lattices/valtype.h"
#include "analysis/lattices/vector.h"
#include "ir/subtypes.h"
#include "wasm-type.h"
#include "gtest/gtest.h"

using namespace wasm;

// Exhaustively test comparison on a subset of a lattice that is arranged like a
// diamond:
//
//   Top
//  /   \.
// A     B
//  \   /
//   Bot
//
template<typename L>
void testDiamondCompare(const L& lattice,
                        const typename L::Element& bot,
                        const typename L::Element& a,
                        const typename L::Element& b,
                        const typename L::Element& top) {
  EXPECT_EQ(lattice.compare(bot, bot), analysis::EQUAL);
  EXPECT_EQ(lattice.compare(bot, a), analysis::LESS);
  EXPECT_EQ(lattice.compare(bot, b), analysis::LESS);
  EXPECT_EQ(lattice.compare(bot, top), analysis::LESS);

  EXPECT_EQ(lattice.compare(a, bot), analysis::GREATER);
  EXPECT_EQ(lattice.compare(a, a), analysis::EQUAL);
  EXPECT_EQ(lattice.compare(a, b), analysis::NO_RELATION);
  EXPECT_EQ(lattice.compare(a, top), analysis::LESS);

  EXPECT_EQ(lattice.compare(b, bot), analysis::GREATER);
  EXPECT_EQ(lattice.compare(b, a), analysis::NO_RELATION);
  EXPECT_EQ(lattice.compare(b, b), analysis::EQUAL);
  EXPECT_EQ(lattice.compare(b, top), analysis::LESS);

  EXPECT_EQ(lattice.compare(top, bot), analysis::GREATER);
  EXPECT_EQ(lattice.compare(top, a), analysis::GREATER);
  EXPECT_EQ(lattice.compare(top, b), analysis::GREATER);
  EXPECT_EQ(lattice.compare(top, top), analysis::EQUAL);
}

// Same as above but for join.
template<typename L>
void testDiamondJoin(const L& lattice,
                     const typename L::Element& bot,
                     const typename L::Element& a,
                     const typename L::Element& b,
                     const typename L::Element& top) {

  auto test =
    [&](const auto& joinee, const auto& joiner, const auto& expected) {
      auto copy = joinee;
      EXPECT_EQ(lattice.join(copy, joiner), joinee != expected);
      EXPECT_EQ(copy, expected);
    };

  test(bot, bot, bot);
  test(bot, a, a);
  test(bot, b, b);
  test(bot, top, top);

  test(a, bot, a);
  test(a, a, a);
  test(a, b, top);
  test(a, top, top);

  test(b, bot, b);
  test(b, a, top);
  test(b, b, b);
  test(b, top, top);

  test(top, bot, top);
  test(top, a, top);
  test(top, b, top);
  test(top, top, top);
}

// Same as above but for meet.
template<typename L>
void testDiamondMeet(const L& lattice,
                     const typename L::Element& bot,
                     const typename L::Element& a,
                     const typename L::Element& b,
                     const typename L::Element& top) {
  auto test =
    [&](const auto& meetee, const auto& meeter, const auto& expected) {
      auto copy = meetee;
      EXPECT_EQ(lattice.meet(copy, meeter), meetee != expected);
      EXPECT_EQ(copy, expected);
    };

  test(bot, bot, bot);
  test(bot, a, bot);
  test(bot, b, bot);
  test(bot, top, bot);

  test(a, bot, bot);
  test(a, a, a);
  test(a, b, bot);
  test(a, top, a);

  test(b, bot, bot);
  test(b, a, bot);
  test(b, b, b);
  test(b, top, b);

  test(top, bot, bot);
  test(top, a, a);
  test(top, b, b);
  test(top, top, top);
}

TEST(BoolLattice, GetBottom) {
  analysis::Bool lattice;
  EXPECT_FALSE(lattice.getBottom());
}

TEST(BoolLattice, GetTop) {
  analysis::Bool lattice;
  EXPECT_TRUE(lattice.getTop());
}

TEST(BoolLattice, Compare) {
  analysis::Bool lattice;
  EXPECT_EQ(lattice.compare(false, false), analysis::EQUAL);
  EXPECT_EQ(lattice.compare(false, true), analysis::LESS);
  EXPECT_EQ(lattice.compare(true, false), analysis::GREATER);
  EXPECT_EQ(lattice.compare(true, true), analysis::EQUAL);
}

TEST(BoolLattice, Join) {
  analysis::Bool lattice;
  analysis::Bool::Element elem = false;

  EXPECT_FALSE(lattice.join(elem, false));
  ASSERT_FALSE(elem);

  EXPECT_TRUE(lattice.join(elem, true));
  ASSERT_TRUE(elem);

  EXPECT_FALSE(lattice.join(elem, false));
  ASSERT_TRUE(elem);

  EXPECT_FALSE(lattice.join(elem, true));
  ASSERT_TRUE(elem);
}

TEST(BoolLattice, Meet) {
  analysis::Bool lattice;
  analysis::Bool::Element elem = true;

  EXPECT_FALSE(lattice.meet(elem, true));
  ASSERT_TRUE(elem);

  EXPECT_TRUE(lattice.meet(elem, false));
  ASSERT_FALSE(elem);

  EXPECT_FALSE(lattice.meet(elem, true));
  ASSERT_FALSE(elem);

  EXPECT_FALSE(lattice.meet(elem, false));
  ASSERT_FALSE(elem);
}

TEST(IntLattice, GetBottom) {
  analysis::Int32 int32;
  EXPECT_EQ(int32.getBottom(), (int32_t)(1ull << 31));

  analysis::Int64 int64;
  EXPECT_EQ(int64.getBottom(), (int64_t)(1ull << 63));

  analysis::UInt32 uint32;
  EXPECT_EQ(uint32.getBottom(), (uint32_t)0);

  analysis::UInt64 uint64;
  EXPECT_EQ(uint64.getBottom(), (uint64_t)0);
}

TEST(IntLattice, GetTop) {
  analysis::Int32 int32;
  EXPECT_EQ(int32.getTop(), (int32_t)((1ull << 31) - 1));

  analysis::Int64 int64;
  EXPECT_EQ(int64.getTop(), (int64_t)((1ull << 63) - 1));

  analysis::UInt32 uint32;
  EXPECT_EQ(uint32.getTop(), (uint32_t)-1ull);

  analysis::UInt64 uint64;
  EXPECT_EQ(uint64.getTop(), (uint64_t)-1ull);
}

TEST(IntLattice, Compare) {
  analysis::Int32 int32;
  EXPECT_EQ(int32.compare(-5, 42), analysis::LESS);
  EXPECT_EQ(int32.compare(42, -5), analysis::GREATER);
  EXPECT_EQ(int32.compare(42, 42), analysis::EQUAL);
}

TEST(IntLattice, Join) {
  analysis::Int32 int32;
  int elem = 0;

  EXPECT_FALSE(int32.join(elem, -10));
  ASSERT_EQ(elem, 0);

  EXPECT_FALSE(int32.join(elem, 0));
  ASSERT_EQ(elem, 0);

  EXPECT_TRUE(int32.join(elem, 100));
  ASSERT_EQ(elem, 100);
}

TEST(IntLattice, Meet) {
  analysis::Int32 int32;
  int elem = 0;

  EXPECT_FALSE(int32.meet(elem, 10));
  ASSERT_EQ(elem, 0);

  EXPECT_FALSE(int32.meet(elem, 0));
  ASSERT_EQ(elem, 0);

  EXPECT_TRUE(int32.meet(elem, -100));
  ASSERT_EQ(elem, -100);
}

TEST(InvertedLattice, GetBottom) {
  analysis::Inverted inverted(analysis::Bool{});
  EXPECT_TRUE(inverted.getBottom());
}

TEST(InvertedLattice, GetTop) {
  analysis::Inverted inverted(analysis::Bool{});
  EXPECT_FALSE(inverted.getTop());
}

TEST(InvertedLattice, Compare) {
  analysis::Inverted inverted(analysis::Bool{});
  EXPECT_EQ(inverted.compare(false, false), analysis::EQUAL);
  EXPECT_EQ(inverted.compare(false, true), analysis::GREATER);
  EXPECT_EQ(inverted.compare(true, false), analysis::LESS);
  EXPECT_EQ(inverted.compare(true, true), analysis::EQUAL);
}

TEST(InvertedLattice, Join) {
  analysis::Inverted inverted(analysis::Bool{});
  analysis::Bool::Element elem = true;

  EXPECT_FALSE(inverted.join(elem, true));
  ASSERT_TRUE(elem);

  EXPECT_TRUE(inverted.join(elem, false));
  ASSERT_FALSE(elem);

  EXPECT_FALSE(inverted.join(elem, true));
  ASSERT_FALSE(elem);

  EXPECT_FALSE(inverted.join(elem, false));
  ASSERT_FALSE(elem);
}

TEST(InvertedLattice, Meet) {
  analysis::Inverted inverted(analysis::Bool{});
  analysis::Bool::Element elem = false;

  EXPECT_FALSE(inverted.meet(elem, false));
  ASSERT_FALSE(elem);

  EXPECT_TRUE(inverted.meet(elem, true));
  ASSERT_TRUE(elem);

  EXPECT_FALSE(inverted.meet(elem, false));
  ASSERT_TRUE(elem);

  EXPECT_FALSE(inverted.meet(elem, true));
  ASSERT_TRUE(elem);
}

TEST(InvertedLattice, DoubleInverted) {
  using DoubleInverted = analysis::Inverted<analysis::Inverted<analysis::Bool>>;
  DoubleInverted identity(analysis::Inverted<analysis::Bool>{analysis::Bool{}});
  EXPECT_FALSE(identity.getBottom());
  EXPECT_TRUE(identity.getTop());
}

TEST(FlatLattice, GetBottom) {
  analysis::Flat<int> flat;
  EXPECT_TRUE(flat.getBottom().isBottom());
  EXPECT_FALSE(flat.getBottom().getVal());
  EXPECT_FALSE(flat.getBottom().isTop());
}

TEST(FlatLattice, GetVal) {
  analysis::Flat<int> flat;
  EXPECT_FALSE(flat.get(10).isBottom());
  ASSERT_TRUE(flat.get(10).getVal());
  EXPECT_FALSE(flat.get(10).isTop());

  auto val = flat.get(10);
  EXPECT_EQ(*val.getVal(), 10);
}

TEST(FlatLattice, GetTop) {
  analysis::Flat<int> flat;
  EXPECT_FALSE(flat.getTop().isBottom());
  EXPECT_FALSE(flat.getTop().getVal());
  EXPECT_TRUE(flat.getTop().isTop());
}

TEST(FlatLattice, Compare) {
  analysis::Flat<int> flat;
  testDiamondCompare(
    flat, flat.getBottom(), flat.get(0), flat.get(1), flat.getTop());
}

TEST(FlatLattice, Join) {
  analysis::Flat<int> flat;
  testDiamondJoin(
    flat, flat.getBottom(), flat.get(0), flat.get(1), flat.getTop());
}

TEST(FlatLattice, MultipleTypes) {
  analysis::Flat<int, std::string> flat;
  testDiamondJoin(
    flat, flat.getBottom(), flat.get(0), flat.get("foo"), flat.getTop());

  auto stringElem = flat.get("foo");

  EXPECT_EQ(stringElem.getVal<0>(), nullptr);
  ASSERT_NE(stringElem.getVal<1>(), nullptr);
  EXPECT_EQ(*stringElem.getVal<1>(), std::string("foo"));

  EXPECT_EQ(stringElem.getVal<int>(), nullptr);
  ASSERT_NE(stringElem.getVal<std::string>(), nullptr);
  EXPECT_EQ(*stringElem.getVal<std::string>(), std::string("foo"));
}

TEST(LiftLattice, GetBottom) {
  analysis::Lift lift{analysis::Bool{}};
  EXPECT_TRUE(lift.getBottom().isBottom());
  EXPECT_FALSE(lift.getBottom().has_value());
}

TEST(LiftLattice, GetVal) {
  analysis::Lift lift{analysis::Bool{}};
  EXPECT_FALSE(lift.get(false).isBottom());
  EXPECT_FALSE(lift.get(true).isBottom());
  EXPECT_FALSE(*lift.get(false));
  EXPECT_TRUE(*lift.get(true));

  analysis::Lift liftInt{analysis::Int32{}};
  EXPECT_FALSE(liftInt.get(10).isBottom());
  EXPECT_EQ(*liftInt.get(0), 0);
  EXPECT_EQ(*liftInt.get(10), 10);
}

TEST(LiftLattice, Compare) {
  analysis::Lift lift{analysis::Flat<bool>{}};
  auto& liftee = lift.lattice;
  auto bot = lift.getBottom();
  auto lifteeBot = lift.get(liftee.getBottom());
  auto a = lift.get(liftee.get(false));
  auto b = lift.get(liftee.get(true));
  auto top = lift.get(liftee.getTop());

  EXPECT_EQ(lift.compare(bot, bot), analysis::EQUAL);
  EXPECT_EQ(lift.compare(bot, lifteeBot), analysis::LESS);
  EXPECT_EQ(lift.compare(bot, a), analysis::LESS);
  EXPECT_EQ(lift.compare(bot, b), analysis::LESS);
  EXPECT_EQ(lift.compare(bot, top), analysis::LESS);

  EXPECT_EQ(lift.compare(lifteeBot, bot), analysis::GREATER);
  EXPECT_EQ(lift.compare(a, bot), analysis::GREATER);
  EXPECT_EQ(lift.compare(b, bot), analysis::GREATER);
  EXPECT_EQ(lift.compare(top, bot), analysis::GREATER);

  EXPECT_EQ(lift.compare(a, b), analysis::NO_RELATION);
  EXPECT_EQ(lift.compare(a, top), analysis::LESS);
  EXPECT_EQ(lift.compare(a, lifteeBot), analysis::GREATER);
}

TEST(LiftLattice, Join) {
  analysis::Lift lift{analysis::Bool{}};
  auto bot = lift.getBottom();
  auto lifteeBot = lift.get(false);
  auto top = lift.get(true);

  // bot u bot = bot
  auto elem = bot;
  EXPECT_FALSE(lift.join(elem, bot));
  EXPECT_EQ(elem, bot);

  // bot u lifteeBot = lifteeBot
  EXPECT_TRUE(lift.join(elem, lifteeBot));
  EXPECT_EQ(elem, lifteeBot);

  // lifteeBot u bot = lifteeBot
  EXPECT_FALSE(lift.join(elem, bot));
  EXPECT_EQ(elem, lifteeBot);

  // lifteeBot u lifteeBot = lifteeBot
  EXPECT_FALSE(lift.join(elem, lifteeBot));
  EXPECT_EQ(elem, lifteeBot);

  // lifteeBot u top = top
  EXPECT_TRUE(lift.join(elem, top));
  EXPECT_EQ(elem, top);

  // top u bot = top
  EXPECT_FALSE(lift.join(elem, bot));
  EXPECT_EQ(elem, top);
}

TEST(ArrayLattice, GetBottom) {
  analysis::Array<analysis::Bool, 2> array{analysis::Bool{}};
  EXPECT_EQ(array.getBottom(),
            (std::array<analysis::Bool::Element, 2>{false, false}));
}

TEST(ArrayLattice, GetTop) {
  analysis::Array<analysis::Bool, 2> array{analysis::Bool{}};
  EXPECT_EQ(array.getTop(),
            (std::array<analysis::Bool::Element, 2>{true, true}));
}

TEST(ArrayLattice, Compare) {
  analysis::Array<analysis::Bool, 2> array{analysis::Bool{}};
  testDiamondCompare(
    array, {false, false}, {false, true}, {true, false}, {true, true});
}

TEST(ArrayLattice, Join) {
  analysis::Array<analysis::Bool, 2> array{analysis::Bool{}};
  testDiamondJoin(
    array, {false, false}, {false, true}, {true, false}, {true, true});
}

TEST(ArrayLattice, Meet) {
  analysis::Array<analysis::Bool, 2> array{analysis::Bool{}};
  testDiamondMeet(
    array, {false, false}, {false, true}, {true, false}, {true, true});
}

TEST(VectorLattice, GetBottom) {
  analysis::Vector<analysis::Bool> vector{analysis::Bool{}, 2};
  EXPECT_EQ(vector.getBottom(),
            (std::vector<analysis::Bool::Element>{false, false}));
}

TEST(VectorLattice, GetTop) {
  analysis::Vector<analysis::Bool> vector{analysis::Bool{}, 2};
  EXPECT_EQ(vector.getTop(),
            (std::vector<analysis::Bool::Element>{true, true}));
}

TEST(VectorLattice, Compare) {
  analysis::Vector<analysis::Bool> vector{analysis::Bool{}, 2};
  testDiamondCompare(
    vector, {false, false}, {false, true}, {true, false}, {true, true});
}

TEST(VectorLattice, Join) {
  analysis::Vector<analysis::Bool> vector{analysis::Bool{}, 2};
  testDiamondJoin(
    vector, {false, false}, {false, true}, {true, false}, {true, true});
}

TEST(VectorLattice, Meet) {
  analysis::Vector<analysis::Bool> vector{analysis::Bool{}, 2};
  testDiamondMeet(
    vector, {false, false}, {false, true}, {true, false}, {true, true});
}

TEST(VectorLattice, JoinSingleton) {
  using Vec = analysis::Vector<analysis::Bool>;
  Vec vector{analysis::Bool{}, 2};
  auto elem = vector.getBottom();

  EXPECT_FALSE(vector.join(elem, Vec::SingletonElement(0, false)));
  EXPECT_EQ(elem, (std::vector<analysis::Bool::Element>{false, false}));

  EXPECT_TRUE(vector.join(elem, Vec::SingletonElement(1, true)));
  EXPECT_EQ(elem, (std::vector<analysis::Bool::Element>{false, true}));
}

TEST(VectorLattice, MeetSingleton) {
  using Vec = analysis::Vector<analysis::Bool>;
  Vec vector{analysis::Bool{}, 2};
  auto elem = vector.getTop();

  EXPECT_FALSE(vector.meet(elem, Vec::SingletonElement(1, true)));
  EXPECT_EQ(elem, (std::vector<analysis::Bool::Element>{true, true}));

  EXPECT_TRUE(vector.meet(elem, Vec::SingletonElement(0, false)));
  EXPECT_EQ(elem, (std::vector<analysis::Bool::Element>{false, true}));
}

TEST(TupleLattice, GetBottom) {
  analysis::Tuple<analysis::Bool, analysis::UInt32> tuple{analysis::Bool{},
                                                          analysis::UInt32{}};
  EXPECT_EQ(tuple.getBottom(), (std::tuple{false, 0}));
}

TEST(TupleLattice, GetTop) {
  analysis::Tuple<analysis::Bool, analysis::UInt32> tuple{analysis::Bool{},
                                                          analysis::UInt32{}};
  EXPECT_EQ(tuple.getTop(), (std::tuple{true, uint32_t(-1)}));
}

TEST(TupleLattice, Compare) {
  analysis::Tuple<analysis::Bool, analysis::UInt32> tuple{analysis::Bool{},
                                                          analysis::UInt32{}};
  testDiamondCompare(tuple, {false, 0}, {false, 1}, {true, 0}, {true, 1});
}

TEST(TupleLattice, Join) {
  analysis::Tuple<analysis::Bool, analysis::UInt32> tuple{analysis::Bool{},
                                                          analysis::UInt32{}};
  testDiamondJoin(tuple, {false, 0}, {false, 1}, {true, 0}, {true, 1});
}

TEST(TupleLattice, Meet) {
  analysis::Tuple<analysis::Bool, analysis::UInt32> tuple{analysis::Bool{},
                                                          analysis::UInt32{}};
  testDiamondMeet(tuple, {false, 0}, {false, 1}, {true, 0}, {true, 1});
}

TEST(ValTypeLattice, GetBottom) {
  analysis::ValType valtype;
  EXPECT_EQ(valtype.getBottom(), Type::unreachable);
}

TEST(ValTypeLattice, GetTop) {
  analysis::ValType valtype;
  EXPECT_EQ(valtype.getTop(), Type::none);
}

TEST(ValTypeLattice, Compare) {
  analysis::ValType valtype;
  testDiamondCompare(
    valtype, Type::unreachable, Type::i32, Type::f32, Type::none);
  testDiamondCompare(valtype,
                     Type(HeapType::none, NonNullable),
                     Type(HeapType::struct_, NonNullable),
                     Type(HeapType::array, Nullable),
                     Type(HeapType::eq, Nullable));
}

TEST(ValTypeLattice, Join) {
  analysis::ValType valtype;
  testDiamondJoin(valtype, Type::unreachable, Type::i32, Type::f32, Type::none);
  testDiamondJoin(valtype,
                  Type(HeapType::none, NonNullable),
                  Type(HeapType::struct_, NonNullable),
                  Type(HeapType::array, Nullable),
                  Type(HeapType::eq, Nullable));
}

TEST(ValTypeLattice, Meet) {
  analysis::ValType valtype;
  testDiamondMeet(valtype, Type::unreachable, Type::i32, Type::f32, Type::none);
  testDiamondMeet(valtype,
                  Type(HeapType::none, NonNullable),
                  Type(HeapType::struct_, NonNullable),
                  Type(HeapType::array, Nullable),
                  Type(HeapType::eq, Nullable));
}

TEST(SharedLattice, GetBottom) {
  analysis::SharedPath<analysis::UInt32> shared{analysis::UInt32{}};
  EXPECT_EQ(*shared.getBottom(), 0u);
}

TEST(SharedLattice, Compare) {
  analysis::SharedPath<analysis::UInt32> shared{analysis::UInt32{}};

  auto zero = shared.getBottom();

  auto one = shared.getBottom();
  shared.join(one, 1);

  // This join will not change the value.
  auto uno = one;
  shared.join(uno, 1);

  auto two = shared.getBottom();
  shared.join(two, 2);

  EXPECT_EQ(shared.compare(zero, zero), analysis::EQUAL);
  EXPECT_EQ(shared.compare(zero, one), analysis::LESS);
  EXPECT_EQ(shared.compare(zero, uno), analysis::LESS);
  EXPECT_EQ(shared.compare(zero, two), analysis::LESS);

  EXPECT_EQ(shared.compare(one, zero), analysis::GREATER);
  EXPECT_EQ(shared.compare(one, one), analysis::EQUAL);
  EXPECT_EQ(shared.compare(one, uno), analysis::EQUAL);
  EXPECT_EQ(shared.compare(one, two), analysis::LESS);

  EXPECT_EQ(shared.compare(two, zero), analysis::GREATER);
  EXPECT_EQ(shared.compare(two, one), analysis::GREATER);
  EXPECT_EQ(shared.compare(two, uno), analysis::GREATER);
  EXPECT_EQ(shared.compare(two, two), analysis::EQUAL);

  EXPECT_EQ(*zero, 2u);
  EXPECT_EQ(*one, 2u);
  EXPECT_EQ(*uno, 2u);
  EXPECT_EQ(*two, 2u);
}

TEST(SharedLattice, Join) {
  analysis::SharedPath<analysis::UInt32> shared{analysis::UInt32{}};

  auto zero = shared.getBottom();

  auto one = shared.getBottom();
  shared.join(one, 1);

  auto two = shared.getBottom();
  shared.join(two, 2);

  {
    auto elem = zero;
    EXPECT_FALSE(shared.join(elem, zero));
    EXPECT_EQ(elem, zero);
  }

  {
    auto elem = zero;
    EXPECT_TRUE(shared.join(elem, one));
    EXPECT_EQ(elem, one);
  }

  {
    auto elem = zero;
    EXPECT_TRUE(shared.join(elem, two));
    EXPECT_EQ(elem, two);
  }

  {
    auto elem = one;
    EXPECT_FALSE(shared.join(elem, zero));
    EXPECT_EQ(elem, one);
  }

  {
    auto elem = one;
    EXPECT_FALSE(shared.join(elem, one));
    EXPECT_EQ(elem, one);
  }

  {
    auto elem = one;
    EXPECT_TRUE(shared.join(elem, two));
    EXPECT_EQ(elem, two);
  }

  {
    auto elem = two;
    EXPECT_FALSE(shared.join(elem, zero));
    EXPECT_EQ(elem, two);
  }

  {
    auto elem = two;
    EXPECT_FALSE(shared.join(elem, one));
    EXPECT_EQ(elem, two);
  }

  {
    auto elem = two;
    EXPECT_FALSE(shared.join(elem, two));
    EXPECT_EQ(elem, two);
  }
}

TEST(SharedLattice, JoinVecSingleton) {
  using Vec = analysis::Vector<analysis::Bool>;
  analysis::SharedPath<Vec> shared{analysis::Vector{analysis::Bool{}, 2}};

  auto elem = shared.getBottom();
  EXPECT_TRUE(shared.join(elem, Vec::SingletonElement(1, true)));
  EXPECT_EQ(*elem, (std::vector<analysis::Bool::Element>{false, true}));
}

TEST(SharedLattice, JoinInvertedVecSingleton) {
  using Vec = analysis::Vector<analysis::Bool>;
  analysis::SharedPath<analysis::Inverted<Vec>> shared{
    analysis::Inverted{analysis::Vector{analysis::Bool{}, 2}}};

  auto elem = shared.getBottom();
  EXPECT_TRUE(shared.join(elem, Vec::SingletonElement(1, false)));
  EXPECT_EQ(*elem, (std::vector<analysis::Bool::Element>{true, false}));
}

TEST(StackLattice, GetBottom) {
  analysis::Stack stack{analysis::Flat<uint32_t>{}};
  EXPECT_EQ(stack.getBottom().size(), 0u);
}

TEST(StackLattice, Compare) {
  analysis::Stack stack{analysis::Flat<uint32_t>{}};
  auto& flat = stack.lattice;
  testDiamondCompare(stack,
                     {},
                     {flat.get(0u)},
                     {flat.get(0u), flat.get(1u)},
                     {flat.get(0u), flat.getTop()});
}

TEST(StackLattice, Join) {
  analysis::Stack stack{analysis::Flat<uint32_t>{}};
  auto& flat = stack.lattice;
  testDiamondJoin(stack,
                  {},
                  {flat.get(0u)},
                  {flat.get(0u), flat.get(1u)},
                  {flat.get(0u), flat.getTop()});
}

TEST(StackLattice, CompareDifferentSizeConflict) {
  analysis::Stack stack{analysis::Bool{}};

  // a=[true, false]: base=true (valid, not bottom=false), top=false
  // b=[true]: base=true (valid), top=true
  //
  // Comparison from the top of stack (rbegin):
  //   false vs true -> LESS, then b is exhausted while a has extra elements.
  //   The size difference implies GREATER, but the element comparison found
  //   LESS, so the stacks are incomparable.
  analysis::Stack<analysis::Bool>::Element a = {true, false};
  analysis::Stack<analysis::Bool>::Element b = {true};
  EXPECT_EQ(stack.compare(a, b), analysis::NO_RELATION);
  EXPECT_EQ(stack.compare(b, a), analysis::NO_RELATION);
}

using OddEvenInt = analysis::Flat<uint32_t>;
using OddEvenBool = analysis::Flat<bool>;
struct OddEvenAbstraction
  : analysis::Abstraction<OddEvenAbstraction, OddEvenInt, OddEvenBool> {
  OddEvenAbstraction()
    : analysis::Abstraction<OddEvenAbstraction, OddEvenInt, OddEvenBool>(
        OddEvenInt{}, OddEvenBool{}) {}

  template<size_t I, typename E1, typename E2> E2 abstract(const E1&) const;

  template<std::size_t I, typename E>
  bool shouldAbstract(const E&, const E&) const;
};

template<>
OddEvenBool::Element
OddEvenAbstraction::abstract<0>(const OddEvenInt::Element& elem) const {
  if (elem.isTop()) {
    return OddEvenBool{}.getTop();
  }
  if (elem.isBottom()) {
    return OddEvenBool{}.getBottom();
  }
  return OddEvenBool{}.get((*elem.getVal() & 1) == 0);
}

template<>
bool OddEvenAbstraction::shouldAbstract<0>(const OddEvenInt::Element&,
                                           const OddEvenInt::Element&) const {
  // Since the elements are not related, they must be different integers.
  // Always abstract them.
  return true;
}

TEST(AbstractionLattice, GetBottom) {
  OddEvenAbstraction abstraction;
  auto expected = OddEvenAbstraction::Element(OddEvenInt{}.getBottom());
  EXPECT_EQ(abstraction.getBottom(), expected);
}

TEST(AbstractionLattice, Join) {
  OddEvenAbstraction abstraction;

  auto expectJoin = [&](const char* file,
                        int line,
                        const auto& joinee,
                        const auto& joiner,
                        const auto& expected) {
    testing::ScopedTrace trace(file, line, "");
    switch (abstraction.compare(joinee, joiner)) {
      case analysis::NO_RELATION:
        EXPECT_NE(joinee, joiner);
        EXPECT_EQ(abstraction.compare(joiner, joinee), analysis::NO_RELATION);
        EXPECT_EQ(abstraction.compare(joinee, expected), analysis::LESS);
        EXPECT_EQ(abstraction.compare(joiner, expected), analysis::LESS);
        break;
      case analysis::EQUAL:
        EXPECT_EQ(joinee, joiner);
        EXPECT_EQ(abstraction.compare(joiner, joinee), analysis::EQUAL);
        EXPECT_EQ(abstraction.compare(joinee, expected), analysis::EQUAL);
        EXPECT_EQ(abstraction.compare(joiner, expected), analysis::EQUAL);
        break;
      case analysis::LESS:
        EXPECT_EQ(joiner, expected);
        EXPECT_EQ(abstraction.compare(joiner, joinee), analysis::GREATER);
        EXPECT_EQ(abstraction.compare(joinee, expected), analysis::LESS);
        EXPECT_EQ(abstraction.compare(joiner, expected), analysis::EQUAL);
        break;
      case analysis::GREATER:
        EXPECT_EQ(joinee, expected);
        EXPECT_EQ(abstraction.compare(joiner, joinee), analysis::LESS);
        EXPECT_EQ(abstraction.compare(joinee, expected), analysis::EQUAL);
        EXPECT_EQ(abstraction.compare(joiner, expected), analysis::LESS);
    }
    {
      auto copy = joinee;
      EXPECT_EQ(abstraction.join(copy, joiner), joinee != expected);
      EXPECT_EQ(copy, expected);
    }
    {
      auto copy = joiner;
      EXPECT_EQ(abstraction.join(copy, joinee), joiner != expected);
      EXPECT_EQ(copy, expected);
    }
  };

#define JOIN(a, b, c) expectJoin(__FILE__, __LINE__, a, b, c)

  auto bot = abstraction.getBottom();
  auto one = OddEvenAbstraction::Element(OddEvenInt{}.get(1u));
  auto two = OddEvenAbstraction::Element(OddEvenInt{}.get(2u));
  auto three = OddEvenAbstraction::Element(OddEvenInt{}.get(3u));
  auto four = OddEvenAbstraction::Element(OddEvenInt{}.get(4u));
  auto even = OddEvenAbstraction::Element(OddEvenBool{}.get(true));
  auto odd = OddEvenAbstraction::Element(OddEvenBool{}.get(false));
  auto top = OddEvenAbstraction::Element(OddEvenBool{}.getTop());

  JOIN(bot, bot, bot);
  JOIN(bot, one, one);
  JOIN(bot, two, two);
  JOIN(bot, even, even);
  JOIN(bot, odd, odd);
  JOIN(bot, top, top);

  JOIN(one, one, one);
  JOIN(one, two, top);
  JOIN(one, three, odd);
  JOIN(one, even, top);
  JOIN(one, odd, odd);

  JOIN(two, two, two);
  JOIN(two, three, top);
  JOIN(two, four, even);
  JOIN(two, even, even);
  JOIN(two, odd, top);
  JOIN(two, top, top);

  JOIN(even, even, even);
  JOIN(even, odd, top);
  JOIN(even, top, top);

  JOIN(odd, odd, odd);
  JOIN(odd, top, top);

#undef JOIN
}

class ConeTypeLatticeTest : public ::testing::Test {
protected:
  HeapType super;
  HeapType sub1;
  HeapType sub2;
  HeapType other;
  analysis::ConeType lattice;

  using Element = analysis::ConeType::Element;
  Element bot;
  Element top;
  Element i32;
  Element i64;

  // The number at the end is the depth of the element.
  Element eqNull3;
  Element eqNonNull3;
  Element structNull2;
  Element structNonNull2;
  Element i31Null;
  Element i31NonNull;
  Element noneNull;
  Element noneNonNull;
  Element superNull1;
  Element superNonNull1;
  Element superNullExact;
  Element superNonNullExact;
  Element sub1Null0;
  Element sub1NonNull0;
  Element sub1NullExact;
  Element sub1NonNullExact;
  Element sub2Null0;
  Element sub2NonNull0;
  Element sub2NullExact;
  Element sub2NonNullExact;
  Element otherNull0;
  Element otherNonNull0;
  Element otherNullExact;
  Element otherNonNullExact;

  // Element created by combining other elements.
  Element eqNull2;
  Element eqNonNull2;
  Element structNull1;
  Element structNonNull1;

  void checkJoin(const Element& a,
                 const Element& b,
                 const Element& join,
                 const char* file,
                 int line) {
    testing::ScopedTrace trace(file, line, "check join");
    Element copy = a;
    EXPECT_EQ(lattice.join(copy, b), a != join);
    EXPECT_EQ(copy, join);
    copy = b;
    EXPECT_EQ(lattice.join(copy, a), b != join);
    EXPECT_EQ(copy, join);
  }
#define CHECK_JOIN(a, b, join) checkJoin(a, b, join, __FILE__, __LINE__)

  void checkMeet(const Element& a,
                 const Element& b,
                 const Element& meet,
                 const char* file,
                 int line) {
    testing::ScopedTrace trace(file, line, "check meet");
    Element copy = a;
    EXPECT_EQ(lattice.meet(copy, b), a != meet);
    EXPECT_EQ(copy, meet);
    copy = b;
    EXPECT_EQ(lattice.meet(copy, a), b != meet);
    EXPECT_EQ(copy, meet);
  }
#define CHECK_MEET(a, b, join) checkMeet(a, b, join, __FILE__, __LINE__)

  void
  checkLess(const Element& a, const Element& b, const char* file, int line) {
    testing::ScopedTrace trace(file, line, "check less");
    EXPECT_EQ(lattice.compare(a, b), analysis::LESS);
    EXPECT_EQ(lattice.compare(b, a), analysis::GREATER);
  }
#define CHECK_LESS(a, b) checkLess(a, b, __FILE__, __LINE__)

  void
  checkGreater(const Element& a, const Element& b, const char* file, int line) {
    testing::ScopedTrace trace(file, line, "check greater");
    EXPECT_EQ(lattice.compare(a, b), analysis::GREATER);
    EXPECT_EQ(lattice.compare(b, a), analysis::LESS);
  }
#define CHECK_GREATER(a, b) checkGreater(a, b, __FILE__, __LINE__)

  void checkUnrelated(const Element& a,
                      const Element& b,
                      const char* file,
                      int line) {
    testing::ScopedTrace trace(file, line, "check unrelated");
    EXPECT_EQ(lattice.compare(a, b), analysis::NO_RELATION);
    EXPECT_EQ(lattice.compare(b, a), analysis::NO_RELATION);
  }
#define CHECK_UNRELATED(a, b) checkUnrelated(a, b, __FILE__, __LINE__)

  void
  checkEqual(const Element& a, const Element& b, const char* file, int line) {
    testing::ScopedTrace trace(file, line, "check equal");
    EXPECT_EQ(lattice.compare(a, b), analysis::EQUAL);
    EXPECT_EQ(lattice.compare(b, a), analysis::EQUAL);
  }
#define CHECK_EQUAL(a, b) checkEqual(a, b, __FILE__, __LINE__)

  void checkPair(const Element& a,
                 const Element& b,
                 const Element& join,
                 const Element& meet,
                 const char* file,
                 int line) {
    testing::ScopedTrace trace(file, line, "check pair");
    CHECK_JOIN(a, b, join);
    CHECK_MEET(a, b, meet);
    switch (lattice.compare(a, b)) {
      case analysis::NO_RELATION:
        // This first check looks redundant, but it's also checking the opposite
        // direction.
        CHECK_UNRELATED(a, b);
        CHECK_LESS(a, join);
        CHECK_LESS(b, join);
        CHECK_GREATER(a, meet);
        CHECK_GREATER(b, meet);
        CHECK_LESS(meet, join);
        break;
      case analysis::EQUAL:
        CHECK_EQUAL(a, b);
        CHECK_EQUAL(a, join);
        CHECK_EQUAL(b, meet);
        break;
      case analysis::LESS:
        CHECK_LESS(a, b);
        CHECK_EQUAL(b, join);
        CHECK_EQUAL(a, meet);
        break;
      case analysis::GREATER:
        CHECK_GREATER(a, b);
        CHECK_EQUAL(a, join);
        CHECK_EQUAL(b, meet);
        break;
    }
  }
#define CHECK_PAIR(a, b, join, meet)                                           \
  checkPair(a, b, join, meet, __FILE__, __LINE__)

public:
  ConeTypeLatticeTest() : lattice(initTypes(this)) {
    bot = lattice.getBottom();
    top = lattice.getTop();
    i32 = lattice.get(Type::i32);
    i64 = lattice.get(Type::i64);
    eqNull3 = lattice.get(Type(HeapType::eq, Nullable));
    eqNonNull3 = lattice.get(Type(HeapType::eq, NonNullable));
    structNull2 = lattice.get(Type(HeapType::struct_, Nullable));
    structNonNull2 = lattice.get(Type(HeapType::struct_, NonNullable));
    i31Null = lattice.get(Type(HeapType::i31, Nullable));
    i31NonNull = lattice.get(Type(HeapType::i31, NonNullable));
    noneNull = lattice.get(Type(HeapType::none, Nullable));
    noneNonNull = lattice.get(Type(HeapType::none, NonNullable));
    superNull1 = lattice.get(Type(super, Nullable));
    superNonNull1 = lattice.get(Type(super, NonNullable));
    superNullExact = lattice.get(Type(super, Nullable, Exact));
    superNonNullExact = lattice.get(Type(super, NonNullable, Exact));
    sub1Null0 = lattice.get(Type(sub1, Nullable));
    sub1NonNull0 = lattice.get(Type(sub1, NonNullable));
    sub1NullExact = lattice.get(Type(sub1, Nullable, Exact));
    sub1NonNullExact = lattice.get(Type(sub1, NonNullable, Exact));
    sub2Null0 = lattice.get(Type(sub2, Nullable));
    sub2NonNull0 = lattice.get(Type(sub2, NonNullable));
    sub2NullExact = lattice.get(Type(sub2, Nullable, Exact));
    sub2NonNullExact = lattice.get(Type(sub2, NonNullable, Exact));
    otherNull0 = lattice.get(Type(other, Nullable));
    otherNonNull0 = lattice.get(Type(other, NonNullable));
    otherNullExact = lattice.get(Type(other, Nullable, Exact));
    otherNonNullExact = lattice.get(Type(other, NonNullable, Exact));

    eqNull2 = Element{eqNull3.type, 2};
    eqNonNull2 = Element{eqNonNull3.type, 2};
    structNull1 = Element{structNull2.type, 1};
    structNonNull1 = Element{structNonNull2.type, 1};
  }

private:
  static std::unordered_map<HeapType, Index>
  initTypes(ConeTypeLatticeTest* self);
};

std::unordered_map<HeapType, Index>
ConeTypeLatticeTest::initTypes(ConeTypeLatticeTest* self) {
  //   0  3
  //  /|
  // 1 2
  TypeBuilder builder(4);
  builder.createRecGroup(0, 4);
  builder[0] = Struct{};
  builder[1] = Struct{};
  builder[2] = Struct{};
  builder[3] = Struct{};
  builder[0].setOpen();
  builder[1].subTypeOf(builder[0]);
  builder[2].subTypeOf(builder[0]);
  auto types = *builder.build();

  self->super = types[0];
  self->sub1 = types[1];
  self->sub2 = types[2];
  self->other = types[3];

  SubTypes subtypes(types);
  return subtypes.getMaxDepths();
}

TEST_F(ConeTypeLatticeTest, GetBottom) {
  EXPECT_TRUE(lattice.getBottom().isBottom());
  EXPECT_EQ(lattice.getBottom().type, Type(Type::unreachable));
  EXPECT_EQ(lattice.getBottom().depth, 0);
}

TEST_F(ConeTypeLatticeTest, GetTop) {
  EXPECT_TRUE(lattice.getTop().isTop());
  EXPECT_EQ(lattice.getTop().type, Type(Type::none));
  EXPECT_EQ(lattice.getTop().depth, 0);
}

TEST_F(ConeTypeLatticeTest, Relations) {
  CHECK_PAIR(bot, bot, bot, bot);
  CHECK_PAIR(bot, top, top, bot);
  CHECK_PAIR(bot, i32, i32, bot);
  CHECK_PAIR(bot, i64, i64, bot);
  CHECK_PAIR(bot, eqNull3, eqNull3, bot);
  CHECK_PAIR(bot, eqNonNull3, eqNonNull3, bot);
  CHECK_PAIR(bot, structNull2, structNull2, bot);
  CHECK_PAIR(bot, structNonNull2, structNonNull2, bot);
  CHECK_PAIR(bot, i31Null, i31Null, bot);
  CHECK_PAIR(bot, i31NonNull, i31NonNull, bot);
  CHECK_PAIR(bot, noneNull, noneNull, bot);
  CHECK_PAIR(bot, noneNonNull, noneNonNull, bot);
  CHECK_PAIR(bot, superNull1, superNull1, bot);
  CHECK_PAIR(bot, superNonNull1, superNonNull1, bot);
  CHECK_PAIR(bot, superNullExact, superNullExact, bot);
  CHECK_PAIR(bot, superNonNullExact, superNonNullExact, bot);

  CHECK_PAIR(top, top, top, top);
  CHECK_PAIR(top, i32, top, i32);
  CHECK_PAIR(top, i64, top, i64);
  CHECK_PAIR(top, eqNull3, top, eqNull3);
  CHECK_PAIR(top, eqNonNull3, top, eqNonNull3);
  CHECK_PAIR(top, structNull2, top, structNull2);
  CHECK_PAIR(top, structNonNull2, top, structNonNull2);
  CHECK_PAIR(top, i31Null, top, i31Null);
  CHECK_PAIR(top, i31NonNull, top, i31NonNull);
  CHECK_PAIR(top, noneNull, top, noneNull);
  CHECK_PAIR(top, noneNonNull, top, noneNonNull);
  CHECK_PAIR(top, superNull1, top, superNull1);
  CHECK_PAIR(top, superNonNull1, top, superNonNull1);
  CHECK_PAIR(top, superNullExact, top, superNullExact);
  CHECK_PAIR(top, superNonNullExact, top, superNonNullExact);

  CHECK_PAIR(i32, i32, i32, i32);
  CHECK_PAIR(i32, i64, top, bot);
  CHECK_PAIR(i32, eqNull3, top, bot);
  CHECK_PAIR(i32, eqNonNull3, top, bot);
  CHECK_PAIR(i32, structNull2, top, bot);
  CHECK_PAIR(i32, structNonNull2, top, bot);
  CHECK_PAIR(i32, i31Null, top, bot);
  CHECK_PAIR(i32, i31NonNull, top, bot);
  CHECK_PAIR(i32, noneNull, top, bot);
  CHECK_PAIR(i32, noneNonNull, top, bot);
  CHECK_PAIR(i32, superNull1, top, bot);
  CHECK_PAIR(i32, superNonNull1, top, bot);
  CHECK_PAIR(i32, superNullExact, top, bot);
  CHECK_PAIR(i32, superNonNullExact, top, bot);

  CHECK_PAIR(eqNull3, eqNull3, eqNull3, eqNull3);
  CHECK_PAIR(eqNull3, eqNonNull3, eqNull3, eqNonNull3);
  CHECK_PAIR(eqNull3, structNull2, eqNull3, structNull2);
  CHECK_PAIR(eqNull3, structNonNull2, eqNull3, structNonNull2);
  CHECK_PAIR(eqNull3, i31Null, eqNull3, i31Null);
  CHECK_PAIR(eqNull3, i31NonNull, eqNull3, i31NonNull);
  CHECK_PAIR(eqNull3, noneNull, eqNull3, noneNull);
  CHECK_PAIR(eqNull3, noneNonNull, eqNull3, noneNonNull);
  CHECK_PAIR(eqNull3, superNull1, eqNull3, superNull1);
  CHECK_PAIR(eqNull3, superNonNull1, eqNull3, superNonNull1);
  CHECK_PAIR(eqNull3, superNullExact, eqNull3, superNullExact);
  CHECK_PAIR(eqNull3, superNonNullExact, eqNull3, superNonNullExact);

  CHECK_PAIR(eqNonNull3, eqNonNull3, eqNonNull3, eqNonNull3);
  CHECK_PAIR(eqNonNull3, structNull2, eqNull3, structNonNull2);
  CHECK_PAIR(eqNonNull3, structNonNull2, eqNonNull3, structNonNull2);
  CHECK_PAIR(eqNonNull3, i31Null, eqNull3, i31NonNull);
  CHECK_PAIR(eqNonNull3, i31NonNull, eqNonNull3, i31NonNull);
  CHECK_PAIR(eqNonNull3, noneNull, eqNull3, noneNonNull);
  CHECK_PAIR(eqNonNull3, noneNonNull, eqNonNull3, noneNonNull);
  CHECK_PAIR(eqNonNull3, superNull1, eqNull3, superNonNull1);
  CHECK_PAIR(eqNonNull3, superNonNull1, eqNonNull3, superNonNull1);
  CHECK_PAIR(eqNonNull3, superNullExact, eqNull3, superNonNullExact);
  CHECK_PAIR(eqNonNull3, superNonNullExact, eqNonNull3, superNonNullExact);

  CHECK_PAIR(structNull2, structNull2, structNull2, structNull2);
  CHECK_PAIR(structNull2, structNonNull2, structNull2, structNonNull2);
  CHECK_PAIR(structNull2, i31Null, eqNull3, noneNull);
  CHECK_PAIR(structNull2, i31NonNull, eqNull3, noneNonNull);
  CHECK_PAIR(structNull2, noneNull, structNull2, noneNull);
  CHECK_PAIR(structNull2, noneNonNull, structNull2, noneNonNull);
  CHECK_PAIR(structNull2, superNull1, structNull2, superNull1);
  CHECK_PAIR(structNull2, superNonNull1, structNull2, superNonNull1);
  CHECK_PAIR(structNull2, superNullExact, structNull2, superNullExact);
  CHECK_PAIR(structNull2, superNonNullExact, structNull2, superNonNullExact);

  CHECK_PAIR(structNonNull2, structNonNull2, structNonNull2, structNonNull2);
  CHECK_PAIR(structNonNull2, i31Null, eqNull3, noneNonNull);
  CHECK_PAIR(structNonNull2, i31NonNull, eqNonNull3, noneNonNull);
  CHECK_PAIR(structNonNull2, noneNull, structNull2, noneNonNull);
  CHECK_PAIR(structNonNull2, noneNonNull, structNonNull2, noneNonNull);
  CHECK_PAIR(structNonNull2, superNull1, structNull2, superNonNull1);
  CHECK_PAIR(structNonNull2, superNonNull1, structNonNull2, superNonNull1);
  CHECK_PAIR(structNonNull2, superNullExact, structNull2, superNonNullExact);
  CHECK_PAIR(
    structNonNull2, superNonNullExact, structNonNull2, superNonNullExact);

  CHECK_PAIR(i31Null, i31Null, i31Null, i31Null);
  CHECK_PAIR(i31Null, i31NonNull, i31Null, i31NonNull);
  CHECK_PAIR(i31Null, noneNull, i31Null, noneNull);
  CHECK_PAIR(i31Null, noneNonNull, i31Null, noneNonNull);
  CHECK_PAIR(i31Null, superNull1, eqNull3, noneNull);
  CHECK_PAIR(i31Null, superNonNull1, eqNull3, noneNonNull);
  CHECK_PAIR(i31Null, superNullExact, eqNull2, noneNull);
  CHECK_PAIR(i31Null, superNonNullExact, eqNull2, noneNonNull);

  CHECK_PAIR(i31NonNull, i31NonNull, i31NonNull, i31NonNull);
  CHECK_PAIR(i31NonNull, noneNull, i31Null, noneNonNull);
  CHECK_PAIR(i31NonNull, noneNonNull, i31NonNull, noneNonNull);
  CHECK_PAIR(i31NonNull, superNull1, eqNull3, noneNonNull);
  CHECK_PAIR(i31NonNull, superNonNull1, eqNonNull3, noneNonNull);
  CHECK_PAIR(i31NonNull, superNullExact, eqNull2, noneNonNull);
  CHECK_PAIR(i31NonNull, superNonNullExact, eqNonNull2, noneNonNull);

  CHECK_PAIR(noneNull, noneNull, noneNull, noneNull);
  CHECK_PAIR(noneNull, noneNonNull, noneNull, noneNonNull);
  CHECK_PAIR(noneNull, superNull1, superNull1, noneNull);
  CHECK_PAIR(noneNull, superNonNull1, superNull1, noneNonNull);
  CHECK_PAIR(noneNull, superNullExact, superNullExact, noneNull);
  CHECK_PAIR(noneNull, superNonNullExact, superNullExact, noneNonNull);

  CHECK_PAIR(noneNonNull, noneNonNull, noneNonNull, noneNonNull);
  CHECK_PAIR(noneNonNull, superNull1, superNull1, noneNonNull);
  CHECK_PAIR(noneNonNull, superNonNull1, superNonNull1, noneNonNull);
  CHECK_PAIR(noneNonNull, superNullExact, superNullExact, noneNonNull);
  CHECK_PAIR(noneNonNull, superNonNullExact, superNonNullExact, noneNonNull);

  CHECK_PAIR(superNull1, superNull1, superNull1, superNull1);
  CHECK_PAIR(superNull1, superNonNull1, superNull1, superNonNull1);
  CHECK_PAIR(superNull1, superNullExact, superNull1, superNullExact);
  CHECK_PAIR(superNull1, superNonNullExact, superNull1, superNonNullExact);
  CHECK_PAIR(superNull1, sub1Null0, superNull1, sub1Null0);
  CHECK_PAIR(superNull1, sub1NonNull0, superNull1, sub1NonNull0);
  CHECK_PAIR(superNull1, sub1NullExact, superNull1, sub1NullExact);
  CHECK_PAIR(superNull1, sub1NonNullExact, superNull1, sub1NonNullExact);
  CHECK_PAIR(superNull1, otherNull0, structNull2, noneNull);
  CHECK_PAIR(superNull1, otherNonNull0, structNull2, noneNonNull);
  CHECK_PAIR(superNull1, otherNullExact, structNull2, noneNull);
  CHECK_PAIR(superNull1, otherNonNullExact, structNull2, noneNonNull);

  CHECK_PAIR(superNonNull1, superNonNull1, superNonNull1, superNonNull1);
  CHECK_PAIR(superNonNull1, superNullExact, superNull1, superNonNullExact);
  CHECK_PAIR(
    superNonNull1, superNonNullExact, superNonNull1, superNonNullExact);
  CHECK_PAIR(superNonNull1, sub1Null0, superNull1, sub1NonNull0);
  CHECK_PAIR(superNonNull1, sub1NonNull0, superNonNull1, sub1NonNull0);
  CHECK_PAIR(superNonNull1, sub1NullExact, superNull1, sub1NonNullExact);
  CHECK_PAIR(superNonNull1, sub1NonNullExact, superNonNull1, sub1NonNullExact);
  CHECK_PAIR(superNonNull1, otherNull0, structNull2, noneNonNull);
  CHECK_PAIR(superNonNull1, otherNonNull0, structNonNull2, noneNonNull);
  CHECK_PAIR(superNonNull1, otherNullExact, structNull2, noneNonNull);
  CHECK_PAIR(superNonNull1, otherNonNullExact, structNonNull2, noneNonNull);

  CHECK_PAIR(superNullExact, superNullExact, superNullExact, superNullExact);
  CHECK_PAIR(
    superNullExact, superNonNullExact, superNullExact, superNonNullExact);
  CHECK_PAIR(superNullExact, sub1Null0, superNull1, noneNull);
  CHECK_PAIR(superNullExact, sub1NonNull0, superNull1, noneNonNull);
  CHECK_PAIR(superNullExact, sub1NullExact, superNull1, noneNull);
  CHECK_PAIR(superNullExact, sub1NonNullExact, superNull1, noneNonNull);
  CHECK_PAIR(superNullExact, otherNull0, structNull1, noneNull);
  CHECK_PAIR(superNullExact, otherNonNull0, structNull1, noneNonNull);
  CHECK_PAIR(superNullExact, otherNullExact, structNull1, noneNull);
  CHECK_PAIR(superNullExact, otherNonNullExact, structNull1, noneNonNull);

  CHECK_PAIR(
    superNonNullExact, superNonNullExact, superNonNullExact, superNonNullExact);
  CHECK_PAIR(superNonNullExact, sub1Null0, superNull1, noneNonNull);
  CHECK_PAIR(superNonNullExact, sub1NonNull0, superNonNull1, noneNonNull);
  CHECK_PAIR(superNonNullExact, sub1NullExact, superNull1, noneNonNull);
  CHECK_PAIR(superNonNullExact, sub1NonNullExact, superNonNull1, noneNonNull);
  CHECK_PAIR(superNonNullExact, otherNull0, structNull1, noneNonNull);
  CHECK_PAIR(superNonNullExact, otherNonNull0, structNonNull1, noneNonNull);
  CHECK_PAIR(superNonNullExact, otherNullExact, structNull1, noneNonNull);
  CHECK_PAIR(superNonNullExact, otherNonNullExact, structNonNull1, noneNonNull);

  CHECK_PAIR(sub1Null0, sub1Null0, sub1Null0, sub1Null0);
  CHECK_PAIR(sub1Null0, sub1NonNull0, sub1Null0, sub1NonNull0);
  CHECK_PAIR(sub1Null0, sub1NullExact, sub1Null0, sub1NullExact);
  CHECK_PAIR(sub1Null0, sub1NonNullExact, sub1Null0, sub1NonNullExact);
  CHECK_PAIR(sub1Null0, sub2Null0, superNull1, noneNull);
  CHECK_PAIR(sub1Null0, sub2NonNull0, superNull1, noneNonNull);
  CHECK_PAIR(sub1Null0, sub2NullExact, superNull1, noneNull);
  CHECK_PAIR(sub1Null0, sub2NonNullExact, superNull1, noneNonNull);
  CHECK_PAIR(sub1Null0, otherNull0, structNull2, noneNull);
  CHECK_PAIR(sub1Null0, otherNonNull0, structNull2, noneNonNull);
  CHECK_PAIR(sub1Null0, otherNullExact, structNull2, noneNull);
  CHECK_PAIR(sub1Null0, otherNonNullExact, structNull2, noneNonNull);

  CHECK_PAIR(sub1NonNull0, sub1NonNull0, sub1NonNull0, sub1NonNull0);
  CHECK_PAIR(sub1NonNull0, sub1NullExact, sub1Null0, sub1NonNullExact);
  CHECK_PAIR(sub1NonNull0, sub1NonNullExact, sub1NonNull0, sub1NonNullExact);
  CHECK_PAIR(sub1NonNull0, sub2Null0, superNull1, noneNonNull);
  CHECK_PAIR(sub1NonNull0, sub2NonNull0, superNonNull1, noneNonNull);
  CHECK_PAIR(sub1NonNull0, sub2NullExact, superNull1, noneNonNull);
  CHECK_PAIR(sub1NonNull0, sub2NonNullExact, superNonNull1, noneNonNull);
  CHECK_PAIR(sub1NonNull0, otherNull0, structNull2, noneNonNull);
  CHECK_PAIR(sub1NonNull0, otherNonNull0, structNonNull2, noneNonNull);
  CHECK_PAIR(sub1NonNull0, otherNullExact, structNull2, noneNonNull);
  CHECK_PAIR(sub1NonNull0, otherNonNullExact, structNonNull2, noneNonNull);

  CHECK_PAIR(sub1NullExact, sub1NullExact, sub1NullExact, sub1NullExact);
  CHECK_PAIR(sub1NullExact, sub1NonNullExact, sub1NullExact, sub1NonNullExact);
  CHECK_PAIR(sub1NullExact, sub2Null0, superNull1, noneNull);
  CHECK_PAIR(sub1NullExact, sub2NonNull0, superNull1, noneNonNull);
  CHECK_PAIR(sub1NullExact, sub2NullExact, superNull1, noneNull);
  CHECK_PAIR(sub1NullExact, sub2NonNullExact, superNull1, noneNonNull);
  CHECK_PAIR(sub1NullExact, otherNull0, structNull2, noneNull);
  CHECK_PAIR(sub1NullExact, otherNonNull0, structNull2, noneNonNull);
  CHECK_PAIR(sub1NullExact, otherNullExact, structNull2, noneNull);
  CHECK_PAIR(sub1NullExact, otherNonNullExact, structNull2, noneNonNull);

  CHECK_PAIR(
    sub1NonNullExact, sub1NonNullExact, sub1NonNullExact, sub1NonNullExact);
  CHECK_PAIR(sub1NonNullExact, sub2Null0, superNull1, noneNonNull);
  CHECK_PAIR(sub1NonNullExact, sub2NonNull0, superNonNull1, noneNonNull);
  CHECK_PAIR(sub1NonNullExact, sub2NullExact, superNull1, noneNonNull);
  CHECK_PAIR(sub1NonNullExact, sub2NonNullExact, superNonNull1, noneNonNull);
  CHECK_PAIR(sub1NonNullExact, otherNull0, structNull2, noneNonNull);
  CHECK_PAIR(sub1NonNullExact, otherNonNull0, structNonNull2, noneNonNull);
  CHECK_PAIR(sub1NonNullExact, otherNullExact, structNull2, noneNonNull);
  CHECK_PAIR(sub1NonNullExact, otherNonNullExact, structNonNull2, noneNonNull);
}

TEST_F(ConeTypeLatticeTest, Depths) {
  TypeBuilder builder(3);
  builder[0].setOpen() = Struct{};
  builder[1].setOpen().subTypeOf(builder[0]) = Struct{};
  builder[2].setOpen().subTypeOf(builder[1]) = Struct{};
  auto built = builder.build();

  HeapType a = (*built)[0];
  HeapType b = (*built)[1];
  HeapType c = (*built)[2];

  Element none{Type(HeapType::none, Nullable), 0};

  Element a0{Type(a, Nullable), 0};
  Element a1{Type(a, Nullable), 1};
  Element a2{Type(a, Nullable), 2};
  Element a3{Type(a, Nullable), 3};

  Element b0{Type(b, Nullable), 0};
  Element b1{Type(b, Nullable), 1};
  Element b2{Type(b, Nullable), 2};

  Element c0{Type(c, Nullable), 0};
  Element c1{Type(c, Nullable), 1};

  CHECK_PAIR(a0, a0, a0, a0);
  CHECK_PAIR(a0, a1, a1, a0);
  CHECK_PAIR(a0, b0, a1, none);
  CHECK_PAIR(a0, b1, a2, none);
  CHECK_PAIR(a0, c0, a2, none);
  CHECK_PAIR(a0, c1, a3, none);

  CHECK_PAIR(a1, a1, a1, a1);
  CHECK_PAIR(a1, b0, a1, b0);
  CHECK_PAIR(a1, b1, a2, b0);
  CHECK_PAIR(a1, c0, a2, none);
  CHECK_PAIR(a1, c1, a3, none);

  CHECK_PAIR(b0, b0, b0, b0);
  CHECK_PAIR(b0, b1, b1, b0);
  CHECK_PAIR(b0, c0, b1, none);
  CHECK_PAIR(b0, c1, b2, none);

  CHECK_PAIR(b1, b1, b1, b1);
  CHECK_PAIR(b1, c0, b1, c0);
  CHECK_PAIR(b1, c1, b2, c0);
}
