tint: Error if statically indexing out of bounds.

Fixed: tint:1665
Change-Id: Icd5f24f3b4d6ebbdc18b536dc426da92558a3a4b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/101183
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/docs/tint/origin-trial-changes.md b/docs/tint/origin-trial-changes.md
index 93c53e5..6c9e632 100644
--- a/docs/tint/origin-trial-changes.md
+++ b/docs/tint/origin-trial-changes.md
@@ -7,6 +7,10 @@
 * `array()` constructor can now infer type and count. [tint:1628](crbug.com/tint/1628)
 * `static_assert` statement has been added. [tint:1625](crbug.com/tint/1625)
 
+### Breaking changes
+
+* Indexing an array, vector or matrix with a compile-time expression that's out-of-bounds is now an error [tint:1665](crbug.com/tint/1665)
+
 ### Deprecated Features
 
 * The list of reserved words has been sync'd to the WGSL specification. [tint:1463](crbug.com/tint/1463)
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index a0974e4..8e46f9a 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -899,27 +899,28 @@
 
 ConstEval::ConstantResult ConstEval::Index(const sem::Expression* obj_expr,
                                            const sem::Expression* idx_expr) {
-    auto obj_val = obj_expr->ConstantValue();
-    if (!obj_val) {
-        return nullptr;
-    }
-
     auto idx_val = idx_expr->ConstantValue();
     if (!idx_val) {
         return nullptr;
     }
 
     uint32_t el_count = 0;
-    sem::Type::ElementOf(obj_val->Type(), &el_count);
+    sem::Type::ElementOf(obj_expr->Type()->UnwrapRef(), &el_count);
 
     AInt idx = idx_val->As<AInt>();
-    if (idx < 0 || idx >= el_count) {
-        auto clamped = std::min<AInt::type>(std::max<AInt::type>(idx, 0), el_count - 1);
-        AddWarning("index " + std::to_string(idx) + " out of bounds [0.." +
-                       std::to_string(el_count - 1) + "]. Clamping index to " +
-                       std::to_string(clamped),
-                   idx_expr->Declaration()->source);
-        idx = clamped;
+    if (idx < 0 || (el_count > 0 && idx >= el_count)) {
+        std::string range;
+        if (el_count > 0) {
+            range = " [0.." + std::to_string(el_count - 1) + "]";
+        }
+        AddError("index " + std::to_string(idx) + " out of bounds" + range,
+                 idx_expr->Declaration()->source);
+        return utils::Failure;
+    }
+
+    auto obj_val = obj_expr->ConstantValue();
+    if (!obj_val) {
+        return nullptr;
     }
 
     return obj_val->Index(static_cast<size_t>(idx));
diff --git a/src/tint/resolver/const_eval_test.cc b/src/tint/resolver/const_eval_test.cc
index 1d5b7d25..f06fa32 100644
--- a/src/tint/resolver/const_eval_test.cc
+++ b/src/tint/resolver/const_eval_test.cc
@@ -2498,34 +2498,16 @@
     auto* expr = IndexAccessor(vec3<i32>(1_i, 2_i, 3_i), Expr(Source{{12, 34}}, 3_i));
     WrapInFunction(expr);
 
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-    EXPECT_EQ(r()->error(), "12:34 warning: index 3 out of bounds [0..2]. Clamping index to 2");
-
-    auto* sem = Sem().Get(expr);
-    ASSERT_NE(sem, nullptr);
-    ASSERT_TRUE(sem->Type()->Is<sem::I32>());
-    EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->As<i32>(), 3_i);
+    EXPECT_FALSE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(), "12:34 error: index 3 out of bounds [0..2]");
 }
 
 TEST_F(ResolverConstEvalTest, Vec3_Index_OOB_Low) {
     auto* expr = IndexAccessor(vec3<i32>(1_i, 2_i, 3_i), Expr(Source{{12, 34}}, -3_i));
     WrapInFunction(expr);
 
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-    EXPECT_EQ(r()->error(), "12:34 warning: index -3 out of bounds [0..2]. Clamping index to 0");
-
-    auto* sem = Sem().Get(expr);
-    ASSERT_NE(sem, nullptr);
-    ASSERT_TRUE(sem->Type()->Is<sem::I32>());
-    EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->As<i32>(), 1_i);
+    EXPECT_FALSE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(), "12:34 error: index -3 out of bounds [0..2]");
 }
 
 TEST_F(ResolverConstEvalTest, Vec3_Swizzle_Scalar) {
@@ -2616,25 +2598,8 @@
         Expr(Source{{12, 34}}, 3_i));
     WrapInFunction(expr);
 
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-    EXPECT_EQ(r()->error(), "12:34 warning: index 3 out of bounds [0..2]. Clamping index to 2");
-
-    auto* sem = Sem().Get(expr);
-    ASSERT_NE(sem, nullptr);
-    auto* vec = sem->Type()->As<sem::Vector>();
-    ASSERT_NE(vec, nullptr);
-    EXPECT_EQ(vec->Width(), 2u);
-    EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->Index(0)->As<f32>(), 5._a);
-
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->Index(1)->As<f32>(), 6._a);
+    EXPECT_FALSE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(), "12:34 error: index 3 out of bounds [0..2]");
 }
 
 TEST_F(ResolverConstEvalTest, Mat3x2_Index_OOB_Low) {
@@ -2643,25 +2608,8 @@
         Expr(Source{{12, 34}}, -3_i));
     WrapInFunction(expr);
 
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-    EXPECT_EQ(r()->error(), "12:34 warning: index -3 out of bounds [0..2]. Clamping index to 0");
-
-    auto* sem = Sem().Get(expr);
-    ASSERT_NE(sem, nullptr);
-    auto* vec = sem->Type()->As<sem::Vector>();
-    ASSERT_NE(vec, nullptr);
-    EXPECT_EQ(vec->Width(), 2u);
-    EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->Index(0)->As<f32>(), 1._a);
-
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->Index(1)->As<f32>(), 2._a);
+    EXPECT_FALSE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(), "12:34 error: index -3 out of bounds [0..2]");
 }
 
 TEST_F(ResolverConstEvalTest, Array_vec3_f32_Index) {
@@ -2702,31 +2650,8 @@
                                Expr(Source{{12, 34}}, 2_i));
     WrapInFunction(expr);
 
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-    EXPECT_EQ(r()->error(), "12:34 warning: index 2 out of bounds [0..1]. Clamping index to 1");
-
-    auto* sem = Sem().Get(expr);
-    ASSERT_NE(sem, nullptr);
-    auto* vec = sem->Type()->As<sem::Vector>();
-    ASSERT_NE(vec, nullptr);
-    EXPECT_TRUE(vec->type()->Is<sem::F32>());
-    EXPECT_EQ(vec->Width(), 3u);
-    EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->Index(0)->As<f32>(), 4_f);
-
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->Index(1)->As<f32>(), 5_f);
-
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->Index(2)->As<f32>(), 6_f);
+    EXPECT_FALSE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(), "12:34 error: index 2 out of bounds [0..1]");
 }
 
 TEST_F(ResolverConstEvalTest, Array_vec3_f32_Index_OOB_Low) {
@@ -2735,34 +2660,18 @@
                                Expr(Source{{12, 34}}, -2_i));
     WrapInFunction(expr);
 
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-    EXPECT_EQ(r()->error(), "12:34 warning: index -2 out of bounds [0..1]. Clamping index to 0");
+    EXPECT_FALSE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(), "12:34 error: index -2 out of bounds [0..1]");
+}
 
