tint: Replace VectorRef with ConstVectorRef.

The elements of the VectorRef is now immutable, but can be
moved, if the caller relinquishes ownership by explicitly using
std::move() at the callsite.

Also add utils::Empty as a way of signalling that a vector should be
constructed with no elements. This is helpful in templated code where
{} cannot be used due to overload ambiguity.

Change-Id: I24a50a13956b0692771a8bc9046336ad46261562
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/97842
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/resolver/builtin_test.cc b/src/tint/resolver/builtin_test.cc
index 7bb7eb2..fe16221 100644
--- a/src/tint/resolver/builtin_test.cc
+++ b/src/tint/resolver/builtin_test.cc
@@ -2082,7 +2082,7 @@
                          testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
 
 static std::string to_str(const std::string& function,
-                          utils::ConstVectorRef<const sem::Parameter*> params) {
+                          utils::VectorRef<const sem::Parameter*> params) {
     std::stringstream out;
     out << function << "(";
     bool first = true;
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index dec4992..71840a8 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -510,9 +510,8 @@
         });
 }
 
-const sem::Constant* ConstEval::ArrayOrStructCtor(
-    const sem::Type* ty,
-    utils::ConstVectorRef<const sem::Expression*> args) {
+const sem::Constant* ConstEval::ArrayOrStructCtor(const sem::Type* ty,
+                                                  utils::VectorRef<const sem::Expression*> args) {
     if (args.IsEmpty()) {
         return ZeroValue(builder, ty);
     }
@@ -532,7 +531,7 @@
 }
 
 const sem::Constant* ConstEval::Conv(const sem::Type* ty,
-                                     utils::ConstVectorRef<const sem::Expression*> args) {
+                                     utils::VectorRef<const sem::Expression*> args) {
     uint32_t el_count = 0;
     auto* el_ty = sem::Type::ElementOf(ty, &el_count);
     if (!el_ty) {
@@ -553,17 +552,17 @@
 }
 
 const sem::Constant* ConstEval::Zero(const sem::Type* ty,
-                                     utils::ConstVectorRef<const sem::Expression*>) {
+                                     utils::VectorRef<const sem::Expression*>) {
     return ZeroValue(builder, ty);
 }
 
 const sem::Constant* ConstEval::Identity(const sem::Type*,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
+                                         utils::VectorRef<const sem::Expression*> args) {
     return args[0]->ConstantValue();
 }
 
 const sem::Constant* ConstEval::VecSplat(const sem::Type* ty,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
+                                         utils::VectorRef<const sem::Expression*> args) {
     if (auto* arg = args[0]->ConstantValue()) {
         return builder.create<Splat>(ty, arg, static_cast<const sem::Vector*>(ty)->Width());
     }
@@ -571,7 +570,7 @@
 }
 
 const sem::Constant* ConstEval::VecCtorS(const sem::Type* ty,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
+                                         utils::VectorRef<const sem::Expression*> args) {
     utils::Vector<const sem::Constant*, 4> els;
     for (auto* arg : args) {
         els.Push(arg->ConstantValue());
@@ -580,7 +579,7 @@
 }
 
 const sem::Constant* ConstEval::VecCtorM(const sem::Type* ty,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
+                                         utils::VectorRef<const sem::Expression*> args) {
     utils::Vector<const sem::Constant*, 4> els;
     for (auto* arg : args) {
         auto* val = arg->ConstantValue();
@@ -605,7 +604,7 @@
 }
 
 const sem::Constant* ConstEval::MatCtorS(const sem::Type* ty,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
+                                         utils::VectorRef<const sem::Expression*> args) {
     auto* m = static_cast<const sem::Matrix*>(ty);
 
     utils::Vector<const sem::Constant*, 4> els;
@@ -621,7 +620,7 @@
 }
 
 const sem::Constant* ConstEval::MatCtorV(const sem::Type* ty,
-                                         utils::ConstVectorRef<const sem::Expression*> args) {
+                                         utils::VectorRef<const sem::Expression*> args) {
     utils::Vector<const sem::Constant*, 4> els;
     for (auto* arg : args) {
         els.Push(arg->ConstantValue());
@@ -668,7 +667,7 @@
 
 const sem::Constant* ConstEval::Swizzle(const sem::Type* ty,
                                         const sem::Expression* vec_expr,
-                                        utils::ConstVectorRef<uint32_t> indices) {
+                                        utils::VectorRef<uint32_t> indices) {
     auto* vec_val = vec_expr->ConstantValue();
     if (!vec_val) {
         return nullptr;
@@ -688,7 +687,7 @@
 }
 
 const sem::Constant* ConstEval::OpComplement(const sem::Type*,
-                                             utils::ConstVectorRef<const sem::Expression*> args) {
+                                             utils::VectorRef<const sem::Expression*> args) {
     auto transform = [&](const sem::Constant* c) {
         auto create = [&](auto i) {
             return CreateElement(builder, c->Type(), decltype(i)(~i.value));
@@ -699,7 +698,7 @@
 }
 
 const sem::Constant* ConstEval::OpMinus(const sem::Type*,
-                                        utils::ConstVectorRef<const sem::Expression*> args) {
+                                        utils::VectorRef<const sem::Expression*> args) {
     auto transform = [&](const sem::Constant* c) {
         auto create = [&](auto i) {  //
                                      // For signed integrals, avoid C++ UB by not negating the
@@ -723,7 +722,7 @@
 }
 
 const sem::Constant* ConstEval::atan2(const sem::Type*,
-                                      utils::ConstVectorRef<const sem::Expression*> args) {
+                                      utils::VectorRef<const sem::Expression*> args) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
         auto create = [&](auto i, auto j) {
             return CreateElement(builder, c0->Type(), decltype(i)(std::atan2(i.value, j.value)));
@@ -735,7 +734,7 @@
 }
 
 const sem::Constant* ConstEval::clamp(const sem::Type*,
-                                      utils::ConstVectorRef<const sem::Expression*> args) {
+                                      utils::VectorRef<const sem::Expression*> args) {
     auto transform = [&](const sem::Constant* c0, const sem::Constant* c1,
                          const sem::Constant* c2) {
         auto create = [&](auto e, auto low, auto high) {
diff --git a/src/tint/resolver/const_eval.h b/src/tint/resolver/const_eval.h
index 4d299f4..5349637 100644
--- a/src/tint/resolver/const_eval.h
+++ b/src/tint/resolver/const_eval.h
@@ -45,8 +45,8 @@
 class ConstEval {
   public:
     /// Typedef for a constant evaluation function
-    using Function = const sem::Constant* (
-        ConstEval::*)(const sem::Type* result_ty, utils::ConstVectorRef<const sem::Expression*>);
+    using Function = const sem::Constant* (ConstEval::*)(const sem::Type* result_ty,
+                                                         utils::VectorRef<const sem::Expression*>);
 
     /// The result type of a method that may raise a diagnostic error and the caller should abort
     /// resolving. Can be one of three distinct values:
@@ -71,7 +71,7 @@
     /// @param args the input arguments
     /// @return the constructed value, or null if the value cannot be calculated
     const sem::Constant* ArrayOrStructCtor(const sem::Type* ty,
-                                           utils::ConstVectorRef<const sem::Expression*> args);
+                                           utils::VectorRef<const sem::Expression*> args);
 
     /// @param ty the target type
     /// @param expr the input expression
@@ -100,7 +100,7 @@
     /// @return the result of the swizzle, or null if the value cannot be calculated
     const sem::Constant* Swizzle(const sem::Type* ty,
                                  const sem::Expression* vector,
-                                 utils::ConstVectorRef<uint32_t> indices);
+                                 utils::VectorRef<uint32_t> indices);
 
     /// Convert the `value` to `target_type`
     /// @param ty the result type
@@ -117,57 +117,55 @@
     /// @param ty the result type
     /// @param args the input arguments
     /// @return the converted value, or null if the value cannot be calculated
-    const sem::Constant* Conv(const sem::Type* ty,
-                              utils::ConstVectorRef<const sem::Expression*> args);
+    const sem::Constant* Conv(const sem::Type* ty, utils::VectorRef<const sem::Expression*> args);
 
     /// Zero value type constructor
     /// @param ty the result type
     /// @param args the input arguments (no arguments provided)
     /// @return the constructed value, or null if the value cannot be calculated
-    const sem::Constant* Zero(const sem::Type* ty,
-                              utils::ConstVectorRef<const sem::Expression*> args);
+    const sem::Constant* Zero(const sem::Type* ty, utils::VectorRef<const sem::Expression*> args);
 
     /// Identity value type constructor
     /// @param ty the result type
     /// @param args the input arguments
     /// @return the constructed value, or null if the value cannot be calculated
     const sem::Constant* Identity(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+                                  utils::VectorRef<const sem::Expression*> args);
 
     /// Vector splat constructor
     /// @param ty the vector type
     /// @param args the input arguments
     /// @return the constructed value, or null if the value cannot be calculated
     const sem::Constant* VecSplat(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+                                  utils::VectorRef<const sem::Expression*> args);
 
     /// Vector constructor using scalars
     /// @param ty the vector type
     /// @param args the input arguments
     /// @return the constructed value, or null if the value cannot be calculated
     const sem::Constant* VecCtorS(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+                                  utils::VectorRef<const sem::Expression*> args);
 
     /// Vector constructor using a mix of scalars and smaller vectors
     /// @param ty the vector type
     /// @param args the input arguments
     /// @return the constructed value, or null if the value cannot be calculated
     const sem::Constant* VecCtorM(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+                                  utils::VectorRef<const sem::Expression*> args);
 
     /// Matrix constructor using scalar values
     /// @param ty the matrix type
     /// @param args the input arguments
     /// @return the constructed value, or null if the value cannot be calculated
     const sem::Constant* MatCtorS(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+                                  utils::VectorRef<const sem::Expression*> args);
 
     /// Matrix constructor using column vectors
     /// @param ty the matrix type
     /// @param args the input arguments
     /// @return the constructed value, or null if the value cannot be calculated
     const sem::Constant* MatCtorV(const sem::Type* ty,
-                                  utils::ConstVectorRef<const sem::Expression*> args);
+                                  utils::VectorRef<const sem::Expression*> args);
 
     ////////////////////////////////////////////////////////////////////////////
     // Operators
@@ -178,14 +176,14 @@
     /// @param args the input arguments
     /// @return the result value, or null if the value cannot be calculated
     const sem::Constant* OpComplement(const sem::Type* ty,
-                                      utils::ConstVectorRef<const sem::Expression*> args);
+                                      utils::VectorRef<const sem::Expression*> args);
 
     /// Minus operator '-'
     /// @param ty the expression type
     /// @param args the input arguments
     /// @return the result value, or null if the value cannot be calculated
     const sem::Constant* OpMinus(const sem::Type* ty,
-                                 utils::ConstVectorRef<const sem::Expression*> args);
+                                 utils::VectorRef<const sem::Expression*> args);
 
     ////////////////////////////////////////////////////////////////////////////
     // Builtins
@@ -195,15 +193,13 @@
     /// @param ty the expression type
     /// @param args the input arguments
     /// @return the result value, or null if the value cannot be calculated
-    const sem::Constant* atan2(const sem::Type* ty,
-                               utils::ConstVectorRef<const sem::Expression*> args);
+    const sem::Constant* atan2(const sem::Type* ty, utils::VectorRef<const sem::Expression*> args);
 
     /// clamp builtin
     /// @param ty the expression type
     /// @param args the input arguments
     /// @return the result value, or null if the value cannot be calculated
-    const sem::Constant* clamp(const sem::Type* ty,
-                               utils::ConstVectorRef<const sem::Expression*> args);
+    const sem::Constant* clamp(const sem::Type* ty, utils::VectorRef<const sem::Expression*> args);
 
   private:
     /// Adds the given error message to the diagnostics
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index bb5791f..57c60b0 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -1026,14 +1026,14 @@
 
     // Prints the list of candidates for emitting diagnostics
     void PrintCandidates(std::ostream& ss,
-                         utils::ConstVectorRef<Candidate> candidates,
+                         utils::VectorRef<Candidate> candidates,
                          const char* intrinsic_name) const;
 
     /// Raises an error when no overload is a clear winner of overload resolution
     void ErrAmbiguousOverload(const char* intrinsic_name,
-                              utils::ConstVectorRef<const sem::Type*> args,
+                              utils::VectorRef<const sem::Type*> args,
                               TemplateState templates,
-                              utils::ConstVectorRef<Candidate> candidates) const;
+                              utils::VectorRef<Candidate> candidates) const;
 
     ProgramBuilder& builder;
     Matchers matchers;
@@ -1604,7 +1604,7 @@
 }
 
 void Impl::PrintCandidates(std::ostream& ss,
-                           utils::ConstVectorRef<Candidate> candidates,
+                           utils::VectorRef<Candidate> candidates,
                            const char* intrinsic_name) const {
     for (auto& candidate : candidates) {
         ss << "  ";
@@ -1638,9 +1638,9 @@
 }
 
 void Impl::ErrAmbiguousOverload(const char* intrinsic_name,
-                                utils::ConstVectorRef<const sem::Type*> args,
+                                utils::VectorRef<const sem::Type*> args,
                                 TemplateState templates,
-                                utils::ConstVectorRef<Candidate> candidates) const {
+                                utils::VectorRef<Candidate> candidates) const {
     std::stringstream ss;
     ss << "ambiguous overload while attempting to match " << intrinsic_name;
     for (size_t i = 0; i < std::numeric_limits<size_t>::max(); i++) {
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index e5d4c65..8f85398 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -1406,7 +1406,8 @@
     return m;
 }
 
-bool Resolver::MaterializeArguments(utils::VectorRef<const sem::Expression*> args,
+template <size_t N>
+bool Resolver::MaterializeArguments(utils::Vector<const sem::Expression*, N>& args,
                                     const sem::CallTarget* target) {
     for (size_t i = 0, n = std::min(args.Length(), target->Parameters().Length()); i < n; i++) {
         const auto* param_ty = target->Parameters()[i]->Type();
@@ -1778,9 +1779,7 @@
                 // there's no need to infer element types.
                 return ty_ctor_or_conv(ty);
             },
-            [&](sem::Function* func) {
-                return FunctionCall(expr, func, std::move(args), arg_behaviors);
-            },
+            [&](sem::Function* func) { return FunctionCall(expr, func, args, arg_behaviors); },
             [&](sem::Variable* var) {
                 auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
                 AddError("cannot call variable '" + name + "'", ident->source);
@@ -1791,7 +1790,7 @@
                 auto name = builder_->Symbols().NameFor(ident->symbol);
                 auto builtin_type = sem::ParseBuiltinType(name);
                 if (builtin_type != sem::BuiltinType::kNone) {
-                    return BuiltinCall(expr, builtin_type, std::move(args));
+                    return BuiltinCall(expr, builtin_type, args);
                 }
 
                 TINT_ICE(Resolver, diagnostics_)
@@ -1809,12 +1808,13 @@
     return validator_.Call(call, current_statement_) ? call : nullptr;
 }
 
+template <size_t N>
 sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
                                  sem::BuiltinType builtin_type,
-                                 utils::VectorRef<const sem::Expression*> args) {
+                                 utils::Vector<const sem::Expression*, N>& args) {
     IntrinsicTable::Builtin builtin;
     {
-        auto arg_tys = utils::Transform<8>(args, [](auto* arg) { return arg->Type(); });
+        auto arg_tys = utils::Transform(args, [](auto* arg) { return arg->Type(); });
         builtin = intrinsic_table_->Lookup(builtin_type, arg_tys, expr->source);
         if (!builtin.sem) {
             return nullptr;
@@ -1876,9 +1876,8 @@
     return call;
 }
 
-void Resolver::CollectTextureSamplerPairs(
-    const sem::Builtin* builtin,
-    utils::ConstVectorRef<const sem::Expression*> args) const {
+void Resolver::CollectTextureSamplerPairs(const sem::Builtin* builtin,
+                                          utils::VectorRef<const sem::Expression*> args) const {
     // Collect a texture/sampler pair for this builtin.
     const auto& signature = builtin->Signature();
     int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
@@ -1896,9 +1895,10 @@
     }
 }
 
+template <size_t N>
 sem::Call* Resolver::FunctionCall(const ast::CallExpression* expr,
                                   sem::Function* target,
-                                  utils::VectorRef<const sem::Expression*> args,
+                                  utils::Vector<const sem::Expression*, N>& args,
                                   sem::Behaviors arg_behaviors) {
     auto sym = expr->target.name->symbol;
     auto name = builder_->Symbols().NameFor(sym);
@@ -1944,9 +1944,8 @@
     return call;
 }
 
-void Resolver::CollectTextureSamplerPairs(
-    sem::Function* func,
-    utils::ConstVectorRef<const sem::Expression*> args) const {
+void Resolver::CollectTextureSamplerPairs(sem::Function* func,
+                                          utils::VectorRef<const sem::Expression*> args) const {
     // Map all texture/sampler pairs from the target function to the
     // current function. These can only be global or parameter
     // variables. Resolve any parameter variables to the corresponding
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index fa5a569..ecbda30 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -193,14 +193,16 @@
     sem::Expression* Bitcast(const ast::BitcastExpression*);
     sem::Call* Call(const ast::CallExpression*);
     sem::Function* Function(const ast::Function*);
+    template <size_t N>
     sem::Call* FunctionCall(const ast::CallExpression*,
                             sem::Function* target,
-                            utils::VectorRef<const sem::Expression*> args,
+                            utils::Vector<const sem::Expression*, N>& args,
                             sem::Behaviors arg_behaviors);
     sem::Expression* Identifier(const ast::IdentifierExpression*);
+    template <size_t N>
     sem::Call* BuiltinCall(const ast::CallExpression*,
                            sem::BuiltinType,
-                           utils::VectorRef<const sem::Expression*> args);
+                           utils::Vector<const sem::Expression*, N>& args);
     sem::Expression* Literal(const ast::LiteralExpression*);
     sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
     sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
@@ -223,7 +225,8 @@
 
     /// Materializes all the arguments in `args` to the parameter types of `target`.
     /// @returns true on success, false on failure.
-    bool MaterializeArguments(utils::VectorRef<const sem::Expression*> args,
+    template <size_t N>
+    bool MaterializeArguments(utils::Vector<const sem::Expression*, N>& args,
                               const sem::CallTarget* target);
 
     /// @returns true if an argument of an abstract numeric type, passed to a parameter of type
@@ -267,9 +270,9 @@
     // CollectTextureSamplerPairs() collects all the texture/sampler pairs from the target function
     // / builtin, and records these on the current function by calling AddTextureSamplerPair().
     void CollectTextureSamplerPairs(sem::Function* func,
-                                    utils::ConstVectorRef<const sem::Expression*> args) const;
+                                    utils::VectorRef<const sem::Expression*> args) const;
     void CollectTextureSamplerPairs(const sem::Builtin* builtin,
-                                    utils::ConstVectorRef<const sem::Expression*> args) const;
+                                    utils::VectorRef<const sem::Expression*> args) const;
 
     /// Resolves the WorkgroupSize for the given function, assigning it to
     /// current_function_
diff --git a/src/tint/sem/type.cc b/src/tint/sem/type.cc
index fcc1654..b4887dd 100644
--- a/src/tint/sem/type.cc
+++ b/src/tint/sem/type.cc
@@ -272,7 +272,7 @@
     return el_ty;
 }
 
-const sem::Type* Type::Common(utils::ConstVectorRef<const Type*> types) {
+const sem::Type* Type::Common(utils::VectorRef<const Type*> types) {
     const auto count = types.Length();
     if (count == 0) {
         return nullptr;
diff --git a/src/tint/sem/type.h b/src/tint/sem/type.h
index c2b83e5..8bac821c 100644
--- a/src/tint/sem/type.h
+++ b/src/tint/sem/type.h
@@ -161,7 +161,7 @@
     /// @returns the lowest-ranking type that all types in `types` can be implicitly converted to,
     ///          or nullptr if there is no consistent common type across all types in `types`.
     /// @see https://www.w3.org/TR/WGSL/#conversion-rank
-    static const sem::Type* Common(utils::ConstVectorRef<const Type*> types);
+    static const sem::Type* Common(utils::VectorRef<const Type*> types);
 
   protected:
     Type();
diff --git a/src/tint/utils/transform.h b/src/tint/utils/transform.h
index 412dbed..2faca46 100644
--- a/src/tint/utils/transform.h
+++ b/src/tint/utils/transform.h
@@ -119,40 +119,6 @@
     return result;
 }
 
-/// Transform performs an element-wise transformation of a vector reference.
-/// @param in the input vector.
-/// @param transform the transformation function with signature: `OUT(IN)`
-/// @tparam N the small-array size of the returned Vector
-/// @returns a new vector with each element of the source vector transformed by `transform`.
-template <size_t N, typename IN, typename TRANSFORMER>
-auto Transform(ConstVectorRef<IN> in, TRANSFORMER&& transform)
-    -> Vector<decltype(transform(in[0])), N> {
-    const auto count = in.Length();
-    Vector<decltype(transform(in[0])), N> result;
-    result.Reserve(count);
-    for (size_t i = 0; i < count; ++i) {
-        result.Push(transform(in[i]));
-    }
-    return result;
-}
-
-/// Transform performs an element-wise transformation of a vector reference.
-/// @param in the input vector.
-/// @param transform the transformation function with signature: `OUT(IN, size_t)`
-/// @tparam N the small-array size of the returned Vector
-/// @returns a new vector with each element of the source vector transformed by `transform`.
-template <size_t N, typename IN, typename TRANSFORMER>
-auto Transform(ConstVectorRef<IN> in, TRANSFORMER&& transform)
-    -> Vector<decltype(transform(in[0], 1u)), N> {
-    const auto count = in.Length();
-    Vector<decltype(transform(in[0], 1u)), N> result;
-    result.Reserve(count);
-    for (size_t i = 0; i < count; ++i) {
-        result.Push(transform(in[i], i));
-    }
-    return result;
-}
-
 /// TransformN performs an element-wise transformation of a vector, transforming and returning at
 /// most `n` elements.
 /// @param in the input vector.
diff --git a/src/tint/utils/transform_test.cc b/src/tint/utils/transform_test.cc
index 656b827..71b3f0c 100644
--- a/src/tint/utils/transform_test.cc
+++ b/src/tint/utils/transform_test.cc
@@ -345,72 +345,5 @@
     }
 }
 
-TEST(TransformTest, ConstVectorRefEmpty) {
-    const Vector<int, 4> empty{};
-    ConstVectorRef<int> ref(empty);
-    {
-        auto transformed = Transform<4>(ref, [](int) -> int {
-            [] { FAIL() << "Callback should not be called for empty vector"; }();
-            return 0;
-        });
-        CHECK_ELEMENT_TYPE(transformed, int);
-        EXPECT_EQ(transformed.Length(), 0u);
-    }
-    {
-        auto transformed = Transform<4>(ref, [](int, size_t) -> int {
-            [] { FAIL() << "Callback should not be called for empty vector"; }();
-            return 0;
-        });
-        CHECK_ELEMENT_TYPE(transformed, int);
-        EXPECT_EQ(transformed.Length(), 0u);
-    }
-}
-
-TEST(TransformTest, ConstVectorRefIdentity) {
-    const Vector<int, 4> input{1, 2, 3, 4};
-    ConstVectorRef<int> ref(input);
-    auto transformed = Transform<8>(ref, [](int i) { return i; });
-    CHECK_ELEMENT_TYPE(transformed, int);
-    EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
-}
-
-TEST(TransformTest, ConstVectorRefIdentityWithIndex) {
-    const Vector<int, 4> input{1, 2, 3, 4};
-    ConstVectorRef<int> ref(input);
-    auto transformed = Transform<2>(ref, [](int i, size_t) { return i; });
-    CHECK_ELEMENT_TYPE(transformed, int);
-    EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
-}
-
-TEST(TransformTest, ConstVectorRefIndex) {
-    const Vector<int, 4> input{10, 20, 30, 40};
-    ConstVectorRef<int> ref(input);
-    {
-        auto transformed = Transform<4>(ref, [](int, size_t idx) { return idx; });
-        CHECK_ELEMENT_TYPE(transformed, size_t);
-        EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u));
-    }
-}
-
-TEST(TransformTest, TransformConstVectorRefSameType) {
-    const Vector<int, 4> input{1, 2, 3, 4};
-    ConstVectorRef<int> ref(input);
-    {
-        auto transformed = Transform<4>(ref, [](int i) { return i * 10; });
-        CHECK_ELEMENT_TYPE(transformed, int);
-        EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40));
-    }
-}
-
-TEST(TransformTest, TransformConstVectorRefDifferentType) {
-    const Vector<int, 4> input{1, 2, 3, 4};
-    ConstVectorRef<int> ref(input);
-    {
-        auto transformed = Transform<4>(ref, [](int i) { return std::to_string(i); });
-        CHECK_ELEMENT_TYPE(transformed, std::string);
-        EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4"));
-    }
-}
-
 }  // namespace
 }  // namespace tint::utils
diff --git a/src/tint/utils/vector.h b/src/tint/utils/vector.h
index d339a49..ee305e2 100644
--- a/src/tint/utils/vector.h
+++ b/src/tint/utils/vector.h
@@ -32,12 +32,18 @@
 template <typename>
 class VectorRef;
 template <typename>
-class ConstVectorRef;
+class VectorRef;
 
 }  // namespace tint::utils
 
 namespace tint::utils {
 
+/// A type used to indicate an empty array.
+struct EmptyType {};
+
+/// An instance of the EmptyType.
+static constexpr EmptyType Empty;
+
 /// A slice represents a contigious array of elements of type T.
 template <typename T>
 struct Slice {
@@ -165,6 +171,9 @@
     Vector() = default;
 
     /// Constructor
+    Vector(EmptyType) {}  // NOLINT(runtime/explicit)
+
+    /// Constructor
     /// @param elements the elements to place into the vector
     Vector(std::initializer_list<T> elements) {
         Reserve(elements.size());
@@ -217,10 +226,7 @@
 
     /// Copy constructor from an immutable vector reference
     /// @param other the vector reference to copy
-    explicit Vector(const ConstVectorRef<T>& other) { Copy(other.slice_); }
-
-    /// Move constructor from an immutable vector reference (invalid)
-    Vector(ConstVectorRef<T>&&) = delete;  // NOLINT(runtime/explicit)
+    explicit Vector(const VectorRef<T>& other) { Copy(other.slice_); }
 
     /// Destructor
     ~Vector() { ClearAndFree(); }
@@ -263,6 +269,26 @@
         return *this;
     }
 
+    /// Assignment operator (differing N length)
+    /// @param other the vector reference to copy
+    /// @returns this vector so calls can be chained
+    Vector& operator=(const VectorRef<T>& other) {
+        if (&other.slice_ != &impl_.slice) {
+            Copy(other.slice_);
+        }
+        return *this;
+    }
+
+    /// Move operator (differing N length)
+    /// @param other the vector reference to copy
+    /// @returns this vector so calls can be chained
+    Vector& operator=(VectorRef<T>&& other) {
+        if (&other.slice_ != &impl_.slice) {
+            MoveOrCopy(std::move(other));
+        }
+        return *this;
+    }
+
     /// Index operator
     /// @param i the element index. Must be less than `len`.
     /// @returns a reference to the i'th element.
@@ -367,7 +393,12 @@
 
     /// Removes and returns the last element from the vector.
     /// @returns the popped element
-    T Pop() { return std::move(impl_.slice.data[--impl_.slice.len]); }
+    T Pop() {
+        auto& el = impl_.slice.data[--impl_.slice.len];
+        auto val = std::move(el);
+        el.~T();
+        return val;
+    }
 
     /// @returns true if the vector is empty.
     bool IsEmpty() const { return impl_.slice.len == 0; }
@@ -419,7 +450,7 @@
 
     /// Friend class
     template <typename>
-    friend class ConstVectorRef;
+    friend class VectorRef;
 
     /// The slice type used by this vector
     using Slice = utils::Slice<T>;
@@ -573,11 +604,10 @@
 
 /// VectorRef is a weak reference to a Vector, used to pass vectors as parameters, avoiding copies
 /// between the caller and the callee. VectorRef can accept a Vector of any 'N' value, decoupling
-/// the caller's vector internal size from the callee's vector size.
-///
-/// A VectorRef tracks the usage of moves either side of the call. If at the call site, a Vector
-/// argument is moved to a VectorRef parameter, and within the callee, the VectorRef parameter is
-/// moved to a Vector, then the Vector heap allocation will be moved. For example:
+/// the caller's vector internal size from the callee's vector size. A VectorRef tracks the usage of
+/// moves either side of the call. If at the call site, a Vector argument is moved to a VectorRef
+/// parameter, and within the callee, the VectorRef parameter is moved to a Vector, then the Vector
+/// heap allocation will be moved. For example:
 ///
 /// ```
 ///     void func_a() {
@@ -596,12 +626,30 @@
     /// The slice type used by this vector reference
     using Slice = utils::Slice<T>;
 
+    /// @returns an empty slice.
+    static Slice& EmptySlice() {
+        static Slice empty;
+        return empty;
+    }
+
   public:
+    /// Constructor - empty reference
+    VectorRef() : slice_(EmptySlice()) {}
+
+    /// Constructor
+    VectorRef(EmptyType) : slice_(EmptySlice()) {}  // NOLINT(runtime/explicit)
+
     /// Constructor from a Vector
     /// @param vector the vector to create a reference of
     template <size_t N>
     VectorRef(Vector<T, N>& vector)  // NOLINT(runtime/explicit)
-        : slice_(vector.impl_.slice), can_move_(false) {}
+        : slice_(vector.impl_.slice) {}
+
+    /// Constructor from a const Vector
+    /// @param vector the vector to create a reference of
+    template <size_t N>
+    VectorRef(const Vector<T, N>& vector)  // NOLINT(runtime/explicit)
+        : slice_(const_cast<Slice&>(vector.impl_.slice)) {}
 
     /// Constructor from a moved Vector
     /// @param vector the vector being moved
@@ -611,7 +659,7 @@
 
     /// Copy constructor
     /// @param other the vector reference
-    VectorRef(const VectorRef& other) : slice_(other.slice_), can_move_(false) {}
+    VectorRef(const VectorRef& other) : slice_(other.slice_) {}
 
     /// Move constructor
     /// @param other the vector reference
@@ -621,7 +669,7 @@
     /// @param other the other vector reference
     template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
     VectorRef(const VectorRef<U>& other)  // NOLINT(runtime/explicit)
-        : slice_(*ReinterpretSlice<T>(&other.slice_)), can_move_(false) {}
+        : slice_(*ReinterpretSlice<T>(&other.slice_)) {}
 
     /// Move constructor with covariance / const conversion
     /// @param other the vector reference
@@ -634,7 +682,7 @@
     /// @see CanReinterpretSlice for rules about conversion
     template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
     VectorRef(Vector<U, N>& vector)  // NOLINT(runtime/explicit)
-        : slice_(*ReinterpretSlice<T>(&vector.impl_.slice)), can_move_(false) {}
+        : slice_(*ReinterpretSlice<T>(&vector.impl_.slice)) {}
 
     /// Constructor from a moved Vector with covariance / const conversion
     /// @param vector the vector to create a reference of
@@ -646,11 +694,6 @@
     /// Index operator
     /// @param i the element index. Must be less than `len`.
     /// @returns a reference to the i'th element.
-    T& operator[](size_t i) { return slice_[i]; }
-
-    /// Index operator
-    /// @param i the element index. Must be less than `len`.
-    /// @returns a reference to the i'th element.
     const T& operator[](size_t i) const { return slice_[i]; }
 
     /// @return the number of elements in the vector
@@ -710,7 +753,7 @@
 
     /// Friend class
     template <typename>
-    friend class ConstVectorRef;
+    friend class VectorRef;
 
     /// The slice of the vector being referenced.
     Slice& slice_;
@@ -718,99 +761,6 @@
     bool can_move_ = false;
 };
 
-/// ConstVectorRef is a weak, immutable reference to a Vector, used to pass vectors as parameters,
-/// avoiding copies between the caller and the callee. VectorRef can accept a Vector of any 'N'
-/// value, decoupling the caller's vector internal size from the callee's vector size.
-template <typename T>
-class ConstVectorRef {
-    /// The slice type used by this vector reference
-    using Slice = utils::Slice<T>;
-
-  public:
-    /// Constructor from a Vector.
-    /// @param vector the vector reference
-    template <size_t N>
-    ConstVectorRef(const Vector<T, N>& vector)  // NOLINT(runtime/explicit)
-        : slice_(vector.impl_.slice) {}
-
-    /// Copy constructor
-    /// @param other the vector reference
-    ConstVectorRef(const ConstVectorRef& other) = default;
-
-    /// Conversion constructor to convert from a non-const to const vector reference
-    /// @param other the vector reference
-    ConstVectorRef(const VectorRef<T>& other) : slice_(other.slice_) {}  // NOLINT(runtime/explicit)
-
-    /// Move constructor. Deleted as this won't move anything.
-    ConstVectorRef(ConstVectorRef&&) = delete;
-
-    /// Constructor from a Vector with covariance / const conversion
-    /// @param vector the vector to create a reference of
-    /// @see CanReinterpretSlice for rules about conversion
-    template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
-    ConstVectorRef(const Vector<U, N>& vector)  // NOLINT(runtime/explicit)
-        : slice_(*ReinterpretSlice<T>(&vector.impl_.slice)) {}
-
-    /// Constructor from a VectorRef with covariance / const conversion
-    /// @param other the vector reference
-    /// @see CanReinterpretSlice for rules about conversion
-    template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
-    ConstVectorRef(const VectorRef<U>& other)  // NOLINT(runtime/explicit)
-        : slice_(*ReinterpretSlice<T>(&other.slice_)) {}
-
-    /// Constructor from a ConstVectorRef with covariance / const conversion
-    /// @param other the vector reference
-    /// @see CanReinterpretSlice for rules about conversion
-    template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
-    ConstVectorRef(const ConstVectorRef<U>& other)  // NOLINT(runtime/explicit)
-        : slice_(*ReinterpretSlice<T>(&other.slice_)) {}
-
-    /// Index operator
-    /// @param i the element index. Must be less than `len`.
-    /// @returns a reference to the i'th element.
-    const T& operator[](size_t i) const { return slice_[i]; }
-
-    /// @return the number of elements in the vector
-    size_t Length() const { return slice_.len; }
-
-    /// @return the number of elements that the vector could hold before a heap allocation needs to
-    /// be made
-    size_t Capacity() const { return slice_.cap; }
-
-    /// @returns true if the vector is empty.
-    bool IsEmpty() const { return slice_.len == 0; }
-
-    /// @returns a reference to the first element in the vector
-    const T& Front() const { return slice_.Front(); }
-
-    /// @returns a reference to the last element in the vector
-    const T& Back() const { return slice_.Back(); }
-
-    /// @returns a pointer to the first element in the vector
-    const T* begin() const { return slice_.begin(); }
-
-    /// @returns a pointer to one past the last element in the vector
-    const T* end() const { return slice_.end(); }
-
-    /// @returns a reverse iterator starting with the last element in the vector
-    auto rbegin() const { return slice_.rbegin(); }
-
-    /// @returns the end for a reverse iterator
-    auto rend() const { return slice_.rend(); }
-
-  private:
-    /// Friend class
-    template <typename, size_t>
-    friend class Vector;
-
-    /// Friend class
-    template <typename>
-    friend class ConstVectorRef;
-
-    /// The slice of the vector being referenced.
-    const Slice& slice_;
-};
-
 /// Helper for converting a Vector to a std::vector.
 /// @note This helper exists to help code migration. Avoid if possible.
 template <typename T, size_t N>
diff --git a/src/tint/utils/vector_test.cc b/src/tint/utils/vector_test.cc
index 6e529dc..24cfcbe 100644
--- a/src/tint/utils/vector_test.cc
+++ b/src/tint/utils/vector_test.cc
@@ -105,12 +105,24 @@
     EXPECT_EQ(vec.Capacity(), 2u);
 }
 
-TEST(TintVectorTest, Empty_NoSmallArray) {
+TEST(TintVectorTest, NoSmallArray) {
     Vector<int, 0> vec;
     EXPECT_EQ(vec.Length(), 0u);
     EXPECT_EQ(vec.Capacity(), 0u);
 }
 
+TEST(TintVectorTest, Empty_SmallArray_Empty) {
+    Vector<int, 2> vec(Empty);
+    EXPECT_EQ(vec.Length(), 0u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+}
+
+TEST(TintVectorTest, Empty_NoSmallArray) {
+    Vector<int, 0> vec(Empty);
+    EXPECT_EQ(vec.Length(), 0u);
+    EXPECT_EQ(vec.Capacity(), 0u);
+}
+
 TEST(TintVectorTest, InitializerList_NoSpill) {
     Vector<std::string, 2> vec{"one", "two"};
     EXPECT_EQ(vec.Length(), 2u);
@@ -800,7 +812,7 @@
     EXPECT_TRUE(AllInternallyHeld(vec));
 }
 
-TEST(TintVectorTest, DoubleMoveAssign_WithSpill) {
+TEST(TintVectorTest, RepeatMoveAssign_WithSpill) {
     Vector<std::string, 1> vec_a{"hello", "world"};
     Vector<std::string, 1> vec_b{"Ciao", "mondo"};
     Vector<std::string, 1> vec_c{"bonjour", "le", "monde"};
@@ -816,6 +828,288 @@
     EXPECT_TRUE(AllExternallyHeld(vec));
 }
 
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N2) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 2> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N2) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 2> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N1) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 1> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N1) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 1> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N3) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 3> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N3) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 3> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N0) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 0> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N0) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 0> vec_b;
+    vec_b = ref;
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_Self_NoSpill) {
+    Vector<std::string, 2> vec{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec)};
+    vec = ref;
+    EXPECT_EQ(vec.Length(), 2u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+    EXPECT_EQ(vec[0], "hello");
+    EXPECT_EQ(vec[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, CopyAssignRef_Self_WithSpill) {
+    Vector<std::string, 1> vec{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec)};
+    vec = ref;
+    EXPECT_EQ(vec.Length(), 2u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+    EXPECT_EQ(vec[0], "hello");
+    EXPECT_EQ(vec[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N2) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 2> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_WithSpill_N2_to_N2) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 2> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N1) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 1> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_SpillSpill_N2_to_N1) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 1> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N3) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 3> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_WithSpill_N2_to_N3) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 3> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N0) {
+    Vector<std::string, 2> vec_a{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 0> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 2u);
+    EXPECT_EQ(vec_b.Capacity(), 2u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_WithSpill_N2_to_N0) {
+    Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+    VectorRef<std::string> ref{std::move(vec_a)};
+    Vector<std::string, 0> vec_b;
+    vec_b = std::move(ref);
+    EXPECT_EQ(vec_b.Length(), 3u);
+    EXPECT_EQ(vec_b.Capacity(), 3u);
+    EXPECT_EQ(vec_b[0], "hello");
+    EXPECT_EQ(vec_b[1], "world");
+    EXPECT_EQ(vec_b[2], "spill");
+    EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_Self_NoSpill) {
+    Vector<std::string, 2> vec{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec)};
+    vec = std::move(ref);
+    EXPECT_EQ(vec.Length(), 2u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+    EXPECT_EQ(vec[0], "hello");
+    EXPECT_EQ(vec[1], "world");
+    EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, MoveAssignRef_Self_WithSpill) {
+    Vector<std::string, 1> vec{"hello", "world"};
+    VectorRef<std::string> ref{std::move(vec)};
+    vec = std::move(ref);
+    EXPECT_EQ(vec.Length(), 2u);
+    EXPECT_EQ(vec.Capacity(), 2u);
+    EXPECT_EQ(vec[0], "hello");
+    EXPECT_EQ(vec[1], "world");
+    EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, RepeatMoveAssignRef_NoSpill) {
+    Vector<std::string, 3> vec_a{"hello", "world"};
+    Vector<std::string, 3> vec_b{"Ciao", "mondo"};
+    Vector<std::string, 3> vec_c{"Bonjour", "le", "monde"};
+    VectorRef<std::string> ref_a{std::move(vec_a)};
+    VectorRef<std::string> ref_b{std::move(vec_b)};
+    VectorRef<std::string> ref_c{std::move(vec_c)};
+    Vector<std::string, 3> vec;
+    vec = std::move(ref_a);
+    vec = std::move(ref_b);
+    vec = std::move(ref_c);
+    EXPECT_EQ(vec.Length(), 3u);
+    EXPECT_EQ(vec.Capacity(), 3u);
+    EXPECT_EQ(vec[0], "Bonjour");
+    EXPECT_EQ(vec[1], "le");
+    EXPECT_EQ(vec[2], "monde");
+    EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, RepeatMoveAssignRef_WithSpill) {
+    Vector<std::string, 1> vec_a{"hello", "world"};
+    Vector<std::string, 1> vec_b{"Ciao", "mondo"};
+    Vector<std::string, 1> vec_c{"bonjour", "le", "monde"};
+    VectorRef<std::string> ref_a{std::move(vec_a)};
+    VectorRef<std::string> ref_b{std::move(vec_b)};
+    VectorRef<std::string> ref_c{std::move(vec_c)};
+    Vector<std::string, 1> vec;
+    vec = std::move(ref_a);
+    vec = std::move(ref_b);
+    vec = std::move(ref_c);
+    EXPECT_EQ(vec.Length(), 3u);
+    EXPECT_EQ(vec.Capacity(), 3u);
+    EXPECT_EQ(vec[0], "bonjour");
+    EXPECT_EQ(vec[1], "le");
+    EXPECT_EQ(vec[2], "monde");
+    EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
 TEST(TintVectorTest, Index) {
     Vector<std::string, 2> vec{"hello", "world"};
     static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec[0])>>);
@@ -1684,7 +1978,7 @@
 TEST(TintVectorRefTest, Index) {
     Vector<std::string, 2> vec{"one", "two"};
     VectorRef<std::string> vec_ref(vec);
-    static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
+    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
     EXPECT_EQ(vec_ref[0], "one");
     EXPECT_EQ(vec_ref[1], "two");
 }
@@ -1755,206 +2049,6 @@
     EXPECT_EQ(vec_ref.end(), &vec[0] + 3);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// TintVectorConstRefTest
-////////////////////////////////////////////////////////////////////////////////
-TEST(TintVectorConstRefTest, CopyVectorConstRef) {
-    Vector<std::string, 1> vec_a{"one", "two"};
-    ConstVectorRef<std::string> vec_ref_a(vec_a);
-    ConstVectorRef<std::string> vec_ref_b(vec_ref_a);
-    Vector<std::string, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], "one");
-    EXPECT_EQ(vec_b[1], "two");
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorConstRef_Upcast) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    ConstVectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<C0*> vec_ref_b(vec_ref_a);  // Up-cast
-    Vector<C0*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorConstRef_AddConst) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    ConstVectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<const C1*> vec_ref_b(vec_ref_a);  // Up-cast
-    Vector<const C1*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorConstRef_UpcastAndAddConst) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    ConstVectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<const C0*> vec_ref_b(vec_ref_a);  // Up-cast
-    Vector<const C0*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVector) {
-    Vector<std::string, 1> vec_a{"one", "two"};
-    ConstVectorRef<std::string> vec_ref(vec_a);
-    Vector<std::string, 2> vec_b(vec_ref);
-    EXPECT_EQ(vec_b[0], "one");
-    EXPECT_EQ(vec_b[1], "two");
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVector_Upcast) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    ConstVectorRef<C0*> vec_ref(vec_a);
-    EXPECT_EQ(vec_ref[0], &c2a);
-    EXPECT_EQ(vec_ref[1], &c2b);
-    Vector<C0*, 2> vec_b(vec_ref);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVector_AddConst) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    ConstVectorRef<const C1*> vec_ref(vec_a);
-    EXPECT_EQ(vec_ref[0], &c2a);
-    EXPECT_EQ(vec_ref[1], &c2b);
-    Vector<const C1*, 2> vec_b(vec_ref);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorRef_Upcast) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    VectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<C0*> vec_ref_b(vec_ref_a);
-    EXPECT_EQ(vec_ref_b[0], &c2a);
-    EXPECT_EQ(vec_ref_b[1], &c2b);
-    Vector<C0*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorRef_AddConst) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    VectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<const C1*> vec_ref_b(vec_ref_a);
-    EXPECT_EQ(vec_ref_b[0], &c2a);
-    EXPECT_EQ(vec_ref_b[1], &c2b);
-    Vector<const C1*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, CopyVectorRef_UpcastAndAddConst) {
-    C2a c2a;
-    C2b c2b;
-    Vector<C1*, 1> vec_a{&c2a, &c2b};
-    VectorRef<C1*> vec_ref_a(vec_a);
-    ConstVectorRef<const C0*> vec_ref_b(vec_ref_a);
-    EXPECT_EQ(vec_ref_b[0], &c2a);
-    EXPECT_EQ(vec_ref_b[1], &c2b);
-    Vector<const C0*, 2> vec_b(vec_ref_b);
-    EXPECT_EQ(vec_b[0], &c2a);
-    EXPECT_EQ(vec_b[1], &c2b);
-    EXPECT_TRUE(AllInternallyHeld(vec_b));  // Copied, not moved
-}
-
-TEST(TintVectorConstRefTest, Index) {
-    Vector<std::string, 2> vec{"one", "two"};
-    ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
-    EXPECT_EQ(vec_ref[0], "one");
-    EXPECT_EQ(vec_ref[1], "two");
-}
-
-TEST(TintVectorConstRefTest, ConstIndex) {
-    Vector<std::string, 2> vec{"one", "two"};
-    const ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
-    EXPECT_EQ(vec_ref[0], "one");
-    EXPECT_EQ(vec_ref[1], "two");
-}
-
-TEST(TintVectorConstRefTest, Length) {
-    Vector<std::string, 2> vec{"one", "two", "three"};
-    ConstVectorRef<std::string> vec_ref(vec);
-    EXPECT_EQ(vec_ref.Length(), 3u);
-}
-
-TEST(TintVectorConstRefTest, Capacity) {
-    Vector<std::string, 5> vec{"one", "two", "three"};
-    ConstVectorRef<std::string> vec_ref(vec);
-    EXPECT_EQ(vec_ref.Capacity(), 5u);
-}
-
-TEST(TintVectorConstRefTest, IsEmpty) {
-    Vector<std::string, 1> vec;
-    ConstVectorRef<std::string> vec_ref(vec);
-    EXPECT_TRUE(vec_ref.IsEmpty());
-    vec.Push("one");
-    EXPECT_FALSE(vec_ref.IsEmpty());
-    vec.Pop();
-    EXPECT_TRUE(vec_ref.IsEmpty());
-}
-
-TEST(TintVectorConstRefTest, FrontBack) {
-    Vector<std::string, 3> vec{"front", "mid", "back"};
-    ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref.Front())>>);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref.Back())>>);
-    EXPECT_EQ(vec_ref.Front(), "front");
-    EXPECT_EQ(vec_ref.Back(), "back");
-}
-
-TEST(TintVectorConstRefTest, ConstFrontBack) {
-    Vector<std::string, 3> vec{"front", "mid", "back"};
-    const ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref.Front())>>);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref.Back())>>);
-    EXPECT_EQ(vec_ref.Front(), "front");
-    EXPECT_EQ(vec_ref.Back(), "back");
-}
-
-TEST(TintVectorConstRefTest, BeginEnd) {
-    Vector<std::string, 3> vec{"front", "mid", "back"};
-    ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec_ref.begin())>>);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec_ref.end())>>);
-    EXPECT_EQ(vec_ref.begin(), &vec[0]);
-    EXPECT_EQ(vec_ref.end(), &vec[0] + 3);
-}
-
-TEST(TintVectorConstRefTest, ConstBeginEnd) {
-    Vector<std::string, 3> vec{"front", "mid", "back"};
-    const ConstVectorRef<std::string> vec_ref(vec);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec_ref.begin())>>);
-    static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec_ref.end())>>);
-    EXPECT_EQ(vec_ref.begin(), &vec[0]);
-    EXPECT_EQ(vec_ref.end(), &vec[0] + 3);
-}
-
 }  // namespace
 }  // namespace tint::utils