tint/transform: PromoteInitializersToLet

Rename PromoteInitializersToConstVar to PromoteInitializersToLet, and
implement promotion of 'const' variables that resolve to array types.

This is required, as the backends will inline variables that resolve to
'const' variables, and so we need to promote any 'const' values that
would emit an array constructor.

Bug: tint:1580
Change-Id: I1b7f5459512b0043385ba741d644ec776c912899
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/94684
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 461556d..b59fa37 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -508,8 +508,8 @@
     "transform/multiplanar_external_texture.h",
     "transform/num_workgroups_from_uniform.cc",
     "transform/num_workgroups_from_uniform.h",
-    "transform/promote_initializers_to_const_var.cc",
-    "transform/promote_initializers_to_const_var.h",
+    "transform/promote_initializers_to_let.cc",
+    "transform/promote_initializers_to_let.h",
     "transform/promote_side_effects_to_decl.cc",
     "transform/promote_side_effects_to_decl.h",
     "transform/remove_continue_in_switch.cc",
@@ -1183,7 +1183,7 @@
       "transform/module_scope_var_to_entry_point_param_test.cc",
       "transform/multiplanar_external_texture_test.cc",
       "transform/num_workgroups_from_uniform_test.cc",
-      "transform/promote_initializers_to_const_var_test.cc",
+      "transform/promote_initializers_to_let_test.cc",
       "transform/promote_side_effects_to_decl_test.cc",
       "transform/remove_continue_in_switch_test.cc",
       "transform/remove_phonies_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 32359b5..50f586e 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -432,8 +432,8 @@
   transform/multiplanar_external_texture.h
   transform/num_workgroups_from_uniform.cc
   transform/num_workgroups_from_uniform.h
-  transform/promote_initializers_to_const_var.cc
-  transform/promote_initializers_to_const_var.h
+  transform/promote_initializers_to_let.cc
+  transform/promote_initializers_to_let.h
   transform/promote_side_effects_to_decl.cc
   transform/promote_side_effects_to_decl.h
   transform/remove_continue_in_switch.cc
@@ -1106,7 +1106,7 @@
       transform/module_scope_var_to_entry_point_param_test.cc
       transform/multiplanar_external_texture_test.cc
       transform/num_workgroups_from_uniform_test.cc
-      transform/promote_initializers_to_const_var_test.cc
+      transform/promote_initializers_to_let_test.cc
       transform/promote_side_effects_to_decl_test.cc
       transform/remove_continue_in_switch_test.cc
       transform/remove_phonies_test.cc