-    auto* sem = Sem().Get(expr);
-    ASSERT_NE(sem, nullptr);
-    auto* vec = sem->Type()->As<sem::Vector>();
-    ASSERT_NE(vec, nullptr);
-    EXPECT_TRUE(vec->type()->Is<sem::F32>());
-    EXPECT_EQ(vec->Width(), 3u);
-    EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->AllZero());
+TEST_F(ResolverConstEvalTest, RuntimeArray_vec3_f32_Index_OOB_Low) {
+    auto* sb = GlobalVar("sb", ty.array(ty.vec3<f32>()), Group(0_a), Binding(0_a),
+                         ast::StorageClass::kStorage);
+    auto* expr = IndexAccessor(sb, Expr(Source{{12, 34}}, -2_i));
+    WrapInFunction(expr);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->Index(0)->As<f32>(), 1_f);
-
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->Index(1)->As<f32>(), 2_f);
-
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
-    EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
-    EXPECT_EQ(sem->ConstantValue()->Index(2)->As<f32>(), 3_f);
+    EXPECT_FALSE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(), "12:34 error: index -2 out of bounds");
 }
 
 TEST_F(ResolverConstEvalTest, ChainedIndex) {
@@ -2861,105 +2770,6 @@
     }
 }
 
-TEST_F(ResolverConstEvalTest, ChainedIndex_OOB) {
-    auto* arr_expr = Construct(ty.array(ty.mat2x3<f32>(), 2_u),        // array<mat2x3<f32>, 2u>
-                               mat2x3<f32>(vec3<f32>(1_f, 2_f, 3_f),   //
-                                           vec3<f32>(4_f, 5_f, 6_f)),  //
-                               mat2x3<f32>(vec3<f32>(7_f, 8_f, 9_f),   //
-                                           vec3<f32>(10_f, 11_f, 12_f)));
-
-    auto* mat_expr = IndexAccessor(arr_expr, Expr(Source{{1, 2}}, -3_i));  // arr[3]
-    auto* vec_expr = IndexAccessor(mat_expr, Expr(Source{{3, 4}}, -2_i));  // arr[3][-2]
-    auto* f32_expr = IndexAccessor(vec_expr, Expr(Source{{5, 6}}, 4_i));   // arr[3][-2][4]
-    WrapInFunction(f32_expr);
-
-    EXPECT_TRUE(r()->Resolve()) << r()->error();
-    EXPECT_EQ(r()->error(), R"(1:2 warning: index -3 out of bounds [0..1]. Clamping index to 0
-3:4 warning: index -2 out of bounds [0..1]. Clamping index to 0
-5:6 warning: index 4 out of bounds [0..2]. Clamping index to 2)");
-
-    {
-        auto* mat = Sem().Get(mat_expr);
-        EXPECT_NE(mat, nullptr);
-        auto* ty = mat->Type()->As<sem::Matrix>();
-        ASSERT_NE(mat->Type(), nullptr);
-        EXPECT_TRUE(ty->ColumnType()->Is<sem::Vector>());
-        EXPECT_EQ(ty->columns(), 2u);
-        EXPECT_EQ(ty->rows(), 3u);
-        EXPECT_EQ(mat->ConstantValue()->Type(), mat->Type());
-        EXPECT_FALSE(mat->ConstantValue()->AllEqual());
-        EXPECT_FALSE(mat->ConstantValue()->AnyZero());
-        EXPECT_FALSE(mat->ConstantValue()->AllZero());
-
-        EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(0)->AllEqual());
-        EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(0)->AnyZero());
-        EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(0)->AllZero());
-        EXPECT_EQ(mat->ConstantValue()->Index(0)->Index(0)->As<f32>(), 1_f);
-
-        EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(1)->AllEqual());
-        EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(1)->AnyZero());
-        EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(1)->AllZero());
-        EXPECT_EQ(mat->ConstantValue()->Index(0)->Index(1)->As<f32>(), 2_f);
-
-        EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(2)->AllEqual());
-        EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(2)->AnyZero());
-        EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(2)->AllZero());
-        EXPECT_EQ(mat->ConstantValue()->Index(0)->Index(2)->As<f32>(), 3_f);
-
-        EXPECT_TRUE(mat->ConstantValue()->Index(1)->Index(0)->AllEqual());
-        EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(0)->AnyZero());
-        EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(0)->AllZero());
-        EXPECT_EQ(mat->ConstantValue()->Index(1)->Index(0)->As<f32>(), 4_f);
-
-        EXPECT_TRUE(mat->ConstantValue()->Index(1)->Index(1)->AllEqual());
-        EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(1)->AnyZero());
-        EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(1)->AllZero());
-        EXPECT_EQ(mat->ConstantValue()->Index(1)->Index(1)->As<f32>(), 5_f);
-
-        EXPECT_TRUE(mat->ConstantValue()->Index(1)->Index(2)->AllEqual());
-        EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(2)->AnyZero());
-        EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(2)->AllZero());
-        EXPECT_EQ(mat->ConstantValue()->Index(1)->Index(2)->As<f32>(), 6_f);
-    }
-    {
-        auto* vec = Sem().Get(vec_expr);
-        EXPECT_NE(vec, nullptr);
-        auto* ty = vec->Type()->As<sem::Vector>();
-        ASSERT_NE(vec->Type(), nullptr);
-        EXPECT_TRUE(ty->type()->Is<sem::F32>());
-        EXPECT_EQ(ty->Width(), 3u);
-        EXPECT_EQ(vec->ConstantValue()->Type(), vec->Type());
-        EXPECT_FALSE(vec->ConstantValue()->AllEqual());
-        EXPECT_FALSE(vec->ConstantValue()->AnyZero());
-        EXPECT_FALSE(vec->ConstantValue()->AllZero());
-
-        EXPECT_TRUE(vec->ConstantValue()->Index(0)->AllEqual());
-        EXPECT_FALSE(vec->ConstantValue()->Index(0)->AnyZero());
-        EXPECT_FALSE(vec->ConstantValue()->Index(0)->AllZero());
-        EXPECT_EQ(vec->ConstantValue()->Index(0)->As<f32>(), 1_f);
-
-        EXPECT_TRUE(vec->ConstantValue()->Index(1)->AllEqual());
-        EXPECT_FALSE(vec->ConstantValue()->Index(1)->AnyZero());
-        EXPECT_FALSE(vec->ConstantValue()->Index(1)->AllZero());
-        EXPECT_EQ(vec->ConstantValue()->Index(1)->As<f32>(), 2_f);
-
-        EXPECT_TRUE(vec->ConstantValue()->Index(2)->AllEqual());
-        EXPECT_FALSE(vec->ConstantValue()->Index(2)->AnyZero());
-        EXPECT_FALSE(vec->ConstantValue()->Index(2)->AllZero());
-        EXPECT_EQ(vec->ConstantValue()->Index(2)->As<f32>(), 3_f);
-    }
-    {
-        auto* f = Sem().Get(f32_expr);
-        EXPECT_NE(f, nullptr);
-        EXPECT_TRUE(f->Type()->Is<sem::F32>());
-        EXPECT_EQ(f->ConstantValue()->Type(), f->Type());
-        EXPECT_TRUE(f->ConstantValue()->AllEqual());
-        EXPECT_FALSE(f->ConstantValue()->AnyZero());
-        EXPECT_FALSE(f->ConstantValue()->AllZero());
-        EXPECT_EQ(f->ConstantValue()->As<f32>(), 3_f);
-    }
-}
-
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // Member accessing
 ////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/transform/robustness.cc b/src/tint/transform/robustness.cc
