[transform] Add BufferArrayAccessors transform

This CL adds a buffer array accessor clamping transform to
the available transforms in Tint.

Bug: tint:101
Change-Id: If9d5b0fb2c3adba723ce2185870b0e10981103a6
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/28980
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 707cab3..69cc327 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -373,6 +373,8 @@
     "src/reader/reader.h",
     "src/scope_stack.h",
     "src/source.h",
+    "src/transform/bound_array_accessors_transform.cc",
+    "src/transform/bound_array_accessors_transform.h",
     "src/transform/vertex_pulling_transform.cc",
     "src/transform/vertex_pulling_transform.h",
     "src/type_determiner.cc",
@@ -745,6 +747,7 @@
     "src/ast/variable_test.cc",
     "src/ast/workgroup_decoration_test.cc",
     "src/scope_stack_test.cc",
+    "src/transform/bound_array_accessors_transform_test.cc",
     "src/transform/vertex_pulling_transform_test.cc",
     "src/type_determiner_test.cc",
     "src/type_manager_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 798bc6a..4325e8e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -194,6 +194,8 @@
   reader/reader.h
   scope_stack.h
   source.h
+  transform/bound_array_accessors_transform.cc
+  transform/bound_array_accessors_transform.h
   transform/vertex_pulling_transform.cc
   transform/vertex_pulling_transform.h
   type_determiner.cc
@@ -355,6 +357,7 @@
   ast/variable_test.cc
   ast/workgroup_decoration_test.cc
   scope_stack_test.cc
+  transform/bound_array_accessors_transform_test.cc
   transform/vertex_pulling_transform_test.cc
   type_determiner_test.cc
   type_manager_test.cc
diff --git a/src/ast/array_accessor_expression.h b/src/ast/array_accessor_expression.h
index 5f90fb6..0359161 100644
--- a/src/ast/array_accessor_expression.h
+++ b/src/ast/array_accessor_expression.h
@@ -60,6 +60,9 @@
   }
   /// @returns the index expression
   Expression* idx_expr() const { return idx_expr_.get(); }
+  /// Removes the index expression from the array accessor
+  /// @returns the unique pointer to the index expression
+  std::unique_ptr<Expression> take_idx_expr() { return std::move(idx_expr_); }
 
   /// @returns true if this is an array accessor expression
   bool IsArrayAccessor() const override;
diff --git a/src/ast/case_statement.h b/src/ast/case_statement.h
index 571eeb3..0b3cf7a 100644
--- a/src/ast/case_statement.h
+++ b/src/ast/case_statement.h
@@ -72,6 +72,8 @@
   }
   /// @returns the case body
   const BlockStatement* body() const { return body_.get(); }
+  /// @returns the case body
+  BlockStatement* body() { return body_.get(); }
 
   /// @returns true if this is a case statement
   bool IsCase() const override;
diff --git a/src/ast/else_statement.h b/src/ast/else_statement.h
index fda156b..c79ffb8 100644
--- a/src/ast/else_statement.h
+++ b/src/ast/else_statement.h
@@ -71,6 +71,8 @@
   }
   /// @returns the else body
   const BlockStatement* body() const { return body_.get(); }
+  /// @returns the else body
+  BlockStatement* body() { return body_.get(); }
 
   /// @returns true if this is a else statement
   bool IsElse() const override;
diff --git a/src/ast/function.h b/src/ast/function.h
index 0dd540b..7e21c28 100644
--- a/src/ast/function.h
+++ b/src/ast/function.h
@@ -163,7 +163,9 @@
     body_ = std::move(body);
   }
   /// @returns the function body
-  BlockStatement* body() const { return body_.get(); }
+  const BlockStatement* body() const { return body_.get(); }
+  /// @returns the function body
+  BlockStatement* body() { return body_.get(); }
 
   /// @returns true if the name and type are both present
   bool IsValid() const override;
diff --git a/src/ast/if_statement.h b/src/ast/if_statement.h
index 8108ebf..53af7b7 100644
--- a/src/ast/if_statement.h
+++ b/src/ast/if_statement.h
@@ -62,6 +62,8 @@
   }
   /// @returns the if body
   const BlockStatement* body() const { return body_.get(); }
+  /// @returns the if body
+  BlockStatement* body() { return body_.get(); }
 
   /// Sets the else statements
   /// @param else_statements the else statements to set
@@ -70,6 +72,9 @@
   }
   /// @returns the else statements
   const ElseStatementList& else_statements() const { return else_statements_; }
+  /// @returns the else statements
+  ElseStatementList& else_statements() { return else_statements_; }
+
   /// @returns true if there are else statements
   bool has_else_statements() const { return !else_statements_.empty(); }
 
diff --git a/src/ast/loop_statement.h b/src/ast/loop_statement.h
index bf8f8a5..edb2381 100644
--- a/src/ast/loop_statement.h
+++ b/src/ast/loop_statement.h
@@ -51,6 +51,8 @@
   }
   /// @returns the body statements
   const BlockStatement* body() const { return body_.get(); }
+  /// @returns the body statements
+  BlockStatement* body() { return body_.get(); }
 
   /// Sets the continuing statements
   /// @param continuing the continuing statements
