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();