utils: Add TINT_SCOPED_ASSIGNMENT()

A helper class and macro used to simplify scope-based assignment of
variables.

Change-Id: I02b3a05240a2c4628f813de931c40d8fba3cb07b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/51480
Auto-Submit: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 8c0a9fe..4d415b8 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -527,6 +527,7 @@
     "utils/get_or_create.h",
     "utils/hash.h",
     "utils/math.h",
+    "utils/scoped_assignment.h",
     "utils/unique_vector.h",
     "writer/append_vector.cc",
     "writer/append_vector.h",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9927bdd..2b4254d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -326,6 +326,7 @@
   utils/get_or_create.h
   utils/hash.h
   utils/math.h
+  utils/scoped_assignment.h
   utils/unique_vector.h
   writer/append_vector.cc
   writer/append_vector.h
@@ -595,6 +596,7 @@
     utils/get_or_create_test.cc
     utils/hash_test.cc
     utils/math_test.cc
+    utils/scoped_assignment_test.cc
     utils/tmpfile_${TINT_OS_CC_SUFFIX}.cc
     utils/tmpfile_test.cc
     utils/tmpfile.h
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 227331b..2450f14 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -62,6 +62,7 @@
 #include "src/sem/variable.h"
 #include "src/utils/get_or_create.h"
 #include "src/utils/math.h"
