ast: Add an IncrementDecrementStatement node

Bug: tint:1488
Change-Id: I9b307a7b53348055af873c137ad6eebbe1dbe5b0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/86001
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 1a4ae17..79beb3d 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -248,6 +248,8 @@
     "ast/identifier_expression.h",
     "ast/if_statement.cc",
     "ast/if_statement.h",
+    "ast/increment_decrement_statement.cc",
+    "ast/increment_decrement_statement.h",
     "ast/index_accessor_expression.cc",
     "ast/index_accessor_expression.h",
     "ast/int_literal_expression.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 138dfde..ae9ef18 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -136,6 +136,8 @@
   ast/identifier_expression.h
   ast/if_statement.cc
   ast/if_statement.h
+  ast/increment_decrement_statement.cc
+  ast/increment_decrement_statement.h
   ast/int_literal_expression.cc
   ast/int_literal_expression.h
   ast/internal_attribute.cc
@@ -681,6 +683,7 @@
     ast/id_attribute_test.cc
     ast/identifier_expression_test.cc
     ast/if_statement_test.cc
+    ast/increment_decrement_statement_test.cc
     ast/index_accessor_expression_test.cc
     ast/int_literal_expression_test.cc
     ast/interpolate_attribute_test.cc
diff --git a/src/tint/ast/increment_decrement_statement.cc b/src/tint/ast/increment_decrement_statement.cc
new file mode 100644
index 0000000..4ec7f87
--- /dev/null
+++ b/src/tint/ast/increment_decrement_statement.cc
@@ -0,0 +1,44 @@
+// Copyright 2022 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/ast/increment_decrement_statement.h"
+
+#include "src/tint/program_builder.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::IncrementDecrementStatement);
+
+namespace tint::ast {
+
+IncrementDecrementStatement::IncrementDecrementStatement(ProgramID pid,
+                                                         const Source& src,
+                                                         const Expression* l,
+                                                         bool inc)
+    : Base(pid, src), lhs(l), increment(inc) {
+  TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, lhs, program_id);
+}
+
+IncrementDecrementStatement::IncrementDecrementStatement(
+    IncrementDecrementStatement&&) = default;
+
+IncrementDecrementStatement::~IncrementDecrementStatement() = default;
+
+const IncrementDecrementStatement* IncrementDecrementStatement::Clone(
+    CloneContext* ctx) const {
+  // Clone arguments outside of create() call to have deterministic ordering
+  auto src = ctx->Clone(source);
+  auto* l = ctx->Clone(lhs);
+  return ctx->dst->create<IncrementDecrementStatement>(src, l, increment);
+}
+
+}  // namespace tint::ast
diff --git a/src/tint/ast/increment_decrement_statement.h b/src/tint/ast/increment_decrement_statement.h
new file mode 100644
index 0000000..f7882da
--- /dev/null
+++ b/src/tint/ast/increment_decrement_statement.h
@@ -0,0 +1,55 @@
+// Copyright 2022 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_TINT_AST_INCREMENT_DECREMENT_STATEMENT_H_
+#define SRC_TINT_AST_INCREMENT_DECREMENT_STATEMENT_H_
+
+#include "src/tint/ast/expression.h"
+#include "src/tint/ast/statement.h"
+
+namespace tint::ast {
+
+/// An increment or decrement statement
+class IncrementDecrementStatement final
+    : public Castable<IncrementDecrementStatement, Statement> {
+ public:
+  /// Constructor
+  /// @param pid the identifier of the program that owns this node
+  /// @param src the source of this node
+  /// @param lhs the LHS expression
+  /// @param inc `true` for increment, `false` for decrement
+  IncrementDecrementStatement(ProgramID pid,
+                              const Source& src,
+                              const Expression* lhs,
+                              bool inc);
+  /// Move constructor
+  IncrementDecrementStatement(IncrementDecrementStatement&&);
+  ~IncrementDecrementStatement() override;
+
+  /// Clones this node and all transitive child nodes using the `CloneContext`
+  /// `ctx`.
+  /// @param ctx the clone context
+  /// @return the newly cloned node
+  const IncrementDecrementStatement* Clone(CloneContext* ctx) const override;
+
+  /// The LHS expression.
+  const Expression* const lhs;
+
+  /// `true` for increment, `false` for decrement.
+  bool increment;
+};
+
+}  // namespace tint::ast
+
+#endif  // SRC_TINT_AST_INCREMENT_DECREMENT_STATEMENT_H_
diff --git a/src/tint/ast/increment_decrement_statement_test.cc b/src/tint/ast/increment_decrement_statement_test.cc
new file mode 100644
index 0000000..ccb5f07
--- /dev/null
+++ b/src/tint/ast/increment_decrement_statement_test.cc
@@ -0,0 +1,68 @@
+// Copyright 2022 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/ast/increment_decrement_statement.h"
+
+#include "gtest/gtest-spi.h"
+#include "src/tint/ast/test_helper.h"
+
+namespace tint::ast {
+namespace {
+
+using IncrementDecrementStatementTest = TestHelper;
+
+TEST_F(IncrementDecrementStatementTest, Creation) {
+  auto* expr = Expr("expr");
+
+  auto* i = create<IncrementDecrementStatement>(expr, true);
+  EXPECT_EQ(i->lhs, expr);
+  EXPECT_TRUE(i->increment);
+}
+
+TEST_F(IncrementDecrementStatementTest, Creation_WithSource) {
+  auto* expr = Expr("expr");
+  auto* i = create<IncrementDecrementStatement>(Source{Source::Location{20, 2}},
+                                                expr, true);
+  auto src = i->source;
+  EXPECT_EQ(i->lhs, expr);
+  EXPECT_TRUE(i->increment);
+  EXPECT_EQ(src.range.begin.line, 20u);
+  EXPECT_EQ(src.range.begin.column, 2u);
+}
+
+TEST_F(IncrementDecrementStatementTest, IsIncrementDecrement) {
+  auto* expr = Expr("expr");
+  auto* i = create<IncrementDecrementStatement>(expr, true);
+  EXPECT_TRUE(i->Is<IncrementDecrementStatement>());
+}
+
+TEST_F(IncrementDecrementStatementTest, Decrement) {
+  auto* expr = Expr("expr");
+  auto* i = create<IncrementDecrementStatement>(expr, false);
+  EXPECT_EQ(i->lhs, expr);
+  EXPECT_FALSE(i->increment);
+}
+
+TEST_F(IncrementDecrementStatementTest, Assert_DifferentProgramID_Expr) {
+  EXPECT_FATAL_FAILURE(
+      {
+        ProgramBuilder b1;
+        ProgramBuilder b2;
+        b1.create<IncrementDecrementStatement>(b2.Expr(true), true);
+      },
+      "internal compiler error");
+}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index eb87d3e..8365ad9 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -46,6 +46,7 @@
 #include "src/tint/ast/i32.h"
 #include "src/tint/ast/id_attribute.h"
 #include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/increment_decrement_statement.h"
 #include "src/tint/ast/index_accessor_expression.h"
 #include "src/tint/ast/interpolate_attribute.h"
 #include "src/tint/ast/invariant_attribute.h"
@@ -2300,6 +2301,46 @@
         Expr(std::forward<RhsExpressionInit>(rhs)), op);
   }
 
