// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <string>
#include <tuple>

#include "gtest/gtest.h"
#include "src/tint/lang/core/enums.h"
#include "src/tint/lang/core/ir/builder.h"
#include "src/tint/lang/core/ir/validator.h"
#include "src/tint/lang/core/ir/validator_test.h"
#include "src/tint/lang/core/number.h"
#include "src/tint/lang/core/type/abstract_float.h"
#include "src/tint/lang/core/type/abstract_int.h"
#include "src/tint/lang/core/type/manager.h"
#include "src/tint/lang/core/type/matrix.h"
#include "src/tint/lang/core/type/memory_view.h"
#include "src/tint/lang/core/type/reference.h"
#include "src/tint/lang/core/type/storage_texture.h"

namespace tint::core::ir {

using namespace tint::core::fluent_types;     // NOLINT
using namespace tint::core::number_suffixes;  // NOLINT

TEST_F(IR_ValidatorTest, Access_NoOperands) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.vec3f());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        auto* access = b.Access(ty.f32(), obj, 0_i);
        access->ClearOperands();
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:14 error: access: expected at least 2 operands, got 0
    %3:f32 = access
             ^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_NoIndices) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.vec3f());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), obj);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:14 error: access: expected at least 2 operands, got 1
    %3:f32 = access %2
             ^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_NoResults) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.vec3f());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        auto* access = b.Access(ty.f32(), obj, 0_i);
        access->ClearResults();
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:13 error: access: expected exactly 1 results, got 0
    undef = access %2, 0i
            ^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_NullObject) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), nullptr, 0_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:21 error: access: operand is undefined
    %2:f32 = access undef, 0u
                    ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_NullIndex) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.vec3f());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), obj, nullptr);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:25 error: access: operand is undefined
    %3:f32 = access %2, undef
                        ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_NegativeIndex) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.vec3f());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), obj, -1_i);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:25 error: access: constant index must be positive, got -1
    %3:f32 = access %2, -1i
                        ^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_OOB_Index_Value) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.mat3x2<f32>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), obj, 1_u, 3_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:29 error: access: index out of bounds for type 'vec2<f32>'
    %3:f32 = access %2, 1u, 3u
                            ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_OOB_Index_Ptr) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<private_, array<array<f32, 2>, 3>>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.ptr<private_, f32>(), obj, 1_u, 3_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:3:55 error: access: index out of bounds for type 'ptr<private, array<f32, 2>, read_write>'
    %3:ptr<private, f32, read_write> = access %2, 1u, 3u
                                                      ^^

