// Copyright 2021 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 "gtest/gtest.h"
#include "src/tint/lang/wgsl/resolver/resolver.h"
#include "src/tint/lang/wgsl/resolver/resolver_helper_test.h"
#include "src/tint/lang/wgsl/sem/for_loop_statement.h"
#include "src/tint/lang/wgsl/sem/if_statement.h"
#include "src/tint/lang/wgsl/sem/switch_statement.h"
#include "src/tint/lang/wgsl/sem/value_expression.h"
#include "src/tint/lang/wgsl/sem/while_statement.h"

namespace tint::resolver {
namespace {

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

class ResolverBehaviorTest : public ResolverTest {
  protected:
    void SetUp() override {
        // Create a function called 'Next' which returns an i32, and has the behavior of {Return},
        // which when called, will have the behavior {Next}.
        // It contains a discard statement, which should not affect the behavior analysis or any
        // related validation.
        Func("Next", tint::Empty, ty.i32(),
             Vector{
                 If(true, Block(Discard())),
                 Return(1_i),
             });
    }
};

TEST_F(ResolverBehaviorTest, ExprBinaryOp_LHS) {
    auto* stmt = Decl(Var("lhs", ty.i32(), Add(Call("Next"), 1_i)));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, ExprBinaryOp_RHS) {
    auto* stmt = Decl(Var("lhs", ty.i32(), Add(1_i, Call("Next"))));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, ExprBitcastOp) {
    auto* stmt = Decl(Var("lhs", ty.u32(), Bitcast<u32>(Call("Next"))));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, ExprIndex_Arr) {
    Func("ArrayDiscardOrNext", tint::Empty, ty.array<i32, 4>(),
         Vector{
             If(true, Block(Discard())),
             Return(Call<array<i32, 4>>()),
         });

    auto* stmt = Decl(Var("lhs", ty.i32(), IndexAccessor(Call("ArrayDiscardOrNext"), 1_i)));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, ExprIndex_Idx) {
    auto* stmt = Decl(Var("lhs", ty.i32(), IndexAccessor("arr", Call("Next"))));

    Func("F", tint::Empty, ty.void_(),
         Vector{
             Decl(Var("arr", ty.array<i32, 4>())),  //
             stmt,
         },
         Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, ExprUnaryOp) {
    auto* stmt = Decl(Var(
        "lhs", ty.i32(), create<ast::UnaryOpExpression>(core::UnaryOp::kComplement, Call("Next"))));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtAssign) {
    auto* stmt = Assign("lhs", "rhs");
    WrapInFunction(Decl(Var("lhs", ty.i32())),  //
                   Decl(Var("rhs", ty.i32())),  //
                   stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtAssign_LHSDiscardOrNext) {
    auto* stmt = Assign(IndexAccessor("lhs", Call("Next")), 1_i);

    Func("F", tint::Empty, ty.void_(),
         Vector{
             Decl(Var("lhs", ty.array<i32, 4>())),  //
             stmt,
         },
         Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtAssign_RHSDiscardOrNext) {
    auto* stmt = Assign("lhs", Call("Next"));

    Func("F", tint::Empty, ty.void_(),
         Vector{
             Decl(Var("lhs", ty.i32())),  //
             stmt,
         },
         Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtBlockEmpty) {
    auto* stmt = Block();
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtBlockSingleStmt) {
    auto* stmt = Block(Return());

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
}

TEST_F(ResolverBehaviorTest, StmtCallReturn) {
    Func("f", tint::Empty, ty.void_(), Vector{Return()});
    auto* stmt = CallStmt(Call("f"));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtCallFuncDiscard) {
    Func("f", tint::Empty, ty.void_(), Vector{Discard()});
    auto* stmt = CallStmt(Call("f"));

    Func("g", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtCallFuncMayDiscard) {
    auto* stmt = For(Decl(Var("v", ty.i32(), Call("Next"))), nullptr, nullptr, Block(Break()));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtBreak) {
    auto* stmt = Break();
    WrapInFunction(Loop(Block(stmt)));

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kBreak);
}

TEST_F(ResolverBehaviorTest, StmtContinue) {
    auto* stmt = Continue();
    WrapInFunction(Loop(Block(If(true, Block(Break())),  //
                              stmt)));

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kContinue);
}

TEST_F(ResolverBehaviorTest, StmtDiscard) {
    auto* stmt = Discard();

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_NoExit) {
    auto* stmt = For(Source{{12, 34}}, nullptr, nullptr, nullptr, Block());
    WrapInFunction(stmt);

    EXPECT_FALSE(r()->Resolve());
    EXPECT_EQ(r()->error(), "12:34 error: for-loop does not exit");
}

TEST_F(ResolverBehaviorTest, StmtForLoopBreak) {
    auto* stmt = For(nullptr, nullptr, nullptr, Block(Break()));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtForLoopContinue_NoExit) {
    auto* stmt = For(Source{{12, 34}}, nullptr, nullptr, nullptr, Block(Continue()));
    WrapInFunction(stmt);

    EXPECT_FALSE(r()->Resolve());
    EXPECT_EQ(r()->error(), "12:34 error: for-loop does not exit");
}

TEST_F(ResolverBehaviorTest, StmtForLoopDiscard) {
    auto* stmt = For(nullptr, nullptr, nullptr, Block(Discard(), Break()));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtForLoopReturn) {
    auto* stmt = For(nullptr, nullptr, nullptr, Block(Return()));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
}

TEST_F(ResolverBehaviorTest, StmtForLoopBreak_InitCallFuncMayDiscard) {
    auto* stmt = For(Decl(Var("v", ty.i32(), Call("Next"))), nullptr, nullptr, Block(Break()));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_InitCallFuncMayDiscard) {
    auto* stmt = For(Decl(Var("v", ty.i32(), Call("Next"))), nullptr, nullptr, Block(Break()));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_CondTrue) {
    auto* stmt = For(nullptr, true, nullptr, Block());
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_CondCallFuncMayDiscard) {
    auto* stmt = For(nullptr, Equal(Call("Next"), 1_i), nullptr, Block());

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtWhileBreak) {
    auto* stmt = While(Expr(true), Block(Break()));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtWhileDiscard) {
    auto* stmt = While(Expr(true), Block(Discard()));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtWhileReturn) {
    auto* stmt = While(Expr(true), Block(Return()));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kReturn, sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtWhileEmpty_CondTrue) {
    auto* stmt = While(Expr(true), Block());
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtWhileEmpty_CondCallFuncMayDiscard) {
    auto* stmt = While(Equal(Call("Next"), 1_i), Block());

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock) {
    auto* stmt = If(true, Block());
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenDiscard) {
    auto* stmt = If(true, Block(Discard()));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock_ElseDiscard) {
    auto* stmt = If(true, Block(), Else(Block(Discard())));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenDiscard_ElseDiscard) {
    auto* stmt = If(true, Block(Discard()), Else(Block(Discard())));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtIfCallFuncMayDiscard_ThenEmptyBlock) {
    auto* stmt = If(Equal(Call("Next"), 1_i), Block());

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock_ElseCallFuncMayDiscard) {
    auto* stmt = If(true, Block(),  //
                    Else(If(Equal(Call("Next"), 1_i), Block())));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtLetDecl) {
    auto* stmt = Decl(Let("v", ty.i32(), Expr(1_i)));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtLetDecl_RHSDiscardOrNext) {
    auto* stmt = Decl(Let("lhs", ty.i32(), Call("Next")));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtLoopEmpty_NoExit) {
    auto* stmt = Loop(Source{{12, 34}}, Block());
    WrapInFunction(stmt);

    EXPECT_FALSE(r()->Resolve());
    EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
}

TEST_F(ResolverBehaviorTest, StmtLoopBreak) {
    auto* stmt = Loop(Block(Break()));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtLoopContinue_NoExit) {
    auto* stmt = Loop(Source{{12, 34}}, Block(Continue()));
    WrapInFunction(stmt);

    EXPECT_FALSE(r()->Resolve());
    EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
}

TEST_F(ResolverBehaviorTest, StmtLoopDiscard) {
    auto* stmt = Loop(Block(Discard(), Break()));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtLoopReturn) {
    auto* stmt = Loop(Block(Return()));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
}

TEST_F(ResolverBehaviorTest, StmtLoopEmpty_ContEmpty_NoExit) {
    auto* stmt = Loop(Source{{12, 34}}, Block(), Block());
    WrapInFunction(stmt);

    EXPECT_FALSE(r()->Resolve());
    EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
}

TEST_F(ResolverBehaviorTest, StmtLoopEmpty_BreakIf) {
    auto* stmt = Loop(Block(), Block(BreakIf(true)));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtReturn) {
    auto* stmt = Return();
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
}

TEST_F(ResolverBehaviorTest, StmtReturn_DiscardOrNext) {
    auto* stmt = Return(Call("Next"));

    Func("F", tint::Empty, ty.i32(), Vector{stmt});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kReturn));
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondTrue_DefaultEmpty) {
    auto* stmt = Switch(1_i, DefaultCase(Block()));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultEmpty) {
    auto* stmt = Switch(1_i, DefaultCase(Block()));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultDiscard) {
    auto* stmt = Switch(1_i, DefaultCase(Block(Discard())));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultReturn) {
    auto* stmt = Switch(1_i, DefaultCase(Block(Return())));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultEmpty) {
    auto* stmt = Switch(1_i, Case(CaseSelector(0_i), Block()), DefaultCase(Block()));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultDiscard) {
    auto* stmt = Switch(1_i, Case(CaseSelector(0_i), Block()), DefaultCase(Block(Discard())));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultReturn) {
    auto* stmt = Switch(1_i, Case(CaseSelector(0_i), Block()), DefaultCase(Block(Return())));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext, sem::Behavior::kReturn));
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Discard_DefaultEmpty) {
    auto* stmt = Switch(1_i, Case(CaseSelector(0_i), Block(Discard())), DefaultCase(Block()));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Discard_DefaultDiscard) {
    auto* stmt =
        Switch(1_i, Case(CaseSelector(0_i), Block(Discard())), DefaultCase(Block(Discard())));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Discard_DefaultReturn) {
    auto* stmt =
        Switch(1_i, Case(CaseSelector(0_i), Block(Discard())), DefaultCase(Block(Return())));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kReturn, sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Discard_Case1Return_DefaultEmpty) {
    auto* stmt = Switch(1_i,                                        //
                        Case(CaseSelector(0_i), Block(Discard())),  //
                        Case(CaseSelector(1_i), Block(Return())),   //
                        DefaultCase(Block()));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext, sem::Behavior::kReturn));
}

TEST_F(ResolverBehaviorTest, StmtSwitch_CondCallFuncMayDiscard_DefaultEmpty) {
    auto* stmt = Switch(Call("Next"), DefaultCase(Block()));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

TEST_F(ResolverBehaviorTest, StmtVarDecl) {
    auto* stmt = Decl(Var("v", ty.i32()));
    WrapInFunction(stmt);

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
}

TEST_F(ResolverBehaviorTest, StmtVarDecl_RHSDiscardOrNext) {
    auto* stmt = Decl(Var("lhs", ty.i32(), Call("Next")));

    Func("F", tint::Empty, ty.void_(), Vector{stmt}, Vector{Stage(ast::PipelineStage::kFragment)});

    ASSERT_TRUE(r()->Resolve()) << r()->error();

    auto* sem = Sem().Get(stmt);
    EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
}

}  // namespace
}  // namespace tint::resolver