index beb1108..e662e56 100644
--- a/src/tint/transform/robustness.cc
+++ b/src/tint/transform/robustness.cc
@@ -22,6 +22,7 @@
 #include "src/tint/sem/block_statement.h"
 #include "src/tint/sem/call.h"
 #include "src/tint/sem/expression.h"
+#include "src/tint/sem/index_accessor_expression.h"
 #include "src/tint/sem/reference.h"
 #include "src/tint/sem/statement.h"
 
@@ -48,150 +49,80 @@
 
     /// Apply bounds clamping to array, vector and matrix indexing
     /// @param expr the array, vector or matrix index expression
-    /// @return the clamped replacement expression, or nullptr if `expr` should be
-    /// cloned without changes.
+    /// @return the clamped replacement expression, or nullptr if `expr` should be cloned without
+    /// changes.
     const ast::IndexAccessorExpression* Transform(const ast::IndexAccessorExpression* expr) {
-        auto* ret_type = ctx.src->Sem().Get(expr->object)->Type();
+        auto* sem =
+            ctx.src->Sem().Get(expr)->UnwrapMaterialize()->As<sem::IndexAccessorExpression>();
+        auto* ret_type = sem->Type();
 
         auto* ref = ret_type->As<sem::Reference>();
         if (ref && omitted_classes.count(ref->StorageClass()) != 0) {
             return nullptr;
         }
 
-        auto* ret_unwrapped = ret_type->UnwrapRef();
-
         ProgramBuilder& b = *ctx.dst;
 
-        struct Value {
-            const ast::Expression* expr = nullptr;  // If null, then is a constant
-            union {
-                uint32_t u32 = 0;  // use if is_signed == false
-                int32_t i32;       // use if is_signed == true
-            };
-            bool is_signed = false;
+        // idx return the cloned index expression, as a u32.
+        auto idx = [&]() -> const ast::Expression* {
+            auto* i = ctx.Clone(expr->index);
+            if (sem->Index()->Type()->UnwrapRef()->is_signed_integer_scalar()) {
+                return b.Construct(b.ty.u32(), i);  // u32(idx)
+            }
+            return i;
         };
 
-        Value size;              // size of the array, vector or matrix
-        size.is_signed = false;  // size is always unsigned
-        if (auto* vec = ret_unwrapped->As<sem::Vector>()) {
-            size.u32 = vec->Width();
-
-        } else if (auto* arr = ret_unwrapped->As<sem::Array>()) {
-            size.u32 = arr->Count();
-        } else if (auto* mat = ret_unwrapped->As<sem::Matrix>()) {
-            // The row accessor would have been an embedded index accessor and already
-            // handled, so we just need to do columns here.
-            size.u32 = mat->columns();
-        } else {
-            return nullptr;
-        }
-
-        if (size.u32 == 0) {
-            if (!ret_unwrapped->Is<sem::Array>()) {
-                b.Diagnostics().add_error(diag::System::Transform, "invalid 0 sized non-array",
-                                          expr->source);
-                return nullptr;
-            }
-            // Runtime sized array
-            auto* arr = ctx.Clone(expr->object);
-            size.expr = b.Call("arrayLength", b.AddressOf(arr));
-        }
-
-        // Calculate the maximum possible index value (size-1u)
-        // Size must be positive (non-zero), so we can safely subtract 1 here
-        // without underflow.
-        Value limit;
-        limit.is_signed = false;  // Like size, limit is always unsigned.
-        if (size.expr) {
-            // Dynamic size
-            limit.expr = b.Sub(size.expr, 1_u);
-        } else {
-            // Constant size
-            limit.u32 = size.u32 - 1u;
-        }
-
-        Value idx;  // index value
-
-        auto* idx_sem = ctx.src->Sem().Get(expr->index);
-        auto* idx_ty = idx_sem->Type()->UnwrapRef();
-        if (!idx_ty->IsAnyOf<sem::I32, sem::U32>()) {
-            TINT_ICE(Transform, b.Diagnostics())
-                << "index must be u32 or i32, got " << idx_sem->Type()->TypeInfo().name;
-            return nullptr;
-        }
-
-        if (auto* idx_constant = idx_sem->ConstantValue()) {
-            // Constant value index
-            auto val = std::get<AInt>(idx_constant->Value());
-            if (idx_constant->Type()->Is<sem::I32>()) {
-                idx.i32 = static_cast<int32_t>(val);
-                idx.is_signed = true;
-            } else if (idx_constant->Type()->Is<sem::U32>()) {
-                idx.u32 = static_cast<uint32_t>(val);
-                idx.is_signed = false;
-            } else {
-                TINT_ICE(Transform, b.Diagnostics()) << "unsupported constant value for accessor "
-                                                     << idx_constant->Type()->TypeInfo().name;
-                return nullptr;
-            }
-        } else {
-            // Dynamic value index
-            idx.expr = ctx.Clone(expr->index);
-            idx.is_signed = idx_ty->Is<sem::I32>();
-        }
-
-        // Clamp the index so that it cannot exceed limit.
-        if (idx.expr || limit.expr) {
-            // One of, or both of idx and limit are non-constant.
-
-            // If the index is signed, cast it to a u32 (with clamping if constant).
-            if (idx.is_signed) {
-                if (idx.expr) {
-                    // We don't use a max(idx, 0) here, as that incurs a runtime
-                    // performance cost, and if the unsigned value will be clamped by
-                    // limit, resulting in a value between [0..limit)
-                    idx.expr = b.Construct<u32>(idx.expr);
-                    idx.is_signed = false;
-                } else {
-                    idx.u32 = static_cast<uint32_t>(std::max(idx.i32, 0));
-                    idx.is_signed = false;
+        auto* clamped_idx = Switch(
+            sem->Object()->Type()->UnwrapRef(),  //
+            [&](const sem::Vector* vec) -> const ast::Expression* {
+                if (sem->Index()->ConstantValue()) {
+                    // Index and size is constant.
+                    // Validation will have rejected any OOB accesses.
+                    return nullptr;
                 }
-            }
 
-            // Convert idx and limit to expressions, so we can emit `min(idx, limit)`.
-            if (!idx.expr) {
-                idx.expr = b.Expr(u32(idx.u32));
-            }
-            if (!limit.expr) {
-                limit.expr = b.Expr(u32(limit.u32));
-            }
+                return b.Call("min", idx(), u32(vec->Width() - 1u));
+            },
+            [&](const sem::Matrix* mat) -> const ast::Expression* {
+                if (sem->Index()->ConstantValue()) {
+                    // Index and size is constant.
+                    // Validation will have rejected any OOB accesses.
+                    return nullptr;
+                }
 
-            // Perform the clamp with `min(idx, limit)`
-            idx.expr = b.Call("min", idx.expr, limit.expr);
-        } else {
-            // Both idx and max are constant.
-            if (idx.is_signed) {
-                // The index is signed. Calculate limit as signed.
-                int32_t signed_limit = static_cast<int32_t>(
-                    std::min<uint32_t>(limit.u32, std::numeric_limits<int32_t>::max()));
-                idx.i32 = std::max(idx.i32, 0);
-                idx.i32 = std::min(idx.i32, signed_limit);
-            } else {
-                // The index is unsigned.
-                idx.u32 = std::min(idx.u32, limit.u32);
-            }
+                return b.Call("min", idx(), u32(mat->columns() - 1u));
+            },
+            [&](const sem::Array* arr) -> const ast::Expression* {
+                const ast::Expression* max = nullptr;
+                if (arr->IsRuntimeSized()) {
+                    // Size is unknown until runtime.
+                    // Must clamp, even if the index is constant.
+                    auto* arr_ptr = b.AddressOf(ctx.Clone(expr->object));
+                    max = b.Sub(b.Call("arrayLength", arr_ptr), 1_u);
+                } else {
+                    if (sem->Index()->ConstantValue()) {
+                        // Index and size is constant.
+                        // Validation will have rejected any OOB accesses.
+                        return nullptr;
+                    }
+                    max = b.Expr(u32(arr->Count() - 1u));
+                }
+                return b.Call("min", idx(), max);
+            },
+            [&](Default) {
+                TINT_ICE(Transform, b.Diagnostics())
+                    << "unhandled object type in robustness of array index: "
+                    << ctx.src->FriendlyName(ret_type->UnwrapRef());
+                return nullptr;
+            });
+
+        if (!clamped_idx) {
+            return nullptr;  // Clamping not needed
         }
 
-        // Convert idx to an expression, so we can emit the new accessor.
-        if (!idx.expr) {
-            idx.expr = idx.is_signed ? static_cast<const ast::Expression*>(b.Expr(i32(idx.i32)))
-                                     : static_cast<const ast::Expression*>(b.Expr(u32(idx.u32)));
-        }
-
-        // Clone arguments outside of create() call to have deterministic ordering
         auto src = ctx.Clone(expr->source);
         auto* obj = ctx.Clone(expr->object);
-        return b.IndexAccessor(src, obj, idx.expr);
+        return b.IndexAccessor(src, obj, clamped_idx);
     }
 
     /// @param type builtin type
diff --git a/src/tint/transform/robustness_test.cc b/src/tint/transform/robustness_test.cc
index 8dab595..e3d08f0 100644
--- a/src/tint/transform/robustness_test.cc
+++ b/src/tint/transform/robustness_test.cc
@@ -25,20 +25,18 @@
     auto* src = R"(
 var<private> a : array<f32, 3>;
 
-let c : u32 = 1u;
-
 fn f() {
-  let b : f32 = a[c];
+  let l : u32 = 1u;
+  let b : f32 = a[l];
 }
 )";
 
     auto* expect = R"(
 var<private> a : array<f32, 3>;
 
-const c : u32 = 1u;
-
 fn f() {
-  let b : f32 = a[1u];
+  let l : u32 = 1u;
+  let b : f32 = a[min(l, 2u)];
 }
 )";
 
@@ -47,6 +45,30 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(RobustnessTest, Array_Let_Idx_Clamp_OutOfOrder) {
+    auto* src = R"(
+fn f() {
+  let c : u32 = 1u;
+  let b : f32 = a[c];
+}
+
+var<private> a : array<f32, 3>;
+)";
+
+    auto* expect = R"(
+fn f() {
+  let c : u32 = 1u;
+  let b : f32 = a[min(c, 2u)];
+}
+
+var<private> a : array<f32, 3>;
+)";
+
+    auto got = Run<Robustness>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(RobustnessTest, Array_Const_Idx_Clamp) {
     auto* src = R"(
 var<private> a : array<f32, 3>;
@@ -64,34 +86,8 @@
 const c : u32 = 1u;
 
 fn f() {
-  let b : f32 = a[1u];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Let_Idx_Clamp_OutOfOrder) {
-    auto* src = R"(
-fn f() {
   let b : f32 = a[c];
 }
-
-let c : u32 = 1u;
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  let b : f32 = a[1u];
-}
-
-const c : u32 = 1u;
-
-var<private> a : array<f32, 3>;
 )";
 
     auto got = Run<Robustness>(src);
