mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-13 07:06:11 +00:00
resolver: Migrate validation to behavior analysis
Migrate some of the validation logic over to use the results of behavior analysis. The most significant changes are: * Unreachable-statements now consider merge-points of control flow. For example, if all branches of a if-statement or switch-statement either return or discard, the next statement will be considered unreachable. * Unreachable statements are no longer an error, but a warning. See https://github.com/gpuweb/gpuweb/issues/2378. * Statements that follow a loops that does not break, or have a conditional will now be considered unreachable. * Unreachable statements produced by the SPIR-V reader are now removed using the new RemoveUnreachableStatements transform. Some other new changes include additional validation for the continuing block for for-loops, to match the rules of a loop continuing block. The new cases this validation is testing for are not expressible in WGSL, but some transforms may produce complex continuing statements that might violate these rules. All the writers are able to decay these complex for-loop continuing statements to regular loops. Bug: tint:1302 Change-Id: I0d8a48c73d5d5c30a1cddf92cc3383a692a58e61 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/71500 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
@@ -30,7 +30,7 @@ TEST_F(ResolverControlBlockValidationTest,
|
||||
// switch (a) {
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(3.14f));
|
||||
auto* var = Var("a", ty.f32(), Expr(3.14f));
|
||||
|
||||
auto* block = Block(Decl(var), Switch(Expr(Source{{12, 34}}, "a"), //
|
||||
DefaultCase()));
|
||||
@@ -48,7 +48,7 @@ TEST_F(ResolverControlBlockValidationTest, SwitchWithoutDefault_Fail) {
|
||||
// switch (a) {
|
||||
// case 1: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch(Source{{12, 34}}, "a", //
|
||||
@@ -68,7 +68,7 @@ TEST_F(ResolverControlBlockValidationTest, SwitchWithTwoDefault_Fail) {
|
||||
// case 1: {}
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
@@ -90,12 +90,16 @@ TEST_F(ResolverControlBlockValidationTest, UnreachableCode_Loop_continue) {
|
||||
// continue;
|
||||
// z = 1;
|
||||
// }
|
||||
WrapInFunction(Loop(Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
|
||||
create<ast::ContinueStatement>(),
|
||||
Assign(Source{{12, 34}}, "z", 1))));
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* cont = Continue();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction(Loop(Block(decl_z, cont, assign_z)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(cont)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
@@ -105,13 +109,16 @@ TEST_F(ResolverControlBlockValidationTest,
|
||||
// {{{continue;}}}
|
||||
// z = 1;
|
||||
// }
|
||||
WrapInFunction(
|
||||
Loop(Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
|
||||
Block(Block(Block(create<ast::ContinueStatement>()))),
|
||||
Assign(Source{{12, 34}}, "z", 1))));
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* cont = Continue();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction(Loop(Block(decl_z, Block(Block(Block(cont))), assign_z)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(cont)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, UnreachableCode_ForLoop_continue) {
|
||||
@@ -120,14 +127,17 @@ TEST_F(ResolverControlBlockValidationTest, UnreachableCode_ForLoop_continue) {
|
||||
// continue;
|
||||
// z = 1;
|
||||
// }
|
||||
WrapInFunction(
|
||||
For(nullptr, nullptr, nullptr,
|
||||
Block(create<ast::ContinueStatement>(),
|
||||
Decl(Source{{12, 34}},
|
||||
Var("z", ty.i32(), ast::StorageClass::kNone)))));
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* cont = Continue();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction(For(nullptr, nullptr, nullptr, //
|
||||
Block(decl_z, cont, assign_z)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(cont)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
@@ -137,31 +147,40 @@ TEST_F(ResolverControlBlockValidationTest,
|
||||
// {{{continue;}}}
|
||||
// z = 1;
|
||||
// }
|
||||
WrapInFunction(
|
||||
For(nullptr, nullptr, nullptr,
|
||||
Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
|
||||
Block(Block(Block(create<ast::ContinueStatement>()))),
|
||||
Assign(Source{{12, 34}}, "z", 1))));
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* cont = Continue();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction(For(nullptr, nullptr, nullptr,
|
||||
Block(decl_z, Block(Block(Block(cont))), assign_z)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(cont)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break) {
|
||||
// switch (1) {
|
||||
// case 1: { break; var a : u32 = 2;}
|
||||
// case 1: {
|
||||
// var z: i32;
|
||||
// break;
|
||||
// z = 1;
|
||||
// default: {}
|
||||
// }
|
||||
auto* decl = Decl(Source{{12, 34}},
|
||||
Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2)));
|
||||
|
||||
WrapInFunction( //
|
||||
Loop(Block(Switch(1, //
|
||||
Case(Expr(1), Block(Break(), decl)), //
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* brk = Break();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction( //
|
||||
Loop(Block(Switch(1, //
|
||||
Case(Expr(1), Block(decl_z, brk, assign_z)), //
|
||||
DefaultCase()))));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(brk)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break_InBlocks) {
|
||||
@@ -171,16 +190,19 @@ TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break_InBlocks) {
|
||||
// default: {}
|
||||
// }
|
||||
// }
|
||||
auto* decl = Decl(Source{{12, 34}},
|
||||
Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2)));
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* brk = Break();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction(Loop(Block(
|
||||
Switch(1, //
|
||||
Case(Expr(1), Block(decl_z, Block(Block(Block(brk))), assign_z)),
|
||||
DefaultCase()))));
|
||||
|
||||
WrapInFunction(Loop(
|
||||
Block(Switch(1, //
|
||||
Case(Expr(1), Block(Block(Block(Block(Break()))), decl)),
|
||||
DefaultCase()))));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(brk)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
@@ -190,7 +212,7 @@ TEST_F(ResolverControlBlockValidationTest,
|
||||
// case 1: {}
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
auto* block = Block(Decl(var), Switch("a", //
|
||||
Case(Source{{12, 34}}, {Expr(1u)}), //
|
||||
@@ -210,7 +232,7 @@ TEST_F(ResolverControlBlockValidationTest,
|
||||
// case -1: {}
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.u32(), ast::StorageClass::kNone, Expr(2u));
|
||||
auto* var = Var("a", ty.u32(), Expr(2u));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
@@ -232,7 +254,7 @@ TEST_F(ResolverControlBlockValidationTest,
|
||||
// case 2u, 3u, 2u: {}
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.u32(), ast::StorageClass::kNone, Expr(3u));
|
||||
auto* var = Var("a", ty.u32(), Expr(3u));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
@@ -259,7 +281,7 @@ TEST_F(ResolverControlBlockValidationTest,
|
||||
// case 0,1,2,-10: {}
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
@@ -285,7 +307,7 @@ TEST_F(ResolverControlBlockValidationTest,
|
||||
// switch (a) {
|
||||
// default: { fallthrough; }
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
auto* fallthrough = create<ast::FallthroughStatement>(Source{{12, 34}});
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
@@ -304,7 +326,7 @@ TEST_F(ResolverControlBlockValidationTest, SwitchCase_Pass) {
|
||||
// default: {}
|
||||
// case 5: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
@@ -323,7 +345,7 @@ TEST_F(ResolverControlBlockValidationTest, SwitchCaseAlias_Pass) {
|
||||
// }
|
||||
|
||||
auto* my_int = Alias("MyInt", ty.u32());
|
||||
auto* var = Var("a", ty.Of(my_int), ast::StorageClass::kNone, Expr(2u));
|
||||
auto* var = Var("a", ty.Of(my_int), Expr(2u));
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", DefaultCase(Source{{12, 34}})));
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ TEST_F(ResolverFunctionValidationTest, NestedLocalMayShadowParameter) {
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
VoidFunctionEndWithoutReturnStatement_Pass) {
|
||||
// fn func { var a:i32 = 2; }
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.void_(),
|
||||
ast::StatementList{
|
||||
@@ -87,7 +87,7 @@ TEST_F(ResolverFunctionValidationTest, FunctionUsingSameVariableName_Pass) {
|
||||
// return func;
|
||||
// }
|
||||
|
||||
auto* var = Var("func", ty.i32(), ast::StorageClass::kNone, Expr(0));
|
||||
auto* var = Var("func", ty.i32(), Expr(0));
|
||||
Func("func", ast::VariableList{}, ty.i32(),
|
||||
ast::StatementList{
|
||||
Decl(var),
|
||||
@@ -103,7 +103,7 @@ TEST_F(ResolverFunctionValidationTest,
|
||||
// fn a() -> void { var b:i32 = 0; }
|
||||
// fn b() -> i32 { return 2; }
|
||||
|
||||
auto* var = Var("b", ty.i32(), ast::StorageClass::kNone, Expr(0));
|
||||
auto* var = Var("b", ty.i32(), Expr(0));
|
||||
Func("a", ast::VariableList{}, ty.void_(),
|
||||
ast::StatementList{
|
||||
Decl(var),
|
||||
@@ -126,14 +126,18 @@ TEST_F(ResolverFunctionValidationTest, UnreachableCode_return) {
|
||||
// a = 2;
|
||||
//}
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
Decl(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2))),
|
||||
Return(),
|
||||
Assign(Source{{12, 34}}, "a", 2),
|
||||
});
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
auto* decl_a = Decl(Var("a", ty.i32()));
|
||||
auto* ret = Return();
|
||||
auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(), {decl_a, ret, assign_a});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(ret)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, UnreachableCode_return_InBlocks) {
|
||||
@@ -143,14 +147,18 @@ TEST_F(ResolverFunctionValidationTest, UnreachableCode_return_InBlocks) {
|
||||
// a = 2;
|
||||
//}
|
||||
|
||||
auto* decl_a = Decl(Var("a", ty.i32()));
|
||||
auto* ret = Return();
|
||||
auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
Decl(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2))),
|
||||
Block(Block(Block(Return()))),
|
||||
Assign(Source{{12, 34}}, "a", 2),
|
||||
});
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
{decl_a, Block(Block(Block(ret))), assign_a});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(ret)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard) {
|
||||
@@ -160,14 +168,17 @@ TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard) {
|
||||
// a = 2;
|
||||
//}
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
Decl(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2))),
|
||||
create<ast::DiscardStatement>(),
|
||||
Assign(Source{{12, 34}}, "a", 2),
|
||||
});
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
auto* decl_a = Decl(Var("a", ty.i32()));
|
||||
auto* discard = Discard();
|
||||
auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(), {decl_a, discard, assign_a});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(discard)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard_InBlocks) {
|
||||
@@ -177,20 +188,24 @@ TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard_InBlocks) {
|
||||
// a = 2;
|
||||
//}
|
||||
|
||||
auto* decl_a = Decl(Var("a", ty.i32()));
|
||||
auto* discard = Discard();
|
||||
auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
Decl(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2))),
|
||||
Block(Block(Block(create<ast::DiscardStatement>()))),
|
||||
Assign(Source{{12, 34}}, "a", 2),
|
||||
});
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
{decl_a, Block(Block(Block(discard))), assign_a});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(discard)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, FunctionEndWithoutReturnStatement_Fail) {
|
||||
// fn func() -> int { var a:i32 = 2; }
|
||||
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.i32(),
|
||||
ast::StatementList{
|
||||
@@ -199,8 +214,7 @@ TEST_F(ResolverFunctionValidationTest, FunctionEndWithoutReturnStatement_Fail) {
|
||||
ast::DecorationList{});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: non-void function must end with a return statement");
|
||||
EXPECT_EQ(r()->error(), "12:34 error: missing return at end of function");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
@@ -221,8 +235,7 @@ TEST_F(ResolverFunctionValidationTest,
|
||||
ast::StatementList{}, ast::DecorationList{});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: non-void function must end with a return statement");
|
||||
EXPECT_EQ(r()->error(), "12:34 error: missing return at end of function");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
@@ -392,7 +405,7 @@ TEST_F(ResolverFunctionValidationTest, FunctionVarInitWithParam) {
|
||||
// }
|
||||
|
||||
auto* bar = Param("bar", ty.f32());
|
||||
auto* baz = Var("baz", ty.f32(), ast::StorageClass::kNone, Expr("bar"));
|
||||
auto* baz = Var("baz", ty.f32(), Expr("bar"));
|
||||
|
||||
Func("foo", ast::VariableList{bar}, ty.void_(), ast::StatementList{Decl(baz)},
|
||||
ast::DecorationList{});
|
||||
|
||||
@@ -809,6 +809,7 @@ bool Resolver::WorkgroupSize(const ast::Function* func) {
|
||||
bool Resolver::Statements(const ast::StatementList& stmts) {
|
||||
sem::Behaviors behaviors{sem::Behavior::kNext};
|
||||
|
||||
bool reachable = true;
|
||||
for (auto* stmt : stmts) {
|
||||
Mark(stmt);
|
||||
auto* sem = Statement(stmt);
|
||||
@@ -816,9 +817,11 @@ bool Resolver::Statements(const ast::StatementList& stmts) {
|
||||
return false;
|
||||
}
|
||||
// s1 s2:(B1∖{Next}) ∪ B2
|
||||
// ValidateStatements will ensure that statements can only follow a Next.
|
||||
behaviors.Remove(sem::Behavior::kNext);
|
||||
behaviors.Add(sem->Behaviors());
|
||||
sem->SetIsReachable(reachable);
|
||||
if (reachable) {
|
||||
behaviors = (behaviors - sem::Behavior::kNext) + sem->Behaviors();
|
||||
}
|
||||
reachable = reachable && sem->Behaviors().Contains(sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
current_statement_->Behaviors() = behaviors;
|
||||
@@ -2660,6 +2663,26 @@ bool Resolver::IsCallStatement(const ast::Expression* expr) const {
|
||||
[&](auto* stmt) { return stmt->expr == expr; });
|
||||
}
|
||||
|
||||
const ast::Statement* Resolver::ClosestContinuing(bool stop_at_loop) const {
|
||||
for (const auto* s = current_statement_; s != nullptr; s = s->Parent()) {
|
||||
if (stop_at_loop && s->Is<sem::LoopStatement>()) {
|
||||
break;
|
||||
}
|
||||
if (s->Is<sem::LoopContinuingBlockStatement>()) {
|
||||
return s->Declaration();
|
||||
}
|
||||
if (auto* f = As<sem::ForLoopStatement>(s->Parent())) {
|
||||
if (f->Declaration()->continuing == s->Declaration()) {
|
||||
return s->Declaration();
|
||||
}
|
||||
if (stop_at_loop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Resolver::TypeConversionSig
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -464,6 +464,14 @@ class Resolver {
|
||||
/// @returns true if `expr` is the current CallStatement's CallExpression
|
||||
bool IsCallStatement(const ast::Expression* expr) const;
|
||||
|
||||
/// Searches the current statement and up through parents of the current
|
||||
/// statement looking for a loop or for-loop continuing statement.
|
||||
/// @returns the closest continuing statement to the current statement that
|
||||
/// (transitively) owns the current statement.
|
||||
/// @param stop_at_loop if true then the function will return nullptr if a
|
||||
/// loop or for-loop was found before the continuing.
|
||||
const ast::Statement* ClosestContinuing(bool stop_at_loop) const;
|
||||
|
||||
/// @returns the resolved symbol (function, type or variable) for the given
|
||||
/// ast::Identifier or ast::TypeName cast to the given semantic type.
|
||||
template <typename SEM = sem::Node>
|
||||
|
||||
@@ -327,28 +327,6 @@ TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_CondCallFuncMayDiscard) {
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopBreak_ContCallFuncMayDiscard) {
|
||||
auto* stmt =
|
||||
For(nullptr, nullptr, CallStmt(Call("DiscardOrNext")), Block(Break()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_ContCallFuncMayDiscard) {
|
||||
auto* stmt = For(nullptr, nullptr, CallStmt(Call("DiscardOrNext")), Block());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock) {
|
||||
auto* stmt = If(true, Block());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
@@ -1000,10 +1000,12 @@ bool Resolver::ValidateFunction(const sem::Function* func) {
|
||||
}
|
||||
|
||||
if (decl->body) {
|
||||
if (!decl->body->Last() ||
|
||||
!decl->body->Last()->Is<ast::ReturnStatement>()) {
|
||||
AddError("non-void function must end with a return statement",
|
||||
decl->source);
|
||||
sem::Behaviors behaviors{sem::Behavior::kNext};
|
||||
if (auto* last = decl->body->Last()) {
|
||||
behaviors = Sem(last)->Behaviors();
|
||||
}
|
||||
if (behaviors.Contains(sem::Behavior::kNext)) {
|
||||
AddError("missing return at end of function", decl->source);
|
||||
return false;
|
||||
}
|
||||
} else if (IsValidationEnabled(
|
||||
@@ -1334,31 +1336,19 @@ bool Resolver::ValidateEntryPoint(const sem::Function* func) {
|
||||
}
|
||||
|
||||
bool Resolver::ValidateStatements(const ast::StatementList& stmts) {
|
||||
bool unreachable = false;
|
||||
for (auto* stmt : stmts) {
|
||||
if (unreachable) {
|
||||
AddError("code is unreachable", stmt->source);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* nested_stmt = stmt;
|
||||
while (auto* block = nested_stmt->As<ast::BlockStatement>()) {
|
||||
if (block->Empty()) {
|
||||
break;
|
||||
}
|
||||
nested_stmt = block->statements.back();
|
||||
}
|
||||
if (nested_stmt->IsAnyOf<ast::ReturnStatement, ast::BreakStatement,
|
||||
ast::ContinueStatement, ast::DiscardStatement>()) {
|
||||
unreachable = true;
|
||||
if (!Sem(stmt)->IsReachable()) {
|
||||
/// TODO(https://github.com/gpuweb/gpuweb/issues/2378): This may need to
|
||||
/// become an error.
|
||||
AddWarning("code is unreachable", stmt->source);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Resolver::ValidateBreakStatement(const sem::Statement* stmt) {
|
||||
if (!stmt->FindFirstParent<sem::LoopBlockStatement>() &&
|
||||
!stmt->FindFirstParent<sem::CaseStatement>()) {
|
||||
if (!stmt->FindFirstParent<sem::LoopBlockStatement, sem::CaseStatement>()) {
|
||||
AddError("break statement must be in a loop or switch case",
|
||||
stmt->Declaration()->source);
|
||||
return false;
|
||||
@@ -1367,15 +1357,17 @@ bool Resolver::ValidateBreakStatement(const sem::Statement* stmt) {
|
||||
}
|
||||
|
||||
bool Resolver::ValidateContinueStatement(const sem::Statement* stmt) {
|
||||
if (auto* block =
|
||||
stmt->FindFirstParent<sem::LoopBlockStatement,
|
||||
sem::LoopContinuingBlockStatement>()) {
|
||||
if (block->Is<sem::LoopContinuingBlockStatement>()) {
|
||||
AddError("continuing blocks must not contain a continue statement",
|
||||
stmt->Declaration()->source);
|
||||
return false;
|
||||
if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ true)) {
|
||||
AddError("continuing blocks must not contain a continue statement",
|
||||
stmt->Declaration()->source);
|
||||
if (continuing != stmt->Declaration() &&
|
||||
continuing != stmt->Parent()->Declaration()) {
|
||||
AddNote("see continuing block here", continuing->source);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt->FindFirstParent<sem::LoopBlockStatement>()) {
|
||||
AddError("continue statement must be in a loop",
|
||||
stmt->Declaration()->source);
|
||||
return false;
|
||||
@@ -1385,12 +1377,12 @@ bool Resolver::ValidateContinueStatement(const sem::Statement* stmt) {
|
||||
}
|
||||
|
||||
bool Resolver::ValidateDiscardStatement(const sem::Statement* stmt) {
|
||||
if (auto* continuing =
|
||||
stmt->FindFirstParent<sem::LoopContinuingBlockStatement>()) {
|
||||
if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false)) {
|
||||
AddError("continuing blocks must not contain a discard statement",
|
||||
stmt->Declaration()->source);
|
||||
if (continuing != stmt->Parent()) {
|
||||
AddNote("see continuing block here", continuing->Declaration()->source);
|
||||
if (continuing != stmt->Declaration() &&
|
||||
continuing != stmt->Parent()->Declaration()) {
|
||||
AddNote("see continuing block here", continuing->source);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1629,6 +1621,20 @@ bool Resolver::ValidateFunctionCall(const sem::Call* call) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (call->Behaviors().Contains(sem::Behavior::kDiscard)) {
|
||||
if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false)) {
|
||||
AddError(
|
||||
"cannot call a function that may discard inside a continuing block",
|
||||
call->Declaration()->source);
|
||||
if (continuing != call->Stmt()->Declaration() &&
|
||||
continuing != call->Stmt()->Parent()->Declaration()) {
|
||||
AddNote("see continuing block here", continuing->source);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2200,12 +2206,12 @@ bool Resolver::ValidateReturn(const ast::ReturnStatement* ret) {
|
||||
}
|
||||
|
||||
auto* sem = Sem(ret);
|
||||
if (auto* continuing =
|
||||
sem->FindFirstParent<sem::LoopContinuingBlockStatement>()) {
|
||||
if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false)) {
|
||||
AddError("continuing blocks must not contain a return statement",
|
||||
ret->source);
|
||||
if (continuing != sem->Parent()) {
|
||||
AddNote("see continuing block here", continuing->Declaration()->source);
|
||||
if (continuing != sem->Declaration() &&
|
||||
continuing != sem->Parent()->Declaration()) {
|
||||
AddNote("see continuing block here", continuing->source);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -455,8 +455,7 @@ TEST_F(ResolverValidationTest,
|
||||
Stmt_Loop_ContinueInLoopBodyBeforeDeclAndAfterDecl_UsageInContinuing) {
|
||||
// loop {
|
||||
// continue; // Bypasses z decl
|
||||
// var z : i32;
|
||||
// continue; // Ok
|
||||
// var z : i32; // unreachable
|
||||
//
|
||||
// continuing {
|
||||
// z = 2;
|
||||
@@ -465,24 +464,25 @@ TEST_F(ResolverValidationTest,
|
||||
|
||||
auto error_loc = Source{{12, 34}};
|
||||
auto* body =
|
||||
Block(create<ast::ContinueStatement>(),
|
||||
Decl(error_loc, Var("z", ty.i32(), ast::StorageClass::kNone)),
|
||||
create<ast::ContinueStatement>());
|
||||
Block(Continue(),
|
||||
Decl(error_loc, Var("z", ty.i32(), ast::StorageClass::kNone)));
|
||||
auto* continuing = Block(Assign(Expr("z"), 2));
|
||||
auto* loop_stmt = Loop(body, continuing);
|
||||
WrapInFunction(loop_stmt);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
R"(12:34 warning: code is unreachable
|
||||
error: continue statement bypasses declaration of 'z'
|
||||
note: identifier 'z' declared here
|
||||
note: identifier 'z' referenced in continuing block here)");
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ResolverValidationTest,
|
||||
Stmt_Loop_ContinueInLoopBodyBeforeDeclAndAfterDecl_UsageInContinuing_InBlocks) { // NOLINT - line length
|
||||
TEST_F(ResolverValidationTest,
|
||||
Stmt_Loop_ContinueInLoopBodyAfterDecl_UsageInContinuing_InBlocks) {
|
||||
// loop {
|
||||
// var z : i32;
|
||||
// {{{continue;}}} // Bypasses z decl
|
||||
// z = 1;
|
||||
// {{{continue;}}}
|
||||
// continue; // Ok
|
||||
//
|
||||
// continuing {
|
||||
@@ -490,16 +490,13 @@ TEST_F(
|
||||
// }
|
||||
// }
|
||||
|
||||
auto* body =
|
||||
Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
|
||||
Block(Block(Block(create<ast::ContinueStatement>()))),
|
||||
Assign(Source{{12, 34}}, "z", 2), create<ast::ContinueStatement>());
|
||||
auto* body = Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
|
||||
Block(Block(Block(Continue()))));
|
||||
auto* continuing = Block(Assign(Expr("z"), 2));
|
||||
auto* loop_stmt = Loop(body, continuing);
|
||||
WrapInFunction(loop_stmt);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverValidationTest,
|
||||
@@ -518,7 +515,7 @@ TEST_F(ResolverValidationTest,
|
||||
auto decl_loc = Source{{56, 78}};
|
||||
auto ref_loc = Source{{90, 12}};
|
||||
auto* body =
|
||||
Block(If(Expr(true), Block(create<ast::ContinueStatement>(cont_loc))),
|
||||
Block(If(Expr(true), Block(Continue(cont_loc))),
|
||||
Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
|
||||
auto* continuing = Block(Assign(Expr(ref_loc, "z"), 2));
|
||||
auto* loop_stmt = Loop(body, continuing);
|
||||
@@ -550,7 +547,7 @@ TEST_F(
|
||||
auto decl_loc = Source{{56, 78}};
|
||||
auto ref_loc = Source{{90, 12}};
|
||||
auto* body =
|
||||
Block(If(Expr(true), Block(create<ast::ContinueStatement>(cont_loc))),
|
||||
Block(If(Expr(true), Block(Continue(cont_loc))),
|
||||
Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
|
||||
|
||||
auto* continuing =
|
||||
@@ -584,7 +581,7 @@ TEST_F(ResolverValidationTest,
|
||||
auto decl_loc = Source{{56, 78}};
|
||||
auto ref_loc = Source{{90, 12}};
|
||||
auto* body =
|
||||
Block(If(Expr(true), Block(create<ast::ContinueStatement>(cont_loc))),
|
||||
Block(If(Expr(true), Block(Continue(cont_loc))),
|
||||
Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
|
||||
auto* compare = create<ast::BinaryExpression>(ast::BinaryOp::kLessThan,
|
||||
Expr(ref_loc, "z"), Expr(2));
|
||||
@@ -617,7 +614,7 @@ TEST_F(ResolverValidationTest,
|
||||
auto decl_loc = Source{{56, 78}};
|
||||
auto ref_loc = Source{{90, 12}};
|
||||
auto* body =
|
||||
Block(If(Expr(true), Block(create<ast::ContinueStatement>(cont_loc))),
|
||||
Block(If(Expr(true), Block(Continue(cont_loc))),
|
||||
Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
|
||||
|
||||
auto* continuing = Block(Loop(Block(Assign(Expr(ref_loc, "z"), 2))));
|
||||
@@ -635,7 +632,8 @@ TEST_F(ResolverValidationTest,
|
||||
Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuing) {
|
||||
// loop {
|
||||
// loop {
|
||||
// continue; // OK: not part of the outer loop
|
||||
// if (true) { continue; } // OK: not part of the outer loop
|
||||
// break;
|
||||
// }
|
||||
// var z : i32;
|
||||
//
|
||||
@@ -644,7 +642,9 @@ TEST_F(ResolverValidationTest,
|
||||
// }
|
||||
// }
|
||||
|
||||
auto* inner_loop = Loop(Block(create<ast::ContinueStatement>()));
|
||||
auto* inner_loop = Loop(Block( //
|
||||
If(true, Block(Continue())), //
|
||||
Break()));
|
||||
auto* body =
|
||||
Block(inner_loop, Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
|
||||
auto* continuing = Block(Assign("z", 2));
|
||||
@@ -658,7 +658,8 @@ TEST_F(ResolverValidationTest,
|
||||
Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingSubscope) {
|
||||
// loop {
|
||||
// loop {
|
||||
// continue; // OK: not part of the outer loop
|
||||
// if (true) { continue; } // OK: not part of the outer loop
|
||||
// break;
|
||||
// }
|
||||
// var z : i32;
|
||||
//
|
||||
@@ -669,7 +670,8 @@ TEST_F(ResolverValidationTest,
|
||||
// }
|
||||
// }
|
||||
|
||||
auto* inner_loop = Loop(Block(create<ast::ContinueStatement>()));
|
||||
auto* inner_loop = Loop(Block(If(true, Block(Continue())), //
|
||||
Break()));
|
||||
auto* body =
|
||||
Block(inner_loop, Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
|
||||
auto* continuing = Block(If(Expr(true), Block(Assign("z", 2))));
|
||||
@@ -683,7 +685,8 @@ TEST_F(ResolverValidationTest,
|
||||
Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingLoop) {
|
||||
// loop {
|
||||
// loop {
|
||||
// continue; // OK: not part of the outer loop
|
||||
// if (true) { continue; } // OK: not part of the outer loop
|
||||
// break;
|
||||
// }
|
||||
// var z : i32;
|
||||
//
|
||||
@@ -694,7 +697,8 @@ TEST_F(ResolverValidationTest,
|
||||
// }
|
||||
// }
|
||||
|
||||
auto* inner_loop = Loop(Block(create<ast::ContinueStatement>()));
|
||||
auto* inner_loop = Loop(Block(If(true, Block(Continue())), //
|
||||
Break()));
|
||||
auto* body =
|
||||
Block(inner_loop, Decl(Var("z", ty.i32(), ast::StorageClass::kNone)));
|
||||
auto* continuing = Block(Loop(Block(Assign("z", 2))));
|
||||
@@ -707,7 +711,7 @@ TEST_F(ResolverValidationTest,
|
||||
TEST_F(ResolverTest, Stmt_Loop_ContinueInLoopBodyAfterDecl_UsageInContinuing) {
|
||||
// loop {
|
||||
// var z : i32;
|
||||
// continue;
|
||||
// if (true) { continue; }
|
||||
//
|
||||
// continuing {
|
||||
// z = 2;
|
||||
@@ -716,7 +720,7 @@ TEST_F(ResolverTest, Stmt_Loop_ContinueInLoopBodyAfterDecl_UsageInContinuing) {
|
||||
|
||||
auto error_loc = Source{{12, 34}};
|
||||
auto* body = Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
|
||||
create<ast::ContinueStatement>());
|
||||
If(true, Block(Continue())));
|
||||
auto* continuing = Block(Assign(Expr(error_loc, "z"), 2));
|
||||
auto* loop_stmt = Loop(body, continuing);
|
||||
WrapInFunction(loop_stmt);
|
||||
@@ -775,7 +779,7 @@ TEST_F(ResolverTest, Stmt_Loop_DiscardInContinuing_Direct) {
|
||||
WrapInFunction(Loop( // loop
|
||||
Block(), // loop block
|
||||
Block( // loop continuing block
|
||||
create<ast::DiscardStatement>(Source{{12, 34}}))));
|
||||
Discard(Source{{12, 34}}))));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
@@ -795,7 +799,7 @@ TEST_F(ResolverTest, Stmt_Loop_DiscardInContinuing_Indirect) {
|
||||
Block(Source{{56, 78}}, // outer loop continuing block
|
||||
Loop( // inner loop
|
||||
Block( // inner loop block
|
||||
create<ast::DiscardStatement>(Source{{12, 34}}))))));
|
||||
Discard(Source{{12, 34}}))))));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
@@ -804,6 +808,32 @@ TEST_F(ResolverTest, Stmt_Loop_DiscardInContinuing_Indirect) {
|
||||
56:78 note: see continuing block here)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, Stmt_Loop_DiscardInContinuing_Indirect_ViaCall) {
|
||||
// fn MayDiscard() { if (true) { discard; } }
|
||||
// fn F() { MayDiscard(); }
|
||||
// loop {
|
||||
// continuing {
|
||||
// loop { F(); }
|
||||
// }
|
||||
// }
|
||||
|
||||
Func("MayDiscard", {}, ty.void_(), {If(true, Block(Discard()))});
|
||||
Func("SomeFunc", {}, ty.void_(), {CallStmt(Call("MayDiscard"))});
|
||||
|
||||
WrapInFunction(Loop( // outer loop
|
||||
Block(), // outer loop block
|
||||
Block(Source{{56, 78}}, // outer loop continuing block
|
||||
Loop( // inner loop
|
||||
Block( // inner loop block
|
||||
CallStmt(Call(Source{{12, 34}}, "SomeFunc")))))));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: cannot call a function that may discard inside a continuing block
|
||||
56:78 note: see continuing block here)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, Stmt_Loop_ContinueInContinuing_Direct) {
|
||||
// loop {
|
||||
// continuing {
|
||||
@@ -814,7 +844,7 @@ TEST_F(ResolverTest, Stmt_Loop_ContinueInContinuing_Direct) {
|
||||
WrapInFunction(Loop( // loop
|
||||
Block(), // loop block
|
||||
Block(Source{{56, 78}}, // loop continuing block
|
||||
create<ast::ContinueStatement>(Source{{12, 34}}))));
|
||||
Continue(Source{{12, 34}}))));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
@@ -836,7 +866,118 @@ TEST_F(ResolverTest, Stmt_Loop_ContinueInContinuing_Indirect) {
|
||||
Block( // outer loop continuing block
|
||||
Loop( // inner loop
|
||||
Block( // inner loop block
|
||||
create<ast::ContinueStatement>(Source{{12, 34}}))))));
|
||||
Continue(Source{{12, 34}}))))));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, Stmt_ForLoop_ReturnInContinuing_Direct) {
|
||||
// for(;; return) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
WrapInFunction(For(nullptr, nullptr, Return(Source{{12, 34}}), //
|
||||
Block(Break())));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: continuing blocks must not contain a return statement)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, Stmt_ForLoop_ReturnInContinuing_Indirect) {
|
||||
// for(;; loop { return }) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
WrapInFunction(For(nullptr, nullptr,
|
||||
Loop(Source{{56, 78}}, //
|
||||
Block(Return(Source{{12, 34}}))), //
|
||||
Block(Break())));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: continuing blocks must not contain a return statement
|
||||
56:78 note: see continuing block here)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, Stmt_ForLoop_DiscardInContinuing_Direct) {
|
||||
// for(;; discard) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
WrapInFunction(For(nullptr, nullptr, Discard(Source{{12, 34}}), //
|
||||
Block(Break())));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: continuing blocks must not contain a discard statement)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, Stmt_ForLoop_DiscardInContinuing_Indirect) {
|
||||
// for(;; loop { discard }) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
WrapInFunction(For(nullptr, nullptr,
|
||||
Loop(Source{{56, 78}}, //
|
||||
Block(Discard(Source{{12, 34}}))), //
|
||||
Block(Break())));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: continuing blocks must not contain a discard statement
|
||||
56:78 note: see continuing block here)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, Stmt_ForLoop_DiscardInContinuing_Indirect_ViaCall) {
|
||||
// fn MayDiscard() { if (true) { discard; } }
|
||||
// fn F() { MayDiscard(); }
|
||||
// for(;; loop { F() }) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
Func("MayDiscard", {}, ty.void_(), {If(true, Block(Discard()))});
|
||||
Func("F", {}, ty.void_(), {CallStmt(Call("MayDiscard"))});
|
||||
|
||||
WrapInFunction(For(nullptr, nullptr,
|
||||
Loop(Source{{56, 78}}, //
|
||||
Block(CallStmt(Call(Source{{12, 34}}, "F")))), //
|
||||
Block(Break())));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: cannot call a function that may discard inside a continuing block
|
||||
56:78 note: see continuing block here)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, Stmt_ForLoop_ContinueInContinuing_Direct) {
|
||||
// for(;; continue) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
WrapInFunction(For(nullptr, nullptr, Continue(Source{{12, 34}}), //
|
||||
Block(Break())));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"12:34 error: continuing blocks must not contain a continue statement");
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, Stmt_ForLoop_ContinueInContinuing_Indirect) {
|
||||
// for(;; loop { continue }) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
WrapInFunction(For(nullptr, nullptr,
|
||||
Loop( //
|
||||
Block(Continue(Source{{12, 34}}))), //
|
||||
Block(Break())));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
@@ -863,12 +1004,12 @@ TEST_F(ResolverTest, Stmt_ForLoop_CondIsNotBool) {
|
||||
}
|
||||
|
||||
TEST_F(ResolverValidationTest, Stmt_ContinueInLoop) {
|
||||
WrapInFunction(Loop(Block(create<ast::ContinueStatement>(Source{{12, 34}}))));
|
||||
WrapInFunction(Loop(Block(Continue(Source{{12, 34}}))));
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverValidationTest, Stmt_ContinueNotInLoop) {
|
||||
WrapInFunction(create<ast::ContinueStatement>(Source{{12, 34}}));
|
||||
WrapInFunction(Continue(Source{{12, 34}}));
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: continue statement must be in a loop");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user