[ir][msl] Add basic type emission

Add emission of basic types (`bool`, `void`, `f32`, `f16`, `i32` and
`u32`) to the MSL IR generator.

Bug: tint:1967
Change-Id: I1b73b721542c888b9e2adf339c1758d3c89004b4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/138820
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 77d6165..8371b8f 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -2216,6 +2216,7 @@
     if (tint_build_ir) {
       sources += [
         "writer/msl/ir/generator_impl_ir_function_test.cc",
+        "writer/msl/ir/generator_impl_ir_type_test.cc",
         "writer/msl/ir/test_helper_ir.h",
       ]
       deps += [ ":libtint_ir_src" ]
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index ee9d6b9..1b64b13 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -1455,6 +1455,7 @@
     if(${TINT_BUILD_IR})
       list(APPEND TINT_TEST_SRCS
         writer/msl/ir/generator_impl_ir_function_test.cc
+        writer/msl/ir/generator_impl_ir_type_test.cc
         writer/msl/ir/test_helper_ir.h
       )
     endif()
diff --git a/src/tint/writer/msl/ir/generator_impl_ir.cc b/src/tint/writer/msl/ir/generator_impl_ir.cc
index 1a4e5b7..4acbeba 100644
--- a/src/tint/writer/msl/ir/generator_impl_ir.cc
+++ b/src/tint/writer/msl/ir/generator_impl_ir.cc
@@ -15,7 +15,14 @@
 #include "src/tint/writer/msl/ir/generator_impl_ir.h"
 
 #include "src/tint/ir/validate.h"
+#include "src/tint/switch.h"
 #include "src/tint/transform/manager.h"
+#include "src/tint/type/bool.h"
+#include "src/tint/type/f16.h"
+#include "src/tint/type/f32.h"
+#include "src/tint/type/i32.h"
+#include "src/tint/type/u32.h"
+#include "src/tint/type/void.h"
 #include "src/tint/utils/scoped_assignment.h"
 
 namespace tint::writer::msl {
@@ -31,6 +38,11 @@
 
 }  // namespace
 
+// Helper for calling TINT_UNIMPLEMENTED() from a Switch(object_ptr) default case.
+#define UNHANDLED_CASE(object_ptr)           \
+    TINT_UNIMPLEMENTED(Writer, diagnostics_) \
+        << "unhandled case in Switch(): " << (object_ptr ? object_ptr->TypeInfo().name : "<null>")
+
 GeneratorImplIr::GeneratorImplIr(ir::Module* module) : IRTextGenerator(module) {}
 
 GeneratorImplIr::~GeneratorImplIr() = default;
@@ -70,8 +82,24 @@
 }
 
 void GeneratorImplIr::EmitFunction(ir::Function* func) {
-    line() << "void " << ir_->NameOf(func).Name() << "() {";
+    {
+        auto out = line();
+        EmitType(out, func->ReturnType());
+        out << " " << ir_->NameOf(func).Name() << "() {";
+    }
     line() << "}";
 }
 
+void GeneratorImplIr::EmitType(utils::StringStream& out, const type::Type* ty) {
+    tint::Switch(
+        ty,                                         //
+        [&](const type::Bool*) { out << "bool"; },  //
+        [&](const type::Void*) { out << "void"; },  //
+        [&](const type::F32*) { out << "float"; },  //
+        [&](const type::F16*) { out << "half"; },   //
+        [&](const type::I32*) { out << "int"; },    //
+        [&](const type::U32*) { out << "uint"; },   //
+        [&](Default) { UNHANDLED_CASE(ty); });
+}
+
 }  // namespace tint::writer::msl
diff --git a/src/tint/writer/msl/ir/generator_impl_ir.h b/src/tint/writer/msl/ir/generator_impl_ir.h
index 584a88f..3a39ee9 100644
--- a/src/tint/writer/msl/ir/generator_impl_ir.h
+++ b/src/tint/writer/msl/ir/generator_impl_ir.h
@@ -17,6 +17,7 @@
 
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/ir/module.h"
+#include "src/tint/utils/string_stream.h"
 #include "src/tint/writer/ir_text_generator.h"
 
 namespace tint::writer::msl {
@@ -35,6 +36,11 @@
     /// Emit the function
     /// @param func the function to emit
     void EmitFunction(ir::Function* func);
+
+    /// Emit a type
+    /// @param out the stream to emit too
+    /// @param ty the type to emit
+    void EmitType(utils::StringStream& out, const type::Type* ty);
 };
 
 }  // namespace tint::writer::msl
diff --git a/src/tint/writer/msl/ir/generator_impl_ir_function_test.cc b/src/tint/writer/msl/ir/generator_impl_ir_function_test.cc
index 5789037..cf3936e 100644
--- a/src/tint/writer/msl/ir/generator_impl_ir_function_test.cc
+++ b/src/tint/writer/msl/ir/generator_impl_ir_function_test.cc
@@ -22,8 +22,9 @@
     func->Block()->Append(b.Return(func));
 
     ASSERT_TRUE(IRIsValid()) << Error();
-
     generator_.EmitFunction(func);
