mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-08-16 08:59:12 +00:00
Discard statements no longer affect the behavior or uniformity analysis. Update the resolver, validator, and several tests to reflect this. Some E2E tests were removed as they had loops that are now considered to be infinite. Use the DemoteToHelper transform to emulate the correct semantics on platforms where discard is (or may) terminate the invocation in a manner that would affect derivative operations. We no longer need the UnwindDiscardFunctions transform for HLSL, which already implements the correct semantics. However, we still run the DemoteToHelper transform for the HLSL backend due to issues with FXC's handling of discard statements (see crbug.com/tint/1118). Fixed: tint:1723 Change-Id: Ib49ff187919ae81c4af8675e1b66acd57e2ff7d2 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/109003 Reviewed-by: Ben Clayton <bclayton@google.com> Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: James Price <jrprice@google.com>
764 lines
24 KiB
C++
764 lines
24 KiB
C++
// Copyright 2021 The Tint Authors.
|
|
//
|
|
// 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 "src/tint/resolver/resolver.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
#include "src/tint/resolver/resolver_test_helper.h"
|
|
#include "src/tint/sem/expression.h"
|
|
#include "src/tint/sem/for_loop_statement.h"
|
|
#include "src/tint/sem/if_statement.h"
|
|
#include "src/tint/sem/switch_statement.h"
|
|
#include "src/tint/sem/while_statement.h"
|
|
|
|
using namespace tint::number_suffixes; // NOLINT
|
|
|
|
namespace tint::resolver {
|
|
namespace {
|
|
|
|
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", utils::Empty, ty.i32(),
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.array<i32, 4>(),
|
|
utils::Vector{
|
|
If(true, Block(Discard())),
|
|
Return(Construct(ty.array<i32, 4>())),
|
|
});
|
|
|
|
auto* stmt = Decl(Var("lhs", ty.i32(), IndexAccessor(Call("ArrayDiscardOrNext"), 1_i)));
|
|
|
|
Func("F", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(),
|
|
utils::Vector{
|
|
Decl(Var("arr", ty.array<i32, 4>())), //
|
|
stmt,
|
|
},
|
|
utils::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>(ast::UnaryOp::kComplement, Call("Next"))));
|
|
|
|
Func("F", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(),
|
|
utils::Vector{
|
|
Decl(Var("lhs", ty.array<i32, 4>())), //
|
|
stmt,
|
|
},
|
|
utils::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", utils::Empty, ty.void_(),
|
|
utils::Vector{
|
|
Decl(Var("lhs", ty.i32())), //
|
|
stmt,
|
|
},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::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", utils::Empty, ty.void_(), utils::Vector{Discard()});
|
|
auto* stmt = CallStmt(Call("f"));
|
|
|
|
Func("g", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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_ContIfTrueBreak) {
|
|
auto* stmt = Loop(Block(), Block(If(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, 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", utils::Empty, ty.i32(), utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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", utils::Empty, ty.void_(), utils::Vector{stmt},
|
|
utils::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
|