+  /// Creates an ast::IncrementDecrementStatement with input lhs.
+  /// @param source the source information
+  /// @param lhs the left hand side expression initializer
+  /// @returns the increment decrement statement pointer
+  template <typename LhsExpressionInit>
+  const ast::IncrementDecrementStatement* Increment(const Source& source,
+                                                    LhsExpressionInit&& lhs) {
+    return create<ast::IncrementDecrementStatement>(
+        source, Expr(std::forward<LhsExpressionInit>(lhs)), true);
+  }
+
+  /// Creates a ast::IncrementDecrementStatement with input lhs.
+  /// @param lhs the left hand side expression initializer
+  /// @returns the increment decrement statement pointer
+  template <typename LhsExpressionInit>
+  const ast::IncrementDecrementStatement* Increment(LhsExpressionInit&& lhs) {
+    return create<ast::IncrementDecrementStatement>(
+        Expr(std::forward<LhsExpressionInit>(lhs)), true);
+  }
+
+  /// Creates an ast::IncrementDecrementStatement with input lhs.
+  /// @param source the source information
+  /// @param lhs the left hand side expression initializer
+  /// @returns the increment decrement statement pointer
+  template <typename LhsExpressionInit>
+  const ast::IncrementDecrementStatement* Decrement(const Source& source,
+                                                    LhsExpressionInit&& lhs) {
+    return create<ast::IncrementDecrementStatement>(
+        source, Expr(std::forward<LhsExpressionInit>(lhs)), false);
+  }
+
+  /// Creates a ast::IncrementDecrementStatement with input lhs.
+  /// @param lhs the left hand side expression initializer
+  /// @returns the increment decrement statement pointer
+  template <typename LhsExpressionInit>
+  const ast::IncrementDecrementStatement* Decrement(LhsExpressionInit&& lhs) {
+    return create<ast::IncrementDecrementStatement>(
+        Expr(std::forward<LhsExpressionInit>(lhs)), false);
+  }
+
   /// Creates a ast::LoopStatement with input body and optional continuing
   /// @param source the source information
   /// @param body the loop body
diff --git a/test/tint/BUILD.gn b/test/tint/BUILD.gn
index c5e8048..7cbdff7 100644
--- a/test/tint/BUILD.gn
+++ b/test/tint/BUILD.gn
@@ -175,6 +175,7 @@
     "../../src/tint/ast/id_attribute_test.cc",
     "../../src/tint/ast/identifier_expression_test.cc",
     "../../src/tint/ast/if_statement_test.cc",
+    "../../src/tint/ast/increment_decrement_statement_test.cc",
     "../../src/tint/ast/index_accessor_expression_test.cc",
     "../../src/tint/ast/int_literal_expression_test.cc",
     "../../src/tint/ast/interpolate_attribute_test.cc",