+
+    ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
     EXPECT_EQ(generator_.result(), R"(
 void foo() {
 }
diff --git a/src/tint/writer/msl/ir/generator_impl_ir_type_test.cc b/src/tint/writer/msl/ir/generator_impl_ir_type_test.cc
new file mode 100644
index 0000000..1140581
--- /dev/null
+++ b/src/tint/writer/msl/ir/generator_impl_ir_type_test.cc
@@ -0,0 +1,857 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gmock/gmock.h"
+
+#include "src/tint/utils/string.h"
+#include "src/tint/writer/msl/ir/test_helper_ir.h"
+
+namespace tint::writer::msl {
+namespace {
+
+using namespace tint::builtin::fluent_types;  // NOLINT
+using namespace tint::number_suffixes;        // NOLINT
+
+// void FormatMSLField(utils::StringStream& out,
+//                     const char* addr,
+//                     const char* type,
+//                     size_t array_count,
+//                     const char* name) {
+//     out << "  /* " << std::string(addr) << " */ ";
+//     if (array_count == 0) {
+//         out << type << " ";
+//     } else {
+//         out << "tint_array<" << type << ", " << std::to_string(array_count) << "> ";
+//     }
+//     out << name << ";\n";
+// }
+//
+// #define CHECK_TYPE_SIZE_AND_ALIGN(TYPE, SIZE, ALIGN)      \  //
+//     static_assert(sizeof(TYPE) == SIZE, "Bad type size"); \  //
+//     static_assert(alignof(TYPE) == ALIGN, "Bad type alignment")
+//
+// // Declare C++ types that match the size and alignment of the types of the same
+// // name in MSL.
+// #define DECLARE_TYPE(NAME, SIZE, ALIGN) \  //
+//     struct alignas(ALIGN) NAME {        \  //
+//         uint8_t _[SIZE];                \  //
+//     };                                  \  //
+//     CHECK_TYPE_SIZE_AND_ALIGN(NAME, SIZE, ALIGN)
+//
+// // Size and alignments taken from the MSL spec:
+// // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+// DECLARE_TYPE(float2, 8, 8);
+// DECLARE_TYPE(float3, 12, 4);
+// DECLARE_TYPE(float4, 16, 16);
+// DECLARE_TYPE(float2x2, 16, 8);
+// DECLARE_TYPE(float2x3, 32, 16);
+// DECLARE_TYPE(float2x4, 32, 16);
+// DECLARE_TYPE(float3x2, 24, 8);
+// DECLARE_TYPE(float3x3, 48, 16);
+// DECLARE_TYPE(float3x4, 48, 16);
+// DECLARE_TYPE(float4x2, 32, 8);
+// DECLARE_TYPE(float4x3, 64, 16);
+// DECLARE_TYPE(float4x4, 64, 16);
+// DECLARE_TYPE(half2, 4, 4);
+// DECLARE_TYPE(packed_half3, 6, 2);
+// DECLARE_TYPE(half4, 8, 8);
+// DECLARE_TYPE(half2x2, 8, 4);
+// DECLARE_TYPE(half2x3, 16, 8);
+// DECLARE_TYPE(half2x4, 16, 8);
+// DECLARE_TYPE(half3x2, 12, 4);
+// DECLARE_TYPE(half3x3, 24, 8);
+// DECLARE_TYPE(half3x4, 24, 8);
+// DECLARE_TYPE(half4x2, 16, 4);
+// DECLARE_TYPE(half4x3, 32, 8);
+// DECLARE_TYPE(half4x4, 32, 8);
+// using uint = unsigned int;
+//
+// using MslGeneratorImplTest = TestHelper;
+//
+// TEST_F(MslGeneratorImplTest, EmitType_Array) {
+//     auto arr = ty.array<bool, 4>();
+//     ast::Type type = GlobalVar("G", arr, builtin::AddressSpace::kPrivate)->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "ary")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "tint_array<bool, 4>");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_ArrayOfArray) {
+//     auto a = ty.array<bool, 4>();
+//     auto b = ty.array(a, 5_u);
+//     ast::Type type = GlobalVar("G", b, builtin::AddressSpace::kPrivate)->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "ary")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "tint_array<tint_array<bool, 4>, 5>");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_ArrayOfArrayOfArray) {
+//     auto a = ty.array<bool, 4>();
+//     auto b = ty.array(a, 5_u);
+//     auto c = ty.array(b, 6_u);
+//     ast::Type type = GlobalVar("G", c, builtin::AddressSpace::kPrivate)->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "ary")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "tint_array<tint_array<tint_array<bool, 4>, 5>, 6>");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_Array_WithoutName) {
+//     auto arr = ty.array<bool, 4>();
+//     ast::Type type = GlobalVar("G", arr, builtin::AddressSpace::kPrivate)->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "tint_array<bool, 4>");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_RuntimeArray) {
+//     auto arr = ty.array<bool, 1>();
+//     ast::Type type = GlobalVar("G", arr, builtin::AddressSpace::kPrivate)->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "ary")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "tint_array<bool, 1>");
+// }
+
+TEST_F(MslGeneratorImplIrTest, EmitType_Bool) {
+    generator_.EmitType(generator_.line(), ty.bool_());
+    ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+    EXPECT_EQ(utils::TrimSpace(generator_.result()), "bool");
+}
+
+TEST_F(MslGeneratorImplIrTest, EmitType_F32) {
+    generator_.EmitType(generator_.line(), ty.f32());
+    ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+    EXPECT_EQ(utils::TrimSpace(generator_.result()), "float");
+}
+
+TEST_F(MslGeneratorImplIrTest, EmitType_F16) {
+    generator_.EmitType(generator_.line(), ty.f16());
+    ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+    EXPECT_EQ(utils::TrimSpace(generator_.result()), "half");
+}
+
+TEST_F(MslGeneratorImplIrTest, EmitType_I32) {
+    generator_.EmitType(generator_.line(), ty.i32());
+    ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+    EXPECT_EQ(utils::TrimSpace(generator_.result()), "int");
+}
+
+// TEST_F(MslGeneratorImplTest, EmitType_Matrix_F32) {
+//     auto* f32 = create<type::F32>();
+//     auto* vec3 = create<type::Vector>(f32, 3u);
+//     auto* mat2x3 = create<type::Matrix>(vec3, 2u);
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, mat2x3, "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "float2x3");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_Matrix_F16) {
+//     auto* f16 = create<type::F16>();
+//     auto* vec3 = create<type::Vector>(f16, 3u);
+//     auto* mat2x3 = create<type::Matrix>(vec3, 2u);
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, mat2x3, "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "half2x3");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_Pointer) {
+//     auto* f32 = create<type::F32>();
+//     auto* p =
+//         create<type::Pointer>(builtin::AddressSpace::kWorkgroup, f32,
+//         builtin::Access::kReadWrite);
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, p, "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "threadgroup float* ");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_Struct) {
+//     auto* s = Structure("S", utils::Vector{
+//                                  Member("a", ty.i32()),
+//                                  Member("b", ty.f32()),
+//                              });
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, program->TypeOf(s), "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "S");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_StructDecl) {
+//     auto* s = Structure("S", utils::Vector{
+//                                  Member("a", ty.i32()),
+//                                  Member("b", ty.f32()),
+//                              });
+//
+//     GeneratorImpl& gen = Build();
+//
+//     TextGenerator::TextBuffer buf;
+//     auto* str = program->TypeOf(s)->As<type::Struct>();
+//     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
+//     EXPECT_EQ(buf.String(), R"(struct S {
+//   int a;
+//   float b;
+// };
+// )");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_NonComposites) {
+//     auto* s = Structure(
+//         "S", utils::Vector{
+//                  Member("a", ty.i32(), utils::Vector{MemberSize(32_a)}),
+//                  Member("b", ty.f32(), utils::Vector{MemberAlign(128_i), MemberSize(128_a)}),
+//                  Member("c", ty.vec2<f32>()),
+//                  Member("d", ty.u32()),
+//                  Member("e", ty.vec3<f32>()),
+//                  Member("f", ty.u32()),
+//                  Member("g", ty.vec4<f32>()),
+//                  Member("h", ty.u32()),
+//                  Member("i", ty.mat2x2<f32>()),
+//                  Member("j", ty.u32()),
+//                  Member("k", ty.mat2x3<f32>()),
+//                  Member("l", ty.u32()),
+//                  Member("m", ty.mat2x4<f32>()),
+//                  Member("n", ty.u32()),
+//                  Member("o", ty.mat3x2<f32>()),
+//                  Member("p", ty.u32()),
+//                  Member("q", ty.mat3x3<f32>()),
+//                  Member("r", ty.u32()),
+//                  Member("s", ty.mat3x4<f32>()),
+//                  Member("t", ty.u32()),
+//                  Member("u", ty.mat4x2<f32>()),
+//                  Member("v", ty.u32()),
+//                  Member("w", ty.mat4x3<f32>()),
+//                  Member("x", ty.u32()),
+//                  Member("y", ty.mat4x4<f32>()),
+//                  Member("z", ty.f32()),
+//              });
+//
+//     ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
+//                                builtin::Access::kRead, Binding(0_a), Group(0_a))
+//                          ->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     TextGenerator::TextBuffer buf;
+//     auto* str = program->TypeOf(type)->As<type::Struct>();
+//     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
+//
+//     // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
+//     // for each field of the structure s.
+// #define ALL_FIELDS()                       \  //
+//     FIELD(0x0000, int, 0, a)               \  //
+//     FIELD(0x0004, int8_t, 124, tint_pad)   \  //
+//     FIELD(0x0080, float, 0, b)             \  //
+//     FIELD(0x0084, int8_t, 124, tint_pad_1) \  //
+//     FIELD(0x0100, float2, 0, c)            \  //
+//     FIELD(0x0108, uint, 0, d)              \  //
+//     FIELD(0x010c, int8_t, 4, tint_pad_2)   \  //
+//     FIELD(0x0110, float3, 0, e)            \  //
+//     FIELD(0x011c, uint, 0, f)              \  //
+//     FIELD(0x0120, float4, 0, g)            \  //
+//     FIELD(0x0130, uint, 0, h)              \  //
+//     FIELD(0x0134, int8_t, 4, tint_pad_3)   \  //
+//     FIELD(0x0138, float2x2, 0, i)          \  //
+//     FIELD(0x0148, uint, 0, j)              \  //
+//     FIELD(0x014c, int8_t, 4, tint_pad_4)   \  //
+//     FIELD(0x0150, float2x3, 0, k)          \  //
+//     FIELD(0x0170, uint, 0, l)              \  //
+//     FIELD(0x0174, int8_t, 12, tint_pad_5)  \  //
+//     FIELD(0x0180, float2x4, 0, m)          \  //
+//     FIELD(0x01a0, uint, 0, n)              \  //
+//     FIELD(0x01a4, int8_t, 4, tint_pad_6)   \  //
+//     FIELD(0x01a8, float3x2, 0, o)          \  //
+//     FIELD(0x01c0, uint, 0, p)              \  //
+//     FIELD(0x01c4, int8_t, 12, tint_pad_7)  \  //
+//     FIELD(0x01d0, float3x3, 0, q)          \  //
+//     FIELD(0x0200, uint, 0, r)              \  //
+//     FIELD(0x0204, int8_t, 12, tint_pad_8)  \  //
+//     FIELD(0x0210, float3x4, 0, s)          \  //
+//     FIELD(0x0240, uint, 0, t)              \  //
+//     FIELD(0x0244, int8_t, 4, tint_pad_9)   \  //
+//     FIELD(0x0248, float4x2, 0, u)          \  //
+//     FIELD(0x0268, uint, 0, v)              \  //
+//     FIELD(0x026c, int8_t, 4, tint_pad_10)  \  //
+//     FIELD(0x0270, float4x3, 0, w)          \  //
+//     FIELD(0x02b0, uint, 0, x)              \  //
+//     FIELD(0x02b4, int8_t, 12, tint_pad_11) \  //
+//     FIELD(0x02c0, float4x4, 0, y)          \  //
+//     FIELD(0x0300, float, 0, z)             \  //
+//     FIELD(0x0304, int8_t, 124, tint_pad_12)
+//
+//     // Check that the generated string is as expected.
+//     utils::StringStream expect;
+//     expect << "struct S {\n";
+// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \  //
+//     FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
+//     ALL_FIELDS()
+// #undef FIELD
+//     expect << "};\n";
+//     EXPECT_EQ(buf.String(), expect.str());
+//
+//     // 1.4 Metal and C++14
+//     // The Metal programming language is a C++14-based Specification with
+//     // extensions and restrictions. Refer to the C++14 Specification (also known
+//     // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
+//     // description of the language grammar.
+//     //
+//     // Tint is written in C++14, so use the compiler to verify the generated
+//     // layout is as expected for C++14 / MSL.
+//     {
+//         struct S {
+// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1>
+// NAME;
+//             ALL_FIELDS()
+// #undef FIELD
+//         };
+//
+// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \ //
+//     EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
+//         ALL_FIELDS()
+// #undef FIELD
+//     }
+// #undef ALL_FIELDS
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_Structures) {
+//     // inner_x: size(1024), align(512)
+//     auto* inner_x =
+//         Structure("inner_x", utils::Vector{
+//                                  Member("a", ty.i32()),
+//                                  Member("b", ty.f32(), utils::Vector{MemberAlign(512_i)}),
+//                              });
+//
+//     // inner_y: size(516), align(4)
+//     auto* inner_y =
+//         Structure("inner_y", utils::Vector{
+//                                  Member("a", ty.i32(), utils::Vector{MemberSize(512_a)}),
+//                                  Member("b", ty.f32()),
+//                              });
+//
+//     auto* s = Structure("S", utils::Vector{
+//                                  Member("a", ty.i32()),
+//                                  Member("b", ty.Of(inner_x)),
+//                                  Member("c", ty.f32()),
+//                                  Member("d", ty.Of(inner_y)),
+//                                  Member("e", ty.f32()),
+//                              });
+//
+//     ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
+//                                builtin::Access::kRead, Binding(0_a), Group(0_a))
+//                          ->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     TextGenerator::TextBuffer buf;
+//     auto* str = program->TypeOf(type)->As<type::Struct>();
+//     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
+//
+//     // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
+//     // for each field of the structure s.
+// #define ALL_FIELDS()                     \  //
+//     FIELD(0x0000, int, 0, a)             \  //
+//     FIELD(0x0004, int8_t, 508, tint_pad) \  //
+//     FIELD(0x0200, inner_x, 0, b)         \  //
+//     FIELD(0x0600, float, 0, c)           \  //
+//     FIELD(0x0604, inner_y, 0, d)         \  //
+//     FIELD(0x0808, float, 0, e)           \  //
+//     FIELD(0x080c, int8_t, 500, tint_pad_1)
+//
+//     // Check that the generated string is as expected.
+//     utils::StringStream expect;
+//     expect << "struct S {\n";
+// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \ //
+//     FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
+//     ALL_FIELDS()
+// #undef FIELD
+//     expect << "};\n";
+//     EXPECT_EQ(buf.String(), expect.str());
+//
+//     // 1.4 Metal and C++14
+//     // The Metal programming language is a C++14-based Specification with
+//     // extensions and restrictions. Refer to the C++14 Specification (also known
+//     // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
+//     // description of the language grammar.
+//     //
+//     // Tint is written in C++14, so use the compiler to verify the generated
+//     // layout is as expected for C++14 / MSL.
+//     {
+//         struct inner_x {
+//             uint32_t a;
+//             alignas(512) float b;
+//         };
+//         CHECK_TYPE_SIZE_AND_ALIGN(inner_x, 1024, 512);
+//
+//         struct inner_y {
+//             uint32_t a[128];
+//             float b;
+//         };
+//         CHECK_TYPE_SIZE_AND_ALIGN(inner_y, 516, 4);
+//
+//         struct S {
+// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1>
+// NAME;
+//             ALL_FIELDS()
+// #undef FIELD
+//         };
+//
+// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \  //
+//     EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
+//         ALL_FIELDS()
+// #undef FIELD
+//     }
+//
+// #undef ALL_FIELDS
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayDefaultStride) {
+//     // inner: size(1024), align(512)
+//     auto* inner = Structure("inner", utils::Vector{
+//                                          Member("a", ty.i32()),
+//                                          Member("b", ty.f32(),
+//                                          utils::Vector{MemberAlign(512_i)}),
+//                                      });
+//
+//     // array_x: size(28), align(4)
+//     auto array_x = ty.array<f32, 7>();
+//
+//     // array_y: size(4096), align(512)
+//     auto array_y = ty.array(ty.Of(inner), 4_u);
+//
+//     // array_z: size(4), align(4)
+//     auto array_z = ty.array<f32>();
+//
+//     auto* s = Structure("S", utils::Vector{
+//                                  Member("a", ty.i32()),
+//                                  Member("b", array_x),
+//                                  Member("c", ty.f32()),
+//                                  Member("d", array_y),
+//                                  Member("e", ty.f32()),
+//                                  Member("f", array_z),
+//                              });
+//
+//     ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
+//                                builtin::Access::kRead, Binding(0_a), Group(0_a))
+//                          ->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     TextGenerator::TextBuffer buf;
+//     auto* str = program->TypeOf(type)->As<type::Struct>();
+//     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
+//
+//     // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
+//     // for each field of the structure s.
+// #define ALL_FIELDS()                     \  //
+//     FIELD(0x0000, int, 0, a)             \  //
+//     FIELD(0x0004, float, 7, b)           \  //
+//     FIELD(0x0020, float, 0, c)           \  //
+//     FIELD(0x0024, int8_t, 476, tint_pad) \  //
+//     FIELD(0x0200, inner, 4, d)           \  //
+//     FIELD(0x1200, float, 0, e)           \  //
+//     FIELD(0x1204, float, 1, f)           \  //
+//     FIELD(0x1208, int8_t, 504, tint_pad_1)
+//
+//     // Check that the generated string is as expected.
+//     utils::StringStream expect;
+//     expect << "struct S {\n";
+// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \  //
+//     FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
+//     ALL_FIELDS()
+// #undef FIELD
+//     expect << "};\n";
+//     EXPECT_EQ(buf.String(), expect.str());
+//
+//     // 1.4 Metal and C++14
+//     // The Metal programming language is a C++14-based Specification with
+//     // extensions and restrictions. Refer to the C++14 Specification (also known
+//     // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
+//     // description of the language grammar.
+//     //
+//     // Tint is written in C++14, so use the compiler to verify the generated
+//     // layout is as expected for C++14 / MSL.
+//     {
+//         struct inner {
+//             uint32_t a;
+//             alignas(512) float b;
+//         };
+//         CHECK_TYPE_SIZE_AND_ALIGN(inner, 1024, 512);
+//
+//         // array_x: size(28), align(4)
+//         using array_x = std::array<float, 7>;
+//         CHECK_TYPE_SIZE_AND_ALIGN(array_x, 28, 4);
+//
+//         // array_y: size(4096), align(512)
+//         using array_y = std::array<inner, 4>;
+//         CHECK_TYPE_SIZE_AND_ALIGN(array_y, 4096, 512);
+//
+//         // array_z: size(4), align(4)
+//         using array_z = std::array<float, 1>;
+//         CHECK_TYPE_SIZE_AND_ALIGN(array_z, 4, 4);
+//
+//         struct S {
+// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1>
+// NAME;
+//             ALL_FIELDS()
+// #undef FIELD
+//         };
+//
+// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \  //
+//     EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
+//         ALL_FIELDS()
+// #undef FIELD
+//     }
+//
+// #undef ALL_FIELDS
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayVec3DefaultStride) {
+//     // array: size(64), align(16)
+//     auto array = ty.array<vec3<f32>, 4>();
+//
+//     auto* s = Structure("S", utils::Vector{
+//                                  Member("a", ty.i32()),
+//                                  Member("b", array),
+//                                  Member("c", ty.i32()),
+//                              });
+//
+//     ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
+//                                builtin::Access::kRead, Binding(0_a), Group(0_a))
+//                          ->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     TextGenerator::TextBuffer buf;
+//     auto* str = program->TypeOf(type)->As<type::Struct>();
+//     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
+//
+//     // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
+//     // for each field of the structure s.
+// #define ALL_FIELDS()                    \  //
+//     FIELD(0x0000, int, 0, a)            \  //
+//     FIELD(0x0004, int8_t, 12, tint_pad) \  //
+//     FIELD(0x0010, float3, 4, b)         \  //
+//     FIELD(0x0050, int, 0, c)            \  //
+//     FIELD(0x0054, int8_t, 12, tint_pad_1)
+//
+//     // Check that the generated string is as expected.
+//     utils::StringStream expect;
+//     expect << "struct S {\n";
+// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \  //
+//     FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
+//     ALL_FIELDS()
+// #undef FIELD
+//     expect << "};\n";
+//     EXPECT_EQ(buf.String(), expect.str());
+// }
+//
+// TEST_F(MslGeneratorImplTest, AttemptTintPadSymbolCollision) {
+//     auto* s = Structure("S", utils::Vector{
+//                                  // uses symbols tint_pad_[0..9] and tint_pad_[20..35]
+//                                  Member("tint_pad_2", ty.i32(), utils::Vector{MemberSize(32_a)}),
+//                                  Member("tint_pad_20", ty.f32(),
+//                                         utils::Vector{MemberAlign(128_i), MemberSize(128_u)}),
+//                                  Member("tint_pad_33", ty.vec2<f32>()),
+//                                  Member("tint_pad_1", ty.u32()),
+//                                  Member("tint_pad_3", ty.vec3<f32>()),
+//                                  Member("tint_pad_7", ty.u32()),
+//                                  Member("tint_pad_25", ty.vec4<f32>()),
+//                                  Member("tint_pad_5", ty.u32()),
+//                                  Member("tint_pad_27", ty.mat2x2<f32>()),
+//                                  Member("tint_pad_24", ty.u32()),
+//                                  Member("tint_pad_23", ty.mat2x3<f32>()),
+//                                  Member("tint_pad", ty.u32()),
+//                                  Member("tint_pad_8", ty.mat2x4<f32>()),
+//                                  Member("tint_pad_26", ty.u32()),
+//                                  Member("tint_pad_29", ty.mat3x2<f32>()),
+//                                  Member("tint_pad_6", ty.u32()),
+//                                  Member("tint_pad_22", ty.mat3x3<f32>()),
+//                                  Member("tint_pad_32", ty.u32()),
+//                                  Member("tint_pad_34", ty.mat3x4<f32>()),
+//                                  Member("tint_pad_35", ty.u32()),
+//                                  Member("tint_pad_30", ty.mat4x2<f32>()),
+//                                  Member("tint_pad_9", ty.u32()),
+//                                  Member("tint_pad_31", ty.mat4x3<f32>()),
+//                                  Member("tint_pad_28", ty.u32()),
+//                                  Member("tint_pad_4", ty.mat4x4<f32>()),
+//                                  Member("tint_pad_21", ty.f32()),
+//                              });
+//
+//     ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
+//                                builtin::Access::kRead, Binding(0_a), Group(0_a))
+//                          ->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     TextGenerator::TextBuffer buf;
+//     auto* str = program->TypeOf(type)->As<type::Struct>();
+//     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
+//     EXPECT_EQ(buf.String(), R"(struct S {
+//   /* 0x0000 */ int tint_pad_2;
+//   /* 0x0004 */ tint_array<int8_t, 124> tint_pad_10;
+//   /* 0x0080 */ float tint_pad_20;
+//   /* 0x0084 */ tint_array<int8_t, 124> tint_pad_11;
+//   /* 0x0100 */ float2 tint_pad_33;
+//   /* 0x0108 */ uint tint_pad_1;
+//   /* 0x010c */ tint_array<int8_t, 4> tint_pad_12;
+//   /* 0x0110 */ float3 tint_pad_3;
+//   /* 0x011c */ uint tint_pad_7;
+//   /* 0x0120 */ float4 tint_pad_25;
+//   /* 0x0130 */ uint tint_pad_5;
+//   /* 0x0134 */ tint_array<int8_t, 4> tint_pad_13;
+//   /* 0x0138 */ float2x2 tint_pad_27;
+//   /* 0x0148 */ uint tint_pad_24;
+//   /* 0x014c */ tint_array<int8_t, 4> tint_pad_14;
+//   /* 0x0150 */ float2x3 tint_pad_23;
+//   /* 0x0170 */ uint tint_pad;
+//   /* 0x0174 */ tint_array<int8_t, 12> tint_pad_15;
+//   /* 0x0180 */ float2x4 tint_pad_8;
+//   /* 0x01a0 */ uint tint_pad_26;
+//   /* 0x01a4 */ tint_array<int8_t, 4> tint_pad_16;
+//   /* 0x01a8 */ float3x2 tint_pad_29;
+//   /* 0x01c0 */ uint tint_pad_6;
+//   /* 0x01c4 */ tint_array<int8_t, 12> tint_pad_17;
+//   /* 0x01d0 */ float3x3 tint_pad_22;
+//   /* 0x0200 */ uint tint_pad_32;
+//   /* 0x0204 */ tint_array<int8_t, 12> tint_pad_18;
+//   /* 0x0210 */ float3x4 tint_pad_34;
+//   /* 0x0240 */ uint tint_pad_35;
+//   /* 0x0244 */ tint_array<int8_t, 4> tint_pad_19;
+//   /* 0x0248 */ float4x2 tint_pad_30;
+//   /* 0x0268 */ uint tint_pad_9;
+//   /* 0x026c */ tint_array<int8_t, 4> tint_pad_36;
+//   /* 0x0270 */ float4x3 tint_pad_31;
+//   /* 0x02b0 */ uint tint_pad_28;
+//   /* 0x02b4 */ tint_array<int8_t, 12> tint_pad_37;
+//   /* 0x02c0 */ float4x4 tint_pad_4;
+//   /* 0x0300 */ float tint_pad_21;
+//   /* 0x0304 */ tint_array<int8_t, 124> tint_pad_38;
+// };
+// )");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_Struct_WithAttribute) {
+//     auto* s = Structure("S", utils::Vector{
+//                                  Member("a", ty.i32()),
+//                                  Member("b", ty.f32()),
+//                              });
+//
+//     ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
+//                                builtin::Access::kRead, Binding(0_a), Group(0_a))
+//                          ->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     TextGenerator::TextBuffer buf;
+//     auto* str = program->TypeOf(type)->As<type::Struct>();
+//     ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
+//     EXPECT_EQ(buf.String(), R"(struct S {
+//   /* 0x0000 */ int a;
+//   /* 0x0004 */ float b;
+// };
+// )");
+// }
+
+TEST_F(MslGeneratorImplIrTest, EmitType_U32) {
+    generator_.EmitType(generator_.line(), ty.u32());
+    ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+    EXPECT_EQ(utils::TrimSpace(generator_.result()), "uint");
+}
+
+// TEST_F(MslGeneratorImplTest, EmitType_Vector) {
+//     auto* f32 = create<type::F32>();
+//     auto* vec3 = create<type::Vector>(f32, 3u);
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, vec3, "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "float3");
+// }
+
+TEST_F(MslGeneratorImplIrTest, EmitType_Void) {
+    generator_.EmitType(generator_.line(), ty.void_());
+    ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+
+    EXPECT_EQ(utils::TrimSpace(generator_.result()), "void");
+}
+
+// TEST_F(MslGeneratorImplTest, EmitType_Sampler) {
+//     auto* sampler = create<type::Sampler>(type::SamplerKind::kSampler);
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, sampler, "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "sampler");
+// }
+//
+// TEST_F(MslGeneratorImplTest, EmitType_SamplerComparison) {
+//     auto* sampler = create<type::Sampler>(type::SamplerKind::kComparisonSampler);
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, sampler, "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "sampler");
+// }
+//
+// struct MslDepthTextureData {
+//     type::TextureDimension dim;
+//     std::string result;
+// };
+// inline std::ostream& operator<<(std::ostream& out, MslDepthTextureData data) {
+//     utils::StringStream str;
+//     str << data.dim;
+//     out << str.str();
+//     return out;
+// }
+// using MslDepthTexturesTest = TestParamHelper<MslDepthTextureData>;
+// TEST_P(MslDepthTexturesTest, Emit) {
+//     auto params = GetParam();
+//
+//     type::DepthTexture s(params.dim);
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, &s, "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), params.result);
+// }
+// INSTANTIATE_TEST_SUITE_P(
+//     MslGeneratorImplTest,
+//     MslDepthTexturesTest,
+//     testing::Values(
+//         MslDepthTextureData{type::TextureDimension::k2d, "depth2d<float, access::sample>"},
+//         MslDepthTextureData{type::TextureDimension::k2dArray,
+//                             "depth2d_array<float, access::sample>"},
+//         MslDepthTextureData{type::TextureDimension::kCube, "depthcube<float, access::sample>"},
+//         MslDepthTextureData{type::TextureDimension::kCubeArray,
+//                             "depthcube_array<float, access::sample>"}));
+//
+// using MslDepthMultisampledTexturesTest = TestHelper;
+// TEST_F(MslDepthMultisampledTexturesTest, Emit) {
+//     type::DepthMultisampledTexture s(type::TextureDimension::k2d);
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, &s, "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "depth2d_ms<float, access::read>");
+// }
+//
+// struct MslTextureData {
+//     type::TextureDimension dim;
+//     std::string result;
+// };
+// inline std::ostream& operator<<(std::ostream& out, MslTextureData data) {
+//     utils::StringStream str;
+//     str << data.dim;
+//     out << str.str();
+//     return out;
+// }
+// using MslSampledtexturesTest = TestParamHelper<MslTextureData>;
+// TEST_P(MslSampledtexturesTest, Emit) {
+//     auto params = GetParam();
+//
+//     auto* f32 = create<type::F32>();
+//     auto* s = create<type::SampledTexture>(params.dim, f32);
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, s, "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), params.result);
+// }
+// INSTANTIATE_TEST_SUITE_P(
+//     MslGeneratorImplTest,
+//     MslSampledtexturesTest,
+//     testing::Values(
+//         MslTextureData{type::TextureDimension::k1d, "texture1d<float, access::sample>"},
+//         MslTextureData{type::TextureDimension::k2d, "texture2d<float, access::sample>"},
+//         MslTextureData{type::TextureDimension::k2dArray, "texture2d_array<float,
+//         access::sample>"}, MslTextureData{type::TextureDimension::k3d, "texture3d<float,
+//         access::sample>"}, MslTextureData{type::TextureDimension::kCube, "texturecube<float,
+//         access::sample>"}, MslTextureData{type::TextureDimension::kCubeArray,
+//                        "texturecube_array<float, access::sample>"}));
+//
+// TEST_F(MslGeneratorImplTest, Emit_TypeMultisampledTexture) {
+//     auto* u32 = create<type::U32>();
+//     auto* ms = create<type::MultisampledTexture>(type::TextureDimension::k2d, u32);
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, ms, "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), "texture2d_ms<uint, access::read>");
+// }
+//
+// struct MslStorageTextureData {
+//     type::TextureDimension dim;
+//     std::string result;
+// };
+// inline std::ostream& operator<<(std::ostream& out, MslStorageTextureData data) {
+//     utils::StringStream str;
+//     str << data.dim;
+//     return out << str.str();
+// }
+// using MslStorageTexturesTest = TestParamHelper<MslStorageTextureData>;
+// TEST_P(MslStorageTexturesTest, Emit) {
+//     auto params = GetParam();
+//
+//     auto s =
+//         ty.storage_texture(params.dim, builtin::TexelFormat::kR32Float, builtin::Access::kWrite);
+//     ast::Type type = GlobalVar("test_var", s, Binding(0_a), Group(0_a))->type;
+//
+//     GeneratorImpl& gen = Build();
+//
+//     utils::StringStream out;
+//     ASSERT_TRUE(gen.EmitType(out, program->TypeOf(type), "")) << gen.Diagnostics();
+//     EXPECT_EQ(out.str(), params.result);
+// }
+// INSTANTIATE_TEST_SUITE_P(
+//     MslGeneratorImplTest,
+//     MslStorageTexturesTest,
+//     testing::Values(
+//         MslStorageTextureData{type::TextureDimension::k1d, "texture1d<float, access::write>"},
+//         MslStorageTextureData{type::TextureDimension::k2d, "texture2d<float, access::write>"},
+//         MslStorageTextureData{type::TextureDimension::k2dArray,
+//                               "texture2d_array<float, access::write>"},
+//         MslStorageTextureData{type::TextureDimension::k3d, "texture3d<float, access::write>"}));
+
+}  // namespace
+}  // namespace tint::writer::msl
diff --git a/src/tint/writer/text_generator.h b/src/tint/writer/text_generator.h
index 7d9113b..8b3147d 100644
--- a/src/tint/writer/text_generator.h
+++ b/src/tint/writer/text_generator.h
@@ -85,22 +85,6 @@
         std::vector<Line> lines;
     };
 