+#include "src/utils/scoped_assignment.h"
 
 namespace tint {
 namespace resolver {
@@ -69,23 +70,6 @@
 
 using IntrinsicType = tint::sem::IntrinsicType;
 
-// Helper class that temporarily assigns a value to a reference for the scope of
-// the object. Once the ScopedAssignment is destructed, the original value is
-// restored.
-template <typename T>
-class ScopedAssignment {
- public:
-  ScopedAssignment(T& ref, T val) : ref_(ref) {
-    old_value_ = ref;
-    ref = val;
-  }
-  ~ScopedAssignment() { ref_ = old_value_; }
-
- private:
-  T& ref_;
-  T old_value_;
-};
-
 bool IsValidStorageTextureDimension(ast::TextureDimension dim) {
   switch (dim) {
     case ast::TextureDimension::k1d:
@@ -324,8 +308,7 @@
       return Type(t->type());
     }
     if (auto* t = ty->As<ast::AccessControl>()) {
-      ScopedAssignment<const ast::AccessControl*> sa(curent_access_control_, t);
-
+      TINT_SCOPED_ASSIGNMENT(curent_access_control_, t);
       if (auto* el = Type(t->type())) {
         return el;
       }
@@ -1182,7 +1165,7 @@
 bool Resolver::Function(ast::Function* func) {
   auto* info = function_infos_.Create<FunctionInfo>(func);
 
-  ScopedAssignment<FunctionInfo*> sa(current_function_, info);
+  TINT_SCOPED_ASSIGNMENT(current_function_, info);
 
   variable_stack_.push_scope();
   for (auto* param : func->params()) {
@@ -1271,8 +1254,7 @@
     sem::BlockStatement* sem_block = builder_->create<sem::BlockStatement>(
         func->body(), nullptr, sem::BlockStatement::Type::kGeneric);
     builder_->Sem().Add(func->body(), sem_block);
-    ScopedAssignment<sem::Statement*> sa_function_body(current_statement_,
-                                                       sem_block);
+    TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block);
     if (!BlockScope(func->body(),
                     [&] { return Statements(func->body()->list()); })) {
       return false;
@@ -1404,7 +1386,7 @@
   }
   builder_->Sem().Add(stmt, sem_statement);
 
-  ScopedAssignment<sem::Statement*> sa(current_statement_, sem_statement);
+  TINT_SCOPED_ASSIGNMENT(current_statement_, sem_statement);
 
   if (stmt->Is<ast::ElseStatement>()) {
     TINT_ICE(diagnostics_)
@@ -1489,7 +1471,7 @@
   sem::BlockStatement* sem_block = builder_->create<sem::BlockStatement>(
       stmt->body(), current_statement_, sem::BlockStatement::Type::kSwitchCase);
   builder_->Sem().Add(stmt->body(), sem_block);
-  ScopedAssignment<sem::Statement*> sa(current_statement_, sem_block);
+  TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block);
   return BlockScope(stmt->body(),
                     [&] { return Statements(stmt->body()->list()); });
 }
@@ -1513,7 +1495,7 @@
     sem::BlockStatement* sem_block = builder_->create<sem::BlockStatement>(
         stmt->body(), current_statement_, sem::BlockStatement::Type::kGeneric);
     builder_->Sem().Add(stmt->body(), sem_block);
-    ScopedAssignment<sem::Statement*> sa(current_statement_, sem_block);
+    TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block);
     if (!BlockScope(stmt->body(),
                     [&] { return Statements(stmt->body()->list()); })) {
       return false;
@@ -1525,7 +1507,7 @@
     auto* sem_else_stmt =
         builder_->create<sem::Statement>(else_stmt, current_statement_);
     builder_->Sem().Add(else_stmt, sem_else_stmt);
-    ScopedAssignment<sem::Statement*> sa(current_statement_, sem_else_stmt);
+    TINT_SCOPED_ASSIGNMENT(current_statement_, sem_else_stmt);
     if (auto* cond = else_stmt->condition()) {
       Mark(cond);
       if (!Expression(cond)) {
@@ -1547,8 +1529,7 @@
           else_stmt->body(), current_statement_,
           sem::BlockStatement::Type::kGeneric);
       builder_->Sem().Add(else_stmt->body(), sem_block);
-      ScopedAssignment<sem::Statement*> sa_else_body(current_statement_,
-                                                     sem_block);
+      TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block);
       if (!BlockScope(else_stmt->body(),
                       [&] { return Statements(else_stmt->body()->list()); })) {
         return false;
@@ -1568,7 +1549,7 @@
   auto* sem_block_body = builder_->create<sem::BlockStatement>(
       stmt->body(), current_statement_, sem::BlockStatement::Type::kLoop);
   builder_->Sem().Add(stmt->body(), sem_block_body);
-  ScopedAssignment<sem::Statement*> body_sa(current_statement_, sem_block_body);
+  TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block_body);
   return BlockScope(stmt->body(), [&] {
     if (!Statements(stmt->body()->list())) {
       return false;
@@ -1581,8 +1562,7 @@
           stmt->continuing(), current_statement_,
           sem::BlockStatement::Type::kLoopContinuing);
       builder_->Sem().Add(stmt->continuing(), sem_block_continuing);
-      ScopedAssignment<sem::Statement*> continuing_sa(current_statement_,
-                                                      sem_block_continuing);
+      TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block_continuing);
       if (!BlockScope(stmt->continuing(),
                       [&] { return Statements(stmt->continuing()->list()); })) {
         return false;
@@ -3063,7 +3043,7 @@
     sem::Statement* sem_statement =
         builder_->create<sem::Statement>(case_stmt, current_statement_);
     builder_->Sem().Add(case_stmt, sem_statement);
-    ScopedAssignment<sem::Statement*> sa(current_statement_, sem_statement);
+    TINT_SCOPED_ASSIGNMENT(current_statement_, sem_statement);
     if (!CaseStatement(case_stmt)) {
       return false;
     }
@@ -3209,8 +3189,8 @@
                               "which semantic information is not available";
     return false;
   }
-  ScopedAssignment<sem::BlockStatement*> sa(
-      current_block_, const_cast<sem::BlockStatement*>(sem_block));
+  TINT_SCOPED_ASSIGNMENT(current_block_,
+                         const_cast<sem::BlockStatement*>(sem_block));
   variable_stack_.push_scope();
   bool result = callback();
   variable_stack_.pop_scope();
diff --git a/src/utils/scoped_assignment.h b/src/utils/scoped_assignment.h
new file mode 100644
index 0000000..58e7cdd
--- /dev/null
+++ b/src/utils/scoped_assignment.h
@@ -0,0 +1,64 @@
+// 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_UTILS_SCOPED_ASSIGNMENT_H_
+#define SRC_UTILS_SCOPED_ASSIGNMENT_H_
+
+#include <type_traits>
+
+namespace tint {
+namespace utils {
+
+/// Helper class that temporarily assigns a value to a variable for the lifetime
+/// of the ScopedAssignment object. Once the ScopedAssignment is destructed, the
+/// original value is restored.
+template <typename T>
+class ScopedAssignment {
+ public:
+  /// Constructor
+  /// @param var the variable to temporarily assign a new value to
+  /// @param val the value to assign to `ref` for the lifetime of this
+  /// ScopedAssignment.
+  ScopedAssignment(T& var, T val) : ref_(var) {
+    old_value_ = var;
+    var = val;
+  }
+
+  /// Destructor
+  /// Restores the original value of the variable.
+  ~ScopedAssignment() { ref_ = old_value_; }
+
+ private:
+  ScopedAssignment(const ScopedAssignment&) = delete;
+  ScopedAssignment& operator=(const ScopedAssignment&) = delete;
+
+  T& ref_;
+  T old_value_;
+};
+
+}  // namespace utils
+}  // namespace tint
+
+#define TINT_CONCAT_2(a, b) a##b
+#define TINT_CONCAT(a, b) TINT_CONCAT_2(a, b)
+
+/// TINT_SCOPED_ASSIGNMENT(var, val) assigns `val` to `var`, and automatically
+/// restores the original value of `var` when exiting the current lexical scope.
+#define TINT_SCOPED_ASSIGNMENT(var, val)                                  \
+  ::tint::utils::ScopedAssignment<std::remove_reference_t<decltype(var)>> \
+  TINT_CONCAT(tint_scoped_assignment_, __COUNTER__) {                     \
+    var, val                                                              \
+  }
+
+#endif  //  SRC_UTILS_SCOPED_ASSIGNMENT_H_
diff --git a/src/utils/scoped_assignment_test.cc b/src/utils/scoped_assignment_test.cc
new file mode 100644
index 0000000..b75bec9
--- /dev/null
+++ b/src/utils/scoped_assignment_test.cc
@@ -0,0 +1,47 @@
+// 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/utils/scoped_assignment.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+TEST(ScopedAssignmentTest, Scopes) {
+  int i = 0;
+  EXPECT_EQ(i, 0);
+  {
+    EXPECT_EQ(i, 0);
+    TINT_SCOPED_ASSIGNMENT(i, 1);
+    EXPECT_EQ(i, 1);
+    {
+      EXPECT_EQ(i, 1);
+      TINT_SCOPED_ASSIGNMENT(i, 2);
+      EXPECT_EQ(i, 2);
+    }
+    {
+      EXPECT_EQ(i, 1);
+      TINT_SCOPED_ASSIGNMENT(i, 3);
+      EXPECT_EQ(i, 3);
+    }
+    EXPECT_EQ(i, 1);
+  }
+  EXPECT_EQ(i, 0);
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 867e8d7..88723f7 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -309,6 +309,7 @@
     "../src/utils/get_or_create_test.cc",
     "../src/utils/hash_test.cc",
     "../src/utils/math_test.cc",
+    "../src/utils/scoped_assignment_test.cc",
     "../src/utils/tmpfile_test.cc",
     "../src/utils/unique_vector_test.cc",
     "../src/writer/append_vector_test.cc",