:2:3 note: in block
  $B1: {
  ^^^

:3:55 note: acceptable range: [0..1]
    %3:ptr<private, f32, read_write> = access %2, 1u, 3u
                                                      ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_StaticallyUnindexableType_Value) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.f32());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), obj, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:25 error: access: type 'f32' cannot be indexed
    %3:f32 = access %2, 1u
                        ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_StaticallyUnindexableType_Ptr) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<private_, f32>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.ptr<private_, f32>(), obj, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(
                    R"(:3:51 error: access: type 'ptr<private, f32, read_write>' cannot be indexed
    %3:ptr<private, f32, read_write> = access %2, 1u
                                                  ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_DynamicallyUnindexableType_Value) {
    auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
                                                              {mod.symbols.New("a"), ty.i32()},
                                                              {mod.symbols.New("b"), ty.i32()},
                                                          });

    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(str_ty);
    auto* idx = b.FunctionParam(ty.i32());
    f->SetParams({obj, idx});

    b.Append(f->Block(), [&] {
        b.Access(ty.i32(), obj, idx);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(R"(:8:25 error: access: type 'MyStruct' cannot be dynamically indexed
    %4:i32 = access %2, %3
                        ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_DynamicallyUnindexableType_Ptr) {
    auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
                                                              {mod.symbols.New("a"), ty.i32()},
                                                              {mod.symbols.New("b"), ty.i32()},
                                                          });

    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<private_, read_write>(str_ty));
    auto* idx = b.FunctionParam(ty.i32());
    f->SetParams({obj, idx});

    b.Append(f->Block(), [&] {
        b.Access(ty.i32(), obj, idx);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:8:25 error: access: type 'ptr<private, MyStruct, read_write>' cannot be dynamically indexed
    %4:i32 = access %2, %3
                        ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_Incorrect_Type_Value_Value) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.mat3x2<f32>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.i32(), obj, 1_u, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:3:14 error: access: result of access chain is type 'f32' but instruction type is 'i32'
    %3:i32 = access %2, 1u, 1u
             ^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_Incorrect_Type_Ptr_Ptr) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<private_, array<array<f32, 2>, 3>>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.ptr<private_, i32>(), obj, 1_u, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:3:40 error: access: result of access chain is type 'ptr<private, f32, read_write>' but instruction type is 'ptr<private, i32, read_write>'
    %3:ptr<private, i32, read_write> = access %2, 1u, 1u
                                       ^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_Incorrect_Type_Ptr_Value) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<private_, array<array<f32, 2>, 3>>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), obj, 1_u, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:3:14 error: access: result of access chain is type 'ptr<private, f32, read_write>' but instruction type is 'f32'
    %3:f32 = access %2, 1u, 1u
             ^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_IndexVectorPtr) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<private_, vec3f>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), obj, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:25 error: access: cannot obtain address of vector element
    %3:f32 = access %2, 1u
                        ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_IndexVectorPtr_WithCapability) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<private_, vec3f>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.ptr<private_, f32>(), obj, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod, Capabilities{Capability::kAllowVectorElementPointer});
    ASSERT_EQ(res, Success) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_IndexVectorPtr_ViaMatrixPtr) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<private_, mat3x2f>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), obj, 1_u, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:29 error: access: cannot obtain address of vector element
    %3:f32 = access %2, 1u, 1u
                            ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_IndexVectorPtr_ViaMatrixPtr_WithCapability) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<private_, mat3x2f>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.ptr<private_, f32>(), obj, 1_u, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod, Capabilities{Capability::kAllowVectorElementPointer});
    ASSERT_EQ(res, Success) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_Incorrect_Ptr_AddressSpace) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<storage, array<f32, 2>, read>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.ptr<uniform, f32, read>(), obj, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:3:34 error: access: result of access chain is type 'ptr<storage, f32, read>' but instruction type is 'ptr<uniform, f32, read>'
    %3:ptr<uniform, f32, read> = access %2, 1u
                                 ^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_Incorrect_Ptr_Access) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<storage, array<f32, 2>, read>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.ptr<storage, f32, read_write>(), obj, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:3:40 error: access: result of access chain is type 'ptr<storage, f32, read>' but instruction type is 'ptr<storage, f32, read_write>'
    %3:ptr<storage, f32, read_write> = access %2, 1u
                                       ^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_IndexVector) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.vec3f());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), obj, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_EQ(res, Success) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_IndexVector_ViaMatrix) {
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.mat3x2<f32>());
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ty.f32(), obj, 1_u, 1_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_EQ(res, Success) << res.Failure();
}