-    /// Constructor
-    TextGenerator();
-    virtual ~TextGenerator();
-
-    /// Increment the emitter indent level
-    void increment_indent() { current_buffer_->IncrementIndent(); }
-    /// Decrement the emitter indent level
-    void decrement_indent() { current_buffer_->DecrementIndent(); }
-
-    /// @returns the result data
-    virtual std::string result() const { return main_buffer_.String(); }
-
-    /// @returns the list of diagnostics raised by the generator.
-    const diag::List& Diagnostics() const { return diagnostics_; }
-
-  protected:
     /// LineWriter is a helper that acts as a string buffer, who's content is
     /// emitted to the TextBuffer as a single line on destruction.
     struct LineWriter {
@@ -134,6 +118,26 @@
         TextBuffer* buffer;
     };
 
+    /// Constructor
+    TextGenerator();
+    virtual ~TextGenerator();
+
+    /// Increment the emitter indent level
+    void increment_indent() { current_buffer_->IncrementIndent(); }
+    /// Decrement the emitter indent level
+    void decrement_indent() { current_buffer_->DecrementIndent(); }
+
+    /// @returns a new LineWriter, used for buffering and writing a line to
+    /// the end of #current_buffer_.
+    LineWriter line() { return LineWriter(current_buffer_); }
+
+    /// @returns the result data
+    virtual std::string result() const { return main_buffer_.String(); }
+
+    /// @returns the list of diagnostics raised by the generator.
+    const diag::List& Diagnostics() const { return diagnostics_; }
+
+  protected:
     /// Helper for writing a '(' on construction and a ')' destruction.
     struct ScopedParen {
         /// Constructor
@@ -169,10 +173,6 @@
         TextBuffer* buffer_;
     };
 
-    /// @returns a new LineWriter, used for buffering and writing a line to
-    /// the end of #current_buffer_.
-    LineWriter line() { return LineWriter(current_buffer_); }
-
     /// @param buffer the TextBuffer to write the line to
     /// @returns a new LineWriter, used for buffering and writing a line to
     /// the end of `buffer`.