@@ -112,7 +108,7 @@
 
     auto* expect = R"(
 fn f() {
-  let b : f32 = a[1u];
+  let b : f32 = a[c];
 }
 
 const c : u32 = 1u;
@@ -281,94 +277,6 @@
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Array_Idx_Negative) {
-    auto* src = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[-1];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[0i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_Negative_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[-1];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[0i];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_OutOfBounds) {
-    auto* src = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[3];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : array<f32, 3>;
-
-fn f() {
-  var b : f32 = a[2i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Array_Idx_OutOfBounds_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[3];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[2i];
-}
-
-var<private> a : array<f32, 3>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
 // TODO(crbug.com/tint/1177) - Validation currently forbids arrays larger than
 // 0xffffffff. If WGSL supports 64-bit indexing, re-enable this test.
 TEST_F(RobustnessTest, DISABLED_LargeArrays_Idx) {
@@ -545,50 +453,6 @@
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Scalar) {
-    auto* src = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a.xy[2];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a.xy[1i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Swizzle_Idx_Scalar_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a.xy[2];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a.xy[1i];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
 TEST_F(RobustnessTest, Vector_Swizzle_Idx_Var) {
     auto* src = R"(
 var<private> a : vec3<f32>;
@@ -693,94 +557,6 @@
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Vector_Idx_Negative) {
-    auto* src = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a[-1];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a[0i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_Negative_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[-1];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[0i];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_OutOfBounds) {
-    auto* src = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a[3];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : vec3<f32>;
-
-fn f() {
-  var b : f32 = a[2i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Vector_Idx_OutOfBounds_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[3];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[2i];
-}
-
-var<private> a : vec3<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
 TEST_F(RobustnessTest, Matrix_Idx_Scalar) {
     auto* src = R"(
 var<private> a : mat3x2<f32>;
@@ -842,7 +618,7 @@
 var<private> c : i32;
 
 fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1i];
+  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1];
 }
 )";
 
@@ -864,7 +640,7 @@
 
     auto* expect = R"(
 fn f() {
-  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1i];
+  var b : f32 = a[min(u32(((c + 2) - 3)), 2u)][1];
 }
 
 var<private> c : i32;
@@ -894,7 +670,7 @@
 var<private> c : i32;
 
 fn f() {
-  var b : f32 = a[1i][min(u32(((c + 2) - 3)), 1u)];
+  var b : f32 = a[1][min(u32(((c + 2) - 3)), 1u)];
 }
 )";
 
@@ -916,7 +692,7 @@
 
     auto* expect = R"(
 fn f() {
-  var b : f32 = a[1i][min(u32(((c + 2) - 3)), 1u)];
+  var b : f32 = a[1][min(u32(((c + 2) - 3)), 1u)];
 }
 
 var<private> c : i32;
@@ -929,182 +705,6 @@
     EXPECT_EQ(expect, str(got));
 }
 
-TEST_F(RobustnessTest, Matrix_Idx_Negative_Column) {
-    auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[-1][1];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[0i][1i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Negative_Column_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[-1][1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[0i][1i];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Negative_Row) {
-    auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2][-1];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2i][0i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_Negative_Row_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[2][-1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[2i][0i];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Column) {
-    auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[5][1];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2i][1i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Column_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[5][1];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[2i][1i];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Row) {
-    auto* src = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2][5];
-}
-)";
-
-    auto* expect = R"(
-var<private> a : mat3x2<f32>;
-
-fn f() {
-  var b : f32 = a[2i][1i];
-}
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
-TEST_F(RobustnessTest, Matrix_Idx_OutOfBounds_Row_OutOfOrder) {
-    auto* src = R"(
-fn f() {
-  var b : f32 = a[2][5];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto* expect = R"(
-fn f() {
-  var b : f32 = a[2i][1i];
-}
-
-var<private> a : mat3x2<f32>;
-)";
-
-    auto got = Run<Robustness>(src);
-
-    EXPECT_EQ(expect, str(got));
-}
-
 // TODO(dsinclair): Implement when constant_id exists
 TEST_F(RobustnessTest, DISABLED_Vector_Constant_Id_Clamps) {
     // @id(1300) override idx : i32;
@@ -1163,7 +763,7 @@
 @group(0) @binding(0) var<storage, read> s : S;
 
 fn f() {
-  var d : f32 = s.b[min(25u, (arrayLength(&(s.b)) - 1u))];
+  var d : f32 = s.b[min(u32(25), (arrayLength(&(s.b)) - 1u))];
 }
 )";
 
@@ -1188,7 +788,7 @@
 
     auto* expect = R"(
 fn f() {
-  var d : f32 = s.b[min(25u, (arrayLength(&(s.b)) - 1u))];
+  var d : f32 = s.b[min(u32(25), (arrayLength(&(s.b)) - 1u))];
 }
 
 @group(0) @binding(0) var<storage, read> s : S;
@@ -1464,7 +1064,7 @@
 const c : u32 = 1u;
 
 fn f() {
-  let b : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
+  let b : f32 = s.b[min(c, (arrayLength(&(s.b)) - 1u))];
   let x : i32 = min(1, 2);
   let y : u32 = arrayLength(&(s.b));
 }
@@ -1477,112 +1077,79 @@
 
 const char* kOmitSourceShader = R"(
 struct S {
-  a : array<f32, 4>,
-  b : array<f32>,
+  vector : vec3<f32>,
+  fixed_arr : array<f32, 4>,
+  runtime_arr : array<f32>,
 };
 @group(0) @binding(0) var<storage, read> s : S;
 
-type UArr = array<vec4<f32>, 4>;
 struct U {
-  a : UArr,
+  vector : vec4<f32>,
+  fixed_arr : array<vec4<f32>, 4>,
 };
 @group(1) @binding(0) var<uniform> u : U;
 
 fn f() {
-  // Signed
-  var i32_sa1 : f32 = s.a[4];
-  var i32_sa2 : f32 = s.a[1];
-  var i32_sa3 : f32 = s.a[0];
-  var i32_sa4 : f32 = s.a[-1];
-  var i32_sa5 : f32 = s.a[-4];
-
-  var i32_sb1 : f32 = s.b[4];
-  var i32_sb2 : f32 = s.b[1];
-  var i32_sb3 : f32 = s.b[0];
-  var i32_sb4 : f32 = s.b[-1];
-  var i32_sb5 : f32 = s.b[-4];
-
-  var i32_ua1 : f32 = u.a[4].x;
-  var i32_ua2 : f32 = u.a[1].x;
-  var i32_ua3 : f32 = u.a[0].x;
-  var i32_ua4 : f32 = u.a[-1].x;
-  var i32_ua5 : f32 = u.a[-4].x;
-
-  // Unsigned
-  var u32_sa1 : f32 = s.a[0u];
-  var u32_sa2 : f32 = s.a[1u];
-  var u32_sa3 : f32 = s.a[3u];
-  var u32_sa4 : f32 = s.a[4u];
-  var u32_sa5 : f32 = s.a[10u];
-  var u32_sa6 : f32 = s.a[100u];
-
-  var u32_sb1 : f32 = s.b[0u];
-  var u32_sb2 : f32 = s.b[1u];
-  var u32_sb3 : f32 = s.b[3u];
-  var u32_sb4 : f32 = s.b[4u];
-  var u32_sb5 : f32 = s.b[10u];
-  var u32_sb6 : f32 = s.b[100u];
-
-  var u32_ua1 : f32 = u.a[0u].x;
-  var u32_ua2 : f32 = u.a[1u].x;
-  var u32_ua3 : f32 = u.a[3u].x;
-  var u32_ua4 : f32 = u.a[4u].x;
-  var u32_ua5 : f32 = u.a[10u].x;
-  var u32_ua6 : f32 = u.a[100u].x;
+  // i32
+  {
+    let i = 0i;
+    var storage_vector : f32 = s.vector[i];
+    var storage_fixed_arr : f32 = s.fixed_arr[i];
+    var storage_runtime_arr : f32 = s.runtime_arr[i];
+    var uniform_vector : f32 = u.vector[i];
+    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
+    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
+  }
+  // u32
+  {
+    let i = 0u;
+    var storage_vector : f32 = s.vector[i];
+    var storage_fixed_arr : f32 = s.fixed_arr[i];
+    var storage_runtime_arr : f32 = s.runtime_arr[i];
+    var uniform_vector : f32 = u.vector[i];
+    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
+    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
+  }
 }
 )";
 
 TEST_F(RobustnessTest, OmitNone) {
-    auto* expect = R"(
+    auto* expect =
+        R"(
 struct S {
-  a : array<f32, 4>,
-  b : array<f32>,
+  vector : vec3<f32>,
+  fixed_arr : array<f32, 4>,
+  runtime_arr : array<f32>,
 }
 
 @group(0) @binding(0) var<storage, read> s : S;
 
-type UArr = array<vec4<f32>, 4>;
-
 struct U {
-  a : UArr,
+  vector : vec4<f32>,
+  fixed_arr : array<vec4<f32>, 4>,
 }
 
 @group(1) @binding(0) var<uniform> u : U;
 
 fn f() {
-  var i32_sa1 : f32 = s.a[3i];
-  var i32_sa2 : f32 = s.a[1i];
-  var i32_sa3 : f32 = s.a[0i];
-  var i32_sa4 : f32 = s.a[0i];
-  var i32_sa5 : f32 = s.a[0i];
-  var i32_sb1 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb3 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb4 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb5 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_ua1 : f32 = u.a[3i].x;
-  var i32_ua2 : f32 = u.a[1i].x;
-  var i32_ua3 : f32 = u.a[0i].x;
-  var i32_ua4 : f32 = u.a[0i].x;
-  var i32_ua5 : f32 = u.a[0i].x;
-  var u32_sa1 : f32 = s.a[0u];
-  var u32_sa2 : f32 = s.a[1u];
-  var u32_sa3 : f32 = s.a[3u];
-  var u32_sa4 : f32 = s.a[3u];
-  var u32_sa5 : f32 = s.a[3u];
-  var u32_sa6 : f32 = s.a[3u];
-  var u32_sb1 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb3 : f32 = s.b[min(3u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb4 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb5 : f32 = s.b[min(10u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb6 : f32 = s.b[min(100u, (arrayLength(&(s.b)) - 1u))];
-  var u32_ua1 : f32 = u.a[0u].x;
-  var u32_ua2 : f32 = u.a[1u].x;
-  var u32_ua3 : f32 = u.a[3u].x;
-  var u32_ua4 : f32 = u.a[3u].x;
-  var u32_ua5 : f32 = u.a[3u].x;
-  var u32_ua6 : f32 = u.a[3u].x;
+  {
+    let i = 0i;
+    var storage_vector : f32 = s.vector[min(u32(i), 2u)];
+    var storage_fixed_arr : f32 = s.fixed_arr[min(u32(i), 3u)];
+    var storage_runtime_arr : f32 = s.runtime_arr[min(u32(i), (arrayLength(&(s.runtime_arr)) - 1u))];
+    var uniform_vector : f32 = u.vector[min(u32(i), 3u)];
+    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[min(u32(i), 3u)];
+    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][min(u32(i), 3u)];
+  }
+  {
+    let i = 0u;
+    var storage_vector : f32 = s.vector[min(i, 2u)];
+    var storage_fixed_arr : f32 = s.fixed_arr[min(i, 3u)];
+    var storage_runtime_arr : f32 = s.runtime_arr[min(i, (arrayLength(&(s.runtime_arr)) - 1u))];
+    var uniform_vector : f32 = u.vector[min(i, 3u)];
+    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[min(i, 3u)];
+    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][min(i, 3u)];
+  }
 }
 )";
 
@@ -1596,56 +1163,42 @@
 }
 
 TEST_F(RobustnessTest, OmitStorage) {
-    auto* expect = R"(
+    auto* expect =
+        R"(
 struct S {
-  a : array<f32, 4>,
-  b : array<f32>,
+  vector : vec3<f32>,
+  fixed_arr : array<f32, 4>,
+  runtime_arr : array<f32>,
 }
 
 @group(0) @binding(0) var<storage, read> s : S;
 
-type UArr = array<vec4<f32>, 4>;
-
 struct U {
-  a : UArr,
+  vector : vec4<f32>,
+  fixed_arr : array<vec4<f32>, 4>,
 }
 
 @group(1) @binding(0) var<uniform> u : U;
 
 fn f() {
-  var i32_sa1 : f32 = s.a[4];
-  var i32_sa2 : f32 = s.a[1];
-  var i32_sa3 : f32 = s.a[0];
-  var i32_sa4 : f32 = s.a[-1];
-  var i32_sa5 : f32 = s.a[-4];
-  var i32_sb1 : f32 = s.b[4];
-  var i32_sb2 : f32 = s.b[1];
-  var i32_sb3 : f32 = s.b[0];
-  var i32_sb4 : f32 = s.b[-1];
-  var i32_sb5 : f32 = s.b[-4];
-  var i32_ua1 : f32 = u.a[3i].x;
-  var i32_ua2 : f32 = u.a[1i].x;
-  var i32_ua3 : f32 = u.a[0i].x;
-  var i32_ua4 : f32 = u.a[0i].x;
-  var i32_ua5 : f32 = u.a[0i].x;
-  var u32_sa1 : f32 = s.a[0u];
-  var u32_sa2 : f32 = s.a[1u];
-  var u32_sa3 : f32 = s.a[3u];
-  var u32_sa4 : f32 = s.a[4u];
-  var u32_sa5 : f32 = s.a[10u];
-  var u32_sa6 : f32 = s.a[100u];
-  var u32_sb1 : f32 = s.b[0u];
-  var u32_sb2 : f32 = s.b[1u];
-  var u32_sb3 : f32 = s.b[3u];
-  var u32_sb4 : f32 = s.b[4u];
-  var u32_sb5 : f32 = s.b[10u];
-  var u32_sb6 : f32 = s.b[100u];
-  var u32_ua1 : f32 = u.a[0u].x;
-  var u32_ua2 : f32 = u.a[1u].x;
-  var u32_ua3 : f32 = u.a[3u].x;
-  var u32_ua4 : f32 = u.a[3u].x;
-  var u32_ua5 : f32 = u.a[3u].x;
-  var u32_ua6 : f32 = u.a[3u].x;
+  {
+    let i = 0i;
+    var storage_vector : f32 = s.vector[i];
+    var storage_fixed_arr : f32 = s.fixed_arr[i];
+    var storage_runtime_arr : f32 = s.runtime_arr[i];
+    var uniform_vector : f32 = u.vector[min(u32(i), 3u)];
+    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[min(u32(i), 3u)];
+    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][min(u32(i), 3u)];
+  }
+  {
+    let i = 0u;
+    var storage_vector : f32 = s.vector[i];
+    var storage_fixed_arr : f32 = s.fixed_arr[i];
+    var storage_runtime_arr : f32 = s.runtime_arr[i];
+    var uniform_vector : f32 = u.vector[min(i, 3u)];
+    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[min(i, 3u)];
+    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][min(i, 3u)];
+  }
 }
 )";
 
