ast: Add ForLoopStatement node

Currently entirely unused.

Bug: tint:952
Change-Id: I3ba2823024b81e927d8fe8a80fac4f82c02eac3e
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56761
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 5b1a3d5..784edba 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -354,6 +354,8 @@
     "ast/fallthrough_statement.h",
     "ast/float_literal.cc",
     "ast/float_literal.h",
+    "ast/for_loop_statement.cc",
+    "ast/for_loop_statement.h",
     "ast/function.cc",
     "ast/function.h",
     "ast/group_decoration.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 914e198..51dc420 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -98,6 +98,8 @@
   ast/fallthrough_statement.h
   ast/float_literal.cc
   ast/float_literal.h
+  ast/for_loop_statement.cc
+  ast/for_loop_statement.h
   ast/function.cc
   ast/function.h
   ast/group_decoration.cc
@@ -550,6 +552,7 @@
     ast/f32_test.cc
     ast/fallthrough_statement_test.cc
     ast/float_literal_test.cc
+    ast/for_loop_statement_test.cc
     ast/function_test.cc
     ast/group_decoration_test.cc
     ast/i32_test.cc
diff --git a/src/ast/for_loop_statement.cc b/src/ast/for_loop_statement.cc
new file mode 100644
index 0000000..dc77b80
--- /dev/null
+++ b/src/ast/for_loop_statement.cc
@@ -0,0 +1,92 @@
+// 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/ast/for_loop_statement.h"
+
+#include "src/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::ForLoopStatement);
+
+namespace tint {
+namespace ast {
+
+ForLoopStatement::ForLoopStatement(ProgramID program_id,
+                                   const Source& source,
+                                   Statement* initializer,
+                                   Expression* condition,
+                                   Statement* continuing,
+                                   BlockStatement* body)
+    : Base(program_id, source),
+      initializer_(initializer),
+      condition_(condition),
+      continuing_(continuing),
+      body_(body) {
+  TINT_ASSERT(AST, body_);
+
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, initializer_, program_id);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition_, program_id);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, continuing_, program_id);
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body_, program_id);
+}
+
+ForLoopStatement::ForLoopStatement(ForLoopStatement&&) = default;
+
+ForLoopStatement::~ForLoopStatement() = default;
+
+ForLoopStatement* ForLoopStatement::Clone(CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source());
+
+  auto* init = ctx->Clone(initializer_);
+  auto* cond = ctx->Clone(condition_);
+  auto* cont = ctx->Clone(continuing_);
+  auto* b = ctx->Clone(body_);
+  return ctx->dst->create<ForLoopStatement>(src, init, cond, cont, b);
+}
+
+void ForLoopStatement::to_str(const sem::Info& sem,
+                              std::ostream& out,
+                              size_t indent) const {
+  make_indent(out, indent);
+  out << "ForLoop {" << std::endl;
+
+  if (initializer_) {
+    make_indent(out, indent + 2);
+    out << "initializer:" << std::endl;
+    initializer_->to_str(sem, out, indent + 4);
+  }
+
+  if (condition_) {
+    make_indent(out, indent + 2);
+    out << "condition:" << std::endl;
+    condition_->to_str(sem, out, indent + 4);
+  }
+
+  if (continuing_) {
+    make_indent(out, indent + 2);
+    out << "continuing:" << std::endl;
+    continuing_->to_str(sem, out, indent + 4);
+  }
+
+  make_indent(out, indent + 2);
+  out << "body:" << std::endl;
+  for (auto* stmt : *body_) {
+    stmt->to_str(sem, out, indent + 4);
+  }
+
+  out << "}" << std::endl;
+}
+
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/for_loop_statement.h b/src/ast/for_loop_statement.h
new file mode 100644
index 0000000..124a17f
--- /dev/null
+++ b/src/ast/for_loop_statement.h
@@ -0,0 +1,91 @@
+// 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.
+
+#ifndef SRC_AST_FOR_LOOP_STATEMENT_H_
+#define SRC_AST_FOR_LOOP_STATEMENT_H_
+
+#include "src/ast/block_statement.h"
+
+namespace tint {
+namespace ast {
+
+class Expression;
+
+/// A for loop statement
+class ForLoopStatement : public Castable<ForLoopStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param program_id the identifier of the program that owns this node
+  /// @param source the for loop statement source
+  /// @param initializer the optional loop initializer statement
+  /// @param condition the optional loop condition expression
+  /// @param continuing the optional continuing statement
+  /// @param body the loop body
+  ForLoopStatement(ProgramID program_id,
+                   const Source& source,
+                   Statement* initializer,
+                   Expression* condition,
+                   Statement* continuing,
+                   BlockStatement* body);
+  /// Move constructor
+  ForLoopStatement(ForLoopStatement&&);
+  ~ForLoopStatement() override;
+
+  /// @returns the initializer statement
+  const Statement* initializer() const { return initializer_; }
+  /// @returns the initializer statement
+  Statement* initializer() { return initializer_; }
+
+  /// @returns the condition expression
+  const Expression* condition() const { return condition_; }
+  /// @returns the condition expression
+  Expression* condition() { return condition_; }
+
+  /// @returns the continuing statement
+  const Statement* continuing() const { return continuing_; }
+  /// @returns the continuing statement
+  Statement* continuing() { return continuing_; }
+
+  /// @returns the loop body block
+  const BlockStatement* body() const { return body_; }
+  /// @returns the loop body block
+  BlockStatement* body() { return body_; }
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  ForLoopStatement* Clone(CloneContext* ctx) const override;
+
+  /// Writes a representation of the node to the output stream
+  /// @param sem the semantic info for the program
+  /// @param out the stream to write to
+  /// @param indent number of spaces to indent the node when writing
+  void to_str(const sem::Info& sem,
+              std::ostream& out,
+              size_t indent) const override;
+
+ private:
+  ForLoopStatement(const ForLoopStatement&) = delete;
+
+  Statement* const initializer_;
+  Expression* const condition_;
+  Statement* const continuing_;
+  BlockStatement* const body_;
+};
+
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_FOR_LOOP_STATEMENT_H_
diff --git a/src/ast/for_loop_statement_test.cc b/src/ast/for_loop_statement_test.cc
new file mode 100644
index 0000000..1542cd0
--- /dev/null
+++ b/src/ast/for_loop_statement_test.cc
@@ -0,0 +1,175 @@
+// 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 "gtest/gtest-spi.h"
+#include "src/ast/binary_expression.h"
+#include "src/ast/test_helper.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using ForLoopStatementTest = TestHelper;
+
+TEST_F(ForLoopStatementTest, Creation) {
+  auto* init = Decl(Var("i", ty.u32()));
+  auto* cond =
+      create<BinaryExpression>(BinaryOp::kLessThan, Expr("i"), Expr(5u));
+  auto* cont = Assign("i", Add("i", 1));
+  auto* body = Block(Return());
+  auto* l = For(init, cond, cont, body);
+
+  EXPECT_EQ(l->initializer(), init);
+  EXPECT_EQ(l->condition(), cond);
+  EXPECT_EQ(l->continuing(), cont);
+  EXPECT_EQ(l->body(), body);
+}
+
+TEST_F(ForLoopStatementTest, Creation_WithSource) {
+  auto* body = Block(Return());
+  auto* l = For(Source{{20u, 2u}}, nullptr, nullptr, nullptr, body);
+  auto src = l->source();
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(ForLoopStatementTest, Creation_Null_InitCondCont) {
+  auto* body = Block(Return());
+  auto* l = For(nullptr, nullptr, nullptr, body);
+  EXPECT_EQ(l->body(), body);
+}
+
+TEST_F(ForLoopStatementTest, Assert_Null_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b;
+        b.For(nullptr, nullptr, nullptr, nullptr);
+      },
+      "internal compiler error");
+}
+
+TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Initializer) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.For(b2.Block(), nullptr, nullptr, b1.Block());
+      },
+      "internal compiler error");
+}
+
+TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Condition) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.For(nullptr, b2.Expr(true), nullptr, b1.Block());
+      },
+      "internal compiler error");
+}
+
+TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Continuing) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.For(nullptr, nullptr, b2.Block(), b1.Block());
+      },
+      "internal compiler error");
+}
+
+TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Body) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.For(nullptr, nullptr, nullptr, b2.Block());
+      },
+      "internal compiler error");
+}
+
+TEST_F(ForLoopStatementTest, ToStr) {
+  auto* body = Block(Return());
+  auto* l = For(nullptr, nullptr, nullptr, body);
+
+  EXPECT_EQ(str(l), R"(ForLoop {
+  body:
+    Return{}
+}
+)");
+}
+
+TEST_F(ForLoopStatementTest, ToStr_With_Init) {
+  auto* body = Block(Return());
+  auto* l = For(Block(), nullptr, nullptr, body);
+
+  EXPECT_EQ(str(l), R"(ForLoop {
+  initializer:
+    Block{
+    }
+  body:
+    Return{}
+}
+)");
+}
+
+TEST_F(ForLoopStatementTest, ToStr_With_Cond) {
+  auto* body = Block(Return());
+  auto* l = For(nullptr, Expr(true), nullptr, body);
+
+  EXPECT_EQ(str(l), R"(ForLoop {
+  condition:
+    ScalarConstructor[not set]{true}
+  body:
+    Return{}
+}
+)");
+}
+
+TEST_F(ForLoopStatementTest, ToStr_With_Cont) {
+  auto* body = Block(Return());
+  auto* l = For(nullptr, nullptr, Block(), body);
+
+  EXPECT_EQ(str(l), R"(ForLoop {
+  continuing:
+    Block{
+    }
+  body:
+    Return{}
+}
+)");
+}
+
+TEST_F(ForLoopStatementTest, ToStr_With_All) {
+  auto* body = Block(Return());
+  auto* l = For(Block(), Expr(true), Block(), body);
+
+  EXPECT_EQ(str(l), R"(ForLoop {
+  initializer:
+    Block{
+    }
+  condition:
+    ScalarConstructor[not set]{true}
+  continuing:
+    Block{
+    }
+  body:
+    Return{}
+}
+)");
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/program_builder.h b/src/program_builder.h
index b5bd076..dcf311c 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -35,6 +35,7 @@
 #include "src/ast/external_texture.h"
 #include "src/ast/f32.h"
 #include "src/ast/float_literal.h"
