resolver: Resolve for-loops Still nothing creates these, yet. Bug: tint:952 Change-Id: If35f9c68ede6c6e41b6e9510f0b60751fea5bd49 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56762 Kokoro: Kokoro <noreply+kokoro@google.com> Auto-Submit: Ben Clayton <bclayton@google.com> Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/resolver/block_test.cc b/src/resolver/block_test.cc index 3701330..ab213f6 100644 --- a/src/resolver/block_test.cc +++ b/src/resolver/block_test.cc
@@ -59,7 +59,6 @@ auto* s = Sem().Get(stmt); ASSERT_NE(s, nullptr); ASSERT_NE(s->Block(), nullptr); - ASSERT_NE(s->Block()->Parent(), nullptr); EXPECT_EQ(s->Block(), s->Block()->FindFirstParent<sem::BlockStatement>()); EXPECT_EQ(s->Block()->Parent(), s->Block()->FindFirstParent<sem::FunctionBlockStatement>()); @@ -69,8 +68,98 @@ EXPECT_EQ(s->Block()->Parent()->Parent(), nullptr); } -// TODO(bclayton): Add tests for other block types (LoopBlockStatement, -// LoopContinuingBlockStatement, SwitchCaseBlockStatement) +TEST_F(ResolverBlockTest, LoopBlock) { + // fn F() { + // loop { + // var x : 32; + // } + // } + auto* stmt = Decl(Var("x", ty.i32())); + auto* loop = Loop(Block(stmt)); + auto* f = Func("F", {}, ty.void_(), {loop}); + + ASSERT_TRUE(r()->Resolve()) << r()->error(); + + auto* s = Sem().Get(stmt); + ASSERT_NE(s, nullptr); + ASSERT_NE(s->Block(), nullptr); + EXPECT_EQ(s->Block(), s->Block()->FindFirstParent<sem::LoopBlockStatement>()); + ASSERT_TRUE(Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent())); + EXPECT_EQ(s->Block()->Parent()->Parent(), + s->Block()->FindFirstParent<sem::FunctionBlockStatement>()); + EXPECT_EQ(s->Block() + ->Parent() + ->Parent() + ->As<sem::FunctionBlockStatement>() + ->Function(), + f); + EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr); +} + +TEST_F(ResolverBlockTest, ForLoopBlock) { + // fn F() { + // for (var i : u32; true; i = i + 1u) { + // return; + // } + // } + auto* init = Decl(Var("i", ty.u32())); + auto* cond = Expr(true); + auto* cont = Assign("i", Add("i", 1u)); + auto* stmt = Return(); + auto* body = Block(stmt); + auto* for_ = For(init, cond, cont, body); + auto* f = Func("F", {}, ty.void_(), {for_}); + + ASSERT_TRUE(r()->Resolve()) << r()->error(); + + { + auto* s = Sem().Get(init); + ASSERT_NE(s, nullptr); + ASSERT_NE(s->Block(), nullptr); + EXPECT_EQ(s->Block(), + s->Block()->FindFirstParent<sem::LoopBlockStatement>()); + ASSERT_TRUE( + Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent())); + } + { // Condition expression's statement is the for-loop itself + auto* s = Sem().Get(cond); + ASSERT_NE(s, nullptr); + ASSERT_NE(s->Stmt()->Block(), nullptr); + EXPECT_EQ( + s->Stmt()->Block(), + s->Stmt()->Block()->FindFirstParent<sem::FunctionBlockStatement>()); + ASSERT_TRUE(Is<sem::FunctionBlockStatement>(s->Stmt()->Block())); + } + { + auto* s = Sem().Get(cont); + ASSERT_NE(s, nullptr); + ASSERT_NE(s->Block(), nullptr); + EXPECT_EQ(s->Block(), + s->Block()->FindFirstParent<sem::LoopBlockStatement>()); + ASSERT_TRUE( + Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent())); + } + { + auto* s = Sem().Get(stmt); + ASSERT_NE(s, nullptr); + ASSERT_NE(s->Block(), nullptr); + EXPECT_EQ(s->Block(), + s->Block()->FindFirstParent<sem::LoopBlockStatement>()); + ASSERT_TRUE( + Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent())); + EXPECT_EQ(s->Block()->Parent()->Parent(), + s->Block()->FindFirstParent<sem::FunctionBlockStatement>()); + EXPECT_EQ(s->Block() + ->Parent() + ->Parent() + ->As<sem::FunctionBlockStatement>() + ->Function(), + f); + EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr); + } +} +// TODO(bclayton): Add tests for other block types +// (LoopContinuingBlockStatement, SwitchCaseBlockStatement) } // namespace } // namespace resolver
diff --git a/src/resolver/control_block_validation_test.cc b/src/resolver/control_block_validation_test.cc index b53bc0d..bc8fc39 100644 --- a/src/resolver/control_block_validation_test.cc +++ b/src/resolver/control_block_validation_test.cc
@@ -109,7 +109,7 @@ "12:34 error: switch statement must have exactly one default clause"); } -TEST_F(ResolverControlBlockValidationTest, UnreachableCode_continue) { +TEST_F(ResolverControlBlockValidationTest, UnreachableCode_Loop_continue) { // loop { // continue; // var z : i32; @@ -122,6 +122,20 @@ EXPECT_EQ(r()->error(), "12:34 error: code is unreachable"); } +TEST_F(ResolverControlBlockValidationTest, UnreachableCode_ForLoop_continue) { + // for (;;;) { + // continue; + // var z : i32; + // } + WrapInFunction( + For(nullptr, nullptr, nullptr, + Block(create<ast::ContinueStatement>(), + Decl(Source{{12, 34}}, + Var("z", ty.i32(), ast::StorageClass::kNone))))); + + EXPECT_FALSE(r()->Resolve()) << r()->error(); + EXPECT_EQ(r()->error(), "12:34 error: code is unreachable"); +} TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break) { // switch (a) { // case 1: { break; var a : u32 = 2;}
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc index 8bd7aa0..307f807 100644 --- a/src/resolver/resolver.cc +++ b/src/resolver/resolver.cc
@@ -28,6 +28,7 @@ #include "src/ast/disable_validation_decoration.h" #include "src/ast/discard_statement.h" #include "src/ast/fallthrough_statement.h" +#include "src/ast/for_loop_statement.h" #include "src/ast/if_statement.h" #include "src/ast/internal_decoration.h" #include "src/ast/interpolate_decoration.h" @@ -1701,6 +1702,9 @@ if (auto* l = stmt->As<ast::LoopStatement>()) { return LoopStatement(l); } + if (auto* l = stmt->As<ast::ForLoopStatement>()) { + return ForLoopStatement(l); + } if (auto* r = stmt->As<ast::ReturnStatement>()) { return Return(r); } @@ -1824,6 +1828,45 @@ }); } +bool Resolver::ForLoopStatement(ast::ForLoopStatement* stmt) { + Mark(stmt->body()); + + auto* sem_block_body = builder_->create<sem::LoopBlockStatement>( + stmt->body(), current_statement_); + builder_->Sem().Add(stmt->body(), sem_block_body); + TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block_body); + + if (auto* initializer = stmt->initializer()) { + Mark(initializer); + if (!Statement(initializer)) { + return false; + } + } + + if (auto* condition = stmt->condition()) { + Mark(condition); + if (!Expression(condition)) { + return false; + } + + if (!TypeOf(condition)->Is<sem::Bool>()) { + AddError("for-loop condition must be bool, got " + TypeNameOf(condition), + condition->source()); + return false; + } + } + + if (auto* continuing = stmt->continuing()) { + Mark(continuing); + if (!Statement(continuing)) { + return false; + } + } + + return BlockScope(stmt->body(), + [&] { return Statements(stmt->body()->list()); }); +} + bool Resolver::Expressions(const ast::ExpressionList& list) { for (auto* expr : list) { Mark(expr);
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h index f1d663d..cf26a39 100644 --- a/src/resolver/resolver.h +++ b/src/resolver/resolver.h
@@ -38,9 +38,10 @@ class BinaryExpression; class BitcastExpression; class CallExpression; -class CaseStatement; class CallStatement; +class CaseStatement; class ConstructorExpression; +class ForLoopStatement; class Function; class IdentifierExpression; class LoopStatement; @@ -244,12 +245,13 @@ bool Constructor(ast::ConstructorExpression*); bool Expression(ast::Expression*); bool Expressions(const ast::ExpressionList&); + bool ForLoopStatement(ast::ForLoopStatement*); bool Function(ast::Function*); + bool FunctionCall(const ast::CallExpression* call); bool GlobalVariable(ast::Variable* var); bool Identifier(ast::IdentifierExpression*); bool IfStatement(ast::IfStatement*); bool IntrinsicCall(ast::CallExpression*, sem::IntrinsicType); - bool FunctionCall(const ast::CallExpression* call); bool LoopStatement(ast::LoopStatement*); bool MemberAccessor(ast::MemberAccessorExpression*); bool Parameter(ast::Variable* param);
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc index 33d4693..c688ad7 100644 --- a/src/resolver/validation_test.cc +++ b/src/resolver/validation_test.cc
@@ -652,6 +652,17 @@ EXPECT_TRUE(r()->Resolve()); } +TEST_F(ResolverTest, Stmt_ForLoop_CondIsNotBool) { + // for (; 1.0f; ) { + // } + + WrapInFunction(For(nullptr, Expr(Source{{12, 34}}, 1.0f), nullptr, Block())); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), + "12:34 error: for-loop condition must be bool, got f32"); +} + TEST_F(ResolverValidationTest, Stmt_ContinueInLoop) { WrapInFunction(Loop(Block(create<ast::ContinueStatement>(Source{{12, 34}})))); EXPECT_TRUE(r()->Resolve()) << r()->error();