@@ -1661,56 +1214,42 @@
 }
 
 TEST_F(RobustnessTest, OmitUniform) {
-    auto* expect = R"(
+    auto* expect =
+        R"(
 struct S {
-  a : array<f32, 4>,
-  b : array<f32>,
+  vector : vec3<f32>,
+  fixed_arr : array<f32, 4>,
+  runtime_arr : array<f32>,
 }
 
 @group(0) @binding(0) var<storage, read> s : S;
 
-type UArr = array<vec4<f32>, 4>;
-
 struct U {
-  a : UArr,
+  vector : vec4<f32>,
+  fixed_arr : array<vec4<f32>, 4>,
 }
 
 @group(1) @binding(0) var<uniform> u : U;
 
 fn f() {
-  var i32_sa1 : f32 = s.a[3i];
-  var i32_sa2 : f32 = s.a[1i];
-  var i32_sa3 : f32 = s.a[0i];
-  var i32_sa4 : f32 = s.a[0i];
-  var i32_sa5 : f32 = s.a[0i];
-  var i32_sb1 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb3 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb4 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_sb5 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var i32_ua1 : f32 = u.a[4].x;
-  var i32_ua2 : f32 = u.a[1].x;
-  var i32_ua3 : f32 = u.a[0].x;
-  var i32_ua4 : f32 = u.a[-1].x;
-  var i32_ua5 : f32 = u.a[-4].x;
-  var u32_sa1 : f32 = s.a[0u];
-  var u32_sa2 : f32 = s.a[1u];
-  var u32_sa3 : f32 = s.a[3u];
-  var u32_sa4 : f32 = s.a[3u];
-  var u32_sa5 : f32 = s.a[3u];
-  var u32_sa6 : f32 = s.a[3u];
-  var u32_sb1 : f32 = s.b[min(0u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb2 : f32 = s.b[min(1u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb3 : f32 = s.b[min(3u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb4 : f32 = s.b[min(4u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb5 : f32 = s.b[min(10u, (arrayLength(&(s.b)) - 1u))];
-  var u32_sb6 : f32 = s.b[min(100u, (arrayLength(&(s.b)) - 1u))];
-  var u32_ua1 : f32 = u.a[0u].x;
-  var u32_ua2 : f32 = u.a[1u].x;
-  var u32_ua3 : f32 = u.a[3u].x;
-  var u32_ua4 : f32 = u.a[4u].x;
-  var u32_ua5 : f32 = u.a[10u].x;
-  var u32_ua6 : f32 = u.a[100u].x;
+  {
+    let i = 0i;
+    var storage_vector : f32 = s.vector[min(u32(i), 2u)];
+    var storage_fixed_arr : f32 = s.fixed_arr[min(u32(i), 3u)];
+    var storage_runtime_arr : f32 = s.runtime_arr[min(u32(i), (arrayLength(&(s.runtime_arr)) - 1u))];
+    var uniform_vector : f32 = u.vector[i];
+    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
+    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
+  }
+  {
+    let i = 0u;
+    var storage_vector : f32 = s.vector[min(i, 2u)];
+    var storage_fixed_arr : f32 = s.fixed_arr[min(i, 3u)];
+    var storage_runtime_arr : f32 = s.runtime_arr[min(i, (arrayLength(&(s.runtime_arr)) - 1u))];
+    var uniform_vector : f32 = u.vector[i];
+    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
+    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
+  }
 }
 )";
 
@@ -1726,56 +1265,42 @@
 }
 
 TEST_F(RobustnessTest, OmitBoth) {
-    auto* expect = R"(
+    auto* expect =
+        R"(
 struct S {
-  a : array<f32, 4>,
-  b : array<f32>,
+  vector : vec3<f32>,
+  fixed_arr : array<f32, 4>,
+  runtime_arr : array<f32>,
 }
 
 @group(0) @binding(0) var<storage, read> s : S;
 
-type UArr = array<vec4<f32>, 4>;
-
 struct U {
-  a : UArr,
+  vector : vec4<f32>,
+  fixed_arr : array<vec4<f32>, 4>,
 }
 
 @group(1) @binding(0) var<uniform> u : U;
 
 fn f() {
-  var i32_sa1 : f32 = s.a[4];
-  var i32_sa2 : f32 = s.a[1];
-  var i32_sa3 : f32 = s.a[0];
-  var i32_sa4 : f32 = s.a[-1];
-  var i32_sa5 : f32 = s.a[-4];
-  var i32_sb1 : f32 = s.b[4];
-  var i32_sb2 : f32 = s.b[1];
-  var i32_sb3 : f32 = s.b[0];
-  var i32_sb4 : f32 = s.b[-1];
-  var i32_sb5 : f32 = s.b[-4];
-  var i32_ua1 : f32 = u.a[4].x;
-  var i32_ua2 : f32 = u.a[1].x;
-  var i32_ua3 : f32 = u.a[0].x;
-  var i32_ua4 : f32 = u.a[-1].x;
-  var i32_ua5 : f32 = u.a[-4].x;
-  var u32_sa1 : f32 = s.a[0u];
-  var u32_sa2 : f32 = s.a[1u];
-  var u32_sa3 : f32 = s.a[3u];
-  var u32_sa4 : f32 = s.a[4u];
-  var u32_sa5 : f32 = s.a[10u];
-  var u32_sa6 : f32 = s.a[100u];
-  var u32_sb1 : f32 = s.b[0u];
-  var u32_sb2 : f32 = s.b[1u];
-  var u32_sb3 : f32 = s.b[3u];
-  var u32_sb4 : f32 = s.b[4u];
-  var u32_sb5 : f32 = s.b[10u];
-  var u32_sb6 : f32 = s.b[100u];
-  var u32_ua1 : f32 = u.a[0u].x;
-  var u32_ua2 : f32 = u.a[1u].x;
-  var u32_ua3 : f32 = u.a[3u].x;
-  var u32_ua4 : f32 = u.a[4u].x;
-  var u32_ua5 : f32 = u.a[10u].x;
-  var u32_ua6 : f32 = u.a[100u].x;
+  {
+    let i = 0i;
+    var storage_vector : f32 = s.vector[i];
+    var storage_fixed_arr : f32 = s.fixed_arr[i];
+    var storage_runtime_arr : f32 = s.runtime_arr[i];
+    var uniform_vector : f32 = u.vector[i];
+    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
+    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
+  }
+  {
+    let i = 0u;
+    var storage_vector : f32 = s.vector[i];
+    var storage_fixed_arr : f32 = s.fixed_arr[i];
+    var storage_runtime_arr : f32 = s.runtime_arr[i];
+    var uniform_vector : f32 = u.vector[i];
+    var uniform_fixed_arr : vec4<f32> = u.fixed_arr[i];
+    var uniform_fixed_arr_vector : f32 = u.fixed_arr[0][i];
+  }
 }
 )";