+#include "src/ast/for_loop_statement.h"
 #include "src/ast/i32.h"
 #include "src/ast/if_statement.h"
 #include "src/ast/interpolate_decoration.h"
@@ -1920,6 +1921,40 @@
     return create<ast::LoopStatement>(body, continuing);
   }
 
+  /// Creates a ast::ForLoopStatement with input body and optional initializer,
+  /// condition and continuing.
+  /// @param source the source information
+  /// @param init the optional loop initializer
+  /// @param cond the optional loop condition
+  /// @param cont the optional loop continuing
+  /// @param body the loop body
+  /// @returns the for loop statement pointer
+  template <typename COND>
+  ast::ForLoopStatement* For(const Source& source,
+                             ast::Statement* init,
+                             COND&& cond,
+                             ast::Statement* cont,
+                             ast::BlockStatement* body) {
+    return create<ast::ForLoopStatement>(
+        source, init, Expr(std::forward<COND>(cond)), cont, body);
+  }
+
+  /// Creates a ast::ForLoopStatement with input body and optional initializer,
+  /// condition and continuing.
+  /// @param init the optional loop initializer
+  /// @param cond the optional loop condition
+  /// @param cont the optional loop continuing
+  /// @param body the loop body
+  /// @returns the for loop statement pointer
+  template <typename COND>
+  ast::ForLoopStatement* For(ast::Statement* init,
+                             COND&& cond,
+                             ast::Statement* cont,
+                             ast::BlockStatement* body) {
+    return create<ast::ForLoopStatement>(init, Expr(std::forward<COND>(cond)),
+                                         cont, body);
+  }
+
   /// Creates a ast::VariableDeclStatement for the input variable
   /// @param source the source information
   /// @param var the variable to wrap in a decl statement
diff --git a/test/BUILD.gn b/test/BUILD.gn
index dd4bcfc..14ce32f 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -169,6 +169,7 @@
     "../src/ast/f32_test.cc",
     "../src/ast/fallthrough_statement_test.cc",
     "../src/ast/float_literal_test.cc",
+    "../src/ast/for_loop_statement_test.cc",
     "../src/ast/function_test.cc",
     "../src/ast/group_decoration_test.cc",
     "../src/ast/i32_test.cc",