TEST_F(IR_ValidatorTest, Access_ExtractPointerFromStruct) {
    auto* ptr = ty.ptr<private_, i32>();
    Vector<core::type::Manager::StructMemberDesc, 1> members{
        core::type::Manager::StructMemberDesc{mod.symbols.New("a"), ptr},
    };
    auto* str = ty.Struct(mod.symbols.New("MyStruct"), std::move(members));
    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam("obj", str);
    f->SetParams({obj});

    b.Append(f->Block(), [&] {
        b.Access(ptr, obj, 0_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod, Capabilities{Capability::kMslAllowEntryPointInterface});
    ASSERT_EQ(res, Success) << res.Failure();
}

TEST_F(IR_ValidatorTest, Load_NullFrom) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.i32()), nullptr));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:19 error: load: operand is undefined
    %2:i32 = load undef
                  ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Load_SourceNotMemoryView) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* let = b.Let("l", 1_i);
        b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.f32()), let->Result()));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(R"(:4:19 error: load: load source operand 'i32' is not a memory view
    %3:f32 = load %l
                  ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Load_TypeMismatch) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, i32>());
        b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.f32()), var->Result()));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(
                    R"(:4:19 error: load: result type 'f32' does not match source store type 'i32'
    %3:f32 = load %2
                  ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Load_MissingResult) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, i32>());
        auto* load = mod.CreateInstruction<ir::Load>(nullptr, var->Result());
        load->ClearResults();
        b.Append(load);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:4:13 error: load: expected exactly 1 results, got 0
    undef = load %2
            ^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Load_NonReadableSource) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, i32, core::Access::kWrite>());
        b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.i32()), var->Result()));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:4:19 error: load: load source operand has a non-readable access type, 'write'
    %3:i32 = load %2
                  ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Load_RuntimeSizedArray) {
    auto* a = b.Var("a", ty.ptr<storage, array<u32>, read_write>());
    a->SetBindingPoint(0, 0);
    mod.root_block->Append(a);

    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        b.Load(a);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(
                                          R"(:7:26 error: load: type 'array<u32>' cannot be loaded
    %3:array<u32> = load %a
                         ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Load_RuntimeSizedArray_InStruct) {
    auto* s = ty.Struct(mod.symbols.New("MyStruct"), {
                                                         {mod.symbols.New("a"), ty.array<u32>()},
                                                     });
    auto* a = b.Var("a", ty.ptr<storage>(s));
    a->SetBindingPoint(0, 0);
    mod.root_block->Append(a);

    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        b.Load(a);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(
                                          R"(:11:24 error: load: type 'MyStruct' cannot be loaded
    %3:MyStruct = load %a
                       ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Load_Atomic) {
    auto* a = b.Var("a", ty.ptr<storage, atomic<u32>, read_write>());
    a->SetBindingPoint(0, 0);
    mod.root_block->Append(a);

    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        b.Load(a);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(
                                          R"(:7:27 error: load: type 'atomic<u32>' cannot be loaded
    %3:atomic<u32> = load %a
                          ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Load_Atomic_InStruct) {
    auto* s = ty.Struct(mod.symbols.New("MyStruct"), {
                                                         {mod.symbols.New("a"), ty.atomic<u32>()},
                                                     });
    auto* a = b.Var("a", ty.ptr<storage>(s));
    a->SetBindingPoint(0, 0);
    mod.root_block->Append(a);

    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        b.Load(a);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(
                                          R"(:11:24 error: load: type 'MyStruct' cannot be loaded
    %3:MyStruct = load %a
                       ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Store_NullTo) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        b.Append(mod.CreateInstruction<ir::Store>(nullptr, b.Constant(42_i)));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:11 error: store: operand is undefined
    store undef, 42i
          ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Store_NullFrom) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, i32>());
        b.Append(mod.CreateInstruction<ir::Store>(var->Result(), nullptr));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:4:15 error: store: operand is undefined
    store %2, undef
              ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Store_NonEmptyResult) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, i32>());
        auto* store = mod.CreateInstruction<ir::Store>(var->Result(), b.Constant(42_i));
        store->SetResult(b.InstructionResult(ty.i32()));
        b.Append(store);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:4:5 error: store: expected exactly 0 results, got 1
    store %2, 42i
    ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Store_TargetNotMemoryView) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* let = b.Let("l", 1_i);
        b.Append(mod.CreateInstruction<ir::Store>(let->Result(), b.Constant(42_u)));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(R"(:4:11 error: store: store target operand 'i32' is not a memory view
    store %l, 42u
          ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Store_TypeMismatch) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, i32>());
        b.Append(mod.CreateInstruction<ir::Store>(var->Result(), b.Constant(42_u)));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(R"(:4:15 error: store: value type 'u32' does not match store type 'i32'
    store %2, 42u
              ^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Store_NoStoreType) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* result = b.InstructionResult(ty.u32());
        result->SetType(nullptr);
        b.Append(mod.CreateInstruction<ir::Store>(result, b.Constant(42_u)));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:11 error: store: operand type is undefined
    store %2, 42u
          ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Store_NoValueType) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, i32>());
        auto* val = b.Construct(ty.u32(), 42_u);
        val->Result()->SetType(nullptr);

        b.Append(mod.CreateInstruction<ir::Store>(var->Result(), val->Result()));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:5:15 error: store: operand type is undefined
    store %2, %3
              ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Store_NonWriteableTarget) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, i32, core::Access::kRead>());
        b.Append(mod.CreateInstruction<ir::Store>(var->Result(), b.Constant(42_i)));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:4:11 error: store: store target operand has a non-writeable access type, 'read'
    store %2, 42i
          ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Store_NonConstructible) {
    auto* a = b.Var("a", ty.ptr<storage, array<u32>, read_write>());
    a->SetBindingPoint(0, 0);
    mod.root_block->Append(a);

    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        // Note: The load is invalid too, but there's no way to produce a non-constructible value
        // that will not hit another validation rule before the constructible check.
        b.Store(a, b.Load(a));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(
                    R"(:8:5 error: store: store type 'array<u32>' is not constructible
    store %a, %3
    ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, LoadVectorElement_NullResult) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        b.Append(
            mod.CreateInstruction<ir::LoadVectorElement>(nullptr, var->Result(), b.Constant(1_i)));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:4:5 error: load_vector_element: result is undefined
    undef = load_vector_element %2, 1i
    ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, LoadVectorElement_NullFrom) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        b.Append(mod.CreateInstruction<ir::LoadVectorElement>(b.InstructionResult(ty.f32()),
                                                              nullptr, b.Constant(1_i)));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:34 error: load_vector_element: operand is undefined
    %2:f32 = load_vector_element undef, 1i
                                 ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, LoadVectorElement_NullIndex) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        b.Append(mod.CreateInstruction<ir::LoadVectorElement>(b.InstructionResult(ty.f32()),
                                                              var->Result(), nullptr));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:4:38 error: load_vector_element: operand is undefined
    %3:f32 = load_vector_element %2, undef
                                     ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, LoadVectorElement_MissingResult) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        auto* load = b.LoadVectorElement(var, b.Constant(1_i));
        load->ClearResults();
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(R"(:4:13 error: load_vector_element: expected exactly 1 results, got 0
    undef = load_vector_element %2, 1i
            ^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, LoadVectorElement_MissingOperands) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        auto* load = b.LoadVectorElement(var, b.Constant(1_i));
        load->ClearOperands();
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(R"(:4:14 error: load_vector_element: expected exactly 2 operands, got 0
    %3:f32 = load_vector_element
             ^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, LoadVectorElement_InvalidIndexType) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        b.LoadVectorElement(var->Result(), 1_f);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:4:38 error: load_vector_element: load vector element index must be an integer scalar
    %3:f32 = load_vector_element %2, 1.0f
                                     ^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, LoadVectorElement_InvalidFromType) {
    auto* f = b.Function("my_func", ty.void_());
    auto* p = b.FunctionParam(ty.mat2x3<f32>());
    f->SetParams({p});

    b.Append(f->Block(), [&] {
        auto* res = b.InstructionResult(ty.f32());
        auto* lve = mod.CreateInstruction<ir::LoadVectorElement>(res, p, b.Constant(1_u));
        b.Append(lve);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:3:34 error: load_vector_element: operand 'mat2x3<f32>' must be a pointer to a vector
    %3:f32 = load_vector_element %2, 1u
                                 ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, LoadVectorElement_ConstantIndexOutOfRange) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec4f>());
        b.LoadVectorElement(var->Result(), 7_u);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:4:38 error: load_vector_element: load vector element index must be in range [0, 3]
    %3:f32 = load_vector_element %2, 7u
                                     ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, StoreVectorElement_NullTo) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        b.Append(mod.CreateInstruction<ir::StoreVectorElement>(nullptr, b.Constant(1_i),
                                                               b.Constant(2_f)));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:3:26 error: store_vector_element: operand is undefined
    store_vector_element undef, 1i, 2.0f
                         ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, StoreVectorElement_NullIndex) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        b.Append(
            mod.CreateInstruction<ir::StoreVectorElement>(var->Result(), nullptr, b.Constant(2_f)));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:4:30 error: store_vector_element: operand is undefined
    store_vector_element %2, undef, 2.0f
                             ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, StoreVectorElement_NullValue) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        b.Append(
            mod.CreateInstruction<ir::StoreVectorElement>(var->Result(), b.Constant(1_i), nullptr));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:4:34 error: store_vector_element: operand is undefined
    store_vector_element %2, 1i, undef
                                 ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, StoreVectorElement_MissingOperands) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        auto* store = b.StoreVectorElement(var, b.Constant(1_i), b.Constant(2_f));
        store->ClearOperands();
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(R"(:4:5 error: store_vector_element: expected exactly 3 operands, got 0
    store_vector_element
    ^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, StoreVectorElement_UnexpectedResult) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        auto* store = b.StoreVectorElement(var, b.Constant(1_i), b.Constant(2_f));
        store->SetResult(b.InstructionResult(ty.f32()));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(R"(:4:5 error: store_vector_element: expected exactly 0 results, got 1
    store_vector_element %2, 1i, 2.0f
    ^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, StoreVectorElement_InvalidIndexType) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        b.StoreVectorElement(var->Result(), 1_f, 1_f);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:4:30 error: store_vector_element: store vector element index must be an integer scalar
    store_vector_element %2, 1.0f, 1.0f
                             ^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, StoreVectorElement_InvalidFromType) {
    auto* f = b.Function("my_func", ty.void_());
    auto* p = b.FunctionParam(ty.mat2x3<f32>());
    f->SetParams({p});

    b.Append(f->Block(), [&] {
        auto* sve =
            mod.CreateInstruction<ir::StoreVectorElement>(p, b.Constant(1_u), b.Constant(2_u));
        b.Append(sve);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:3:26 error: store_vector_element: operand 'mat2x3<f32>' must be a pointer to a vector
    store_vector_element %2, 1u, 2u
                         ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, StoreVectorElement_ConstantIndexOutOfRange) {
    auto* f = b.Function("my_func", ty.void_());

    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec2f>());
        b.StoreVectorElement(var->Result(), 7_u, 1_f);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:4:30 error: store_vector_element: store vector element index must be in range [0, 1]
    store_vector_element %2, 7u, 1.0f
                             ^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, LoadVectorElement_MismatchedResultType) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        auto* res = b.InstructionResult(ty.i32());
        auto* lve =
            mod.CreateInstruction<ir::LoadVectorElement>(res, var->Result(), b.Constant(1_i));
        b.Append(lve);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr("error: load_vector_element: result type 'i32' does not match "
                                   "vector pointer element type 'f32'"))
        << res.Failure();
}