diff --git a/src/tint/transform/promote_initializers_to_const_var.cc b/src/tint/transform/promote_initializers_to_const_var.cc
deleted file mode 100644
index 6e0ba55..0000000
--- a/src/tint/transform/promote_initializers_to_const_var.cc
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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/transform/promote_initializers_to_const_var.h"
-#include "src/tint/program_builder.h"
-#include "src/tint/sem/call.h"
-#include "src/tint/sem/statement.h"
-#include "src/tint/sem/type_constructor.h"
-#include "src/tint/transform/utils/hoist_to_decl_before.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::transform::PromoteInitializersToConstVar);
-
-namespace tint::transform {
-
-PromoteInitializersToConstVar::PromoteInitializersToConstVar() = default;
-
-PromoteInitializersToConstVar::~PromoteInitializersToConstVar() = default;
-
-void PromoteInitializersToConstVar::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
-    HoistToDeclBefore hoist_to_decl_before(ctx);
-
-    // Hoists array and structure initializers to a constant variable, declared
-    // just before the statement of usage.
-    auto type_ctor_to_let = [&](const ast::CallExpression* expr) {
-        auto* ctor = ctx.src->Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
-        if (!ctor->Target()->Is<sem::TypeConstructor>()) {
-            return true;
-        }
-        auto* sem_stmt = ctor->Stmt();
-        if (!sem_stmt) {
-            // Expression is outside of a statement. This usually means the
-            // expression is part of a global (module-scope) constant declaration.
-            // These must be constexpr, and so cannot contain the type of
-            // expressions that must be sanitized.
-            return true;
-        }
-
-        auto* stmt = sem_stmt->Declaration();
-
-        if (auto* src_var_decl = stmt->As<ast::VariableDeclStatement>()) {
-            if (src_var_decl->variable->constructor == expr) {
-                // This statement is just a variable declaration with the
-                // initializer as the constructor value. This is what we're
-                // attempting to transform to, and so ignore.
-                return true;
-            }
-        }
-
-        auto* src_ty = ctor->Type();
-        if (!src_ty->IsAnyOf<sem::Array, sem::Struct>()) {
-            // We only care about array and struct initializers
-            return true;
-        }
-
-        return hoist_to_decl_before.Add(ctor, expr, true);
-    };
-
-    for (auto* node : ctx.src->ASTNodes().Objects()) {
-        if (auto* call_expr = node->As<ast::CallExpression>()) {
-            if (!type_ctor_to_let(call_expr)) {
-                return;
-            }
-        }
-    }
-
-    hoist_to_decl_before.Apply();
-    ctx.Clone();
-}
-
-}  // namespace tint::transform
diff --git a/src/tint/transform/promote_initializers_to_const_var_test.cc b/src/tint/transform/promote_initializers_to_const_var_test.cc
deleted file mode 100644
index f322478..0000000
--- a/src/tint/transform/promote_initializers_to_const_var_test.cc
+++ /dev/null
@@ -1,625 +0,0 @@
-// 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/tint/transform/promote_initializers_to_const_var.h"
-
-#include "src/tint/transform/test_helper.h"
-
-namespace tint::transform {
-namespace {
-
-using PromoteInitializersToConstVarTest = TransformTest;
-
-TEST_F(PromoteInitializersToConstVarTest, EmptyModule) {
-    auto* src = "";
-    auto* expect = "";
-
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, BasicArray) {
-    auto* src = R"(
-fn f() {
-  var f0 = 1.0;
-  var f1 = 2.0;
-  var f2 = 3.0;
-  var f3 = 4.0;
-  var i = array<f32, 4u>(f0, f1, f2, f3)[2];
-}
-)";
-
-    auto* expect = R"(
-fn f() {
-  var f0 = 1.0;
-  var f1 = 2.0;
-  var f2 = 3.0;
-  var f3 = 4.0;
-  let tint_symbol = array<f32, 4u>(f0, f1, f2, f3);
-  var i = tint_symbol[2];
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, BasicStruct) {
-    auto* src = R"(
-struct S {
-  a : i32,
-  b : f32,
-  c : vec3<f32>,
-};
-
-fn f() {
-  var x = S(1, 2.0, vec3<f32>()).b;
-}
-)";
-
-    auto* expect = R"(
-struct S {
-  a : i32,
-  b : f32,
-  c : vec3<f32>,
-}
-
-fn f() {
-  let tint_symbol = S(1, 2.0, vec3<f32>());
-  var x = tint_symbol.b;
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, BasicStruct_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var x = S(1, 2.0, vec3<f32>()).b;
-}
-
-struct S {
-  a : i32,
-  b : f32,
-  c : vec3<f32>,
-};
-)";
-
-    auto* expect = R"(
-fn f() {
-  let tint_symbol = S(1, 2.0, vec3<f32>());
-  var x = tint_symbol.b;
-}
-
-struct S {
-  a : i32,
-  b : f32,
-  c : vec3<f32>,
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopInit) {
-    auto* src = R"(
-fn f() {
-  var insert_after = 1;
-  for(var i = array<f32, 4u>(0.0, 1.0, 2.0, 3.0)[2]; ; ) {
-    break;
-  }
-}
-)";
-
-    auto* expect = R"(
-fn f() {
-  var insert_after = 1;
-  let tint_symbol = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-  for(var i = tint_symbol[2]; ; ) {
-    break;
-  }
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, StructInForLoopInit) {
-    auto* src = R"(
-struct S {
-  a : i32,
-  b : f32,
-  c : vec3<f32>,
-};
-
-fn f() {
-  var insert_after = 1;
-  for(var x = S(1, 2.0, vec3<f32>()).b; ; ) {
-    break;
-  }
-}
-)";
-
-    auto* expect = R"(
-struct S {
-  a : i32,
-  b : f32,
-  c : vec3<f32>,
-}
-
-fn f() {
-  var insert_after = 1;
-  let tint_symbol = S(1, 2.0, vec3<f32>());
-  for(var x = tint_symbol.b; ; ) {
-    break;
-  }
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, StructInForLoopInit_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var insert_after = 1;
-  for(var x = S(1, 2.0, vec3<f32>()).b; ; ) {
-    break;
-  }
-}
-
-struct S {
-  a : i32,
-  b : f32,
-  c : vec3<f32>,
-};
-)";
-
-    auto* expect = R"(
-fn f() {
-  var insert_after = 1;
-  let tint_symbol = S(1, 2.0, vec3<f32>());
-  for(var x = tint_symbol.b; ; ) {
-    break;
-  }
-}
-
-struct S {
-  a : i32,
-  b : f32,
-  c : vec3<f32>,
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopCond) {
-    auto* src = R"(
-fn f() {
-  var f = 1.0;
-  for(; f == array<f32, 1u>(f)[0]; f = f + 1.0) {
-    var marker = 1;
-  }
-}
-)";
-
-    auto* expect = R"(
-fn f() {
-  var f = 1.0;
-  loop {
-    let tint_symbol = array<f32, 1u>(f);
-    if (!((f == tint_symbol[0]))) {
-      break;
-    }
-    {
-      var marker = 1;
-    }
-
-    continuing {
-      f = (f + 1.0);
-    }
-  }
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopCont) {
-    auto* src = R"(
-fn f() {
-  var f = 0.0;
-  for(; f < 10.0; f = f + array<f32, 1u>(1.0)[0]) {
-    var marker = 1;
-  }
-}
-)";
-
-    auto* expect = R"(
-fn f() {
-  var f = 0.0;
-  loop {
-    if (!((f < 10.0))) {
-      break;
-    }
-    {
-      var marker = 1;
-    }
-
-    continuing {
-      let tint_symbol = array<f32, 1u>(1.0);
-      f = (f + tint_symbol[0]);
-    }
-  }
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInForLoopInitCondCont) {
-    auto* src = R"(
-fn f() {
-  for(var f = array<f32, 1u>(0.0)[0];
-      f < array<f32, 1u>(1.0)[0];
-      f = f + array<f32, 1u>(2.0)[0]) {
-    var marker = 1;
-  }
-}
-)";
-
-    auto* expect = R"(
-fn f() {
-  let tint_symbol = array<f32, 1u>(0.0);
-  {
-    var f = tint_symbol[0];
-    loop {
-      let tint_symbol_1 = array<f32, 1u>(1.0);
-      if (!((f < tint_symbol_1[0]))) {
-        break;
-      }
-      {
-        var marker = 1;
-      }
-
-      continuing {
-        let tint_symbol_2 = array<f32, 1u>(2.0);
-        f = (f + tint_symbol_2[0]);
-      }
-    }
-  }
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInElseIf) {
-    auto* src = R"(
-fn f() {
-  var f = 1.0;
-  if (true) {
-    var marker = 0;
-  } else if (f == array<f32, 2u>(f, f)[0]) {
-    var marker = 1;
-  }
-}
-)";
-
-    auto* expect = R"(
-fn f() {
-  var f = 1.0;
-  if (true) {
-    var marker = 0;
-  } else {
-    let tint_symbol = array<f32, 2u>(f, f);
-    if ((f == tint_symbol[0])) {
-      var marker = 1;
-    }
-  }
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInElseIfChain) {
-    auto* src = R"(
-fn f() {
-  var f = 1.0;
-  if (true) {
-    var marker = 0;
-  } else if (true) {
-    var marker = 1;
-  } else if (f == array<f32, 2u>(f, f)[0]) {
-    var marker = 2;
-  } else if (f == array<f32, 2u>(f, f)[1]) {
-    var marker = 3;
-  } else if (true) {
-    var marker = 4;
-  } else {
-    var marker = 5;
-  }
-}
-)";
-
-    auto* expect = R"(
-fn f() {
-  var f = 1.0;
-  if (true) {
-    var marker = 0;
-  } else if (true) {
-    var marker = 1;
-  } else {
-    let tint_symbol = array<f32, 2u>(f, f);
-    if ((f == tint_symbol[0])) {
-      var marker = 2;
-    } else {
-      let tint_symbol_1 = array<f32, 2u>(f, f);
-      if ((f == tint_symbol_1[1])) {
-        var marker = 3;
-      } else if (true) {
-        var marker = 4;
-      } else {
-        var marker = 5;
-      }
-    }
-  }
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, ArrayInArrayArray) {
-    auto* src = R"(
-fn f() {
-  var i = array<array<f32, 2u>, 2u>(array<f32, 2u>(1.0, 2.0), array<f32, 2u>(3.0, 4.0))[0][1];
-}
-)";
-
-    auto* expect = R"(
-fn f() {
-  let tint_symbol = array<f32, 2u>(1.0, 2.0);
-  let tint_symbol_1 = array<f32, 2u>(3.0, 4.0);
-  let tint_symbol_2 = array<array<f32, 2u>, 2u>(tint_symbol, tint_symbol_1);
-  var i = tint_symbol_2[0][1];
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, StructNested) {
-    auto* src = R"(
-struct S1 {
-  a : i32,
-};
-
-struct S2 {
-  a : i32,
-  b : S1,
-  c : i32,
-};
-
-struct S3 {
-  a : S2,
-};
-
-fn f() {
-  var x = S3(S2(1, S1(2), 3)).a.b.a;
-}
-)";
-
-    auto* expect = R"(
-struct S1 {
-  a : i32,
-}
-
-struct S2 {
-  a : i32,
-  b : S1,
-  c : i32,
-}
-
-struct S3 {
-  a : S2,
-}
-
-fn f() {
-  let tint_symbol = S1(2);
-  let tint_symbol_1 = S2(1, tint_symbol, 3);
-  let tint_symbol_2 = S3(tint_symbol_1);
-  var x = tint_symbol_2.a.b.a;
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, Mixed) {
-    auto* src = R"(
-struct S1 {
-  a : i32,
-};
-
-struct S2 {
-  a : array<S1, 3u>,
-};
-
-fn f() {
-  var x = S2(array<S1, 3u>(S1(1), S1(2), S1(3))).a[1].a;
-}
-)";
-
-    auto* expect = R"(
-struct S1 {
-  a : i32,
-}
-
-struct S2 {
-  a : array<S1, 3u>,
-}
-
-fn f() {
-  let tint_symbol = S1(1);
-  let tint_symbol_1 = S1(2);
-  let tint_symbol_2 = S1(3);
-  let tint_symbol_3 = array<S1, 3u>(tint_symbol, tint_symbol_1, tint_symbol_2);
-  let tint_symbol_4 = S2(tint_symbol_3);
-  var x = tint_symbol_4.a[1].a;
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, Mixed_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var x = S2(array<S1, 3u>(S1(1), S1(2), S1(3))).a[1].a;
-}
-
-struct S2 {
-  a : array<S1, 3u>,
-};
-
-struct S1 {
-  a : i32,
-};
-)";
-
-    auto* expect = R"(
-fn f() {
-  let tint_symbol = S1(1);
-  let tint_symbol_1 = S1(2);
-  let tint_symbol_2 = S1(3);
-  let tint_symbol_3 = array<S1, 3u>(tint_symbol, tint_symbol_1, tint_symbol_2);
-  let tint_symbol_4 = S2(tint_symbol_3);
-  var x = tint_symbol_4.a[1].a;
-}
-
-struct S2 {
-  a : array<S1, 3u>,
-}
-
-struct S1 {
-  a : i32,
-}
-)";
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, NoChangeOnVarDecl) {
-    auto* src = R"(
-struct S {
-  a : i32,
-  b : f32,
-  c : i32,
-}
-
-fn f() {
-  var local_arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-  var local_str = S(1, 2.0, 3);
-}
-
-let module_arr : array<f32, 4u> = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-
-let module_str : S = S(1, 2.0, 3);
-)";
-
-    auto* expect = src;
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(PromoteInitializersToConstVarTest, NoChangeOnVarDecl_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var local_arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-  var local_str = S(1, 2.0, 3);
-}
-
-let module_str : S = S(1, 2.0, 3);
-
-struct S {
-  a : i32,
-  b : f32,
-  c : i32,
-}
-
-let module_arr : array<f32, 4u> = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
-)";
-
-    auto* expect = src;
-
-    DataMap data;
-    auto got = Run<PromoteInitializersToConstVar>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-}  // namespace
-}  // namespace tint::transform
diff --git a/src/tint/transform/promote_initializers_to_let.cc b/src/tint/transform/promote_initializers_to_let.cc
new file mode 100644
index 0000000..b57fb25
--- /dev/null
+++ b/src/tint/transform/promote_initializers_to_let.cc
@@ -0,0 +1,106 @@
+// 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/transform/promote_initializers_to_let.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/type_constructor.h"
+#include "src/tint/transform/utils/hoist_to_decl_before.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::PromoteInitializersToLet);
+
+namespace tint::transform {
+
+PromoteInitializersToLet::PromoteInitializersToLet() = default;
+
+PromoteInitializersToLet::~PromoteInitializersToLet() = default;
+
+void PromoteInitializersToLet::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
+    HoistToDeclBefore hoist_to_decl_before(ctx);
+
+    // Hoists array and structure initializers to a constant variable, declared
+    // just before the statement of usage.
+    auto promote = [&](const sem::Expression* expr) {
+        auto* sem_stmt = expr->Stmt();
+        if (!sem_stmt) {
+            // Expression is outside of a statement. This usually means the
+            // expression is part of a global (module-scope) constant declaration.
+            // These must be constexpr, and so cannot contain the type of
+            // expressions that must be sanitized.
+            return true;
+        }
+
+        auto* stmt = sem_stmt->Declaration();
+
+        if (auto* src_var_decl = stmt->As<ast::VariableDeclStatement>()) {
+            if (src_var_decl->variable->constructor == expr->Declaration()) {
+                // This statement is just a variable declaration with the
+                // initializer as the constructor value. This is what we're
+                // attempting to transform to, and so ignore.
+                return true;
+            }
+        }
+
+        auto* src_ty = expr->Type();
+        if (!src_ty->IsAnyOf<sem::Array, sem::Struct>()) {
+            // We only care about array and struct initializers
+            return true;
+        }
+
+        return hoist_to_decl_before.Add(expr, expr->Declaration(), true);
+    };
+
+    for (auto* node : ctx.src->ASTNodes().Objects()) {
+        bool ok = Switch(
+            node,  //
+            [&](const ast::CallExpression* expr) {
+                if (auto* sem = ctx.src->Sem().Get(expr)) {
+                    auto* ctor = sem->UnwrapMaterialize()->As<sem::Call>();
+                    if (ctor->Target()->Is<sem::TypeConstructor>()) {
+                        return promote(sem);
+                    }
+                }
+                return true;
+            },
+            [&](const ast::IdentifierExpression* expr) {
+                if (auto* user = ctx.src->Sem().Get<sem::VariableUser>(expr)) {
+                    // Identifier resolves to a variable
+                    if (auto* stmt = user->Stmt()) {
+                        if (auto* decl = stmt->Declaration()->As<ast::VariableDeclStatement>();
+                            decl && decl->variable->Is<ast::Const>()) {
+                            // The identifier is used on the RHS of a 'const' declaration. Ignore.
+                            return true;
+                        }
+                    }
+                    if (user->Variable()->Declaration()->Is<ast::Const>()) {
+                        // The identifier resolves to a 'const' variable, but isn't used to
+                        // initialize another 'const'. This needs promoting.
+                        return promote(user);
+                    }
+                }
+                return true;
+            },
+            [&](Default) { return true; });
+
+        if (!ok) {
+            return;
+        }
+    }
+
+    hoist_to_decl_before.Apply();
+    ctx.Clone();
+}
+
+}  // namespace tint::transform
diff --git a/src/tint/transform/promote_initializers_to_const_var.h b/src/tint/transform/promote_initializers_to_let.h
similarity index 63%
rename from src/tint/transform/promote_initializers_to_const_var.h
rename to src/tint/transform/promote_initializers_to_let.h
index 67a32c4..41f99d7 100644
--- a/src/tint/transform/promote_initializers_to_const_var.h
+++ b/src/tint/transform/promote_initializers_to_let.h
@@ -12,23 +12,26 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SRC_TINT_TRANSFORM_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
-#define SRC_TINT_TRANSFORM_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
+#ifndef SRC_TINT_TRANSFORM_PROMOTE_INITIALIZERS_TO_LET_H_
+#define SRC_TINT_TRANSFORM_PROMOTE_INITIALIZERS_TO_LET_H_
 
 #include "src/tint/transform/transform.h"
 
 namespace tint::transform {
 
-/// A transform that hoists the array and structure initializers to a constant
-/// variable, declared just before the statement of usage.
+/// A transform that hoists array and structure constructors, and identifiers resolving to a
+/// 'const' array to a 'let' variable, declared just before the statement of usage.
+/// This transform is used by backends that do not support expressions that operate on an immediate
+/// array or structure. For example, the following is not immediately expressable for HLSL:
+///   `array<i32, 2>(1, 2)[0]`
 /// @see crbug.com/tint/406
-class PromoteInitializersToConstVar : public Castable<PromoteInitializersToConstVar, Transform> {
+class PromoteInitializersToLet : public Castable<PromoteInitializersToLet, Transform> {
   public:
     /// Constructor
-    PromoteInitializersToConstVar();
+    PromoteInitializersToLet();
 
     /// Destructor
-    ~PromoteInitializersToConstVar() override;
+    ~PromoteInitializersToLet() override;
 
   protected:
     /// Runs the transform using the CloneContext built for transforming a
@@ -42,4 +45,4 @@
 
 }  // namespace tint::transform
 
-#endif  // SRC_TINT_TRANSFORM_PROMOTE_INITIALIZERS_TO_CONST_VAR_H_
+#endif  // SRC_TINT_TRANSFORM_PROMOTE_INITIALIZERS_TO_LET_H_
diff --git a/src/tint/transform/promote_initializers_to_let_test.cc b/src/tint/transform/promote_initializers_to_let_test.cc
new file mode 100644
index 0000000..addb6dd
--- /dev/null
+++ b/src/tint/transform/promote_initializers_to_let_test.cc
@@ -0,0 +1,1233 @@
+// 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/tint/transform/promote_initializers_to_let.h"
+
+#include "src/tint/transform/test_helper.h"
+
+namespace tint::transform {
+namespace {
+
+using PromoteInitializersToLetTest = TransformTest;
+
+TEST_F(PromoteInitializersToLetTest, EmptyModule) {
+    auto* src = "";
+    auto* expect = "";
+
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, BasicArray) {
+    auto* src = R"(
+fn f() {
+  var f0 = 1.0;
+  var f1 = 2.0;
+  var f2 = 3.0;
+  var f3 = 4.0;
+  var i = array<f32, 4u>(f0, f1, f2, f3)[2];
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  var f0 = 1.0;
+  var f1 = 2.0;
+  var f2 = 3.0;
+  var f3 = 4.0;
+  let tint_symbol = array<f32, 4u>(f0, f1, f2, f3);
+  var i = tint_symbol[2];
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, BasicStruct) {
+    auto* src = R"(
+struct S {
+  a : i32,
+  b : f32,
+  c : vec3<f32>,
+};
+
+fn f() {
+  var x = S(1, 2.0, vec3<f32>()).b;
+}
+)";
+
+    auto* expect = R"(
+struct S {
+  a : i32,
+  b : f32,
+  c : vec3<f32>,
+}
+
+fn f() {
+  let tint_symbol = S(1, 2.0, vec3<f32>());
+  var x = tint_symbol.b;
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, BasicStruct_OutOfOrder) {
+    auto* src = R"(
+fn f() {
+  var x = S(1, 2.0, vec3<f32>()).b;
+}
+
+struct S {
+  a : i32,
+  b : f32,
+  c : vec3<f32>,
+};
+)";
+
+    auto* expect = R"(
+fn f() {
+  let tint_symbol = S(1, 2.0, vec3<f32>());
+  var x = tint_symbol.b;
+}
+
+struct S {
+  a : i32,
+  b : f32,
+  c : vec3<f32>,
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, GlobalConstBasicArray) {
+    auto* src = R"(
+const f0 = 1.0;
+
+const f1 = 2.0;
+
+const C = array<f32, 2u>(f0, f1);
+
+fn f() {
+  var f0 = 100.0;
+  var f1 = 100.0;
+  var i = C[1];
+}
+)";
+
+    auto* expect = R"(
+const f0 = 1.0;
+
+const f1 = 2.0;
+
+const C = array<f32, 2u>(f0, f1);
+
+fn f() {
+  var f0 = 100.0;
+  var f1 = 100.0;
+  let tint_symbol = C;
+  var i = tint_symbol[1];
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, GlobalConstBasicArray_OutOfOrder) {
+    auto* src = R"(
+fn f() {
+  var f0 = 100.0;
+  var f1 = 100.0;
+  var i = C[1];
+}
+
+const C = array<f32, 2u>(f0, f1);
+
+const f0 = 1.0;
+
+const f1 = 2.0;
+)";
+
+    auto* expect = R"(
+fn f() {
+  var f0 = 100.0;
+  var f1 = 100.0;
+  let tint_symbol = C;
+  var i = tint_symbol[1];
+}
+
+const C = array<f32, 2u>(f0, f1);
+
+const f0 = 1.0;
+
+const f1 = 2.0;
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, LocalConstBasicArray) {
+    auto* src = R"(
+fn f() {
+  const f0 = 1.0;
+  const f1 = 2.0;
+  const C = array<f32, 2u>(f0, f1);
+  var i = C[1];
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  const f0 = 1.0;
+  const f1 = 2.0;
+  const C = array<f32, 2u>(f0, f1);
+  let tint_symbol = C;
+  var i = tint_symbol[1];
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, ArrayInForLoopInit) {
+    auto* src = R"(
+fn f() {
+  var insert_after = 1;
+  for(var i = array<f32, 4u>(0.0, 1.0, 2.0, 3.0)[2]; ; ) {
+    break;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  var insert_after = 1;
+  let tint_symbol = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+  for(var i = tint_symbol[2]; ; ) {
+    break;
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, LocalConstArrayInForLoopInit) {
+    auto* src = R"(
+fn f() {
+  const arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+  var insert_after = 1;
+  for(var i = arr[2]; ; ) {
+    break;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  const arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+  var insert_after = 1;
+  let tint_symbol = arr;
+  for(var i = tint_symbol[2]; ; ) {
+    break;
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, GlobalConstArrayInForLoopInit) {
+    auto* src = R"(
+const arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+
+fn f() {
+  var insert_after = 1;
+  for(var i = arr[2]; ; ) {
+    break;
+  }
+}
+)";
+
+    auto* expect = R"(
+const arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+
+fn f() {
+  var insert_after = 1;
+  let tint_symbol = arr;
+  for(var i = tint_symbol[2]; ; ) {
+    break;
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, StructInForLoopInit) {
+    auto* src = R"(
+struct S {
+  a : i32,
+  b : f32,
+  c : vec3<f32>,
+};
+
+fn f() {
+  var insert_after = 1;
+  for(var x = S(1, 2.0, vec3<f32>()).b; ; ) {
+    break;
+  }
+}
+)";
+
+    auto* expect = R"(
+struct S {
+  a : i32,
+  b : f32,
+  c : vec3<f32>,
+}
+
+fn f() {
+  var insert_after = 1;
+  let tint_symbol = S(1, 2.0, vec3<f32>());
+  for(var x = tint_symbol.b; ; ) {
+    break;
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, StructInForLoopInit_OutOfOrder) {
+    auto* src = R"(
+fn f() {
+  var insert_after = 1;
+  for(var x = S(1, 2.0, vec3<f32>()).b; ; ) {
+    break;
+  }
+}
+
+struct S {
+  a : i32,
+  b : f32,
+  c : vec3<f32>,
+};
+)";
+
+    auto* expect = R"(
+fn f() {
+  var insert_after = 1;
+  let tint_symbol = S(1, 2.0, vec3<f32>());
+  for(var x = tint_symbol.b; ; ) {
+    break;
+  }
+}
+
+struct S {
+  a : i32,
+  b : f32,
+  c : vec3<f32>,
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, ArrayInForLoopCond) {
+    auto* src = R"(
+fn f() {
+  var f = 1.0;
+  for(; f == array<f32, 1u>(f)[0]; f = f + 1.0) {
+    var marker = 1;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  var f = 1.0;
+  loop {
+    let tint_symbol = array<f32, 1u>(f);
+    if (!((f == tint_symbol[0]))) {
+      break;
+    }
+    {
+      var marker = 1;
+    }
+
+    continuing {
+      f = (f + 1.0);
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, LocalConstArrayInForLoopCond) {
+    auto* src = R"(
+fn f() {
+  const f = 1.0;
+  const arr = array<f32, 1u>(f);
+  for(var i = f; i == arr[0]; i = i + 1.0) {
+    var marker = 1;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  const f = 1.0;
+  const arr = array<f32, 1u>(f);
+  {
+    var i = f;
+    loop {
+      let tint_symbol = arr;
+      if (!((i == tint_symbol[0]))) {
+        break;
+      }
+      {
+        var marker = 1;
+      }
+
+      continuing {
+        i = (i + 1.0);
+      }
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, GlobalConstArrayInForLoopCond) {
+    auto* src = R"(
+const f = 1.0;
+
+const arr = array<f32, 1u>(f);
+
+fn F() {
+  for(var i = f; i == arr[0]; i = i + 1.0) {
+    var marker = 1;
+  }
+}
+)";
+
+    auto* expect = R"(
+const f = 1.0;
+
+const arr = array<f32, 1u>(f);
+
+fn F() {
+  {
+    var i = f;
+    loop {
+      let tint_symbol = arr;
+      if (!((i == tint_symbol[0]))) {
+        break;
+      }
+      {
+        var marker = 1;
+      }
+
+      continuing {
+        i = (i + 1.0);
+      }
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, ArrayInForLoopCont) {
+    auto* src = R"(
+fn f() {
+  var f = 0.0;
+  for(; f < 10.0; f = f + array<f32, 1u>(1.0)[0]) {
+    var marker = 1;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  var f = 0.0;
+  loop {
+    if (!((f < 10.0))) {
+      break;
+    }
+    {
+      var marker = 1;
+    }
+
+    continuing {
+      let tint_symbol = array<f32, 1u>(1.0);
+      f = (f + tint_symbol[0]);
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, LocalConstArrayInForLoopCont) {
+    auto* src = R"(
+fn f() {
+  const arr = array<f32, 1u>(1.0);
+  var f = 0.0;
+  for(; f < 10.0; f = f + arr[0]) {
+    var marker = 1;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  const arr = array<f32, 1u>(1.0);
+  var f = 0.0;
+  loop {
+    if (!((f < 10.0))) {
+      break;
+    }
+    {
+      var marker = 1;
+    }
+
+    continuing {
+      let tint_symbol = arr;
+      f = (f + tint_symbol[0]);
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, GlobalConstArrayInForLoopCont) {
+    auto* src = R"(
+const arr = array<f32, 1u>(1.0);
+
+fn f() {
+  var f = 0.0;
+  for(; f < 10.0; f = f + arr[0]) {
+    var marker = 1;
+  }
+}
+)";
+
+    auto* expect = R"(
+const arr = array<f32, 1u>(1.0);
+
+fn f() {
+  var f = 0.0;
+  loop {
+    if (!((f < 10.0))) {
+      break;
+    }
+    {
+      var marker = 1;
+    }
+
+    continuing {
+      let tint_symbol = arr;
+      f = (f + tint_symbol[0]);
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, ArrayInForLoopInitCondCont) {
+    auto* src = R"(
+fn f() {
+  for(var f = array<f32, 1u>(0.0)[0];
+      f < array<f32, 1u>(1.0)[0];
+      f = f + array<f32, 1u>(2.0)[0]) {
+    var marker = 1;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  let tint_symbol = array<f32, 1u>(0.0);
+  {
+    var f = tint_symbol[0];
+    loop {
+      let tint_symbol_1 = array<f32, 1u>(1.0);
+      if (!((f < tint_symbol_1[0]))) {
+        break;
+      }
+      {
+        var marker = 1;
+      }
+
+      continuing {
+        let tint_symbol_2 = array<f32, 1u>(2.0);
+        f = (f + tint_symbol_2[0]);
+      }
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, LocalConstArrayInForLoopInitCondCont) {
+    auto* src = R"(
+fn f() {
+  const arr_a = array<f32, 1u>(0.0);
+  const arr_b = array<f32, 1u>(1.0);
+  const arr_c = array<f32, 1u>(2.0);
+  for(var f = arr_a[0]; f < arr_b[0]; f = f + arr_c[0]) {
+    var marker = 1;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  const arr_a = array<f32, 1u>(0.0);
+  const arr_b = array<f32, 1u>(1.0);
+  const arr_c = array<f32, 1u>(2.0);
+  let tint_symbol = arr_a;
+  {
+    var f = tint_symbol[0];
+    loop {
+      let tint_symbol_1 = arr_b;
+      if (!((f < tint_symbol_1[0]))) {
+        break;
+      }
+      {
+        var marker = 1;
+      }
+
+      continuing {
+        let tint_symbol_2 = arr_c;
+        f = (f + tint_symbol_2[0]);
+      }
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, ArrayInElseIf) {
+    auto* src = R"(
+fn f() {
+  var f = 1.0;
+  if (true) {
+    var marker = 0;
+  } else if (f == array<f32, 2u>(f, f)[0]) {
+    var marker = 1;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  var f = 1.0;
+  if (true) {
+    var marker = 0;
+  } else {
+    let tint_symbol = array<f32, 2u>(f, f);
+    if ((f == tint_symbol[0])) {
+      var marker = 1;
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, ArrayInElseIfChain) {
+    auto* src = R"(
+fn f() {
+  var f = 1.0;
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else if (f == array<f32, 2u>(f, f)[0]) {
+    var marker = 2;
+  } else if (f == array<f32, 2u>(f, f)[1]) {
+    var marker = 3;
+  } else if (true) {
+    var marker = 4;
+  } else {
+    var marker = 5;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  var f = 1.0;
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else {
+    let tint_symbol = array<f32, 2u>(f, f);
+    if ((f == tint_symbol[0])) {
+      var marker = 2;
+    } else {
+      let tint_symbol_1 = array<f32, 2u>(f, f);
+      if ((f == tint_symbol_1[1])) {
+        var marker = 3;
+      } else if (true) {
+        var marker = 4;
+      } else {
+        var marker = 5;
+      }
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, LocalConstArrayInElseIfChain) {
+    auto* src = R"(
+fn f() {
+  const f = 1.0;
+  const arr = array<f32, 2u>(f, f);
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else if (f == arr[0]) {
+    var marker = 2;
+  } else if (f == arr[1]) {
+    var marker = 3;
+  } else if (true) {
+    var marker = 4;
+  } else {
+    var marker = 5;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  const f = 1.0;
+  const arr = array<f32, 2u>(f, f);
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else {
+    let tint_symbol = arr;
+    if ((f == tint_symbol[0])) {
+      var marker = 2;
+    } else {
+      let tint_symbol_1 = arr;
+      if ((f == tint_symbol_1[1])) {
+        var marker = 3;
+      } else if (true) {
+        var marker = 4;
+      } else {
+        var marker = 5;
+      }
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, GlobalConstArrayInElseIfChain) {
+    auto* src = R"(
+const f = 1.0;
+
+const arr = array<f32, 2u>(f, f);
+
+fn F() {
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else if (f == arr[0]) {
+    var marker = 2;
+  } else if (f == arr[1]) {
+    var marker = 3;
+  } else if (true) {
+    var marker = 4;
+  } else {
+    var marker = 5;
+  }
+}
+)";
+
+    auto* expect = R"(
+const f = 1.0;
+
+const arr = array<f32, 2u>(f, f);
+
+fn F() {
+  if (true) {
+    var marker = 0;
+  } else if (true) {
+    var marker = 1;
+  } else {
+    let tint_symbol = arr;
+    if ((f == tint_symbol[0])) {
+      var marker = 2;
+    } else {
+      let tint_symbol_1 = arr;
+      if ((f == tint_symbol_1[1])) {
+        var marker = 3;
+      } else if (true) {
+        var marker = 4;
+      } else {
+        var marker = 5;
+      }
+    }
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, ArrayInArrayArray) {
+    auto* src = R"(
+fn f() {
+  var i = array<array<f32, 2u>, 2u>(array<f32, 2u>(1.0, 2.0), array<f32, 2u>(3.0, 4.0))[0][1];
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  let tint_symbol = array<f32, 2u>(1.0, 2.0);
+  let tint_symbol_1 = array<f32, 2u>(3.0, 4.0);
+  let tint_symbol_2 = array<array<f32, 2u>, 2u>(tint_symbol, tint_symbol_1);
+  var i = tint_symbol_2[0][1];
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, LocalConstArrayInArrayArray) {
+    auto* src = R"(
+fn f() {
+  const arr_0 = array<f32, 2u>(1.0, 2.0);
+  const arr_1 = array<f32, 2u>(3.0, 4.0);
+  const arr_2 = array<array<f32, 2u>, 2u>(arr_0, arr_1);
+  var i = arr_2[0][1];
+}
+)";
+
+    auto* expect = R"(
+fn f() {
+  const arr_0 = array<f32, 2u>(1.0, 2.0);
+  const arr_1 = array<f32, 2u>(3.0, 4.0);
+  const arr_2 = array<array<f32, 2u>, 2u>(arr_0, arr_1);
+  let tint_symbol = arr_2;
+  var i = tint_symbol[0][1];
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, GlobalConstArrayInArrayArray) {
+    auto* src = R"(
+const arr_0 = array<f32, 2u>(1.0, 2.0);
+
+const arr_1 = array<f32, 2u>(3.0, 4.0);
+
+const arr_2 = array<array<f32, 2u>, 2u>(arr_0, arr_1);
+
+fn f() {
+  var i = arr_2[0][1];
+}
+)";
+
+    auto* expect = R"(
+const arr_0 = array<f32, 2u>(1.0, 2.0);
+
+const arr_1 = array<f32, 2u>(3.0, 4.0);
+
+const arr_2 = array<array<f32, 2u>, 2u>(arr_0, arr_1);
+
+fn f() {
+  let tint_symbol = arr_2;
+  var i = tint_symbol[0][1];
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, StructNested) {
+    auto* src = R"(
+struct S1 {
+  a : i32,
+};
+
+struct S2 {
+  a : i32,
+  b : S1,
+  c : i32,
+};
+
+struct S3 {
+  a : S2,
+};
+
+fn f() {
+  var x = S3(S2(1, S1(2), 3)).a.b.a;
+}
+)";
+
+    auto* expect = R"(
+struct S1 {
+  a : i32,
+}
+
+struct S2 {
+  a : i32,
+  b : S1,
+  c : i32,
+}
+
+struct S3 {
+  a : S2,
+}
+
+fn f() {
+  let tint_symbol = S1(2);
+  let tint_symbol_1 = S2(1, tint_symbol, 3);
+  let tint_symbol_2 = S3(tint_symbol_1);
+  var x = tint_symbol_2.a.b.a;
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, Mixed) {
+    auto* src = R"(
+struct S1 {
+  a : i32,
+};
+
+struct S2 {
+  a : array<S1, 3u>,
+};
+
+fn f() {
+  var x = S2(array<S1, 3u>(S1(1), S1(2), S1(3))).a[1].a;
+}
+)";
+
+    auto* expect = R"(
+struct S1 {
+  a : i32,
+}
+
+struct S2 {
+  a : array<S1, 3u>,
+}
+
+fn f() {
+  let tint_symbol = S1(1);
+  let tint_symbol_1 = S1(2);
+  let tint_symbol_2 = S1(3);
+  let tint_symbol_3 = array<S1, 3u>(tint_symbol, tint_symbol_1, tint_symbol_2);
+  let tint_symbol_4 = S2(tint_symbol_3);
+  var x = tint_symbol_4.a[1].a;
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, Mixed_OutOfOrder) {
+    auto* src = R"(
+fn f() {
+  var x = S2(array<S1, 3u>(S1(1), S1(2), S1(3))).a[1].a;
+}
+
+struct S2 {
+  a : array<S1, 3u>,
+};
+
+struct S1 {
+  a : i32,
+};
+)";
+
+    auto* expect = R"(
+fn f() {
+  let tint_symbol = S1(1);
+  let tint_symbol_1 = S1(2);
+  let tint_symbol_2 = S1(3);
+  let tint_symbol_3 = array<S1, 3u>(tint_symbol, tint_symbol_1, tint_symbol_2);
+  let tint_symbol_4 = S2(tint_symbol_3);
+  var x = tint_symbol_4.a[1].a;
+}
+
+struct S2 {
+  a : array<S1, 3u>,
+}
+
+struct S1 {
+  a : i32,
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, NoChangeOnVarDecl) {
+    auto* src = R"(
+struct S {
+  a : i32,
+  b : f32,
+  c : i32,
+}
+
+fn f() {
+  var local_arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+  var local_str = S(1, 2.0, 3);
+}
+
+let module_arr : array<f32, 4u> = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+
+let module_str : S = S(1, 2.0, 3);
+)";
+
+    auto* expect = src;
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, NoChangeOnVarDecl_OutOfOrder) {
+    auto* src = R"(
+fn f() {
+  var local_arr = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+  var local_str = S(1, 2.0, 3);
+}
+
+let module_str : S = S(1, 2.0, 3);
+
+struct S {
+  a : i32,
+  b : f32,
+  c : i32,
+}
+
+let module_arr : array<f32, 4u> = array<f32, 4u>(0.0, 1.0, 2.0, 3.0);
+)";
+
+    auto* expect = src;
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteInitializersToLetTest, ForLoopShadowing) {
+    auto* src = R"(
+fn X() {
+  var i = 10;
+  for(var f = 0; f < 10; f = f + array<i32, 1u>(i)[0]) {
+      var i = 20;
+  }
+}
+
+fn Y() {
+  var i = 10;
+  for(var f = 0; f < array<i32, 1u>(i)[0]; f = f + 1) {
+      var i = 20;
+  }
+}
+
+fn Z() {
+  var i = 10;
+  for(var f = array<i32, 1u>(i)[0]; f < 10; f = f + 1) {
+      var i = 20;
+  }
+}
+)";
+
+    auto* expect = R"(
+fn X() {
+  var i = 10;
+  {
+    var f = 0;
+    loop {
+      if (!((f < 10))) {
+        break;
+      }
+      {
+        var i = 20;
+      }
+
+      continuing {
+        let tint_symbol = array<i32, 1u>(i);
+        f = (f + tint_symbol[0]);
+      }
+    }
+  }
+}
+
+fn Y() {
+  var i = 10;
+  {
+    var f = 0;
+    loop {
+      let tint_symbol_1 = array<i32, 1u>(i);
+      if (!((f < tint_symbol_1[0]))) {
+        break;
+      }
+      {
+        var i = 20;
+      }
+
+      continuing {
+        f = (f + 1);
+      }
+    }
+  }
+}
+
+fn Z() {
+  var i = 10;
+  let tint_symbol_2 = array<i32, 1u>(i);
+  for(var f = tint_symbol_2[0]; (f < 10); f = (f + 1)) {
+    var i = 20;
+  }
+}
+)";
+
+    DataMap data;
+    auto got = Run<PromoteInitializersToLet>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace tint::transform
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 6603ce7..d2cb602 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -58,7 +58,7 @@
 #include "src/tint/transform/fold_trivial_single_use_lets.h"
 #include "src/tint/transform/loop_to_for_loop.h"
 #include "src/tint/transform/manager.h"
-#include "src/tint/transform/promote_initializers_to_const_var.h"
+#include "src/tint/transform/promote_initializers_to_let.h"
 #include "src/tint/transform/promote_side_effects_to_decl.h"
 #include "src/tint/transform/remove_phonies.h"
 #include "src/tint/transform/renamer.h"
@@ -226,7 +226,7 @@
         options.binding_points, options.access_controls, options.allow_collisions);
     manager.Add<transform::BindingRemapper>();
 
-    manager.Add<transform::PromoteInitializersToConstVar>();
+    manager.Add<transform::PromoteInitializersToLet>();
     manager.Add<transform::AddEmptyEntryPoint>();
     manager.Add<transform::AddSpirvBlockAttribute>();
     data.Add<transform::CanonicalizeEntryPointIO::Config>(
@@ -1773,7 +1773,7 @@
             // to a shader-creation time constant value, and this can be removed.
             if (auto constant = sem->ConstantValue()) {
                 // We do not want to inline array constants, as this will undo the work of
-                // PromoteInitializersToConstVar, which ensures that arrays are declarated in 'let's
+                // PromoteInitializersToLet, which ensures that arrays are declarated in 'let's
                 // before their usage.
                 if (!constant.Type()->Is<sem::Array>()) {
                     return EmitConstant(out, constant);
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index c2a03b8..8139e6b 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -60,7 +60,7 @@
 #include "src/tint/transform/loop_to_for_loop.h"
 #include "src/tint/transform/manager.h"
 #include "src/tint/transform/num_workgroups_from_uniform.h"
-#include "src/tint/transform/promote_initializers_to_const_var.h"
+#include "src/tint/transform/promote_initializers_to_let.h"
 #include "src/tint/transform/promote_side_effects_to_decl.h"
 #include "src/tint/transform/remove_continue_in_switch.h"
 #include "src/tint/transform/remove_phonies.h"
@@ -231,7 +231,7 @@
     // DecomposeMemoryAccess special-cases the arrayLength() intrinsic, which
     // will be transformed by CalculateArrayLength
     manager.Add<transform::CalculateArrayLength>();
-    manager.Add<transform::PromoteInitializersToConstVar>();
+    manager.Add<transform::PromoteInitializersToLet>();
 
     manager.Add<transform::RemoveContinueInSwitch>();
 
@@ -2618,7 +2618,7 @@
             // to a shader-creation time constant value, and this can be removed.
             if (auto constant = sem->ConstantValue()) {
                 // We do not want to inline array constants, as this will undo the work of
-                // PromoteInitializersToConstVar, which ensures that arrays are declarated in 'let's
+                // PromoteInitializersToLet, which ensures that arrays are declarated in 'let's
                 // before their usage.
                 if (!constant.Type()->Is<sem::Array>()) {
                     return EmitConstant(out, constant);
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index a13e58a..dd25dcb 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -64,7 +64,7 @@
 #include "src/tint/transform/expand_compound_assignment.h"
 #include "src/tint/transform/manager.h"
 #include "src/tint/transform/module_scope_var_to_entry_point_param.h"
-#include "src/tint/transform/promote_initializers_to_const_var.h"
+#include "src/tint/transform/promote_initializers_to_let.h"
 #include "src/tint/transform/promote_side_effects_to_decl.h"
 #include "src/tint/transform/remove_phonies.h"
 #include "src/tint/transform/simplify_pointers.h"
@@ -204,7 +204,7 @@
     manager.Add<transform::ExpandCompoundAssignment>();
     manager.Add<transform::PromoteSideEffectsToDecl>();
     manager.Add<transform::UnwindDiscardFunctions>();
-    manager.Add<transform::PromoteInitializersToConstVar>();
+    manager.Add<transform::PromoteInitializersToLet>();
 
     manager.Add<transform::VectorizeScalarMatrixConstructors>();
     manager.Add<transform::RemovePhonies>();
@@ -1709,7 +1709,7 @@
             // to a shader-creation time constant value, and this can be removed.
             if (auto constant = sem->ConstantValue()) {
                 // We do not want to inline array constants, as this will undo the work of
-                // PromoteInitializersToConstVar, which ensures that arrays are declarated in 'let's
+                // PromoteInitializersToLet, which ensures that arrays are declarated in 'let's
                 // before their usage.
                 if (!constant.Type()->Is<sem::Array>()) {
                     return EmitConstant(out, constant);