@@ -59,6 +61,8 @@
   }
   /// @returns the continuing statements
   const BlockStatement* continuing() const { return continuing_.get(); }
+  /// @returns the continuing statements
+  BlockStatement* continuing() { return continuing_.get(); }
   /// @returns true if there are continuing statements in the loop
   bool has_continuing() const {
     return continuing_ != nullptr && !continuing_->empty();
diff --git a/src/ast/sint_literal.h b/src/ast/sint_literal.h
index 9d23cf7..3995148 100644
--- a/src/ast/sint_literal.h
+++ b/src/ast/sint_literal.h
@@ -34,6 +34,9 @@
   /// @returns true if this is a signed int literal
   bool IsSint() const override;
 
+  /// Updates the literals value
+  /// @param val the value to set
+  void set_value(int32_t val) { value_ = val; }
   /// @returns the int literal value
   int32_t value() const { return value_; }
 
diff --git a/src/ast/uint_literal.h b/src/ast/uint_literal.h
index 7eb1311..6189ee4 100644
--- a/src/ast/uint_literal.h
+++ b/src/ast/uint_literal.h
@@ -34,6 +34,9 @@
   /// @returns true if this is a uint literal
   bool IsUint() const override;
 
+  /// Updates the literals value
+  /// @param val the value to set
+  void set_value(uint32_t val) { value_ = val; }
   /// @returns the uint literal value
   uint32_t value() const { return value_; }
 
diff --git a/src/transform/bound_array_accessors_transform.cc b/src/transform/bound_array_accessors_transform.cc
new file mode 100644
index 0000000..8ae5bd8
--- /dev/null
+++ b/src/transform/bound_array_accessors_transform.cc
@@ -0,0 +1,258 @@
+// Copyright 2020 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/transform/bound_array_accessors_transform.h"
+
+#include "src/ast/assignment_statement.h"
+#include "src/ast/binary_expression.h"
+#include "src/ast/bitcast_expression.h"
+#include "src/ast/block_statement.h"
+#include "src/ast/call_expression.h"
+#include "src/ast/call_statement.h"
+#include "src/ast/case_statement.h"
+#include "src/ast/else_statement.h"
+#include "src/ast/if_statement.h"
+#include "src/ast/loop_statement.h"
+#include "src/ast/member_accessor_expression.h"
+#include "src/ast/return_statement.h"
+#include "src/ast/scalar_constructor_expression.h"
+#include "src/ast/sint_literal.h"
+#include "src/ast/switch_statement.h"
+#include "src/ast/type/array_type.h"
+#include "src/ast/type/matrix_type.h"
+#include "src/ast/type/u32_type.h"
+#include "src/ast/type/vector_type.h"
+#include "src/ast/type_constructor_expression.h"
+#include "src/ast/uint_literal.h"
+#include "src/ast/unary_op_expression.h"
+#include "src/ast/variable.h"
+#include "src/ast/variable_decl_statement.h"
+#include "src/type_manager.h"
+
+namespace tint {
+namespace transform {
+
+BoundArrayAccessorsTransform::BoundArrayAccessorsTransform(Context* ctx,
+                                                           ast::Module* mod)
+    : ctx_(ctx), mod_(mod) {}
+
+BoundArrayAccessorsTransform::~BoundArrayAccessorsTransform() = default;
+
+bool BoundArrayAccessorsTransform::Run() {
+  // We skip over global variables as the constructor for a global must be a
+  // constant expression. There can't be any array accessors as per the current
+  // grammar.
+
+  for (auto& func : mod_->functions()) {
+    scope_stack_.push_scope();
+    if (!ProcessStatement(func->body())) {
+      return false;
+    }
+    scope_stack_.pop_scope();
+  }
+  return true;
+}
+
+bool BoundArrayAccessorsTransform::ProcessStatement(ast::Statement* stmt) {
+  if (stmt->IsAssign()) {
+    auto* as = stmt->AsAssign();
+    return ProcessExpression(as->lhs()) && ProcessExpression(as->rhs());
+  } else if (stmt->IsBlock()) {
+    for (auto& s : *(stmt->AsBlock())) {
+      if (!ProcessStatement(s.get())) {
+        return false;
+      }
+    }
+  } else if (stmt->IsBreak()) {
+    /* nop */
+  } else if (stmt->IsCall()) {
+    return ProcessExpression(stmt->AsCall()->expr());
+  } else if (stmt->IsCase()) {
+    return ProcessStatement(stmt->AsCase()->body());
+  } else if (stmt->IsContinue()) {
+    /* nop */
+  } else if (stmt->IsDiscard()) {
+    /* nop */
+  } else if (stmt->IsElse()) {
+    auto* e = stmt->AsElse();
+    return ProcessExpression(e->condition()) && ProcessStatement(e->body());
+  } else if (stmt->IsFallthrough()) {
+    /* nop */
+  } else if (stmt->IsIf()) {
+    auto* e = stmt->AsIf();
+    if (!ProcessExpression(e->condition()) || !ProcessStatement(e->body())) {
+      return false;
+    }
+    for (auto& s : e->else_statements()) {
+      if (!ProcessStatement(s.get())) {
+        return false;
+      }
+    }
+  } else if (stmt->IsLoop()) {
+    auto* l = stmt->AsLoop();
+    if (l->has_continuing() && !ProcessStatement(l->continuing())) {
+      return false;
+    }
+    return ProcessStatement(l->body());
+  } else if (stmt->IsReturn()) {
+    if (stmt->AsReturn()->has_value()) {
+      return ProcessExpression(stmt->AsReturn()->value());
+    }
+  } else if (stmt->IsSwitch()) {
+    auto* s = stmt->AsSwitch();
+    if (!ProcessExpression(s->condition())) {
+      return false;
+    }
+
+    for (auto& c : s->body()) {
+      if (!ProcessStatement(c.get())) {
+        return false;
+      }
+    }
+  } else if (stmt->IsVariableDecl()) {
+    auto* v = stmt->AsVariableDecl()->variable();
+    if (v->has_constructor() && !ProcessExpression(v->constructor())) {
+      return false;
+    }
+    scope_stack_.set(v->name(), v);
+  } else {
+    error_ = "unknown statement in bound array accessors transform";
+    return false;
+  }
+  return true;
+}
+
+bool BoundArrayAccessorsTransform::ProcessExpression(ast::Expression* expr) {
+  if (expr->IsArrayAccessor()) {
+    return ProcessArrayAccessor(expr->AsArrayAccessor());
+  } else if (expr->IsBitcast()) {
+    return ProcessExpression(expr->AsBitcast()->expr());
+  } else if (expr->IsCall()) {
+    auto* c = expr->AsCall();
+    if (!ProcessExpression(c->func())) {
+      return false;
+    }
+    for (auto& e : c->params()) {
+      if (!ProcessExpression(e.get())) {
+        return false;
+      }
+    }
+  } else if (expr->IsIdentifier()) {
+    /* nop */
+  } else if (expr->IsConstructor()) {
+    auto* c = expr->AsConstructor();
+    if (c->IsTypeConstructor()) {
+      for (auto& e : c->AsTypeConstructor()->values()) {
+        if (!ProcessExpression(e.get())) {
+          return false;
+        }
+      }
+    }
+  } else if (expr->IsMemberAccessor()) {
+    auto* m = expr->AsMemberAccessor();
+    return ProcessExpression(m->structure()) && ProcessExpression(m->member());
+  } else if (expr->IsBinary()) {
+    auto* b = expr->AsBinary();
+    return ProcessExpression(b->lhs()) && ProcessExpression(b->rhs());
+  } else if (expr->IsUnaryOp()) {
+    return ProcessExpression(expr->AsUnaryOp()->expr());
+  } else {
+    error_ = "unknown statement in bound array accessors transform";
+    return false;
+  }
+  return true;
+}
+
+bool BoundArrayAccessorsTransform::ProcessArrayAccessor(
+    ast::ArrayAccessorExpression* expr) {
+  if (!ProcessExpression(expr->array()) ||
+      !ProcessExpression(expr->idx_expr())) {
+    return false;
+  }
+
+  auto* ret_type = expr->array()->result_type()->UnwrapAliasPtrAlias();
+  if (!ret_type->IsArray() && !ret_type->IsMatrix() && !ret_type->IsVector()) {
+    return true;
+  }
+
+  if (ret_type->IsVector() || ret_type->IsArray()) {
+    uint32_t size = ret_type->IsVector() ? ret_type->AsVector()->size()
+                                         : ret_type->AsArray()->size();
+    if (size == 0) {
+      error_ = "invalid 0 size for array or vector";
+      return false;
+    }
+
+    if (!ProcessAccessExpression(expr, size)) {
+      return false;
+    }
+  } else {
+    // The row accessor would have been an embedded array accessor and already
+    // handled, so we just need to do columns here.
+    uint32_t size = ret_type->AsMatrix()->columns();
+    if (!ProcessAccessExpression(expr, size)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool BoundArrayAccessorsTransform::ProcessAccessExpression(
+    ast::ArrayAccessorExpression* expr,
+    uint32_t size) {
+  // Scalar constructor we can re-write the value to be within bounds.
+  if (expr->idx_expr()->IsConstructor() &&
+      expr->idx_expr()->AsConstructor()->IsScalarConstructor()) {
+    auto* lit =
+        expr->idx_expr()->AsConstructor()->AsScalarConstructor()->literal();
+    if (lit->IsSint()) {
+      int32_t val = lit->AsSint()->value();
+      if (val < 0) {
+        val = 0;
+      } else if (val >= int32_t(size)) {
+        val = int32_t(size) - 1;
+      }
+      lit->AsSint()->set_value(val);
+    } else if (lit->IsUint()) {
+      uint32_t val = lit->AsUint()->value();
+      if (val >= size - 1) {
+        val = size - 1;
+      }
+      lit->AsUint()->set_value(val);
+    } else {
+      error_ = "unknown scalar constructor type for accessor";
+      return false;
+    }
+  } else {
+    auto* u32 = ctx_->type_mgr().Get(std::make_unique<ast::type::U32Type>());
+
+    ast::ExpressionList params;
+    params.push_back(expr->take_idx_expr());
+    params.push_back(std::make_unique<ast::ScalarConstructorExpression>(
+        std::make_unique<ast::UintLiteral>(u32, 0)));
+    params.push_back(std::make_unique<ast::ScalarConstructorExpression>(
+        std::make_unique<ast::UintLiteral>(u32, size - 1)));
+
+    auto call_expr = std::make_unique<ast::CallExpression>(
+        std::make_unique<ast::IdentifierExpression>("clamp"),
+        std::move(params));
+    call_expr->set_result_type(u32);
+
+    expr->set_idx_expr(std::move(call_expr));
+  }
+  return true;
+}
+
+}  // namespace transform
+}  // namespace tint
diff --git a/src/transform/bound_array_accessors_transform.h b/src/transform/bound_array_accessors_transform.h
new file mode 100644
index 0000000..2a40225
--- /dev/null
+++ b/src/transform/bound_array_accessors_transform.h
@@ -0,0 +1,64 @@
+// Copyright 2020 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_TRANSFORM_BOUND_ARRAY_ACCESSORS_TRANSFORM_H_
+#define SRC_TRANSFORM_BOUND_ARRAY_ACCESSORS_TRANSFORM_H_
+
+#include "src/ast/array_accessor_expression.h"
+#include "src/ast/expression.h"
+#include "src/ast/module.h"
+#include "src/ast/statement.h"
+#include "src/context.h"
+#include "src/scope_stack.h"
+
+#include <string>
+
+namespace tint {
+namespace transform {
+
+/// This transformer is responsible for clamping all array accesses to be within
+/// the bounds of the array. Any access before the start of the array will clamp
+/// to zero and any access past the end of the array will clamp to
+/// (array length - 1).
+class BoundArrayAccessorsTransform {
+ public:
+  /// Constructor
+  /// @param ctx the Tint context object
+  /// @param mod the module transform
+  explicit BoundArrayAccessorsTransform(Context* ctx, ast::Module* mod);
+  ~BoundArrayAccessorsTransform();
+
+  /// @returns true if the transformation was successful
+  bool Run();
+
+  /// @returns error messages
+  const std::string& error() { return error_; }
+
+ private:
+  bool ProcessStatement(ast::Statement* stmt);
+  bool ProcessExpression(ast::Expression* expr);
+  bool ProcessArrayAccessor(ast::ArrayAccessorExpression* expr);
+  bool ProcessAccessExpression(ast::ArrayAccessorExpression* expr,
+                               uint32_t size);
+
+  Context* ctx_ = nullptr;
+  ast::Module* mod_ = nullptr;
+  std::string error_;
+  ScopeStack<ast::Variable*> scope_stack_;
+};
+
+}  // namespace transform
+}  // namespace tint
+
+#endif  // SRC_TRANSFORM_BOUND_ARRAY_ACCESSORS_TRANSFORM_H_
diff --git a/src/transform/bound_array_accessors_transform_test.cc b/src/transform/bound_array_accessors_transform_test.cc
new file mode 100644
index 0000000..b1b774d
--- /dev/null
+++ b/src/transform/bound_array_accessors_transform_test.cc
@@ -0,0 +1,1119 @@
+// Copyright 2020 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/transform/bound_array_accessors_transform.h"
+
+#include <memory>
+
+#include "gtest/gtest.h"
+#include "src/ast/array_accessor_expression.h"
+#include "src/ast/binary_expression.h"
+#include "src/ast/block_statement.h"
+#include "src/ast/call_expression.h"
+#include "src/ast/function.h"
+#include "src/ast/identifier_expression.h"
+#include "src/ast/module.h"
+#include "src/ast/scalar_constructor_expression.h"
+#include "src/ast/sint_literal.h"
+#include "src/ast/storage_class.h"
+#include "src/ast/type/array_type.h"
+#include "src/ast/type/f32_type.h"
+#include "src/ast/type/i32_type.h"
+#include "src/ast/type/matrix_type.h"
+#include "src/ast/type/pointer_type.h"
+#include "src/ast/type/u32_type.h"
+#include "src/ast/type/vector_type.h"
+#include "src/ast/type/void_type.h"
+#include "src/ast/uint_literal.h"
+#include "src/ast/variable.h"
+#include "src/ast/variable_decl_statement.h"
+#include "src/context.h"
+#include "src/type_determiner.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+class BoundArrayAccessorsTest : public testing::Test {
+ public:
+  BoundArrayAccessorsTest() : td_(&ctx_, &mod_), transform_(&ctx_, &mod_) {}
+
+  ast::BlockStatement* SetupFunctionAndBody() {
+    auto func = std::make_unique<ast::Function>("func", ast::VariableList{},
+                                                &void_type_);
+    auto block = std::make_unique<ast::BlockStatement>();
+    body_ = block.get();
+    func->set_body(std::move(block));
+    mod_.AddFunction(std::move(func));
+    return body_;
+  }
+
+  void DeclareVariable(std::unique_ptr<ast::Variable> var) {
+    ASSERT_NE(body_, nullptr);
+    body_->append(std::make_unique<ast::VariableDeclStatement>(std::move(var)));
+  }
+
+  TypeDeterminer* td() { return &td_; }
+
+  BoundArrayAccessorsTransform* transform() { return &transform_; }
+
+ private:
+  Context ctx_;
+  ast::Module mod_;
+  TypeDeterminer td_;
+  ast::type::VoidType void_type_;
+  BoundArrayAccessorsTransform transform_;
+  ast::BlockStatement* body_ = nullptr;
+};
+
+TEST_F(BoundArrayAccessorsTest, Ptrs_Clamp) {
+  // var a : array<f32, 3>;
+  // const c : u32 =  1;
+  // const b : ptr<function, f32> = a[c]
+  //
+  //   -> const b : ptr<function, i32> = a[clamp(c, 0, 2)]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::ArrayType ary(&f32, 3);
+  ast::type::PointerType ptr_type(&f32, ast::StorageClass::kFunction);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &ary));
+
+  auto c_var =
+      std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &u32);
+  c_var->set_is_const(true);
+  DeclareVariable(std::move(c_var));
+
+  auto access_idx = std::make_unique<ast::IdentifierExpression>("c");
+  auto* access_ptr = access_idx.get();
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("a"), std::move(access_idx));
+  auto* ptr = accessor.get();
+
+  auto b = std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction,
+                                           &ptr_type);
+  b->set_constructor(std::move(accessor));
+  b->set_is_const(true);
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->idx_expr()->IsCall());
+
+  auto* idx = ptr->idx_expr()->AsCall();
+  ASSERT_TRUE(idx->func()->IsIdentifier());
+  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp");
+
+  ASSERT_EQ(idx->params().size(), 3u);
+  ASSERT_EQ(idx->params()[0].get(), access_ptr);
+
+  ASSERT_TRUE(idx->params()[1]->IsConstructor());
+  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
+  auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u);
+
+  ASSERT_TRUE(idx->params()[2]->IsConstructor());
+  ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor());
+  scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Array_Idx_Nested_Scalar) {
+  // var a : array<f32, 3>;
+  // var b : array<f32, 5>;
+  // var i : u32;
+  // var c : f32 = a[b[i]];
+  //
+  // -> var c : f32 = a[clamp(b[clamp(i, 0, 4)], 0, 2)];
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::ArrayType ary3(&f32, 3);
+  ast::type::ArrayType ary5(&f32, 5);
+
+  SetupFunctionAndBody();
+  DeclareVariable(std::make_unique<ast::Variable>(
+      "a", ast::StorageClass::kFunction, &ary3));
+  DeclareVariable(std::make_unique<ast::Variable>(
+      "b", ast::StorageClass::kFunction, &ary5));
+  DeclareVariable(
+      std::make_unique<ast::Variable>("i", ast::StorageClass::kFunction, &u32));
+
+  auto b_access_idx = std::make_unique<ast::IdentifierExpression>("i");
+  auto* b_access_ptr = b_access_idx.get();
+
+  auto a_access_idx = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("b"),
+      std::move(b_access_idx));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("a"),
+      std::move(a_access_idx));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->idx_expr()->IsCall());
+
+  auto* idx = ptr->idx_expr()->AsCall();
+  ASSERT_TRUE(idx->func()->IsIdentifier());
+  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp");
+
+  ASSERT_EQ(idx->params().size(), 3u);
+
+  auto* sub = idx->params()[0].get();
+  ASSERT_TRUE(sub->IsArrayAccessor());
+  ASSERT_TRUE(sub->AsArrayAccessor()->idx_expr()->IsCall());
+
+  auto* sub_idx = sub->AsArrayAccessor()->idx_expr()->AsCall();
+  ASSERT_TRUE(sub_idx->func()->IsIdentifier());
+  EXPECT_EQ(sub_idx->func()->AsIdentifier()->name(), "clamp");
+
+  ASSERT_EQ(sub_idx->params()[0].get(), b_access_ptr);
+
+  ASSERT_TRUE(sub_idx->params()[1]->IsConstructor());
+  ASSERT_TRUE(sub_idx->params()[1]->AsConstructor()->IsScalarConstructor());
+  auto* scalar = sub_idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u);
+
+  ASSERT_TRUE(sub_idx->params()[2]->IsConstructor());
+  ASSERT_TRUE(sub_idx->params()[2]->AsConstructor()->IsScalarConstructor());
+  scalar = sub_idx->params()[2]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 4u);
+
+  ASSERT_TRUE(idx->params()[1]->IsConstructor());
+  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
+  scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u);
+
+  ASSERT_TRUE(idx->params()[2]->IsConstructor());
+  ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor());
+  scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Array_Idx_Scalar) {
+  // var a : array<f32, 3>
+  // var b : f32 = a[1];
+  //
+  // -> var b : f32 = a[1];
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::ArrayType ary(&f32, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &ary));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("a"),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::UintLiteral>(&u32, 1u)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Array_Idx_Expr) {
+  // var a : array<f32, 3>
+  // var c : u32;
+  // var b : f32 = a[c + 2 - 3]
+  //
+  // -> var b : f32 = a[clamp((c + 2 - 3), 0, 2)]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::ArrayType ary(&f32, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &ary));
+  DeclareVariable(
+      std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &u32));
+
+  auto access_idx = std::make_unique<ast::BinaryExpression>(
+      ast::BinaryOp::kAdd, std::make_unique<ast::IdentifierExpression>("c"),
+      std::make_unique<ast::BinaryExpression>(
+          ast::BinaryOp::kSubtract,
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 2)),
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 3))));
+  auto* access_ptr = access_idx.get();
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("a"), std::move(access_idx));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->idx_expr()->IsCall());
+
+  auto* idx = ptr->idx_expr()->AsCall();
+  ASSERT_TRUE(idx->func()->IsIdentifier());
+  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp");
+
+  ASSERT_EQ(idx->params().size(), 3u);
+  ASSERT_EQ(idx->params()[0].get(), access_ptr);
+
+  ASSERT_TRUE(idx->params()[1]->IsConstructor());
+  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
+  auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u);
+
+  ASSERT_TRUE(idx->params()[2]->IsConstructor());
+  ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor());
+  scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Array_Idx_Negative) {
+  // var a : array<f32, 3>
+  // var b : f32 = a[-1]
+  //
+  // -> var b : f32 = a[0]
+
+  ast::type::F32Type f32;
+  ast::type::I32Type i32;
+  ast::type::ArrayType ary(&f32, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &ary));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("a"),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::SintLiteral>(&i32, -1)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsSint());
+  EXPECT_EQ(scalar->literal()->AsSint()->value(), 0);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsI32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Array_Idx_OutOfBounds) {
+  // var a : array<f32, 3>
+  // var b : f32 = a[3]
+  //
+  // -> var b : f32 = a[2]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::ArrayType ary(&f32, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &ary));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("a"),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::UintLiteral>(&u32, 3u)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Vector_Idx_Scalar) {
+  // var a : vec3<f32>
+  // var b : f32 = a[1];
+  //
+  // -> var b : f32 = a[1]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::VectorType vec(&f32, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &vec));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("a"),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::UintLiteral>(&u32, 1u)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Vector_Idx_Expr) {
+  // var a : vec3<f32>
+  // var c : u32;
+  // var b : f32 = a[c + 2 - 3]
+  //
+  // -> var b : f32 = a[clamp((c + 2 - 3), 0, 2)]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::VectorType vec(&f32, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &vec));
+  DeclareVariable(
+      std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &u32));
+
+  auto access_idx = std::make_unique<ast::BinaryExpression>(
+      ast::BinaryOp::kAdd, std::make_unique<ast::IdentifierExpression>("c"),
+      std::make_unique<ast::BinaryExpression>(
+          ast::BinaryOp::kSubtract,
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 2)),
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 3))));
+  auto* access_ptr = access_idx.get();
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("a"), std::move(access_idx));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->idx_expr()->IsCall());
+
+  auto* idx = ptr->idx_expr()->AsCall();
+  ASSERT_TRUE(idx->func()->IsIdentifier());
+  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp");
+
+  ASSERT_EQ(idx->params().size(), 3u);
+  ASSERT_EQ(idx->params()[0].get(), access_ptr);
+
+  ASSERT_TRUE(idx->params()[1]->IsConstructor());
+  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
+  auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u);
+
+  ASSERT_TRUE(idx->params()[2]->IsConstructor());
+  ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor());
+  scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Vector_Idx_Negative) {
+  // var a : vec3<f32>
+  // var b : f32 = a[-1]
+  //
+  // -> var b : f32 = a[0]
+
+  ast::type::F32Type f32;
+  ast::type::I32Type i32;
+  ast::type::VectorType vec(&f32, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &vec));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("a"),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::SintLiteral>(&i32, -1)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsSint());
+  EXPECT_EQ(scalar->literal()->AsSint()->value(), 0);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsI32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Vector_Idx_OutOfBounds) {
+  // var a : vec3<f32>
+  // var b : f32 = a[3]
+  //
+  // -> var b : f32 = a[2]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::VectorType vec(&f32, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &vec));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::IdentifierExpression>("a"),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::UintLiteral>(&u32, 3u)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Scalar) {
+  // var a : mat3x2<f32>
+  // var b : f32 = a[2][1];
+  //
+  // -> var b : f32 = a[2][1]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::MatrixType mat(&f32, 2, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::ArrayAccessorExpression>(
+          std::make_unique<ast::IdentifierExpression>("a"),
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 2u))),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::UintLiteral>(&u32, 1u)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+
+  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
+  auto* ary = ptr->array()->AsArrayAccessor();
+  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
+
+  ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ary->idx_expr()->result_type()->IsU32());
+
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Expr_Column) {
+  // var a : mat3x2<f32>
+  // var c : u32;
+  // var b : f32 = a[c + 2 - 3][1]
+  //
+  // -> var b : f32 = a[clamp((c + 2 - 3), 0, 2)][1]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::MatrixType mat(&f32, 2, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+  DeclareVariable(
+      std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &u32));
+
+  auto access_idx = std::make_unique<ast::BinaryExpression>(
+      ast::BinaryOp::kAdd, std::make_unique<ast::IdentifierExpression>("c"),
+      std::make_unique<ast::BinaryExpression>(
+          ast::BinaryOp::kSubtract,
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 2)),
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 3))));
+  auto* access_ptr = access_idx.get();
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::ArrayAccessorExpression>(
+          std::make_unique<ast::IdentifierExpression>("a"),
+          std::move(access_idx)),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::UintLiteral>(&u32, 1u)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+
+  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
+  auto* ary = ptr->array()->AsArrayAccessor();
+
+  ASSERT_TRUE(ary->idx_expr()->IsCall());
+  auto* idx = ary->idx_expr()->AsCall();
+  ASSERT_TRUE(idx->func()->IsIdentifier());
+  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp");
+
+  ASSERT_EQ(idx->params().size(), 3u);
+  ASSERT_EQ(idx->params()[0].get(), access_ptr);
+
+  ASSERT_TRUE(idx->params()[1]->IsConstructor());
+  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
+  auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u);
+
+  ASSERT_TRUE(idx->params()[2]->IsConstructor());
+  ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor());
+  scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
+
+  ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ary->idx_expr()->result_type()->IsU32());
+
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Expr_Row) {
+  // var a : mat3x2<f32>
+  // var c : u32;
+  // var b : f32 = a[1][c + 2 - 3]
+  //
+  // -> var b : f32 = a[1][clamp((c + 2 - 3), 0, 1)]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::MatrixType mat(&f32, 2, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+  DeclareVariable(
+      std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &u32));
+
+  auto access_idx = std::make_unique<ast::BinaryExpression>(
+      ast::BinaryOp::kAdd, std::make_unique<ast::IdentifierExpression>("c"),
+      std::make_unique<ast::BinaryExpression>(
+          ast::BinaryOp::kSubtract,
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 2)),
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 3))));
+  auto* access_ptr = access_idx.get();
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::ArrayAccessorExpression>(
+          std::make_unique<ast::IdentifierExpression>("a"),
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 1u))),
+      std::move(access_idx));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+
+  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
+  auto* ary = ptr->array()->AsArrayAccessor();
+
+  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
+
+  ASSERT_TRUE(ptr->idx_expr()->IsCall());
+  auto* idx = ptr->idx_expr()->AsCall();
+  ASSERT_TRUE(idx->func()->IsIdentifier());
+  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp");
+
+  ASSERT_EQ(idx->params().size(), 3u);
+  ASSERT_EQ(idx->params()[0].get(), access_ptr);
+
+  ASSERT_TRUE(idx->params()[1]->IsConstructor());
+  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
+  scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u);
+
+  ASSERT_TRUE(idx->params()[2]->IsConstructor());
+  ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor());
+  scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
+
+  ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ary->idx_expr()->result_type()->IsU32());
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Negative_Column) {
+  // var a : mat3x2<f32>
+  // var b : f32 = a[-1][1]
+  //
+  // -> var b : f32 = a[0][1]
+  ast::type::F32Type f32;
+  ast::type::I32Type i32;
+  ast::type::MatrixType mat(&f32, 2, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::ArrayAccessorExpression>(
+          std::make_unique<ast::IdentifierExpression>("a"),
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::SintLiteral>(&i32, -1))),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::SintLiteral>(&i32, 1)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+
+  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
+  auto* ary = ptr->array()->AsArrayAccessor();
+  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsSint());
+  EXPECT_EQ(scalar->literal()->AsSint()->value(), 0);
+
+  ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ary->idx_expr()->result_type()->IsI32());
+
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsSint());
+  EXPECT_EQ(scalar->literal()->AsSint()->value(), 1);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsI32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Negative_Row) {
+  // var a : mat3x2<f32>
+  // var b : f32 = a[2][-1]
+  //
+  // -> var b : f32 = a[2][0]
+  ast::type::F32Type f32;
+  ast::type::I32Type i32;
+  ast::type::MatrixType mat(&f32, 2, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::ArrayAccessorExpression>(
+          std::make_unique<ast::IdentifierExpression>("a"),
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::SintLiteral>(&i32, 2))),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::SintLiteral>(&i32, -1)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+
+  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
+  auto* ary = ptr->array()->AsArrayAccessor();
+  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsSint());
+  EXPECT_EQ(scalar->literal()->AsSint()->value(), 2);
+
+  ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ary->idx_expr()->result_type()->IsI32());
+
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsSint());
+  EXPECT_EQ(scalar->literal()->AsSint()->value(), 0);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsI32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Matrix_Idx_OutOfBounds_Column) {
+  // var a : mat3x2<f32>
+  // var b : f32 = a[5][1]
+  //
+  // -> var b : f32 = a[2][1]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::MatrixType mat(&f32, 2, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::ArrayAccessorExpression>(
+          std::make_unique<ast::IdentifierExpression>("a"),
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 5u))),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::UintLiteral>(&u32, 1u)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+
+  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
+  auto* ary = ptr->array()->AsArrayAccessor();
+  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
+
+  ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ary->idx_expr()->result_type()->IsU32());
+
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+TEST_F(BoundArrayAccessorsTest, Matrix_Idx_OutOfBounds_Row) {
+  // var a : mat3x2<f32>
+  // var b : f32 = a[2][5]
+  //
+  // -> var b : f32 = a[2][1]
+
+  ast::type::F32Type f32;
+  ast::type::U32Type u32;
+  ast::type::MatrixType mat(&f32, 2, 3);
+
+  SetupFunctionAndBody();
+  DeclareVariable(
+      std::make_unique<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+
+  auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+      std::make_unique<ast::ArrayAccessorExpression>(
+          std::make_unique<ast::IdentifierExpression>("a"),
+          std::make_unique<ast::ScalarConstructorExpression>(
+              std::make_unique<ast::UintLiteral>(&u32, 2u))),
+      std::make_unique<ast::ScalarConstructorExpression>(
+          std::make_unique<ast::UintLiteral>(&u32, 5u)));
+  auto* ptr = accessor.get();
+
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kFunction, &f32);
+  b->set_constructor(std::move(accessor));
+  DeclareVariable(std::move(b));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  ASSERT_TRUE(transform()->Run());
+  ASSERT_TRUE(ptr->IsArrayAccessor());
+
+  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
+  auto* ary = ptr->array()->AsArrayAccessor();
+  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
+
+  ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ary->idx_expr()->result_type()->IsU32());
+
+  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+
+  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(scalar->literal()->IsUint());
+  EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
+
+  ASSERT_NE(ptr->idx_expr()->result_type(), nullptr);
+  ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32());
+}
+
+// TODO(dsinclair): Implement when constant_id exists
+TEST_F(BoundArrayAccessorsTest, DISABLED_Vector_Constant_Id_Clamps) {
+  // [[constant_id(1300)]] const idx : i32;
+  // var a : vec3<f32>
+  // var b : f32 = a[idx]
+  //
+  // ->var b : f32 =  a[clamp(idx, 0, 2)]
+}
+
+// TODO(dsinclair): Implement when constant_id exists
+TEST_F(BoundArrayAccessorsTest, DISABLED_Array_Constant_Id_Clamps) {
+  // [[constant_id(1300)]] const idx : i32;
+  // var a : array<f32, 4>
+  // var b : f32 = a[idx]
+  //
+  // -> var b : f32 = a[clamp(idx, 0, 3)]
+}
+
+// TODO(dsinclair): Implement when constant_id exists
+TEST_F(BoundArrayAccessorsTest, DISABLED_Matrix_Column_Constant_Id_Clamps) {
+  // [[constant_id(1300)]] const idx : i32;
+  // var a : mat3x2<f32>
+  // var b : f32 = a[idx][1]
+  //
+  // -> var b : f32 = a[clamp(idx, 0, 2)][1]
+}
+
+// TODO(dsinclair): Implement when constant_id exists
+TEST_F(BoundArrayAccessorsTest, DISABLED_Matrix_Row_Constant_Id_Clamps) {
+  // [[constant_id(1300)]] const idx : i32;
+  // var a : mat3x2<f32>
+  // var b : f32 = a[1][idx]
+  //
+  // -> var b : f32 = a[1][clamp(idx, 0, 1)]
+}
+
+// TODO(dsinclair): Implement when we have arrayLength for Runtime Arrays
+TEST_F(BoundArrayAccessorsTest, DISABLED_RuntimeArray_Clamps) {
+  // struct S {
+  //   a : f32;
+  //   b : array<f32>;
+  // }
+  // S s;
+  // var b : f32 = s.b[25]
+  //
+  // -> var b : f32 = s.b[clamp(25, 0, arrayLength(s.b))]
+}
+
+// TODO(dsinclair): Clamp atomics when available.
+TEST_F(BoundArrayAccessorsTest, DISABLED_Atomics_Clamp) {
+  FAIL();
+}
+
+// TODO(dsinclair): Clamp texture coord values. Depends on:
+// https://github.com/gpuweb/gpuweb/issues/1107
+TEST_F(BoundArrayAccessorsTest, DISABLED_TextureCoord_Clamp) {
+  FAIL();
+}
+
+// TODO(dsinclair): Test for scoped variables when Lexical Scopes implemented
+TEST_F(BoundArrayAccessorsTest, DISABLED_Scoped_Variable) {
+  // var a : array<f32, 3>;
+  // var i : u32;
+  // {
+  //    var a : array<f32, 5>;
+  //    var b : f32 = a[i];
+  // }
+  // var c : f32 = a[i];
+  //
+  // -> var b : f32 = a[clamp(i, 0, 4)];
+  //    var c : f32 = a[clamp(i, 0, 2)];
+  FAIL();
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/transform/vertex_pulling_transform.cc b/src/transform/vertex_pulling_transform.cc
index 76b2522..ab668f9 100644
--- a/src/transform/vertex_pulling_transform.cc
+++ b/src/transform/vertex_pulling_transform.cc
@@ -37,7 +37,6 @@
 
 namespace tint {
 namespace transform {
-
 namespace {
 
 static const char kVertexBufferNamePrefix[] = "_tint_pulling_vertex_buffer_";