TEST_F(IR_ValidatorTest, StoreVectorElement_MismatchedValueType) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f>());
        auto* sve = mod.CreateInstruction<ir::StoreVectorElement>(var->Result(), b.Constant(1_i),
                                                                  b.Constant(2_i));
        b.Append(sve);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr("error: store_vector_element: value type 'i32' does not match "
                                   "vector pointer element type 'f32'"))
        << res.Failure();
}

TEST_F(IR_ValidatorTest, StoreVectorElement_NonWriteableTarget) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr<function, vec3f, core::Access::kRead>());
        auto* sve = mod.CreateInstruction<ir::StoreVectorElement>(var->Result(), b.Constant(1_i),
                                                                  b.Constant(2_f));
        b.Append(sve);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr("error: store_vector_element: store_vector_element target "
                                   "operand has a non-writeable access type, 'read'"))
        << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_MissingValue) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec4f()));
        auto* swizzle = b.Swizzle(ty.vec4f(), b.Load(var), {3, 2, 1, 0});
        swizzle->ClearOperands();
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:5:20 error: swizzle: expected exactly 1 operands, got 0
    %4:vec4<f32> = swizzle undef, wzyx
                   ^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_NullValue) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec4f()));
        auto* swizzle = b.Swizzle(ty.vec4f(), b.Load(var), {3, 2, 1, 0});
        swizzle->SetOperand(0, nullptr);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(error: swizzle: operand is undefined
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_MissingResult) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec4f()));
        auto* swizzle = b.Swizzle(ty.vec4f(), b.Load(var), {3, 2, 1, 0});
        swizzle->ClearResults();
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:5:13 error: swizzle: expected exactly 1 results, got 0
    undef = swizzle %3, wzyx
            ^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_NullResult) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec4f()));
        auto* swizzle = b.Swizzle(ty.vec4f(), b.Load(var), {3, 2, 1, 0});
        swizzle->SetResult(nullptr);
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:5:5 error: swizzle: result is undefined
    undef = swizzle %3, wzyx
    ^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_NoIndices) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec4f()));
        auto* swizzle = b.Swizzle(ty.vec4f(), b.Load(var), {3, 2, 1, 0});
        auto indices = Vector<uint32_t, 0>();
        swizzle->SetIndices(std::move(indices));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:5:20 error: swizzle: expected at least 1 indices
    %4:vec4<f32> = swizzle %3,
                   ^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_TooManyIndices) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec4f()));
        auto* swizzle = b.Swizzle(ty.vec4f(), b.Load(var), {3, 2, 1, 0});
        auto indices = Vector<uint32_t, 5>{1, 1, 1, 1, 1};
        swizzle->SetIndices(std::move(indices));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:5:20 error: swizzle: expected at most 4 indices
    %4:vec4<f32> = swizzle %3, yyyyy
                   ^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_InvalidIndices) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec4f()));
        auto* swizzle = b.Swizzle(ty.vec4f(), b.Load(var), {3, 2, 1, 0});
        auto indices = Vector<uint32_t, 4>{4, 3, 2, 1};
        swizzle->SetIndices(std::move(indices));
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:5:20 error: swizzle: invalid index value
    %4:vec4<f32> = swizzle %3, wzy
                   ^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_NotVector) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.f32()));
        b.Swizzle(ty.vec4f(), b.Load(var), {3, 2, 1, 0});
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(
                    R"(:5:20 error: swizzle: object of swizzle, %3, is not a vector, 'f32'
    %4:vec4<f32> = swizzle %3, wzyx
                   ^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_TooSmallResult) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec4f()));
        b.Swizzle(ty.vec2f(), b.Load(var), {3, 2, 1, 0});
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:5:20 error: swizzle: result type 'vec2<f32>' does not match expected type, 'vec4<f32>'
    %4:vec2<f32> = swizzle %3, wzyx
                   ^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_TooLargeResult) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec4f()));
        b.Swizzle(ty.vec4f(), b.Load(var), {3, 2});
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(:5:20 error: swizzle: result type 'vec4<f32>' does not match expected type, 'vec2<f32>'
    %4:vec4<f32> = swizzle %3, wz
                   ^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_WrongTypeResult) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec4f()));
        b.Swizzle(ty.vec2u(), b.Load(var), {3, 2});
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(
        res.Failure().reason,
        testing::HasSubstr(
            R"(5:20 error: swizzle: result type 'vec2<u32>' does not match expected type, 'vec2<f32>'
    %4:vec2<u32> = swizzle %3, wz
                   ^^^^^^^
)")) << res.Failure();
}

