Convert most remaining usages to utils::StringStream.

This CL converts most of the remaining Tint usages (leaving out the
fuzzer code and some float_to_string code).

Bug: tint:1686
Change-Id: I4d5cef176c15479250861903870ec5bec0f95b5e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/122002
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index d69b978..89eb6eef 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -293,7 +293,10 @@
     "demangler.cc",
     "demangler.h",
   ]
-  deps = [ ":libtint_program_src" ]
+  deps = [
+    ":libtint_base_src",
+    ":libtint_program_src",
+  ]
 }
 
 libtint_source_set("libtint_initializer_src") {
diff --git a/src/tint/ast/float_literal_expression_test.cc b/src/tint/ast/float_literal_expression_test.cc
index 5ec5f0f..6374583 100644
--- a/src/tint/ast/float_literal_expression_test.cc
+++ b/src/tint/ast/float_literal_expression_test.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/test_helper.h"
 
+#include "src/tint/utils/string_stream.h"
+
 namespace tint::ast {
 namespace {
 
@@ -42,7 +44,7 @@
 
 TEST_F(FloatLiteralExpressionTest, SuffixStringStream) {
     auto to_str = [](FloatLiteralExpression::Suffix suffix) {
-        std::stringstream ss;
+        utils::StringStream ss;
         ss << suffix;
         return ss.str();
     };
diff --git a/src/tint/ast/int_literal_expression_test.cc b/src/tint/ast/int_literal_expression_test.cc
index 969e1b9..9481c30 100644
--- a/src/tint/ast/int_literal_expression_test.cc
+++ b/src/tint/ast/int_literal_expression_test.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/test_helper.h"
 
+#include "src/tint/utils/string_stream.h"
+
 namespace tint::ast {
 namespace {
 
@@ -42,7 +44,7 @@
 
 TEST_F(IntLiteralExpressionTest, SuffixStringStream) {
     auto to_str = [](IntLiteralExpression::Suffix suffix) {
-        std::stringstream ss;
+        utils::StringStream ss;
         ss << suffix;
         return ss.str();
     };
diff --git a/src/tint/bench/benchmark.cc b/src/tint/bench/benchmark.cc
index c00e51f..6b072db 100644
--- a/src/tint/bench/benchmark.cc
+++ b/src/tint/bench/benchmark.cc
@@ -19,6 +19,8 @@
 #include <utility>
 #include <vector>
 
+#include "src/tint/utils/string_stream.h"
+
 namespace tint::bench {
 namespace {
 
@@ -44,7 +46,7 @@
     fseek(file, 0, SEEK_END);
     const auto file_size = static_cast<size_t>(ftell(file));
     if (0 != (file_size % sizeof(T))) {
-        std::stringstream err;
+        utils::StringStream err;
         err << "File " << input_file
             << " does not contain an integral number of objects: " << file_size
             << " bytes in the file, require " << sizeof(T) << " bytes per object";
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index cd46885..12d5b8c0 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -37,6 +37,7 @@
 #include "src/tint/cmd/helper.h"
 #include "src/tint/utils/io/command.h"
 #include "src/tint/utils/string.h"
+#include "src/tint/utils/string_stream.h"
 #include "src/tint/utils/transform.h"
 #include "src/tint/val/val.h"
 #include "tint/tint.h"
@@ -245,7 +246,7 @@
 std::vector<std::string> split_on_char(std::string list, char c) {
     std::vector<std::string> res;
 
-    std::stringstream str(list);
+    std::istringstream str(list);
     while (str.good()) {
         std::string substr;
         getline(str, substr, c);
@@ -1034,7 +1035,7 @@
          }},
     };
     auto transform_names = [&] {
-        std::stringstream names;
+        tint::utils::StringStream names;
         for (auto& t : transforms) {
             names << "   " << t.name << std::endl;
         }
diff --git a/src/tint/debug.h b/src/tint/debug.h
index 43bc052..5caeb5d 100644
--- a/src/tint/debug.h
+++ b/src/tint/debug.h
@@ -21,6 +21,7 @@
 #include "src/tint/diagnostic/formatter.h"
 #include "src/tint/diagnostic/printer.h"
 #include "src/tint/utils/compiler_macros.h"
+#include "src/tint/utils/string_stream.h"
 
 namespace tint {
 
@@ -71,7 +72,7 @@
     const size_t line_;
     diag::System system_;
     diag::List& diagnostics_;
-    std::stringstream msg_;
+    utils::StringStream msg_;
 };
 
 }  // namespace tint
diff --git a/src/tint/demangler.cc b/src/tint/demangler.cc
index d68a62a..146a31c 100644
--- a/src/tint/demangler.cc
+++ b/src/tint/demangler.cc
@@ -15,6 +15,7 @@
 #include "src/tint/demangler.h"
 
 #include "src/tint/program.h"
+#include "src/tint/utils/string_stream.h"
 
 namespace tint {
 namespace {
@@ -29,7 +30,7 @@
 Demangler::~Demangler() = default;
 
 std::string Demangler::Demangle(const SymbolTable& symbols, const std::string& str) const {
-    std::stringstream out;
+    utils::StringStream out;
 
     size_t pos = 0;
     for (;;) {
diff --git a/src/tint/diagnostic/formatter.cc b/src/tint/diagnostic/formatter.cc
index db69397..18520fc 100644
--- a/src/tint/diagnostic/formatter.cc
+++ b/src/tint/diagnostic/formatter.cc
@@ -20,6 +20,7 @@
 
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/diagnostic/printer.h"
+#include "src/tint/utils/string_stream.h"
 
 namespace tint::diag {
 namespace {
@@ -41,7 +42,7 @@
 }
 
 std::string to_str(const Source::Location& location) {
-    std::stringstream ss;
+    utils::StringStream ss;
     if (location.line > 0) {
         ss << location.line;
         if (location.column > 0) {
@@ -75,7 +76,7 @@
         auto str = stream.str();
         if (str.length() > 0) {
             printer->write(str, style);
-            std::stringstream reset;
+            utils::StringStream reset;
             stream.swap(reset);
         }
     }
@@ -95,12 +96,12 @@
     /// repeat queues the character c to be written to the printer n times.
     /// @param c the character to print `n` times
     /// @param n the number of times to print character `c`
-    void repeat(char c, size_t n) { std::fill_n(std::ostream_iterator<char>(stream), n, c); }
+    void repeat(char c, size_t n) { stream.repeat(c, n); }
 
   private:
     Printer* printer;
     diag::Style style;
-    std::stringstream stream;
+    utils::StringStream stream;
 };
 
 Formatter::Formatter() {}
diff --git a/src/tint/diagnostic/printer.h b/src/tint/diagnostic/printer.h
index b2ac105..9e4ce7c 100644
--- a/src/tint/diagnostic/printer.h
+++ b/src/tint/diagnostic/printer.h
@@ -19,6 +19,8 @@
 #include <sstream>
 #include <string>
 
+#include "src/tint/utils/string_stream.h"
+
 namespace tint::diag {
 
 class List;
@@ -73,7 +75,7 @@
     void write(const std::string& str, const Style&) override;
 
   private:
-    std::stringstream stream;
+    utils::StringStream stream;
 };
 
 }  // namespace tint::diag
diff --git a/src/tint/number_test.cc b/src/tint/number_test.cc
index fe03663..7ae5e37 100644
--- a/src/tint/number_test.cc
+++ b/src/tint/number_test.cc
@@ -19,6 +19,7 @@
 
 #include "src/tint/program_builder.h"
 #include "src/tint/utils/compiler_macros.h"
+#include "src/tint/utils/string_stream.h"
 
 #include "gtest/gtest.h"
 
@@ -254,7 +255,7 @@
     float input_value = GetParam().input_value;
     float quantized_value = GetParam().quantized_value;
 
-    std::stringstream ss;
+    utils::StringStream ss;
     ss << "input value = " << input_value << ", expected quantized value = " << quantized_value;
     SCOPED_TRACE(ss.str());
 
@@ -269,7 +270,7 @@
     float input_value = GetParam().input_value;
     uint16_t representation = GetParam().f16_bit_pattern;
 
-    std::stringstream ss;
+    utils::StringStream ss;
     ss << "input value = " << input_value
        << ", expected binary16 bits representation = " << std::hex << std::showbase
        << representation;
@@ -282,7 +283,7 @@
     float input_value = GetParam().quantized_value;
     uint16_t representation = GetParam().f16_bit_pattern;
 
-    std::stringstream ss;
+    utils::StringStream ss;
     ss << "binary16 bits representation = " << std::hex << std::showbase << representation
        << " expected value = " << input_value;
     SCOPED_TRACE(ss.str());
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 28ecebd..0030ca1 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -36,6 +36,7 @@
 #include "src/tint/utils/block_allocator.h"
 #include "src/tint/utils/hash.h"
 #include "src/tint/utils/map.h"
+#include "src/tint/utils/string_stream.h"
 
 using namespace tint::number_suffixes;  // NOLINT
 
@@ -695,7 +696,7 @@
     : Base(pid, nid), op(o), type(ty), address_space(as), buffer(buf) {}
 DecomposeMemoryAccess::Intrinsic::~Intrinsic() = default;
 std::string DecomposeMemoryAccess::Intrinsic::InternalName() const {
-    std::stringstream ss;
+    utils::StringStream ss;
     switch (op) {
         case Op::kLoad:
             ss << "intrinsic_load_";
diff --git a/src/tint/transform/direct_variable_access.cc b/src/tint/transform/direct_variable_access.cc
index a6ef9d8..00d1e67 100644
--- a/src/tint/transform/direct_variable_access.cc
+++ b/src/tint/transform/direct_variable_access.cc
@@ -32,6 +32,7 @@
 #include "src/tint/type/abstract_int.h"
 #include "src/tint/utils/reverse.h"
 #include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::DirectVariableAccess);
 TINT_INSTANTIATE_TYPEINFO(tint::transform::DirectVariableAccess::Config);
@@ -687,7 +688,7 @@
                 // Build an appropriate variant function name.
                 // This is derived from the original function name and the pointer parameter
                 // chains.
-                std::stringstream ss;
+                utils::StringStream ss;
                 ss << ctx.src->Symbols().NameFor(target->Declaration()->name->symbol);
                 for (auto* param : target->Parameters()) {
                     if (auto indices = target_signature.Find(param)) {
@@ -1080,7 +1081,7 @@
 
     /// @returns a name describing the given shape
     std::string AccessShapeName(const AccessShape& shape) {
-        std::stringstream ss;
+        utils::StringStream ss;
 
         if (IsPrivateOrFunction(shape.root.address_space)) {
             ss << "F";
diff --git a/src/tint/transform/vertex_pulling.cc b/src/tint/transform/vertex_pulling.cc
index ecc7576..09a68bb 100644
--- a/src/tint/transform/vertex_pulling.cc
+++ b/src/tint/transform/vertex_pulling.cc
@@ -26,6 +26,7 @@
 #include "src/tint/utils/compiler_macros.h"
 #include "src/tint/utils/map.h"
 #include "src/tint/utils/math.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::VertexPulling);
 TINT_INSTANTIATE_TYPEINFO(tint::transform::VertexPulling::Config);
@@ -59,7 +60,7 @@
 /// @param out the std::ostream to write to
 /// @param format the VertexFormat to write
 /// @returns out so calls can be chained
-std::ostream& operator<<(std::ostream& out, VertexFormat format) {
+utils::StringStream& operator<<(utils::StringStream& out, VertexFormat format) {
     switch (format) {
         case VertexFormat::kUint8x2:
             return out << "uint8x2";
@@ -379,7 +380,7 @@
 
                 // Base types must match between the vertex stream and the WGSL variable
                 if (!IsTypeCompatible(var_dt, fmt_dt)) {
-                    std::stringstream err;
+                    utils::StringStream err;
                     err << "VertexAttributeDescriptor for location "
                         << std::to_string(attribute_desc.shader_location) << " has format "
                         << attribute_desc.format << " but shader expects "
diff --git a/src/tint/type/array.cc b/src/tint/type/array.cc
index dedf986..96c745c 100644
--- a/src/tint/type/array.cc
+++ b/src/tint/type/array.cc
@@ -21,6 +21,7 @@
 #include "src/tint/type/manager.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::Array);
 
@@ -81,7 +82,7 @@
 }
 
 std::string Array::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
+    utils::StringStream out;
     if (!IsStrideImplicit()) {
         out << "@stride(" << stride_ << ") ";
     }
diff --git a/src/tint/type/atomic.cc b/src/tint/type/atomic.cc
index 0459394..39e2aae 100644
--- a/src/tint/type/atomic.cc
+++ b/src/tint/type/atomic.cc
@@ -19,6 +19,7 @@
 #include "src/tint/type/manager.h"
 #include "src/tint/type/reference.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::Atomic);
 
@@ -42,7 +43,7 @@
 }
 
 std::string Atomic::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
+    utils::StringStream out;
     out << "atomic<" << subtype_->FriendlyName(symbols) << ">";
     return out.str();
 }
diff --git a/src/tint/type/depth_multisampled_texture.cc b/src/tint/type/depth_multisampled_texture.cc
index 84d7c32..fc3b753 100644
--- a/src/tint/type/depth_multisampled_texture.cc
+++ b/src/tint/type/depth_multisampled_texture.cc
@@ -19,6 +19,7 @@
 #include "src/tint/type/manager.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::DepthMultisampledTexture);
 
@@ -46,7 +47,7 @@
 }
 
 std::string DepthMultisampledTexture::FriendlyName(const SymbolTable&) const {
-    std::ostringstream out;
+    utils::StringStream out;
     out << "texture_depth_multisampled_" << dim();
     return out.str();
 }
diff --git a/src/tint/type/depth_texture.cc b/src/tint/type/depth_texture.cc
index ca216e7..90a6127 100644
--- a/src/tint/type/depth_texture.cc
+++ b/src/tint/type/depth_texture.cc
@@ -19,6 +19,7 @@
 #include "src/tint/type/manager.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::DepthTexture);
 
@@ -47,7 +48,7 @@
 }
 
 std::string DepthTexture::FriendlyName(const SymbolTable&) const {
-    std::ostringstream out;
+    utils::StringStream out;
     out << "texture_depth_" << dim();
     return out.str();
 }
diff --git a/src/tint/type/matrix.cc b/src/tint/type/matrix.cc
index 76970e6..195a9e9 100644
--- a/src/tint/type/matrix.cc
+++ b/src/tint/type/matrix.cc
@@ -19,6 +19,7 @@
 #include "src/tint/type/manager.h"
 #include "src/tint/type/vector.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::Matrix);
 
@@ -51,7 +52,7 @@
 }
 
 std::string Matrix::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
+    utils::StringStream out;
     out << "mat" << columns_ << "x" << rows_ << "<" << subtype_->FriendlyName(symbols) << ">";
     return out.str();
 }
diff --git a/src/tint/type/multisampled_texture.cc b/src/tint/type/multisampled_texture.cc
index 182ae88..0b2dfd0 100644
--- a/src/tint/type/multisampled_texture.cc
+++ b/src/tint/type/multisampled_texture.cc
@@ -19,6 +19,7 @@
 #include "src/tint/type/manager.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::MultisampledTexture);
 
@@ -40,7 +41,7 @@
 }
 
 std::string MultisampledTexture::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
+    utils::StringStream out;
     out << "texture_multisampled_" << dim() << "<" << type_->FriendlyName(symbols) << ">";
     return out.str();
 }
diff --git a/src/tint/type/pointer.cc b/src/tint/type/pointer.cc
index 17bbe1a..aa77816 100644
--- a/src/tint/type/pointer.cc
+++ b/src/tint/type/pointer.cc
@@ -19,6 +19,7 @@
 #include "src/tint/type/manager.h"
 #include "src/tint/type/reference.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::Pointer);
 
@@ -43,7 +44,7 @@
 }
 
 std::string Pointer::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
+    utils::StringStream out;
     out << "ptr<";
     if (address_space_ != builtin::AddressSpace::kUndefined) {
         out << address_space_ << ", ";
diff --git a/src/tint/type/reference.cc b/src/tint/type/reference.cc
index 5db6300..03ed224 100644
--- a/src/tint/type/reference.cc
+++ b/src/tint/type/reference.cc
@@ -18,6 +18,7 @@
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/type/manager.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::Reference);
 
@@ -44,7 +45,7 @@
 }
 
 std::string Reference::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
+    utils::StringStream out;
     out << "ref<";
     if (address_space_ != builtin::AddressSpace::kUndefined) {
         out << address_space_ << ", ";
diff --git a/src/tint/type/sampled_texture.cc b/src/tint/type/sampled_texture.cc
index b3e7375..b3e6e10 100644
--- a/src/tint/type/sampled_texture.cc
+++ b/src/tint/type/sampled_texture.cc
@@ -19,6 +19,7 @@
 #include "src/tint/type/manager.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::SampledTexture);
 
@@ -39,7 +40,7 @@
 }
 
 std::string SampledTexture::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
+    utils::StringStream out;
     out << "texture_" << dim() << "<" << type_->FriendlyName(symbols) << ">";
     return out.str();
 }
diff --git a/src/tint/type/storage_texture.cc b/src/tint/type/storage_texture.cc
index beb5da6..180db4f 100644
--- a/src/tint/type/storage_texture.cc
+++ b/src/tint/type/storage_texture.cc
@@ -19,6 +19,7 @@
 #include "src/tint/type/manager.h"
 #include "src/tint/type/u32.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::StorageTexture);
 
@@ -43,7 +44,7 @@
 }
 
 std::string StorageTexture::FriendlyName(const SymbolTable&) const {
-    std::ostringstream out;
+    utils::StringStream out;
     out << "texture_storage_" << dim() << "<" << texel_format_ << ", " << access_ << ">";
     return out.str();
 }
diff --git a/src/tint/type/struct.cc b/src/tint/type/struct.cc
index c39916b..7649691 100644
--- a/src/tint/type/struct.cc
+++ b/src/tint/type/struct.cc
@@ -22,6 +22,7 @@
 #include "src/tint/symbol_table.h"
 #include "src/tint/type/manager.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::Struct);
 TINT_INSTANTIATE_TYPEINFO(tint::type::StructMember);
@@ -96,7 +97,7 @@
 }
 
 std::string Struct::Layout(const tint::SymbolTable& symbols) const {
-    std::stringstream ss;
+    utils::StringStream ss;
 
     auto member_name_of = [&](const StructMember* sm) { return symbols.NameFor(sm->Name()); };
 
diff --git a/src/tint/type/vector.cc b/src/tint/type/vector.cc
index 6db1465..9f1f415 100644
--- a/src/tint/type/vector.cc
+++ b/src/tint/type/vector.cc
@@ -18,6 +18,7 @@
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/type/manager.h"
 #include "src/tint/utils/hash.h"
+#include "src/tint/utils/string_stream.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::type::Vector);
 
@@ -47,7 +48,7 @@
 }
 
 std::string Vector::FriendlyName(const SymbolTable& symbols) const {
-    std::ostringstream out;
+    utils::StringStream out;
     if (packed_) {
         out << "__packed_";
     }
diff --git a/src/tint/utils/enum_set_test.cc b/src/tint/utils/enum_set_test.cc
index 4cdeb63..a6bb169 100644
--- a/src/tint/utils/enum_set_test.cc
+++ b/src/tint/utils/enum_set_test.cc
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "gmock/gmock.h"
+#include "src/tint/utils/string_stream.h"
 
 namespace tint::utils {
 namespace {
@@ -232,7 +233,7 @@
 }
 
 TEST(EnumSetTest, Ostream) {
-    std::stringstream ss;
+    utils::StringStream ss;
     ss << EnumSet<E>(E::A, E::C);
     EXPECT_EQ(ss.str(), "{A, C}");
 }
diff --git a/src/tint/utils/io/command_windows.cc b/src/tint/utils/io/command_windows.cc
index abe7242..31d0308 100644
--- a/src/tint/utils/io/command_windows.cc
+++ b/src/tint/utils/io/command_windows.cc
@@ -21,6 +21,7 @@
 #include <string>
 
 #include "src/tint/utils/defer.h"
+#include "src/tint/utils/string_stream.h"
 
 namespace tint::utils {
 
@@ -197,7 +198,7 @@
     si.hStdError = stderr_pipe.write;
     si.hStdInput = stdin_pipe.read;
 
-    std::stringstream args;
+    utils::StringStream args;
     args << path_;
     for (auto& arg : arguments) {
         if (!arg.empty()) {
diff --git a/src/tint/utils/io/tmpfile.h b/src/tint/utils/io/tmpfile.h
index 24e7208..7949a371 100644
--- a/src/tint/utils/io/tmpfile.h
+++ b/src/tint/utils/io/tmpfile.h
@@ -18,6 +18,8 @@
 #include <sstream>
 #include <string>
 
+#include "src/tint/utils/string_stream.h"
+
 namespace tint::utils {
 
 /// TmpFile constructs a temporary file that can be written to, and is
@@ -55,7 +57,7 @@
     /// @return a reference to this TmpFile
     template <typename T>
     inline TmpFile& operator<<(T&& data) {
-        std::stringstream ss;
+        utils::StringStream ss;
         ss << data;
         std::string str = ss.str();
         Append(str.data(), str.size());
diff --git a/src/tint/utils/string.h b/src/tint/utils/string.h
index 9bf6e7a..e77e637 100644
--- a/src/tint/utils/string.h
+++ b/src/tint/utils/string.h
@@ -43,7 +43,7 @@
 /// @returns value printed as a string via the std::ostream `<<` operator
 template <typename T>
 std::string ToString(const T& value) {
-    std::stringstream s;
+    utils::StringStream s;
     s << value;
     return s.str();
 }
@@ -52,7 +52,7 @@
 /// @returns value printed as a string via the std::ostream `<<` operator
 template <typename... TYs>
 std::string ToString(const std::variant<TYs...>& value) {
-    std::stringstream s;
+    utils::StringStream s;
     s << std::visit([&](auto& v) { return ToString(v); }, value);
     return s.str();
 }
diff --git a/src/tint/utils/string_stream.h b/src/tint/utils/string_stream.h
index 6cb6efc..893d2b9 100644
--- a/src/tint/utils/string_stream.h
+++ b/src/tint/utils/string_stream.h
@@ -16,9 +16,11 @@
 #define SRC_TINT_UTILS_STRING_STREAM_H_
 
 #include <functional>
+#include <iterator>
 #include <limits>
 #include <sstream>
 #include <string>
+#include <utility>
 
 namespace tint::utils {
 
@@ -89,6 +91,15 @@
         return *this;
     }
 
+    /// Swaps streams
+    /// @param other stream to swap too
+    void swap(StringStream& other) { sstream_.swap(other.sstream_); }
+
+    /// repeat queues the character c to be written to the printer n times.
+    /// @param c the character to print `n` times
+    /// @param n the number of times to print character `c`
+    void repeat(char c, size_t n) { std::fill_n(std::ostream_iterator<char>(sstream_), n, c); }
+
     /// The callback to emit a `endl` to the stream
     using StdEndl = std::ostream& (*)(std::ostream&);
 
diff --git a/src/tint/utils/vector_test.cc b/src/tint/utils/vector_test.cc
index 6245476..0604732 100644
--- a/src/tint/utils/vector_test.cc
+++ b/src/tint/utils/vector_test.cc
@@ -20,6 +20,7 @@
 #include "gmock/gmock.h"
 
 #include "src/tint/utils/bitcast.h"
+#include "src/tint/utils/string_stream.h"
 
 namespace tint::utils {
 namespace {
@@ -1788,7 +1789,7 @@
 }
 
 TEST(TintVectorTest, ostream) {
-    std::stringstream ss;
+    utils::StringStream ss;
     ss << Vector{1, 2, 3};
     EXPECT_EQ(ss.str(), "[1, 2, 3]");
 }
@@ -2065,7 +2066,7 @@
 }
 
 TEST(TintVectorRefTest, ostream) {
-    std::stringstream ss;
+    utils::StringStream ss;
     Vector vec{1, 2, 3};
     const VectorRef<int> vec_ref(vec);
     ss << vec_ref;