TEST_F(IR_ValidatorTest, Swizzle_OOBIndex) {
    auto* f = b.Function("my_func", ty.void_());
    b.Append(f->Block(), [&] {
        auto* var = b.Var(ty.ptr(function, ty.vec2f()));
        b.Swizzle(ty.vec4f(), b.Load(var), {3, 1, 1, 0});
        b.Return(f);
    });

    auto res = ir::Validate(mod);
    ASSERT_NE(res, Success);
    EXPECT_THAT(res.Failure().reason,
                testing::HasSubstr(R"(:5:20 error: swizzle: invalid index value
    %4:vec4<f32> = swizzle %3, wyyx
                   ^^^^^^^
)")) << res.Failure();
}

using IR_ValidatorAccessIndexTypeTest = IRTestParamHelper<std::tuple<bool, TypeBuilderFn>>;

TEST_P(IR_ValidatorAccessIndexTypeTest, IndexType) {
    auto allowed = std::get<0>(GetParam());
    auto* index_ty = std::get<1>(GetParam())(ty);

    auto* f = b.Function("my_func", ty.void_());
    auto* obj = b.FunctionParam(ty.ptr<private_, array<f32, 4>>());
    auto* idx = b.FunctionParam(index_ty);
    f->SetParams({obj, idx});

    b.Append(f->Block(), [&] {
        b.Access(ty.ptr<private_, f32>(), obj, idx);
        b.Return(f);
    });

    Capabilities caps{Capability::kAllow8BitIntegers, Capability::kAllow16BitIntegers,
                      Capability::kAllow64BitIntegers};

    auto res = ir::Validate(mod, caps);
    if (allowed) {
        EXPECT_EQ(res, Success) << res.Failure();
    } else {
        EXPECT_NE(res, Success);
        EXPECT_THAT(
            res.Failure().reason,
            testing::HasSubstr("index type '" + index_ty->FriendlyName() + "' must be i32 or u32"));
    }
}

INSTANTIATE_TEST_SUITE_P(IR_ValidatorTest,
                         IR_ValidatorAccessIndexTypeTest,
                         testing::Values(std::make_tuple(true, TypeBuilder<i32>),
                                         std::make_tuple(true, TypeBuilder<u32>),
                                         std::make_tuple(false, TypeBuilder<f32>),
                                         std::make_tuple(false, TypeBuilder<u64>),
                                         std::make_tuple(false, TypeBuilder<f16>),
                                         std::make_tuple(false, TypeBuilder<i8>),
                                         std::make_tuple(false, TypeBuilder<u8>)));

}  // namespace tint::core::ir
