[tint][ir] Convert IR methods to use Result.

This converts many of the IR methods to use `Result` instead of
`diag::Result`.

Bug: 383726508
Change-Id: Ib8fc5f722e8a42dcb3d68efd19ddbc4346b3668e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/230994
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/dawn/native/d3d/ShaderUtils.cpp b/src/dawn/native/d3d/ShaderUtils.cpp
index 2095727..794c024 100644
--- a/src/dawn/native/d3d/ShaderUtils.cpp
+++ b/src/dawn/native/d3d/ShaderUtils.cpp
@@ -264,7 +264,7 @@
     }
 
     TRACE_EVENT0(tracePlatform.UnsafeGetValue(), General, "tint::hlsl::writer::Generate");
-    tint::diag::Result<tint::hlsl::writer::Output> result;
+    tint::Result<tint::hlsl::writer::Output> result;
     if (r.useTintIR) {
         // Convert the AST program to an IR module.
         auto ir = tint::wgsl::reader::ProgramToLoweredIR(transformedProgram);
@@ -281,7 +281,7 @@
 
             DAWN_INVALID_IF(substituteOverridesResult != tint::Success,
                             "Pipeline override substitution (IR) failed:\n%s",
-                            substituteOverridesResult.Failure().reason.Str());
+                            substituteOverridesResult.Failure().reason);
         }
 
         result = tint::hlsl::writer::Generate(ir.Get(), r.tintOptions);
@@ -311,7 +311,7 @@
     }
 
     DAWN_INVALID_IF(result != tint::Success, "An error occurred while generating HLSL:\n%s",
-                    result.Failure().reason.Str());
+                    result.Failure().reason);
 
     if (r.useTintIR && r.stage == SingleShaderStage::Vertex) {
         usesVertexIndex = result->has_vertex_index;
diff --git a/src/dawn/native/metal/ShaderModuleMTL.mm b/src/dawn/native/metal/ShaderModuleMTL.mm
index 3c92839..9da18dd 100644
--- a/src/dawn/native/metal/ShaderModuleMTL.mm
+++ b/src/dawn/native/metal/ShaderModuleMTL.mm
@@ -215,7 +215,6 @@
     uint32_t sampleMask,
     const RenderPipeline* renderPipeline,
     const BindingInfoArray& moduleBindingInfo) {
-
     std::ostringstream errorStream;
     errorStream << "Tint MSL failure:\n";
 
@@ -346,14 +345,14 @@
                         tint::core::ir::transform::SubstituteOverrides(ir.Get(), cfg);
                     DAWN_INVALID_IF(substituteOverridesResult != tint::Success,
                                     "Pipeline override substitution (IR) failed:\n%s",
-                                    substituteOverridesResult.Failure().reason.Str());
+                                    substituteOverridesResult.Failure().reason);
                 }
 
                 // Generate MSL.
                 auto result = tint::msl::writer::Generate(ir.Get(), r.tintOptions);
                 DAWN_INVALID_IF(result != tint::Success,
                                 "An error occurred while generating MSL:\n%s",
-                                result.Failure().reason.Str());
+                                result.Failure().reason);
 
                 // Workgroup validation has to come after `Generate` because it may require
                 // overrides to have been substituted.
diff --git a/src/dawn/native/opengl/ShaderModuleGL.cpp b/src/dawn/native/opengl/ShaderModuleGL.cpp
index d29dbeb..f0e3a2f 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.cpp
+++ b/src/dawn/native/opengl/ShaderModuleGL.cpp
@@ -536,7 +536,7 @@
                         tint::core::ir::transform::SubstituteOverrides(ir.Get(), cfg);
                     DAWN_INVALID_IF(substituteOverridesResult != tint::Success,
                                     "Pipeline override substitution (IR) failed:\n%s",
-                                    substituteOverridesResult.Failure().reason.Str());
+                                    substituteOverridesResult.Failure().reason);
                 }
 
                 // Generate GLSL from Tint IR.
@@ -544,7 +544,7 @@
                     tint::glsl::writer::Generate(ir.Get(), r.tintOptions, remappedEntryPoint);
                 DAWN_INVALID_IF(result != tint::Success,
                                 "An error occurred while generating GLSL:\n%s",
-                                result.Failure().reason.Str());
+                                result.Failure().reason);
 
                 // Workgroup validation has to come after `Generate` because it may require
                 // overrides to have been substituted.
diff --git a/src/dawn/native/vulkan/ShaderModuleVk.cpp b/src/dawn/native/vulkan/ShaderModuleVk.cpp
index 01d5ab5..43b1859 100644
--- a/src/dawn/native/vulkan/ShaderModuleVk.cpp
+++ b/src/dawn/native/vulkan/ShaderModuleVk.cpp
@@ -417,7 +417,7 @@
                     tint::core::ir::transform::SingleEntryPoint(ir.Get(), r.entryPointName);
                 DAWN_INVALID_IF(singleEntryPointResult != tint::Success,
                                 "Pipeline single entry point (IR) failed:\n%s",
-                                singleEntryPointResult.Failure().reason.Str());
+                                singleEntryPointResult.Failure().reason);
 
                 if (r.substituteOverrideConfig) {
                     // this needs to run after SingleEntryPoint transform which removes unused
@@ -428,14 +428,14 @@
                         tint::core::ir::transform::SubstituteOverrides(ir.Get(), cfg);
                     DAWN_INVALID_IF(substituteOverridesResult != tint::Success,
                                     "Pipeline override substitution (IR) failed:\n%s",
-                                    substituteOverridesResult.Failure().reason.Str());
+                                    substituteOverridesResult.Failure().reason);
                 }
 
                 // Generate SPIR-V from Tint IR.
                 auto tintResult = tint::spirv::writer::Generate(ir.Get(), r.tintOptions);
                 DAWN_INVALID_IF(tintResult != tint::Success,
                                 "An error occurred while generating SPIR-V\n%s",
-                                tintResult.Failure().reason.Str());
+                                tintResult.Failure().reason);
 
                 // Workgroup validation has to come after `Generate` because it may require
                 // overrides to have been substituted.
diff --git a/src/tint/api/tint.cc b/src/tint/api/tint.cc
index 5a3ff9d..b4c495a 100644
--- a/src/tint/api/tint.cc
+++ b/src/tint/api/tint.cc
@@ -74,7 +74,7 @@
     tint::Program::printer = [](const tint::Program& program) {
         auto result = wgsl::writer::Generate(program, {});
         if (result != Success) {
-            return result.Failure().reason.Str();
+            return result.Failure().reason;
         }
         return result->wgsl;
     };
diff --git a/src/tint/cmd/bench/bench.cc b/src/tint/cmd/bench/bench.cc
index 7a62cb5..5174bbc 100644
--- a/src/tint/cmd/bench/bench.cc
+++ b/src/tint/cmd/bench/bench.cc
@@ -36,15 +36,15 @@
 
 namespace tint::bench {
 
-diag::Result<Source::File> GetWgslFile(std::string name) {
+Result<Source::File> GetWgslFile(std::string name) {
     auto wgsl = kBenchmarkInputs.find(name);
     if (wgsl == kBenchmarkInputs.end()) {
-        return diag::Failure{"failed to find WGSL shader for '" + name + "'"};
+        return Failure{"failed to find WGSL shader for '" + name + "'"};
     }
     return tint::Source::File("<input>", wgsl->second);
 }
 
-diag::Result<ProgramAndFile> GetWgslProgram(std::string name) {
+Result<ProgramAndFile> GetWgslProgram(std::string name) {
     auto res = GetWgslFile(name);
     if (res != Success) {
         return res.Failure();
@@ -52,7 +52,7 @@
     auto file = std::make_unique<Source::File>(res.Get());
     auto program = wgsl::reader::Parse(file.get());
     if (!program.IsValid()) {
-        return diag::Failure{program.Diagnostics()};
+        return Failure{program.Diagnostics().Str()};
     }
     return ProgramAndFile{std::move(program), std::move(file)};
 }
diff --git a/src/tint/cmd/bench/bench.h b/src/tint/cmd/bench/bench.h
index 2920c52..e52469f 100644
--- a/src/tint/cmd/bench/bench.h
+++ b/src/tint/cmd/bench/bench.h
@@ -33,8 +33,8 @@
 
 #include "benchmark/benchmark.h"  // IWYU pragma: export
 #include "src/tint/lang/wgsl/program/program.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/macros/compiler.h"
+#include "src/tint/utils/result.h"
 
 // Autogenerated header that defines the benchmark shaders and macros for registering benchmarks.
 #include "src/tint/cmd/bench/benchmark_inputs.h"  // GEN_BUILD:IGNORE_INCLUDE
@@ -52,12 +52,12 @@
 /// Benchmarks that are SPIR-V shaders will have been converted to WGSL at startup.
 /// @param name the benchmark input name
 /// @returns the Source::File
-diag::Result<Source::File> GetWgslFile(std::string name);
+Result<Source::File> GetWgslFile(std::string name);
 
 /// GetWgslProgram parses the WGSL for a benchmark input program with the given name.
 /// @param name the benchmark input name
 /// @returns the parsed WGSL program
-diag::Result<ProgramAndFile> GetWgslProgram(std::string name);
+Result<ProgramAndFile> GetWgslProgram(std::string name);
 
 }  // namespace tint::bench
 
diff --git a/src/tint/cmd/fuzz/ir/as/main.cc b/src/tint/cmd/fuzz/ir/as/main.cc
index d14246b..180ca91 100644
--- a/src/tint/cmd/fuzz/ir/as/main.cc
+++ b/src/tint/cmd/fuzz/ir/as/main.cc
@@ -172,27 +172,27 @@
 /// enables, and validation.
 /// @param program the program to generate
 /// @returns generated module on success, tint::failure on failure
-tint::diag::Result<tint::core::ir::Module> GenerateIrModule(const tint::Program& program) {
+tint::Result<tint::core::ir::Module> GenerateIrModule(const tint::Program& program) {
     if (program.AST().Enables().Any(tint::wgsl::reader::IsUnsupportedByIR)) {
-        return tint::diag::Failure{"Unsupported enable used in shader"};
+        return tint::Failure{"Unsupported enable used in shader"};
     }
 
     auto transformed = tint::wgsl::ApplySubstituteOverrides(program);
     auto& src = transformed ? transformed.value() : program;
     if (!src.IsValid()) {
-        return tint::diag::Failure{src.Diagnostics()};
+        return tint::Failure{src.Diagnostics().Str()};
     }
 
     auto ir = tint::wgsl::reader::ProgramToLoweredIR(src);
     if (ir != tint::Success) {
-        return ir.Failure();
+        return tint::Failure{ir.Failure().reason.Str()};
     }
 
     if (auto val = tint::core::ir::Validate(ir.Get()); val != tint::Success) {
         return val.Failure();
     }
 
-    return ir;
+    return ir.Move();
 }
 
 /// @returns a fuzzer test case protobuf for the given program.
diff --git a/src/tint/cmd/fuzz/wgsl/fuzz.cc b/src/tint/cmd/fuzz/wgsl/fuzz.cc
index b37df1b..bbc6539 100644
--- a/src/tint/cmd/fuzz/wgsl/fuzz.cc
+++ b/src/tint/cmd/fuzz/wgsl/fuzz.cc
@@ -143,7 +143,7 @@
     tint::Program::printer = [](const tint::Program& program) {
         auto result = tint::wgsl::writer::Generate(program, {});
         if (result != Success) {
-            return result.Failure().reason.Str();
+            return result.Failure().reason;
         }
         return result->wgsl;
     };
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 684e197..b754264 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -804,7 +804,7 @@
             tint::core::ir::transform::SingleEntryPoint(ir.Get(), options.ep_name);
         if (singleEntryPointResult != tint::Success) {
             std::cerr << "Pipeline single entry point (IR) failed:\n"
-                      << singleEntryPointResult.Failure().reason.Str() << "\n";
+                      << singleEntryPointResult.Failure().reason << "\n";
         }
     }
 
@@ -817,7 +817,7 @@
             tint::core::ir::transform::SubstituteOverrides(ir.Get(), ir_cfg);
         if (substituteOverridesResult != tint::Success) {
             std::cerr << "Pipeline override substitution (IR) failed:\n"
-                      << substituteOverridesResult.Failure().reason.Str() << "\n";
+                      << substituteOverridesResult.Failure().reason << "\n";
             return false;
         }
     }
@@ -987,7 +987,7 @@
             tint::core::ir::transform::SingleEntryPoint(ir.Get(), options.ep_name);
         if (singleEntryPointResult != tint::Success) {
             std::cerr << "Pipeline single entry point (IR) failed:\n"
-                      << singleEntryPointResult.Failure().reason.Str() << "\n";
+                      << singleEntryPointResult.Failure().reason << "\n";
         }
     }
 
@@ -1000,7 +1000,7 @@
             tint::core::ir::transform::SubstituteOverrides(ir.Get(), ir_cfg);
         if (substituteOverridesResult != tint::Success) {
             std::cerr << "Pipeline override substitution (IR) failed:\n"
-                      << substituteOverridesResult.Failure().reason.Str() << "\n";
+                      << substituteOverridesResult.Failure().reason << "\n";
             return false;
         }
     }
@@ -1122,7 +1122,7 @@
     gen_options.compiler = for_fxc ? tint::hlsl::writer::Options::Compiler::kFXC
                                    : tint::hlsl::writer::Options::Compiler::kDXC;
 
-    tint::diag::Result<tint::hlsl::writer::Output> result;
+    tint::Result<tint::hlsl::writer::Output> result;
     if (options.use_ir) {
         // Convert the AST program to an IR module.
         auto ir = tint::wgsl::reader::ProgramToLoweredIR(res.Get());
@@ -1136,7 +1136,7 @@
                 tint::core::ir::transform::SingleEntryPoint(ir.Get(), options.ep_name);
             if (singleEntryPointResult != tint::Success) {
                 std::cerr << "Pipeline single entry point (IR) failed:\n"
-                          << singleEntryPointResult.Failure().reason.Str() << "\n";
+                          << singleEntryPointResult.Failure().reason << "\n";
             }
         }
 
@@ -1149,7 +1149,7 @@
                 tint::core::ir::transform::SubstituteOverrides(ir.Get(), ir_cfg);
             if (substituteOverridesResult != tint::Success) {
                 std::cerr << "Pipeline override substitution (IR) failed:\n"
-                          << substituteOverridesResult.Failure().reason.Str() << "\n";
+                          << substituteOverridesResult.Failure().reason << "\n";
                 return false;
             }
         }
@@ -1318,7 +1318,7 @@
             tint::core::ir::transform::SingleEntryPoint(ir.Get(), options.ep_name);
         if (singleEntryPointResult != tint::Success) {
             std::cerr << "Pipeline single entry point (IR) failed:\n"
-                      << singleEntryPointResult.Failure().reason.Str() << "\n";
+                      << singleEntryPointResult.Failure().reason << "\n";
         }
     }
 
@@ -1331,7 +1331,7 @@
             tint::core::ir::transform::SubstituteOverrides(ir.Get(), ir_cfg);
         if (substituteOverridesResult != tint::Success) {
             std::cerr << "Pipeline override substitution (IR) failed:\n"
-                      << substituteOverridesResult.Failure().reason.Str() << "\n";
+                      << substituteOverridesResult.Failure().reason << "\n";
             return false;
         }
     }
diff --git a/src/tint/lang/core/ir/const_param_validator.cc b/src/tint/lang/core/ir/const_param_validator.cc
index 187a409..3c022eb 100644
--- a/src/tint/lang/core/ir/const_param_validator.cc
+++ b/src/tint/lang/core/ir/const_param_validator.cc
@@ -64,7 +64,7 @@
 
     /// Runs the const param validator over the module provided during construction
     /// @returns success or failure
-    diag::Result<SuccessType> Run();
+    Result<SuccessType> Run();
 
     void CheckSubgroupCall(const CoreBuiltinCall* call);
     void CheckBuiltinCall(const BuiltinCall* call);
@@ -290,7 +290,7 @@
     }
 }
 
-diag::Result<SuccessType> ConstParamValidator::Run() {
+Result<SuccessType> ConstParamValidator::Run() {
     auto instructions = this->mod_.Instructions();
 
     for (auto inst : instructions) {
@@ -302,7 +302,7 @@
     }
 
     if (diagnostics_.ContainsErrors()) {
-        return diag::Failure{std::move(diagnostics_)};
+        return Failure{diagnostics_.Str()};
     }
 
     return Success;
@@ -310,10 +310,9 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ValidateConstParam(Module& mod) {
+Result<SuccessType> ValidateConstParam(Module& mod) {
     ConstParamValidator v(mod);
-    auto ret = v.Run();
-    return ret;
+    return v.Run();
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/const_param_validator.h b/src/tint/lang/core/ir/const_param_validator.h
index 3c63a9f..7fc257f 100644
--- a/src/tint/lang/core/ir/const_param_validator.h
+++ b/src/tint/lang/core/ir/const_param_validator.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_CONST_PARAM_VALIDATOR_H_
 #define SRC_TINT_LANG_CORE_IR_CONST_PARAM_VALIDATOR_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -40,7 +40,7 @@
 /// Validates the constant params for all instructions in the IR.
 /// @param mod the module to validate
 /// @returns success or failure
-diag::Result<SuccessType> ValidateConstParam(Module& mod);
+Result<SuccessType> ValidateConstParam(Module& mod);
 
 }  // namespace tint::core::ir
 
diff --git a/src/tint/lang/core/ir/const_param_validator_test.cc b/src/tint/lang/core/ir/const_param_validator_test.cc
index 6b1762d..1d961f8 100644
--- a/src/tint/lang/core/ir/const_param_validator_test.cc
+++ b/src/tint/lang/core/ir/const_param_validator_test.cc
@@ -39,7 +39,6 @@
 #include "src/tint/lang/core/type/manager.h"
 #include "src/tint/lang/core/type/storage_texture.h"
 #include "src/tint/lang/core/type/struct.h"
-#include "src/tint/utils/diagnostic/source.h"
 
 // These unit tests are used for internal development. CTS validation does a more complete job of
 // testing all expectations for const and override parameters.
@@ -94,8 +93,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
-              "5:7 error: value 65505.0 cannot be represented as 'f16'");
+    EXPECT_EQ(res.Failure().reason, "5:7 error: value 65505.0 cannot be represented as 'f16'");
 }
 
 TEST_F(IR_ConstParamValidatorTest, CorrectDomainSubgroupsShuffleXor) {
@@ -144,7 +142,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               R"(5:7 error: The mask argument of subgroupShuffleXor must be less than 128)");
 }
 
@@ -170,7 +168,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               R"(5:7 error: The delta argument of subgroupShuffleDown must be less than 128)");
 }
 
@@ -197,7 +195,7 @@
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         R"(5:7 error: The sourceLaneIndex argument of subgroupShuffle must be less than 128)");
 }
 
@@ -224,7 +222,7 @@
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         R"(5:7 error: The sourceLaneIndex argument of subgroupShuffle must be less than 128)");
 }
 
@@ -252,7 +250,7 @@
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         R"(5:7 error: The sourceLaneIndex argument of subgroupShuffle must be greater than or equal to zero)");
 }
 
@@ -279,7 +277,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               "5:7 error: 'offset' + 'count' must be less than or equal to the bit width of 'e'");
 }
 
@@ -331,7 +329,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               "5:7 error: 'offset' + 'count' must be less than or equal to the bit width of 'e'");
 }
 
@@ -361,7 +359,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               "5:7 error: 'offset' + 'count' must be less than or equal to the bit width of 'e'");
 }
 
@@ -390,7 +388,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               "5:7 error: 'offset' + 'count' must be less than or equal to the bit width of 'e'");
 }
 
@@ -444,7 +442,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               "5:7 error: clamp called with 'low' (2.0) greater than 'high' (1.0)");
 }
 
@@ -498,7 +496,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               "5:7 error: clamp called with 'low' (4.0) greater than 'high' (2.0)");
 }
 
@@ -552,7 +550,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               "5:7 error: smoothstep called with 'low' (3.0) equal to 'high' (3.0)");
 }
 
@@ -580,7 +578,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(), "5:7 error: e2 must be less than or equal to 128");
+    EXPECT_EQ(res.Failure().reason, "5:7 error: e2 must be less than or equal to 128");
 }
 
 TEST_F(IR_ConstParamValidatorTest, CorrectDomainLdexp_Vec) {
@@ -632,7 +630,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(), "5:7 error: e2 must be less than or equal to 16");
+    EXPECT_EQ(res.Failure().reason, "5:7 error: e2 must be less than or equal to 16");
 }
 
 TEST_F(IR_ConstParamValidatorTest, CorrectDomainLdexp) {
@@ -682,8 +680,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
-              "5:7 error: value 65505.0 cannot be represented as 'f16'");
+    EXPECT_EQ(res.Failure().reason, "5:7 error: value 65505.0 cannot be represented as 'f16'");
 }
 
 TEST_F(IR_ConstParamValidatorTest, CorrectPack2x16Float) {
@@ -759,7 +756,7 @@
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         "5:7 error: shift left value must be less than the bit width of the lhs, which is 32");
 }
 
@@ -788,7 +785,7 @@
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         "5:7 error: shift left value must be less than the bit width of the lhs, which is 32");
 }
 
@@ -840,7 +837,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(), "5:7 error: integer division by zero is invalid");
+    EXPECT_EQ(res.Failure().reason, "5:7 error: integer division by zero is invalid");
 }
 
 TEST_F(IR_ConstParamValidatorTest, IncorrectDomainModulo_Vec) {
@@ -867,7 +864,7 @@
 
     auto res = ir::ValidateConstParam(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(), "5:7 error: integer division by zero is invalid");
+    EXPECT_EQ(res.Failure().reason, "5:7 error: integer division by zero is invalid");
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/add_empty_entry_point.cc b/src/tint/lang/core/ir/transform/add_empty_entry_point.cc
index e9fc0a4..e000f98 100644
--- a/src/tint/lang/core/ir/transform/add_empty_entry_point.cc
+++ b/src/tint/lang/core/ir/transform/add_empty_entry_point.cc
@@ -51,7 +51,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> AddEmptyEntryPoint(Module& ir) {
+Result<SuccessType> AddEmptyEntryPoint(Module& ir) {
     auto result =
         ValidateAndDumpIfNeeded(ir, "core.AddEmptyEntryPoint", kAddEmptyEntryPointCapabilities);
     if (result != Success) {
diff --git a/src/tint/lang/core/ir/transform/add_empty_entry_point.h b/src/tint/lang/core/ir/transform/add_empty_entry_point.h
index 45d62ed..12a9233 100644
--- a/src/tint/lang/core/ir/transform/add_empty_entry_point.h
+++ b/src/tint/lang/core/ir/transform/add_empty_entry_point.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_ADD_EMPTY_ENTRY_POINT_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -46,7 +46,7 @@
 /// Add an empty entry point to the module, if no other entry points exist.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> AddEmptyEntryPoint(Module& module);
+Result<SuccessType> AddEmptyEntryPoint(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/add_empty_entry_point_fuzz.cc b/src/tint/lang/core/ir/transform/add_empty_entry_point_fuzz.cc
index aa750c0..a5836d3 100644
--- a/src/tint/lang/core/ir/transform/add_empty_entry_point_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/add_empty_entry_point_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> AddEmptyEntryPointFuzzer(Module& ir, const fuzz::ir::Context&) {
-    auto res = AddEmptyEntryPoint(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return AddEmptyEntryPoint(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/array_length_from_uniform.cc b/src/tint/lang/core/ir/transform/array_length_from_uniform.cc
index c8632ab..c111128 100644
--- a/src/tint/lang/core/ir/transform/array_length_from_uniform.cc
+++ b/src/tint/lang/core/ir/transform/array_length_from_uniform.cc
@@ -230,7 +230,7 @@
 
 }  // namespace
 
-diag::Result<ArrayLengthFromUniformResult> ArrayLengthFromUniform(
+Result<ArrayLengthFromUniformResult> ArrayLengthFromUniform(
     Module& ir,
     BindingPoint ubo_binding,
     const std::unordered_map<BindingPoint, uint32_t>& bindpoint_to_size_index) {
diff --git a/src/tint/lang/core/ir/transform/array_length_from_uniform.h b/src/tint/lang/core/ir/transform/array_length_from_uniform.h
index 2977a96..0b1288e 100644
--- a/src/tint/lang/core/ir/transform/array_length_from_uniform.h
+++ b/src/tint/lang/core/ir/transform/array_length_from_uniform.h
@@ -31,7 +31,7 @@
 #include <unordered_map>
 
 #include "src/tint/api/common/binding_point.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -63,7 +63,7 @@
 /// @param ubo_binding the binding point to use for the uniform buffer
 /// @param bindpoint_to_size_index the map from binding point to an index which holds the size
 /// @returns the transform result or failure
-diag::Result<ArrayLengthFromUniformResult> ArrayLengthFromUniform(
+Result<ArrayLengthFromUniformResult> ArrayLengthFromUniform(
     Module& module,
     BindingPoint ubo_binding,
     const std::unordered_map<BindingPoint, uint32_t>& bindpoint_to_size_index);
diff --git a/src/tint/lang/core/ir/transform/array_length_from_uniform_fuzz.cc b/src/tint/lang/core/ir/transform/array_length_from_uniform_fuzz.cc
index 07d0d3c..5f26884 100644
--- a/src/tint/lang/core/ir/transform/array_length_from_uniform_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/array_length_from_uniform_fuzz.cc
@@ -42,7 +42,7 @@
     const std::unordered_map<BindingPoint, uint32_t>& bindpoint_to_size_index) {
     if (auto res = ArrayLengthFromUniform(module, ubo_binding, bindpoint_to_size_index);
         res != Success) {
-        return Failure{res.Failure().reason.Str()};
+        return res.Failure();
     }
 
     return Success;
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
index f91fa83..e45b4e5 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
@@ -181,7 +181,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> Bgra8UnormPolyfill(Module& ir) {
+Result<SuccessType> Bgra8UnormPolyfill(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.Bgra8UnormPolyfill");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h
index 3fbcc12..e17454a 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_BGRA8UNORM_POLYFILL_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_BGRA8UNORM_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// bgra8unorm to rgba8unorm, inserting swizzles before and after texture accesses as necessary.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> Bgra8UnormPolyfill(Module& module);
+Result<SuccessType> Bgra8UnormPolyfill(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_fuzz.cc b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_fuzz.cc
index 243620b..d6252ad 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> Bgra8UnormPolyfillFuzzer(Module& ir, const fuzz::ir::Context&) {
-    auto res = Bgra8UnormPolyfill(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return Bgra8UnormPolyfill(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill.cc b/src/tint/lang/core/ir/transform/binary_polyfill.cc
index 541e0dc..4a2da27 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/binary_polyfill.cc
@@ -200,7 +200,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BinaryPolyfill(Module& ir, const BinaryPolyfillConfig& config) {
+Result<SuccessType> BinaryPolyfill(Module& ir, const BinaryPolyfillConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.BinaryPolyfill");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill.h b/src/tint/lang/core/ir/transform/binary_polyfill.h
index 3cf87e2..1af3933 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill.h
+++ b/src/tint/lang/core/ir/transform/binary_polyfill.h
@@ -28,8 +28,8 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_BINARY_POLYFILL_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_BINARY_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/reflection.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -54,7 +54,7 @@
 /// @param module the module to transform
 /// @param config the polyfill configuration
 /// @returns success or failure
-diag::Result<SuccessType> BinaryPolyfill(Module& module, const BinaryPolyfillConfig& config);
+Result<SuccessType> BinaryPolyfill(Module& module, const BinaryPolyfillConfig& config);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill_fuzz.cc b/src/tint/lang/core/ir/transform/binary_polyfill_fuzz.cc
index 084d9e27..666bde9 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/binary_polyfill_fuzz.cc
@@ -36,11 +36,7 @@
 Result<SuccessType> BinaryPolyfillFuzzer(Module& ir,
                                          const fuzz::ir::Context&,
                                          const BinaryPolyfillConfig& config) {
-    auto res = BinaryPolyfill(ir, config);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return BinaryPolyfill(ir, config);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/binding_remapper.cc b/src/tint/lang/core/ir/transform/binding_remapper.cc
index a020e5a..ac6bb74 100644
--- a/src/tint/lang/core/ir/transform/binding_remapper.cc
+++ b/src/tint/lang/core/ir/transform/binding_remapper.cc
@@ -69,7 +69,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BindingRemapper(
+Result<SuccessType> BindingRemapper(
     Module& ir,
     const std::unordered_map<BindingPoint, BindingPoint>& binding_points) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.BindingRemapper");
diff --git a/src/tint/lang/core/ir/transform/binding_remapper.h b/src/tint/lang/core/ir/transform/binding_remapper.h
index e8b3ba0..83dbb08 100644
--- a/src/tint/lang/core/ir/transform/binding_remapper.h
+++ b/src/tint/lang/core/ir/transform/binding_remapper.h
@@ -31,7 +31,7 @@
 #include <unordered_map>
 
 #include "src/tint/api/common/binding_point.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -44,7 +44,7 @@
 /// @param module the module to transform
 /// @param binding_points the remapping data
 /// @returns success or failure
-diag::Result<SuccessType> BindingRemapper(
+Result<SuccessType> BindingRemapper(
     Module& module,
     const std::unordered_map<BindingPoint, BindingPoint>& binding_points);
 
diff --git a/src/tint/lang/core/ir/transform/binding_remapper_fuzz.cc b/src/tint/lang/core/ir/transform/binding_remapper_fuzz.cc
index 526e6d1..d38cc04 100644
--- a/src/tint/lang/core/ir/transform/binding_remapper_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/binding_remapper_fuzz.cc
@@ -37,11 +37,7 @@
     Module& ir,
     const fuzz::ir::Context&,
     const std::unordered_map<BindingPoint, BindingPoint>& binding_points) {
-    auto res = BindingRemapper(ir, binding_points);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return BindingRemapper(ir, binding_points);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs.cc b/src/tint/lang/core/ir/transform/block_decorated_structs.cc
index 6186a59..a03b5b6 100644
--- a/src/tint/lang/core/ir/transform/block_decorated_structs.cc
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs.cc
@@ -117,7 +117,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BlockDecoratedStructs(Module& ir) {
+Result<SuccessType> BlockDecoratedStructs(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.BlockDecoratedStructs");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs.h b/src/tint/lang/core/ir/transform/block_decorated_structs.h
index 063c34f..551ac66 100644
--- a/src/tint/lang/core/ir/transform/block_decorated_structs.h
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_BLOCK_DECORATED_STRUCTS_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_BLOCK_DECORATED_STRUCTS_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// existing store type in a new structure if necessary.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> BlockDecoratedStructs(Module& module);
+Result<SuccessType> BlockDecoratedStructs(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc b/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc
index 01e28f0..93c5fde 100644
--- a/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> BlockDecoratedStructsFuzzer(Module& ir, const fuzz::ir::Context&) {
-    auto res = BlockDecoratedStructs(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return BlockDecoratedStructs(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.cc b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
index 0437e7c..c012a30 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
@@ -1144,7 +1144,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BuiltinPolyfill(Module& ir, const BuiltinPolyfillConfig& config) {
+Result<SuccessType> BuiltinPolyfill(Module& ir, const BuiltinPolyfillConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.BuiltinPolyfill");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.h b/src/tint/lang/core/ir/transform/builtin_polyfill.h
index b82ade5..a3e6c5e 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.h
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.h
@@ -28,8 +28,8 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_BUILTIN_POLYFILL_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_BUILTIN_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/reflection.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -108,7 +108,7 @@
 /// @param module the module to transform
 /// @param config the polyfill configuration
 /// @returns success or failure
-diag::Result<SuccessType> BuiltinPolyfill(Module& module, const BuiltinPolyfillConfig& config);
+Result<SuccessType> BuiltinPolyfill(Module& module, const BuiltinPolyfillConfig& config);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill_fuzz.cc b/src/tint/lang/core/ir/transform/builtin_polyfill_fuzz.cc
index 6f2e16f..b37d755 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill_fuzz.cc
@@ -36,11 +36,7 @@
 Result<SuccessType> BuiltinPolyfillFuzzer(Module& ir,
                                           const fuzz::ir::Context&,
                                           const BuiltinPolyfillConfig& config) {
-    auto res = BuiltinPolyfill(ir, config);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return BuiltinPolyfill(ir, config);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/combine_access_instructions.cc b/src/tint/lang/core/ir/transform/combine_access_instructions.cc
index 1e64860..a65d04d 100644
--- a/src/tint/lang/core/ir/transform/combine_access_instructions.cc
+++ b/src/tint/lang/core/ir/transform/combine_access_instructions.cc
@@ -79,7 +79,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> CombineAccessInstructions(Module& ir) {
+Result<SuccessType> CombineAccessInstructions(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.CombineAccessInstructions");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/combine_access_instructions.h b/src/tint/lang/core/ir/transform/combine_access_instructions.h
index 0cd0bc3..cc5432b 100644
--- a/src/tint/lang/core/ir/transform/combine_access_instructions.h
+++ b/src/tint/lang/core/ir/transform/combine_access_instructions.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_COMBINE_ACCESS_INSTRUCTIONS_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_COMBINE_ACCESS_INSTRUCTIONS_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -40,7 +40,7 @@
 /// CombineAccessInstructions is a transform that combines chains of access instructions.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> CombineAccessInstructions(Module& module);
+Result<SuccessType> CombineAccessInstructions(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/combine_access_instructions_fuzz.cc b/src/tint/lang/core/ir/transform/combine_access_instructions_fuzz.cc
index c1e76cf..03ca62d 100644
--- a/src/tint/lang/core/ir/transform/combine_access_instructions_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/combine_access_instructions_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> CombineAccessInstructionsFuzzer(Module& ir, const fuzz::ir::Context&) {
-    auto res = CombineAccessInstructions(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return CombineAccessInstructions(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/conversion_polyfill.cc b/src/tint/lang/core/ir/transform/conversion_polyfill.cc
index 3035c7a..becfdf9 100644
--- a/src/tint/lang/core/ir/transform/conversion_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/conversion_polyfill.cc
@@ -188,7 +188,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ConversionPolyfill(Module& ir, const ConversionPolyfillConfig& config) {
+Result<SuccessType> ConversionPolyfill(Module& ir, const ConversionPolyfillConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.ConversionPolyfill");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/conversion_polyfill.h b/src/tint/lang/core/ir/transform/conversion_polyfill.h
index 7311998..ca09eb4 100644
--- a/src/tint/lang/core/ir/transform/conversion_polyfill.h
+++ b/src/tint/lang/core/ir/transform/conversion_polyfill.h
@@ -28,8 +28,8 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_CONVERSION_POLYFILL_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_CONVERSION_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/reflection.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -52,8 +52,7 @@
 /// @param module the module to transform
 /// @param config the polyfill configuration
 /// @returns success or failure
-diag::Result<SuccessType> ConversionPolyfill(Module& module,
-                                             const ConversionPolyfillConfig& config);
+Result<SuccessType> ConversionPolyfill(Module& module, const ConversionPolyfillConfig& config);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/conversion_polyfill_fuzz.cc b/src/tint/lang/core/ir/transform/conversion_polyfill_fuzz.cc
index a2945a7..0003daf 100644
--- a/src/tint/lang/core/ir/transform/conversion_polyfill_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/conversion_polyfill_fuzz.cc
@@ -36,11 +36,7 @@
 Result<SuccessType> ConversionPolyfillFuzzer(Module& ir,
                                              const fuzz::ir::Context&,
                                              const ConversionPolyfillConfig& config) {
-    auto res = ConversionPolyfill(ir, config);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return ConversionPolyfill(ir, config);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper.cc b/src/tint/lang/core/ir/transform/demote_to_helper.cc
index bbb130a..24e2529 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper.cc
+++ b/src/tint/lang/core/ir/transform/demote_to_helper.cc
@@ -213,7 +213,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> DemoteToHelper(Module& ir) {
+Result<SuccessType> DemoteToHelper(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.DemoteToHelper", kDemoteToHelperCapabilities);
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper.h b/src/tint/lang/core/ir/transform/demote_to_helper.h
index 9d71f30..dff14cc 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper.h
+++ b/src/tint/lang/core/ir/transform/demote_to_helper.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_DEMOTE_TO_HELPER_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -52,7 +52,7 @@
 /// buffers and textures.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> DemoteToHelper(Module& module);
+Result<SuccessType> DemoteToHelper(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper_fuzz.cc b/src/tint/lang/core/ir/transform/demote_to_helper_fuzz.cc
index ca72823..98975c9 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/demote_to_helper_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> DemoteToHelperFuzzer(Module& ir, const fuzz::ir::Context&) {
-    auto res = DemoteToHelper(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return DemoteToHelper(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access.cc b/src/tint/lang/core/ir/transform/direct_variable_access.cc
index 41f42ec..b5917e3 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access.cc
@@ -703,8 +703,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> DirectVariableAccess(Module& ir,
-                                               const DirectVariableAccessOptions& options) {
+Result<SuccessType> DirectVariableAccess(Module& ir, const DirectVariableAccessOptions& options) {
     auto result =
         ValidateAndDumpIfNeeded(ir, "core.DirectVariableAccess", kDirectVariableAccessCapabilities);
     if (result != Success) {
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access.h b/src/tint/lang/core/ir/transform/direct_variable_access.h
index af321cf..da92c94 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access.h
+++ b/src/tint/lang/core/ir/transform/direct_variable_access.h
@@ -29,8 +29,8 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/reflection.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -70,9 +70,9 @@
 ///
 /// @param module the module to transform
 /// @param options the options
-/// @returns error diagnostics on failure
-diag::Result<SuccessType> DirectVariableAccess(Module& module,
-                                               const DirectVariableAccessOptions& options);
+/// @returns error on failure
+Result<SuccessType> DirectVariableAccess(Module& module,
+                                         const DirectVariableAccessOptions& options);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access_fuzz.cc b/src/tint/lang/core/ir/transform/direct_variable_access_fuzz.cc
index 272ea68..4e53c3b 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access_fuzz.cc
@@ -36,11 +36,7 @@
 Result<SuccessType> DirectVariableAccessFuzzer(Module& ir,
                                                const fuzz::ir::Context&,
                                                const DirectVariableAccessOptions& options) {
-    auto res = DirectVariableAccess(ir, options);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return DirectVariableAccess(ir, options);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc b/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc
index 31d49a1..c94bad5 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc
@@ -78,13 +78,13 @@
 
         auto res = DirectVariableAccess(module.Get(), transform_options);
         if (res != Success) {
-            return "DirectVariableAccess failed:\n" + res.Failure().reason.Str();
+            return "DirectVariableAccess failed:\n" + res.Failure().reason;
         }
 
         auto pre_raise = ir::Disassembler(module.Get()).Plain();
 
         if (auto raise = wgsl::writer::Raise(module.Get()); raise != Success) {
-            return "wgsl::writer::Raise failed:\n" + res.Failure().reason.Str();
+            return "wgsl::writer::Raise failed:\n" + res.Failure().reason;
         }
 
         auto program_out = wgsl::writer::IRToProgram(module.Get(), program_options);
@@ -97,7 +97,7 @@
 
         auto output = wgsl::writer::Generate(program_out, wgsl::writer::Options{});
         if (output != Success) {
-            return "wgsl::writer::IRToProgram() failed: \n" + output.Failure().reason.Str() +
+            return "wgsl::writer::IRToProgram() failed: \n" + output.Failure().reason +
                    "\n\nIR:\n" + ir::Disassembler(module.Get()).Plain();
         }
 
diff --git a/src/tint/lang/core/ir/transform/helper_test.h b/src/tint/lang/core/ir/transform/helper_test.h
index 64319de..6d675e2 100644
--- a/src/tint/lang/core/ir/transform/helper_test.h
+++ b/src/tint/lang/core/ir/transform/helper_test.h
@@ -62,7 +62,7 @@
     /// @param transform_func the transform to run
     /// @param args the arguments to the transform function
     template <typename TRANSFORM, typename... ARGS>
-    diag::Result<SuccessType> RunWithFailure(TRANSFORM&& transform_func, ARGS&&... args) {
+    Result<SuccessType> RunWithFailure(TRANSFORM&& transform_func, ARGS&&... args) {
         return transform_func(mod, std::forward<ARGS>(args)...);
     }
 
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
index f3b3904..b49ed9b 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
@@ -75,7 +75,7 @@
     Function* gamma_correction = nullptr;
 
     /// Process the module.
-    diag::Result<SuccessType> Process() {
+    Result<SuccessType> Process() {
         // Find module-scope variables that need to be replaced.
         if (!ir.root_block->IsEmpty()) {
             Vector<Instruction*, 4> to_remove;
@@ -118,14 +118,14 @@
 
     /// Replace an external texture variable declaration.
     /// @param old_var the variable declaration to replace
-    diag::Result<SuccessType> ReplaceVar(Var* old_var) {
+    Result<SuccessType> ReplaceVar(Var* old_var) {
         auto name = ir.NameOf(old_var);
         auto bp = old_var->BindingPoint();
         auto itr = multiplanar_map.find(bp.value());
         if (DAWN_UNLIKELY(itr == multiplanar_map.end())) {
             std::stringstream err;
             err << "ExternalTextureOptions missing binding entry for " << bp.value();
-            return diag::Failure{err.str()};
+            return Failure{err.str()};
         }
         const auto& new_binding_points = itr->second;
 
@@ -606,7 +606,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> MultiplanarExternalTexture(
+Result<SuccessType> MultiplanarExternalTexture(
     Module& ir,
     const tint::transform::multiplanar::BindingsMap& multiplanar_map) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.MultiplanarExternalTexture");
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture.h b/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
index 5c370b1..26214f4 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_
 
 #include "src/tint/lang/core/common/multiplanar_options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -44,7 +44,7 @@
 /// @param module the module to transform
 /// @param options the external texture options
 /// @returns success or failure
-diag::Result<SuccessType> MultiplanarExternalTexture(
+Result<SuccessType> MultiplanarExternalTexture(
     Module& module,
     const tint::transform::multiplanar::BindingsMap& options);
 
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc
index ff8ebbd..b8e7b0d 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc
@@ -37,11 +37,7 @@
     Module& ir,
     const fuzz::ir::Context&,
     const tint::transform::multiplanar::BindingsMap& multiplanar_map) {
-    auto res = MultiplanarExternalTexture(ir, multiplanar_map);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return MultiplanarExternalTexture(ir, multiplanar_map);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/prepare_push_constants.cc b/src/tint/lang/core/ir/transform/prepare_push_constants.cc
index 3afb6db..5728b64 100644
--- a/src/tint/lang/core/ir/transform/prepare_push_constants.cc
+++ b/src/tint/lang/core/ir/transform/prepare_push_constants.cc
@@ -127,8 +127,8 @@
 
 }  // namespace
 
-diag::Result<PushConstantLayout> PreparePushConstants(Module& ir,
-                                                      const PreparePushConstantsConfig& config) {
+Result<PushConstantLayout> PreparePushConstants(Module& ir,
+                                                const PreparePushConstantsConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.PreparePushConstants");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/core/ir/transform/prepare_push_constants.h b/src/tint/lang/core/ir/transform/prepare_push_constants.h
index 89f91cd..6067b24 100644
--- a/src/tint/lang/core/ir/transform/prepare_push_constants.h
+++ b/src/tint/lang/core/ir/transform/prepare_push_constants.h
@@ -31,8 +31,8 @@
 #include <map>
 
 #include "src/tint/utils/containers/hashmap.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/reflection.h"
+#include "src/tint/utils/result.h"
 #include "src/tint/utils/symbol/symbol.h"
 
 // Forward declarations.
@@ -87,8 +87,8 @@
 /// @param module the module to transform
 /// @param config the transform config
 /// @returns the generated push constant layout or failure
-diag::Result<PushConstantLayout> PreparePushConstants(Module& module,
-                                                      const PreparePushConstantsConfig& config);
+Result<PushConstantLayout> PreparePushConstants(Module& module,
+                                                const PreparePushConstantsConfig& config);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/prepare_push_constants_test.cc b/src/tint/lang/core/ir/transform/prepare_push_constants_test.cc
index cf1eca0..d30db05 100644
--- a/src/tint/lang/core/ir/transform/prepare_push_constants_test.cc
+++ b/src/tint/lang/core/ir/transform/prepare_push_constants_test.cc
@@ -37,7 +37,7 @@
 
 class IR_PreparePushConstantsTests : public TransformTest {
   public:
-    diag::Result<PushConstantLayout> Run(PreparePushConstantsConfig config) {
+    Result<PushConstantLayout> Run(PreparePushConstantsConfig config) {
         // Run the transform.
         auto result = PreparePushConstants(mod, config);
         EXPECT_EQ(result, Success);
diff --git a/src/tint/lang/core/ir/transform/preserve_padding.cc b/src/tint/lang/core/ir/transform/preserve_padding.cc
index 9966009..c06ecd8 100644
--- a/src/tint/lang/core/ir/transform/preserve_padding.cc
+++ b/src/tint/lang/core/ir/transform/preserve_padding.cc
@@ -170,7 +170,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> PreservePadding(Module& ir) {
+Result<SuccessType> PreservePadding(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.PreservePadding", kPreservePaddingCapabilities);
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/preserve_padding.h b/src/tint/lang/core/ir/transform/preserve_padding.h
index 3d0d9ad..c4f7c89 100644
--- a/src/tint/lang/core/ir/transform/preserve_padding.h
+++ b/src/tint/lang/core/ir/transform/preserve_padding.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_PRESERVE_PADDING_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -49,7 +49,7 @@
 /// @note assumes that DirectVariableAccess will be run afterwards for backends that need it.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> PreservePadding(Module& module);
+Result<SuccessType> PreservePadding(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/preserve_padding_fuzz.cc b/src/tint/lang/core/ir/transform/preserve_padding_fuzz.cc
index 4ed9929..741f4f1 100644
--- a/src/tint/lang/core/ir/transform/preserve_padding_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/preserve_padding_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> PreservePaddingFuzzer(Module& ir, const fuzz::ir::Context&) {
-    auto res = PreservePadding(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return PreservePadding(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc b/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc
index 539ec1e..3650b09 100644
--- a/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc
+++ b/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc
@@ -115,7 +115,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> PreventInfiniteLoops(Module& ir) {
+Result<SuccessType> PreventInfiniteLoops(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.PreventInfiniteLoops");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/prevent_infinite_loops.h b/src/tint/lang/core/ir/transform/prevent_infinite_loops.h
index e69b0e7..82fa555 100644
--- a/src/tint/lang/core/ir/transform/prevent_infinite_loops.h
+++ b/src/tint/lang/core/ir/transform/prevent_infinite_loops.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_PREVENT_INFINITE_LOOPS_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_PREVENT_INFINITE_LOOPS_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -43,7 +43,7 @@
 ///
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> PreventInfiniteLoops(Module& module);
+Result<SuccessType> PreventInfiniteLoops(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc b/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc
index 3d82fc5..445b2ca 100644
--- a/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc
+++ b/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc
@@ -112,7 +112,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> RemoveContinueInSwitch(Module& ir) {
+Result<SuccessType> RemoveContinueInSwitch(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.RemoveContinueInSwitch",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowVectorElementPointer,
diff --git a/src/tint/lang/core/ir/transform/remove_continue_in_switch.h b/src/tint/lang/core/ir/transform/remove_continue_in_switch.h
index dfa103c..6a15b33 100644
--- a/src/tint/lang/core/ir/transform/remove_continue_in_switch.h
+++ b/src/tint/lang/core/ir/transform/remove_continue_in_switch.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_REMOVE_CONTINUE_IN_SWITCH_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_REMOVE_CONTINUE_IN_SWITCH_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -45,7 +45,7 @@
 /// the loop.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> RemoveContinueInSwitch(Module& module);
+Result<SuccessType> RemoveContinueInSwitch(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/remove_terminator_args.cc b/src/tint/lang/core/ir/transform/remove_terminator_args.cc
index 709568c..5eb80ca 100644
--- a/src/tint/lang/core/ir/transform/remove_terminator_args.cc
+++ b/src/tint/lang/core/ir/transform/remove_terminator_args.cc
@@ -161,7 +161,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> RemoveTerminatorArgs(Module& ir) {
+Result<SuccessType> RemoveTerminatorArgs(Module& ir) {
     auto result =
         ValidateAndDumpIfNeeded(ir, "core.RemoveTerminatorArgs", kRemoveTerminatorArgsCapabilities);
     if (result != Success) {
diff --git a/src/tint/lang/core/ir/transform/remove_terminator_args.h b/src/tint/lang/core/ir/transform/remove_terminator_args.h
index e6b1760..e23a23d 100644
--- a/src/tint/lang/core/ir/transform/remove_terminator_args.h
+++ b/src/tint/lang/core/ir/transform/remove_terminator_args.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_REMOVE_TERMINATOR_ARGS_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -56,7 +56,7 @@
 /// textual languages.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> RemoveTerminatorArgs(Module& module);
+Result<SuccessType> RemoveTerminatorArgs(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/remove_terminator_args_fuzz.cc b/src/tint/lang/core/ir/transform/remove_terminator_args_fuzz.cc
index f5e78de..3bb6a7f 100644
--- a/src/tint/lang/core/ir/transform/remove_terminator_args_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/remove_terminator_args_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> RemoveTerminatorArgsFuzzer(Module& ir, const fuzz::ir::Context&) {
-    auto res = RemoveTerminatorArgs(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return RemoveTerminatorArgs(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/rename_conflicts.cc b/src/tint/lang/core/ir/transform/rename_conflicts.cc
index ebb0d66..1bb6c57 100644
--- a/src/tint/lang/core/ir/transform/rename_conflicts.cc
+++ b/src/tint/lang/core/ir/transform/rename_conflicts.cc
@@ -294,7 +294,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> RenameConflicts(core::ir::Module& ir) {
+Result<SuccessType> RenameConflicts(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.RenameConflicts", kRenameConflictsCapabilities);
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/rename_conflicts.h b/src/tint/lang/core/ir/transform/rename_conflicts.h
index 4eb4f8f..fd93e05 100644
--- a/src/tint/lang/core/ir/transform/rename_conflicts.h
+++ b/src/tint/lang/core/ir/transform/rename_conflicts.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_RENAME_CONFLICTS_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -56,7 +56,7 @@
 /// scope or a parent scope.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> RenameConflicts(core::ir::Module& module);
+Result<SuccessType> RenameConflicts(core::ir::Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/rename_conflicts_fuzz.cc b/src/tint/lang/core/ir/transform/rename_conflicts_fuzz.cc
index 698125b..84a6f70 100644
--- a/src/tint/lang/core/ir/transform/rename_conflicts_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/rename_conflicts_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> RenameConflictsFuzzer(Module& ir, const fuzz::ir::Context&) {
-    auto res = RenameConflicts(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return RenameConflicts(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/robustness.cc b/src/tint/lang/core/ir/transform/robustness.cc
index 678fa43..b9dd071 100644
--- a/src/tint/lang/core/ir/transform/robustness.cc
+++ b/src/tint/lang/core/ir/transform/robustness.cc
@@ -380,7 +380,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> Robustness(Module& ir, const RobustnessConfig& config) {
+Result<SuccessType> Robustness(Module& ir, const RobustnessConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.Robustness");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/robustness.h b/src/tint/lang/core/ir/transform/robustness.h
index 02fd488..c845ca5 100644
--- a/src/tint/lang/core/ir/transform/robustness.h
+++ b/src/tint/lang/core/ir/transform/robustness.h
@@ -31,8 +31,8 @@
 #include <unordered_set>
 
 #include "src/tint/api/common/binding_point.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/reflection.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -86,7 +86,7 @@
 /// @param module the module to transform
 /// @param config the robustness configuration
 /// @returns success or failure
-diag::Result<SuccessType> Robustness(Module& module, const RobustnessConfig& config);
+Result<SuccessType> Robustness(Module& module, const RobustnessConfig& config);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/robustness_fuzz.cc b/src/tint/lang/core/ir/transform/robustness_fuzz.cc
index 7f732c2..d0f894a 100644
--- a/src/tint/lang/core/ir/transform/robustness_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/robustness_fuzz.cc
@@ -41,11 +41,7 @@
         return Failure{"config.bindings_ignored is not empty"};
     }
 
-    auto res = Robustness(module, config);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return Robustness(module, config);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/single_entry_point.cc b/src/tint/lang/core/ir/transform/single_entry_point.cc
index e96ac4b..3c09ae4 100644
--- a/src/tint/lang/core/ir/transform/single_entry_point.cc
+++ b/src/tint/lang/core/ir/transform/single_entry_point.cc
@@ -94,7 +94,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> SingleEntryPoint(Module& ir, std::string_view entry_point_name) {
+Result<SuccessType> SingleEntryPoint(Module& ir, std::string_view entry_point_name) {
     auto result = ValidateAndDumpIfNeeded(
         ir, "core.SingleEntryPoint", core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     if (result != Success) {
diff --git a/src/tint/lang/core/ir/transform/single_entry_point.h b/src/tint/lang/core/ir/transform/single_entry_point.h
index fbc4999..6869afa 100644
--- a/src/tint/lang/core/ir/transform/single_entry_point.h
+++ b/src/tint/lang/core/ir/transform/single_entry_point.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_SINGLE_ENTRY_POINT_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_SINGLE_ENTRY_POINT_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// @param module the module to transform
 /// @param entry_point_name the entry point name
 /// @returns success or failure
-diag::Result<SuccessType> SingleEntryPoint(Module& module, std::string_view entry_point_name);
+Result<SuccessType> SingleEntryPoint(Module& module, std::string_view entry_point_name);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/std140.cc b/src/tint/lang/core/ir/transform/std140.cc
index f61dc55..ac9a685 100644
--- a/src/tint/lang/core/ir/transform/std140.cc
+++ b/src/tint/lang/core/ir/transform/std140.cc
@@ -449,7 +449,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> Std140(Module& ir) {
+Result<SuccessType> Std140(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.Std140", kStd140Capabilities);
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/std140.h b/src/tint/lang/core/ir/transform/std140.h
index b4fb98f..31f718a 100644
--- a/src/tint/lang/core/ir/transform/std140.h
+++ b/src/tint/lang/core/ir/transform/std140.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_STD140_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -49,7 +49,7 @@
 /// pointer parameters.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> Std140(Module& module);
+Result<SuccessType> Std140(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/std140_fuzz.cc b/src/tint/lang/core/ir/transform/std140_fuzz.cc
index 6a68cfa..3857902 100644
--- a/src/tint/lang/core/ir/transform/std140_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/std140_fuzz.cc
@@ -53,11 +53,7 @@
         return Failure{"Cannot run module"};
     }
 
-    auto res = Std140(module);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return Std140(module);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/substitute_overrides.cc b/src/tint/lang/core/ir/transform/substitute_overrides.cc
index 682fe6a..abfabf2 100644
--- a/src/tint/lang/core/ir/transform/substitute_overrides.cc
+++ b/src/tint/lang/core/ir/transform/substitute_overrides.cc
@@ -376,22 +376,24 @@
 
 SubstituteOverridesConfig::SubstituteOverridesConfig() = default;
 
-diag::Result<SuccessType> SubstituteOverrides(Module& ir, const SubstituteOverridesConfig& cfg) {
-    auto result =
-        ValidateAndDumpIfNeeded(ir, "core.SubstituteOverrides", kSubstituteOverridesCapabilities);
-    if (result != Success) {
-        return result;
+Result<SuccessType> SubstituteOverrides(Module& ir, const SubstituteOverridesConfig& cfg) {
+    {
+        auto result = ValidateAndDumpIfNeeded(ir, "core.SubstituteOverrides",
+                                              kSubstituteOverridesCapabilities);
+        if (result != Success) {
+            return result.Failure();
+        }
     }
-
-    result = State{ir, cfg}.Process();
-    if (result != Success) {
-        return result;
+    {
+        auto result = State{ir, cfg}.Process();
+        if (result != Success) {
+            return Failure{result.Failure().reason.Str()};
+        }
     }
 
     // TODO(crbug.com/382300469): This function should take in a constant module but it does not due
     // to missing constant functions.
-    result = tint::core::ir::ValidateConstParam(ir);
-    return result;
+    return tint::core::ir::ValidateConstParam(ir);
 }
 
 }  // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/substitute_overrides.h b/src/tint/lang/core/ir/transform/substitute_overrides.h
index df234aa..92c5182 100644
--- a/src/tint/lang/core/ir/transform/substitute_overrides.h
+++ b/src/tint/lang/core/ir/transform/substitute_overrides.h
@@ -32,8 +32,8 @@
 
 #include "src/tint/api/common/override_id.h"
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/reflection.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -74,7 +74,7 @@
 /// Substitute overrides to their constant values.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> SubstituteOverrides(Module& module, const SubstituteOverridesConfig& cfg);
+Result<SuccessType> SubstituteOverrides(Module& module, const SubstituteOverridesConfig& cfg);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/substitute_overrides_fuzz.cc b/src/tint/lang/core/ir/transform/substitute_overrides_fuzz.cc
index 8a515f3..5e67e06 100644
--- a/src/tint/lang/core/ir/transform/substitute_overrides_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/substitute_overrides_fuzz.cc
@@ -37,11 +37,7 @@
 Result<SuccessType> SubstituteOverridesFuzzer(Module& ir,
                                               const fuzz::ir::Context&,
                                               const SubstituteOverridesConfig& cfg) {
-    auto res = SubstituteOverrides(ir, cfg);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return SubstituteOverrides(ir, cfg);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/substitute_overrides_test.cc b/src/tint/lang/core/ir/transform/substitute_overrides_test.cc
index 74d3c8b..69d9ceb 100644
--- a/src/tint/lang/core/ir/transform/substitute_overrides_test.cc
+++ b/src/tint/lang/core/ir/transform/substitute_overrides_test.cc
@@ -78,7 +78,7 @@
     SubstituteOverridesConfig cfg{};
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
-    EXPECT_EQ(result.Failure().reason.Str(),
+    EXPECT_EQ(result.Failure().reason,
               R"(1:2 error: Initializer not provided for override, and override not overridden.)");
 }
 
@@ -367,7 +367,7 @@
     cfg.map[OverrideId{2}] = 125.0;
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
-    EXPECT_EQ(result.Failure().reason.Str(),
+    EXPECT_EQ(result.Failure().reason,
               R"(error: The sourceLaneIndex argument of subgroupShuffle must be less than 128)");
 }
 
@@ -411,8 +411,7 @@
     cfg.map[OverrideId{2}] = -65505.0 - 4.0;
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
-    EXPECT_EQ(result.Failure().reason.Str(),
-              R"(error: value -65505.0 cannot be represented as 'f16')");
+    EXPECT_EQ(result.Failure().reason, R"(error: value -65505.0 cannot be represented as 'f16')");
 }
 
 TEST_F(IR_SubstituteOverridesTest, OverrideWithComplexGenError) {
@@ -452,7 +451,7 @@
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
     EXPECT_EQ(
-        result.Failure().reason.Str(),
+        result.Failure().reason,
         R"(1:2 error: '340282346638528859811704183484516925440.0 + 340282346638528859811704183484516925440.0' cannot be represented as 'f32')");
 }
 
@@ -1127,8 +1126,7 @@
 
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
-    EXPECT_EQ(result.Failure().reason.Str(),
-              R"(error: '1.0 / 0.0' cannot be represented as 'f32')");
+    EXPECT_EQ(result.Failure().reason, R"(error: '1.0 / 0.0' cannot be represented as 'f32')");
 }
 
 TEST_F(IR_SubstituteOverridesTest, OverrideCondComplexConstExprSuccess) {
@@ -1458,7 +1456,7 @@
 
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
-    EXPECT_EQ(result.Failure().reason.Str(), R"(error: index 10 out of bounds [0..3])");
+    EXPECT_EQ(result.Failure().reason, R"(error: index 10 out of bounds [0..3])");
 }
 
 TEST_F(IR_SubstituteOverridesTest, OverrideRuntimeSizedArrayFailure) {
@@ -1500,7 +1498,7 @@
 
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
-    EXPECT_EQ(result.Failure().reason.Str(), R"(error: index -10 out of bounds)");
+    EXPECT_EQ(result.Failure().reason, R"(error: index -10 out of bounds)");
 }
 
 TEST_F(IR_SubstituteOverridesTest, OverrideConstruct) {
@@ -1592,7 +1590,7 @@
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
     EXPECT_EQ(
-        result.Failure().reason.Str(),
+        result.Failure().reason,
         R"(error: Pipeline overridable constant 2 with value (-100.0)  is not representable in type (u32))");
 }
 
@@ -1614,7 +1612,7 @@
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
     EXPECT_EQ(
-        result.Failure().reason.Str(),
+        result.Failure().reason,
         R"(error: Pipeline overridable constant 2 with value (8000000000.0)  is not representable in type (i32))");
 }
 
@@ -1636,7 +1634,7 @@
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
     EXPECT_EQ(
-        result.Failure().reason.Str(),
+        result.Failure().reason,
         R"(error: Pipeline overridable constant 2 with value (31399999999999998802000170346751583059968.0)  is not representable in type (f32))");
 }
 
@@ -1658,7 +1656,7 @@
     auto result = RunWithFailure(SubstituteOverrides, cfg);
     ASSERT_NE(result, Success);
     EXPECT_EQ(
-        result.Failure().reason.Str(),
+        result.Failure().reason,
         R"(error: Pipeline overridable constant 2 with value (65505.0)  is not representable in type (f16))");
 }
 
diff --git a/src/tint/lang/core/ir/transform/value_to_let.cc b/src/tint/lang/core/ir/transform/value_to_let.cc
index 03dfc02..1c193bf 100644
--- a/src/tint/lang/core/ir/transform/value_to_let.cc
+++ b/src/tint/lang/core/ir/transform/value_to_let.cc
@@ -229,7 +229,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ValueToLet(Module& ir, const ValueToLetConfig& cfg) {
+Result<SuccessType> ValueToLet(Module& ir, const ValueToLetConfig& cfg) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.ValueToLet", kValueToLetCapabilities);
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/value_to_let.h b/src/tint/lang/core/ir/transform/value_to_let.h
index 56a983c..ec00711 100644
--- a/src/tint/lang/core/ir/transform/value_to_let.h
+++ b/src/tint/lang/core/ir/transform/value_to_let.h
@@ -29,8 +29,8 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_VALUE_TO_LET_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/reflection.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -70,8 +70,8 @@
 ///
 /// @param module the module to transform
 /// @param cfg the configuration
-/// @returns error diagnostics on failure
-diag::Result<SuccessType> ValueToLet(Module& module, const ValueToLetConfig& cfg);
+/// @returns error on failure
+Result<SuccessType> ValueToLet(Module& module, const ValueToLetConfig& cfg);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/value_to_let_fuzz.cc b/src/tint/lang/core/ir/transform/value_to_let_fuzz.cc
index 7d3dd93..d714a00 100644
--- a/src/tint/lang/core/ir/transform/value_to_let_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/value_to_let_fuzz.cc
@@ -53,7 +53,7 @@
 
     auto res = ValueToLet(module, config);
     if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
+        return Failure{res.Failure().reason};
     }
     return Success;
 }
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
index 2cdc519..96996bf 100644
--- a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
+++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
@@ -93,7 +93,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> VectorizeScalarMatrixConstructors(Module& ir) {
+Result<SuccessType> VectorizeScalarMatrixConstructors(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.VectorizeScalarMatrixConstructors",
                                           kVectorizeScalarMatrixConstructorsCapabilities);
     if (result != Success) {
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h
index 4a3307a..6954ffe 100644
--- a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h
+++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_VECTORIZE_SCALAR_MATRIX_CONSTRUCTORS_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -50,7 +50,7 @@
 ///
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> VectorizeScalarMatrixConstructors(Module& module);
+Result<SuccessType> VectorizeScalarMatrixConstructors(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors_fuzz.cc b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors_fuzz.cc
index 4f1b5ee..86e0916 100644
--- a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> VectorizeScalarMatrixConstructorsFuzzer(Module& ir, const fuzz::ir::Context&) {
-    auto res = VectorizeScalarMatrixConstructors(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return VectorizeScalarMatrixConstructors(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/transform/vertex_pulling.cc b/src/tint/lang/core/ir/transform/vertex_pulling.cc
index b58940e..009fbb2 100644
--- a/src/tint/lang/core/ir/transform/vertex_pulling.cc
+++ b/src/tint/lang/core/ir/transform/vertex_pulling.cc
@@ -687,7 +687,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> VertexPulling(core::ir::Module& ir, const VertexPullingConfig& config) {
+Result<SuccessType> VertexPulling(core::ir::Module& ir, const VertexPullingConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.VertexPulling");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/core/ir/transform/vertex_pulling.h b/src/tint/lang/core/ir/transform/vertex_pulling.h
index b993e1d..fe6fe51 100644
--- a/src/tint/lang/core/ir/transform/vertex_pulling.h
+++ b/src/tint/lang/core/ir/transform/vertex_pulling.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_VERTEX_PULLING_H_
 
 #include "src/tint/api/common/vertex_pulling_config.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -53,8 +53,7 @@
 /// @param module the module to transform
 /// @param config the vertex pulling configuration
 /// @returns success or failure
-diag::Result<SuccessType> VertexPulling(core::ir::Module& module,
-                                        const VertexPullingConfig& config);
+Result<SuccessType> VertexPulling(core::ir::Module& module, const VertexPullingConfig& config);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
index 02f32bb..5a842e7 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
@@ -305,7 +305,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ZeroInitWorkgroupMemory(Module& ir) {
+Result<SuccessType> ZeroInitWorkgroupMemory(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "core.ZeroInitWorkgroupMemory");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.h b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.h
index 8df5aa1..f73df08 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.h
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_ZERO_INIT_WORKGROUP_MEMORY_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_ZERO_INIT_WORKGROUP_MEMORY_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// zero-initialize workgroup memory used by that entry point.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> ZeroInitWorkgroupMemory(Module& module);
+Result<SuccessType> ZeroInitWorkgroupMemory(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc
index bc9470b..970217d 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> ZeroInitWorkgroupMemoryFuzzer(Module& ir, const fuzz::ir::Context&) {
-    auto res = ZeroInitWorkgroupMemory(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return ZeroInitWorkgroupMemory(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 8a1c995..701f9eb 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -751,7 +751,7 @@
 
     /// Runs the validator over the module provided during construction
     /// @returns success or failure
-    diag::Result<SuccessType> Run();
+    Result<SuccessType> Run();
 
   private:
     /// Runs validation to confirm the structural soundness of the module.
@@ -1369,7 +1369,7 @@
     return *disassembler_;
 }
 
-diag::Result<SuccessType> Validator::Run() {
+Result<SuccessType> Validator::Run() {
     RunStructuralSoundnessChecks();
 
     if (!diagnostics_.ContainsErrors()) {
@@ -1382,7 +1382,7 @@
 
     if (diagnostics_.ContainsErrors()) {
         diagnostics_.AddNote(Source{}) << "# Disassembly\n" << Disassemble().Text();
-        return diag::Failure{std::move(diagnostics_)};
+        return Failure{diagnostics_.Str()};
     }
     return Success;
 }
@@ -3691,14 +3691,18 @@
 
 }  // namespace
 
-diag::Result<SuccessType> Validate(const Module& mod, Capabilities capabilities) {
+Result<SuccessType> Validate(const Module& mod, Capabilities capabilities) {
     Validator v(mod, capabilities);
-    return v.Run();
+    auto res = v.Run();
+    if (res != Success) {
+        return res;
+    }
+    return Success;
 }
 
-diag::Result<SuccessType> ValidateAndDumpIfNeeded([[maybe_unused]] const Module& ir,
-                                                  [[maybe_unused]] const char* msg,
-                                                  [[maybe_unused]] Capabilities capabilities) {
+Result<SuccessType> ValidateAndDumpIfNeeded([[maybe_unused]] const Module& ir,
+                                            [[maybe_unused]] const char* msg,
+                                            [[maybe_unused]] Capabilities capabilities) {
 #if TINT_DUMP_IR_WHEN_VALIDATING
     auto printer = StyledTextPrinter::Create(stdout);
     std::cout << "=========================================================\n";
diff --git a/src/tint/lang/core/ir/validator.h b/src/tint/lang/core/ir/validator.h
index 9bfef76..2ef66dc 100644
--- a/src/tint/lang/core/ir/validator.h
+++ b/src/tint/lang/core/ir/validator.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_VALIDATOR_H_
 
 #include "src/tint/utils/containers/enum_set.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -73,16 +73,16 @@
 /// @param mod the module to validate
 /// @param capabilities the optional capabilities that are allowed
 /// @returns success or failure
-diag::Result<SuccessType> Validate(const Module& mod, Capabilities capabilities = {});
+Result<SuccessType> Validate(const Module& mod, Capabilities capabilities = {});
 
 /// Validates the module @p ir and dumps its contents if required by the build configuration.
 /// @param ir the module to transform
 /// @param msg the msg to accompany the output
 /// @param capabilities the optional capabilities that are allowed
 /// @returns success or failure
-diag::Result<SuccessType> ValidateAndDumpIfNeeded(const Module& ir,
-                                                  const char* msg,
-                                                  Capabilities capabilities = {});
+Result<SuccessType> ValidateAndDumpIfNeeded(const Module& ir,
+                                            const char* msg,
+                                            Capabilities capabilities = {});
 
 }  // namespace tint::core::ir
 
diff --git a/src/tint/lang/core/ir/validator_access_test.cc b/src/tint/lang/core/ir/validator_access_test.cc
index ef54174..2fa8a3c 100644
--- a/src/tint/lang/core/ir/validator_access_test.cc
+++ b/src/tint/lang/core/ir/validator_access_test.cc
@@ -62,11 +62,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:14 error: access: expected at least 2 operands, got 0
     %3:f32 = access
              ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_NoIndices) {
@@ -81,11 +81,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:14 error: access: expected at least 2 operands, got 1
     %3:f32 = access %2
              ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_NoResults) {
@@ -101,11 +101,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:13 error: access: expected exactly 1 results, got 0
     undef = access %2, 0i
             ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_NullObject) {
@@ -117,11 +117,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:21 error: access: operand is undefined
     %2:f32 = access undef, 0u
                     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_NullIndex) {
@@ -136,11 +136,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:25 error: access: operand is undefined
     %3:f32 = access %2, undef
                         ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_NegativeIndex) {
@@ -155,11 +155,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:25 error: access: constant index must be positive, got -1
     %3:f32 = access %2, -1i
                         ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_OOB_Index_Value) {
@@ -174,11 +174,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:29 error: access: index out of bounds for type 'vec2<f32>'
     %3:f32 = access %2, 1u, 3u
                             ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_OOB_Index_Ptr) {
@@ -194,7 +194,7 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:55 error: access: index out of bounds for type 'ptr<private, array<f32, 2>, read_write>'
     %3:ptr<private, f32, read_write> = access %2, 1u, 3u
@@ -207,7 +207,7 @@
 :3:55 note: acceptable range: [0..1]
     %3:ptr<private, f32, read_write> = access %2, 1u, 3u
                                                       ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_StaticallyUnindexableType_Value) {
@@ -222,11 +222,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:25 error: access: type 'f32' cannot be indexed
     %3:f32 = access %2, 1u
                         ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_StaticallyUnindexableType_Ptr) {
@@ -241,12 +241,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:3:51 error: access: type 'ptr<private, f32, read_write>' cannot be indexed
     %3:ptr<private, f32, read_write> = access %2, 1u
                                                   ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_DynamicallyUnindexableType_Value) {
@@ -268,11 +268,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:8:25 error: access: type 'MyStruct' cannot be dynamically indexed
     %4:i32 = access %2, %3
                         ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_DynamicallyUnindexableType_Ptr) {
@@ -294,12 +294,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:8:25 error: access: type 'ptr<private, MyStruct, read_write>' cannot be dynamically indexed
     %4:i32 = access %2, %3
                         ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_Incorrect_Type_Value_Value) {
@@ -315,12 +315,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:14 error: access: result of access chain is type 'f32' but instruction type is 'i32'
     %3:i32 = access %2, 1u, 1u
              ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_Incorrect_Type_Ptr_Ptr) {
@@ -336,12 +336,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:40 error: access: result of access chain is type 'ptr<private, f32, read_write>' but instruction type is 'ptr<private, i32, read_write>'
     %3:ptr<private, i32, read_write> = access %2, 1u, 1u
                                        ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_Incorrect_Type_Ptr_Value) {
@@ -357,12 +357,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:14 error: access: result of access chain is type 'ptr<private, f32, read_write>' but instruction type is 'f32'
     %3:f32 = access %2, 1u, 1u
              ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_IndexVectorPtr) {
@@ -377,11 +377,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:25 error: access: cannot obtain address of vector element
     %3:f32 = access %2, 1u
                         ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_IndexVectorPtr_WithCapability) {
@@ -410,11 +410,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:29 error: access: cannot obtain address of vector element
     %3:f32 = access %2, 1u, 1u
                             ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_IndexVectorPtr_ViaMatrixPtr_WithCapability) {
@@ -444,12 +444,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:34 error: access: result of access chain is type 'ptr<storage, f32, read>' but instruction type is 'ptr<uniform, f32, read>'
     %3:ptr<uniform, f32, read> = access %2, 1u
                                  ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_Incorrect_Ptr_Access) {
@@ -465,12 +465,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:40 error: access: result of access chain is type 'ptr<storage, f32, read>' but instruction type is 'ptr<storage, f32, read_write>'
     %3:ptr<storage, f32, read_write> = access %2, 1u
                                        ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Access_IndexVector) {
@@ -530,11 +530,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:19 error: load: operand is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:19 error: load: operand is undefined
     %2:i32 = load undef
                   ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Load_SourceNotMemoryView) {
@@ -549,11 +548,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:4:19 error: load: load source operand 'i32' is not a memory view
     %3:f32 = load %l
                   ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Load_TypeMismatch) {
@@ -567,12 +566,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:4:19 error: load: result type 'f32' does not match source store type 'i32'
     %3:f32 = load %2
                   ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Load_MissingResult) {
@@ -588,11 +587,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:4:13 error: load: expected exactly 1 results, got 0
     undef = load %2
             ^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Load_NonReadableSource) {
@@ -607,12 +606,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:4:19 error: load: load source operand has a non-readable access type, 'write'
     %3:i32 = load %2
                   ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Store_NullTo) {
@@ -625,11 +624,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:11 error: store: operand is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:11 error: store: operand is undefined
     store undef, 42i
           ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Store_NullFrom) {
@@ -643,11 +641,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:4:15 error: store: operand is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:4:15 error: store: operand is undefined
     store %2, undef
               ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Store_NonEmptyResult) {
@@ -663,11 +660,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:4:5 error: store: expected exactly 0 results, got 1
     store %2, 42i
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Store_TargetNotMemoryView) {
@@ -682,11 +679,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:4:11 error: store: store target operand 'i32' is not a memory view
     store %l, 42u
           ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Store_TypeMismatch) {
@@ -701,11 +698,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:4:15 error: store: value type 'u32' does not match store type 'i32'
     store %2, 42u
               ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Store_NoStoreType) {
@@ -720,11 +717,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:11 error: store: operand type is undefined
     store %2, 42u
           ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Store_NoValueType) {
@@ -741,11 +738,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:15 error: store: operand type is undefined
     store %2, %3
               ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Store_NonWriteableTarget) {
@@ -760,12 +757,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:4:11 error: store: store target operand has a non-writeable access type, 'read'
     store %2, 42i
           ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, LoadVectorElement_NullResult) {
@@ -780,11 +777,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:4:5 error: load_vector_element: result is undefined
     undef = load_vector_element %2, 1i
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, LoadVectorElement_NullFrom) {
@@ -798,11 +795,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:34 error: load_vector_element: operand is undefined
     %2:f32 = load_vector_element undef, 1i
                                  ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, LoadVectorElement_NullIndex) {
@@ -817,11 +814,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:4:38 error: load_vector_element: operand is undefined
     %3:f32 = load_vector_element %2, undef
                                      ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, LoadVectorElement_MissingResult) {
@@ -837,11 +834,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:4:13 error: load_vector_element: expected exactly 1 results, got 0
     undef = load_vector_element %2, 1i
             ^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, LoadVectorElement_MissingOperands) {
@@ -857,11 +854,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:4:14 error: load_vector_element: expected exactly 2 operands, got 0
     %3:f32 = load_vector_element
              ^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, StoreVectorElement_NullTo) {
@@ -875,11 +872,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:26 error: store_vector_element: operand is undefined
     store_vector_element undef, 1i, 2.0f
                          ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, StoreVectorElement_NullIndex) {
@@ -894,11 +891,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:4:30 error: store_vector_element: operand is undefined
     store_vector_element %2, undef, 2.0f
                              ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, StoreVectorElement_NullValue) {
@@ -913,11 +910,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:4:34 error: store_vector_element: operand is undefined
     store_vector_element %2, 1i, undef
                                  ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, StoreVectorElement_MissingOperands) {
@@ -933,11 +930,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:4:5 error: store_vector_element: expected exactly 3 operands, got 0
     store_vector_element
     ^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, StoreVectorElement_UnexpectedResult) {
@@ -953,11 +950,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:4:5 error: store_vector_element: expected exactly 0 results, got 1
     store_vector_element %2, 1i, 2.0f
     ^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_MissingValue) {
@@ -971,11 +968,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:20 error: swizzle: expected exactly 1 operands, got 0
     %4:vec4<f32> = swizzle undef, wzyx
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_NullValue) {
@@ -989,9 +986,8 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(error: swizzle: operand is undefined
-)")) << res.Failure().reason.Str();
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(error: swizzle: operand is undefined
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_MissingResult) {
@@ -1005,11 +1001,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:13 error: swizzle: expected exactly 1 results, got 0
     undef = swizzle %3, wzyx
             ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_NullResult) {
@@ -1023,11 +1019,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:5:5 error: swizzle: result is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:5:5 error: swizzle: result is undefined
     undef = swizzle %3, wzyx
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_NoIndices) {
@@ -1042,11 +1037,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:20 error: swizzle: expected at least 1 indices
     %4:vec4<f32> = swizzle %3,
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_TooManyIndices) {
@@ -1061,11 +1056,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:20 error: swizzle: expected at most 4 indices
     %4:vec4<f32> = swizzle %3, yyyyy
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_InvalidIndices) {
@@ -1080,11 +1075,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:20 error: swizzle: invalid index value
     %4:vec4<f32> = swizzle %3, wzy
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_NotVector) {
@@ -1097,12 +1092,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:5:20 error: swizzle: object of swizzle, %3, is not a vector, 'f32'
     %4:vec4<f32> = swizzle %3, wzyx
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_TooSmallResult) {
@@ -1116,12 +1111,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:20 error: swizzle: result type 'vec2<f32>' does not match expected type, 'vec4<f32>'
     %4:vec2<f32> = swizzle %3, wzyx
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_TooLargeResult) {
@@ -1135,12 +1130,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:20 error: swizzle: result type 'vec4<f32>' does not match expected type, 'vec2<f32>'
     %4:vec4<f32> = swizzle %3, wz
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_WrongTypeResult) {
@@ -1154,12 +1149,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(5:20 error: swizzle: result type 'vec2<u32>' does not match expected type, 'vec2<f32>'
     %4:vec2<u32> = swizzle %3, wz
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Swizzle_OOBIndex) {
@@ -1172,11 +1167,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:20 error: swizzle: invalid index value
     %4:vec4<f32> = swizzle %3, wyyx
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/validator_bench.cc b/src/tint/lang/core/ir/validator_bench.cc
index 42449cb..b4e228b 100644
--- a/src/tint/lang/core/ir/validator_bench.cc
+++ b/src/tint/lang/core/ir/validator_bench.cc
@@ -45,7 +45,7 @@
 void ValidateIR(benchmark::State& state, std::string input_name) {
     auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
-        state.SkipWithError(res.Failure().reason.Str());
+        state.SkipWithError(res.Failure().reason);
         return;
     }
 
@@ -59,7 +59,7 @@
     for (auto _ : state) {
         auto val_res = Validate(ir.Get(), {});
         if (val_res != Success) {
-            state.SkipWithError(val_res.Failure().reason.Str());
+            state.SkipWithError(val_res.Failure().reason);
         }
     }
 }
diff --git a/src/tint/lang/core/ir/validator_builtin_test.cc b/src/tint/lang/core/ir/validator_builtin_test.cc
index d8a62e5..1cde5cc 100644
--- a/src/tint/lang/core/ir/validator_builtin_test.cc
+++ b/src/tint/lang/core/ir/validator_builtin_test.cc
@@ -53,11 +53,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: __point_size must be used in a vertex shader entry point
 %f = @fragment func():f32 [@__point_size] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_PointSize_WrongIODirection) {
@@ -69,11 +69,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:19 error: __point_size must be an output of a shader entry point
 %f = @vertex func(%size:f32 [@__point_size]):vec4<f32> [@position] {
                   ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_PointSize_WrongType) {
@@ -84,11 +84,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:6:1 error: __point_size must be a f32
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:6:1 error: __point_size must be a f32
 %f = @vertex func():OutputStruct {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_ClipDistances_WrongStage) {
@@ -100,11 +99,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: clip_distances must be used in a vertex shader entry point
 %f = @fragment func():array<f32, 2> [@clip_distances] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_ClipDistances_WrongIODirection) {
@@ -116,11 +115,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:19 error: clip_distances must be an output of a shader entry point
 %f = @vertex func(%distances:array<f32, 2> [@clip_distances]):vec4<f32> [@position] {
                   ^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_ClipDistances_WrongType) {
@@ -132,11 +131,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:6:1 error: clip_distances must be an array<f32, N>, where N <= 8
 %f = @vertex func():OutputStruct {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_FragDepth_WrongStage) {
@@ -148,11 +147,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:6:1 error: frag_depth must be used in a fragment shader entry point
 %f = @vertex func():OutputStruct {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_FragDepth_WrongIODirection) {
@@ -164,11 +163,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:21 error: frag_depth must be an output of a shader entry point
 %f = @fragment func(%depth:f32 [@frag_depth]):void {
                     ^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_FragDepth_WrongType) {
@@ -179,11 +178,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:1:1 error: frag_depth must be a f32
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:1 error: frag_depth must be a f32
 %f = @fragment func():u32 [@frag_depth] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_FrontFacing_WrongStage) {
@@ -194,12 +192,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:19 error: front_facing must be used in a fragment shader entry point
 %f = @vertex func(%facing:bool [@front_facing]):vec4<f32> [@position] {
                   ^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_FrontFacing_WrongIODirection) {
@@ -211,11 +209,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: front_facing must be an input of a shader entry point
 %f = @fragment func():bool [@front_facing] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_FrontFacing_WrongType) {
@@ -226,11 +224,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:1:21 error: front_facing must be a bool
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:21 error: front_facing must be a bool
 %f = @fragment func(%facing:u32 [@front_facing]):void {
                     ^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_GlobalInvocationId_WrongStage) {
@@ -242,12 +239,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:21 error: global_invocation_id must be used in a compute shader entry point
 %f = @fragment func(%invocation:vec3<u32> [@global_invocation_id]):void {
                     ^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_GlobalInvocationId_WrongIODirection) {
@@ -259,12 +256,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:1 error: global_invocation_id must be an input of a shader entry point
 %f = @compute @workgroup_size(1u, 1u, 1u) func():vec3<u32> [@global_invocation_id] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_GlobalInvocationId_WrongType) {
@@ -275,11 +272,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:48 error: global_invocation_id must be an vec3<u32>
 %f = @compute @workgroup_size(1u, 1u, 1u) func(%invocation:u32 [@global_invocation_id]):void {
                                                ^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_InstanceIndex_WrongStage) {
@@ -290,12 +287,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:21 error: instance_index must be used in a vertex shader entry point
 %f = @fragment func(%instance:u32 [@instance_index]):void {
                     ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_InstanceIndex_WrongIODirection) {
@@ -307,11 +304,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:6:1 error: instance_index must be an input of a shader entry point
 %f = @vertex func():OutputStruct {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_InstanceIndex_WrongType) {
@@ -322,11 +319,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:19 error: instance_index must be an u32
 %f = @vertex func(%instance:i32 [@instance_index]):vec4<f32> [@position] {
                   ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_LocalInvocationId_WrongStage) {
@@ -337,12 +334,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:21 error: local_invocation_id must be used in a compute shader entry point
 %f = @fragment func(%id:vec3<u32> [@local_invocation_id]):void {
                     ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_LocalInvocationId_WrongIODirection) {
@@ -354,12 +351,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:1 error: local_invocation_id must be an input of a shader entry point
 %f = @compute @workgroup_size(1u, 1u, 1u) func():vec3<u32> [@local_invocation_id] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_LocalInvocationId_WrongType) {
@@ -370,11 +367,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:48 error: local_invocation_id must be an vec3<u32>
 %f = @compute @workgroup_size(1u, 1u, 1u) func(%id:u32 [@local_invocation_id]):void {
                                                ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_LocalInvocationIndex_WrongStage) {
@@ -386,12 +383,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:21 error: local_invocation_index must be used in a compute shader entry point
 %f = @fragment func(%index:u32 [@local_invocation_index]):void {
                     ^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_LocalInvocationIndex_WrongIODirection) {
@@ -403,12 +400,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:1 error: local_invocation_index must be an input of a shader entry point
 %f = @compute @workgroup_size(1u, 1u, 1u) func():u32 [@local_invocation_index] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_LocalInvocationIndex_WrongType) {
@@ -419,11 +416,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:48 error: local_invocation_index must be an u32
 %f = @compute @workgroup_size(1u, 1u, 1u) func(%index:i32 [@local_invocation_index]):void {
                                                ^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_NumWorkgroups_WrongStage) {
@@ -434,12 +431,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:21 error: num_workgroups must be used in a compute shader entry point
 %f = @fragment func(%num:vec3<u32> [@num_workgroups]):void {
                     ^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_NumWorkgroups_WrongIODirection) {
@@ -452,11 +449,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: num_workgroups must be an input of a shader entry point
 %f = @compute @workgroup_size(1u, 1u, 1u) func():vec3<u32> [@num_workgroups] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_NumWorkgroups_WrongType) {
@@ -467,11 +464,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:48 error: num_workgroups must be an vec3<u32>
 %f = @compute @workgroup_size(1u, 1u, 1u) func(%num:u32 [@num_workgroups]):void {
                                                ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SampleIndex_WrongStage) {
@@ -482,12 +479,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:19 error: sample_index must be used in a fragment shader entry point
 %f = @vertex func(%index:u32 [@sample_index]):vec4<f32> [@position] {
                   ^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SampleIndex_WrongIODirection) {
@@ -499,11 +496,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: sample_index must be an input of a shader entry point
 %f = @fragment func():u32 [@sample_index] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SampleIndex_WrongType) {
@@ -514,11 +511,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:1:21 error: sample_index must be an u32
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:21 error: sample_index must be an u32
 %f = @fragment func(%index:f32 [@sample_index]):void {
                     ^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_VertexIndex_WrongStage) {
@@ -530,11 +526,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:21 error: vertex_index must be used in a vertex shader entry point
 %f = @fragment func(%index:u32 [@vertex_index]):void {
                     ^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_VertexIndex_WrongIODirection) {
@@ -546,11 +542,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:6:1 error: vertex_index must be an input of a shader entry point
 %f = @vertex func():OutputStruct {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_VertexIndex_WrongType) {
@@ -561,11 +557,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:1:19 error: vertex_index must be an u32
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:19 error: vertex_index must be an u32
 %f = @vertex func(%index:f32 [@vertex_index]):vec4<f32> [@position] {
                   ^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_WorkgroupId_WrongStage) {
@@ -577,11 +572,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(1:21 error: workgroup_id must be used in a compute shader entry point
 %f = @fragment func(%id:vec3<u32> [@workgroup_id]):void {
                     ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_WorkgroupId_WrongIODirection) {
@@ -594,11 +589,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: workgroup_id must be an input of a shader entry point
 %f = @compute @workgroup_size(1u, 1u, 1u) func():vec3<u32> [@workgroup_id] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_WorkgroupId_WrongType) {
@@ -609,11 +604,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:48 error: workgroup_id must be an vec3<u32>
 %f = @compute @workgroup_size(1u, 1u, 1u) func(%id:u32 [@workgroup_id]):void {
                                                ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_Position_WrongStage) {
@@ -624,12 +619,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:48 error: position must be used in a fragment or vertex shader entry point
 %f = @compute @workgroup_size(1u, 1u, 1u) func(%pos:vec4<f32> [@position]):void {
                                                ^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_Position_WrongIODirectionForVertex) {
@@ -641,11 +636,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:19 error: position must be an output for a vertex entry point
 %f = @vertex func(%pos:vec4<f32> [@position]):vec4<f32> [@position] {
                   ^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_Position_WrongIODirectionForFragment) {
@@ -657,11 +652,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: position must be an input for a fragment entry point
 %f = @fragment func():vec4<f32> [@position] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_Position_WrongType) {
@@ -672,11 +667,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:21 error: position must be an vec4<f32>
 %f = @fragment func(%pos:f32 [@position]):void {
                     ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SampleMask_WrongStage) {
@@ -688,11 +683,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:19 error: sample_mask must be used in a fragment entry point
 %f = @vertex func(%mask:u32 [@sample_mask]):vec4<f32> [@position] {
                   ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SampleMask_InputValid) {
@@ -723,11 +718,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:1:21 error: sample_mask must be an u32
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:21 error: sample_mask must be an u32
 %f = @fragment func(%mask:f32 [@sample_mask]):void {
                     ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SubgroupSize_WrongStage) {
@@ -739,12 +733,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:19 error: subgroup_size must be used in a compute or fragment shader entry point
 %f = @vertex func(%size:u32 [@subgroup_size]):vec4<f32> [@position] {
                   ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SubgroupSize_WrongIODirection) {
@@ -756,11 +750,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: subgroup_size must be an input of a shader entry point
 %f = @fragment func():u32 [@subgroup_size] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SubgroupSize_WrongType) {
@@ -771,11 +765,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:48 error: subgroup_size must be an u32
 %f = @compute @workgroup_size(1u, 1u, 1u) func(%size:i32 [@subgroup_size]):void {
                                                ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SubgroupInvocationId_WrongStage) {
@@ -787,12 +781,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:19 error: subgroup_invocation_id must be used in a compute or fragment shader entry point
 %f = @vertex func(%id:u32 [@subgroup_invocation_id]):vec4<f32> [@position] {
                   ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SubgroupInvocationId_WrongIODirection) {
@@ -803,12 +797,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:1 error: subgroup_invocation_id must be an input of a shader entry point
 %f = @fragment func():u32 [@subgroup_invocation_id] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Builtin_SubgroupInvocationId_WrongType) {
@@ -819,11 +813,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:48 error: subgroup_invocation_id must be an u32
 %f = @compute @workgroup_size(1u, 1u, 1u) func(%id:i32 [@subgroup_invocation_id]):void {
                                                ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Bitcast_MissingArg) {
@@ -836,11 +830,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:14 error: bitcast: expected exactly 1 operands, got 0
     %2:i32 = bitcast
              ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Bitcast_NullArg) {
@@ -852,11 +846,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:22 error: bitcast: operand is undefined
     %2:i32 = bitcast undef
                      ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Bitcast_MissingResult) {
@@ -869,11 +863,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:13 error: bitcast: expected exactly 1 results, got 0
     undef = bitcast 1u
             ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Bitcast_NullResult) {
@@ -886,11 +880,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: bitcast: result is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: bitcast: result is undefined
     undef = bitcast 1u
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 namespace {
@@ -925,7 +918,7 @@
     } else {
         ASSERT_NE(res, Success) << "Bitcast should NOT be defined for '" << src_ty->FriendlyName()
                                 << "' -> '" << dest_ty->FriendlyName() << "'";
-        EXPECT_THAT(res.Failure().reason.Str(), testing::HasSubstr("bitcast is not defined"));
+        EXPECT_THAT(res.Failure().reason, testing::HasSubstr("bitcast is not defined"));
     }
 }
 
diff --git a/src/tint/lang/core/ir/validator_call_test.cc b/src/tint/lang/core/ir/validator_call_test.cc
index b4e2ca9..25da335 100644
--- a/src/tint/lang/core/ir/validator_call_test.cc
+++ b/src/tint/lang/core/ir/validator_call_test.cc
@@ -60,11 +60,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:20 error: call: %g is not part of the module
     %2:void = call %g
                    ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToEntryPointFunction) {
@@ -79,11 +79,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:20 error: call: call target must not have a pipeline stage
     %2:void = call %g
                    ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToFunctionTooFewArguments) {
@@ -99,12 +99,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:8:20 error: call: function has 2 parameters, but call provides 1 arguments
     %5:void = call %g, 42i
                    ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToFunctionTooManyArguments) {
@@ -120,12 +120,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:8:20 error: call: function has 2 parameters, but call provides 3 arguments
     %5:void = call %g, 1i, 2i, 3i
                    ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToFunctionWrongArgType) {
@@ -142,12 +142,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:8:28 error: call: type 'i32' of function parameter 1 does not match argument type 'f32'
     %6:void = call %g, 1i, 2.0f, 3i
                            ^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToFunctionNullArg) {
@@ -163,11 +163,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:8:24 error: call: operand is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:8:24 error: call: operand is undefined
     %4:void = call %g, undef
                        ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToNullFunction) {
@@ -183,11 +182,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:8:20 error: call: operand is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:8:20 error: call: operand is undefined
     %3:void = call undef
                    ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToFunctionNoResult) {
@@ -203,11 +201,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:8:13 error: call: expected exactly 1 results, got 0
     undef = call %g
             ^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToFunctionNoOperands) {
@@ -223,11 +221,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:8:15 error: call: expected at least 1 operands, got 0
     %3:void = call
               ^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToNonFunctionTarget) {
@@ -244,11 +242,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:20 error: call: target not defined or not a function
     %2:void = call 0i
                    ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToBuiltin_MissingResult) {
@@ -261,11 +259,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: abs: result is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: abs: result is undefined
     undef = abs 1.0f
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToBuiltin_MismatchResultType) {
@@ -279,12 +276,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:14 error: abs: call result type 'i32' does not match builtin return type 'f32'
     %2:i32 = abs 1.0f
              ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToBuiltin_MissingArg) {
@@ -300,11 +297,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:5:18 error: abs: operand is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:5:18 error: abs: operand is undefined
     %4:f32 = abs undef
                  ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToBuiltin_OutOfScopeArg) {
@@ -321,11 +317,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:5:18 error: abs: %5 is not in scope
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:5:18 error: abs: %5 is not in scope
     %4:f32 = abs %5
                  ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToBuiltin_TooFewResults) {
@@ -341,11 +336,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:13 error: abs: expected exactly 1 results, got 0
     undef = abs %3
             ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToBuiltin_TooManyResults) {
@@ -361,11 +356,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:14 error: abs: expected exactly 1 results, got 2
     %3:f32 = abs %3
              ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToBuiltin_TooFewArgs) {
@@ -381,9 +376,9 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:14 error: abs: no matching call to 'abs()')"))
-        << res.Failure().reason.Str();
+        << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, CallToBuiltin_TooManyArgs) {
@@ -399,9 +394,9 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:14 error: abs: no matching call to 'abs(f32, f32)')"))
-        << res.Failure().reason.Str();
+        << res.Failure().reason;
 }
 
 // Test that a user declared structure cannot be used as the result type for a frexp builtin, even
@@ -423,12 +418,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(error: frexp: call result type '__frexp_result_f32' does not match builtin return type '__frexp_result_f32'
     %2:__frexp_result_f32 = frexp 1.0f
                             ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 // Test that a user declared structure cannot be used as the result type for a modf builtin, even
@@ -450,12 +445,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(error: modf: call result type '__modf_result_vec4_f32' does not match builtin return type '__modf_result_vec4_f32'
     %2:__modf_result_vec4_f32 = modf vec4<f32>(1.0f)
                                 ^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 // Test that a user declared structure cannot be used as the result type for an
@@ -480,12 +475,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(error: atomicCompareExchangeWeak: call result type '__atomic_compare_exchange_result_u32' does not match builtin return type '__atomic_compare_exchange_result_u32'
     %3:__atomic_compare_exchange_result_u32 = atomicCompareExchangeWeak %a, 0u, 1u
                                               ^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/validator_flow_control_test.cc b/src/tint/lang/core/ir/validator_flow_control_test.cc
index bf1781d..80e21db 100644
--- a/src/tint/lang/core/ir/validator_flow_control_test.cc
+++ b/src/tint/lang/core/ir/validator_flow_control_test.cc
@@ -62,11 +62,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: discard: expected exactly 0 operands, got 1
     discard
     ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Discard_TooManyResults) {
@@ -85,11 +85,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: discard: expected exactly 0 results, got 1
     discard
     ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Discard_RootBlock) {
@@ -97,12 +97,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:3 error: discard: root block: invalid instruction: tint::core::ir::Discard
   discard
   ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Discard_NotInFragment) {
@@ -122,11 +122,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:5 error: discard: cannot be called in non-fragment end point
     discard
     ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Terminate_RootBlock) {
@@ -137,12 +137,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:3 error: terminate_invocation: root block: invalid instruction: tint::core::ir::TerminateInvocation
   terminate_invocation
   ^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Terminate_MissingResult) {
@@ -155,11 +155,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:5 error: terminate_invocation: expected exactly 0 results, got 1
     terminate_invocation
     ^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Block_TerminatorInMiddle) {
@@ -172,11 +172,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: return: must be the last instruction in the block
     ret
     ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, If_RootBlock) {
@@ -187,11 +187,11 @@
     auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:2:3 error: if: root block: invalid instruction: tint::core::ir::If
   if true [t: $B2] {  # if_1
   ^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, If_EmptyFalse) {
@@ -218,11 +218,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:4:7 error: block does not end in a terminator instruction
       $B2: {  # true
       ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, If_ConditionIsBool) {
@@ -237,11 +237,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:8 error: if: condition type must be 'bool'
     if 1i [t: $B2, f: $B3] {  # if_1
        ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, If_ConditionIsNullptr) {
@@ -256,11 +256,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:8 error: if: operand is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:8 error: if: operand is undefined
     if undef [t: $B2, f: $B3] {  # if_1
        ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, If_NullResult) {
@@ -277,11 +276,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: if: result is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: if: result is undefined
     undef = if true [t: $B2, f: $B3] {  # if_1
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Loop_RootBlock) {
@@ -291,12 +289,12 @@
 
     auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:3 error: loop: root block: invalid instruction: tint::core::ir::Loop
   loop [b: $B2] {  # loop_1
   ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Loop_OnlyBody) {
@@ -321,11 +319,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:4:7 error: block does not end in a terminator instruction
       $B2: {  # body
       ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Switch_RootBlock) {
@@ -336,12 +334,12 @@
 
     auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:3 error: switch: root block: invalid instruction: tint::core::ir::Switch
   switch 1i [c: (default, $B2)] {  # switch_1
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitIf) {
@@ -368,11 +366,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:9 error: exit_if: has no parent control instruction
         exit_if  # undef
         ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitIf_LessOperandsThenIfParams) {
@@ -391,11 +389,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:5:9 error: exit_if: provides 1 value but 'if' expects 2 values
         exit_if 1i  # if_1
         ^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitIf_MoreOperandsThenIfParams) {
@@ -414,11 +412,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:5:9 error: exit_if: provides 3 values but 'if' expects 2 values
         exit_if 1i, 2.0f, 3i  # if_1
         ^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitIf_WithResult) {
@@ -454,12 +452,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:21 error: exit_if: operand with type 'i32' does not match 'if' target type 'f32'
         exit_if 1i, 2i  # if_1
                     ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitIf_NotInParentIf) {
@@ -474,11 +472,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:8:5 error: exit_if: found outside all control instructions
     exit_if  # if_1
     ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitIf_InvalidJumpsOverIf) {
@@ -502,11 +500,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:7:13 error: exit_if: if target jumps over other control instructions
             exit_if  # if_1
             ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitIf_InvalidJumpOverSwitch) {
@@ -531,11 +529,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:7:13 error: exit_if: if target jumps over other control instructions
             exit_if  # if_1
             ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitIf_InvalidJumpOverLoop) {
@@ -559,11 +557,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:7:13 error: exit_if: if target jumps over other control instructions
             exit_if  # if_1
             ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitSwitch) {
@@ -594,11 +592,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:9 error: exit_switch: has no parent control instruction
         exit_switch  # undef
         ^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitSwitch_LessOperandsThenSwitchParams) {
@@ -618,12 +616,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:5:9 error: exit_switch: provides 1 value but 'switch' expects 2 values
         exit_switch 1i  # switch_1
         ^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitSwitch_MoreOperandsThenSwitchParams) {
@@ -642,12 +640,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:5:9 error: exit_switch: provides 3 values but 'switch' expects 2 values
         exit_switch 1i, 2.0f, 3i  # switch_1
         ^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitSwitch_WithResult) {
@@ -685,12 +683,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:25 error: exit_switch: operand with type 'i32' does not match 'switch' target type 'f32'
         exit_switch 1i, 2i  # switch_1
                         ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitSwitch_NotInParentSwitch) {
@@ -710,12 +708,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:10:9 error: exit_switch: switch not found in parent control instructions
         exit_switch  # switch_1
         ^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitSwitch_JumpsOverIfs) {
@@ -772,7 +770,7 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:7:13 error: exit_switch: switch target jumps over other control instructions
             exit_switch  # switch_1
@@ -785,7 +783,7 @@
 :5:9 note: first control instruction jumped
         switch 0i [c: (default, $B3)] {  # switch_2
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitSwitch_InvalidJumpOverLoop) {
@@ -807,7 +805,7 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:7:13 error: exit_switch: switch target jumps over other control instructions
             exit_switch  # switch_1
@@ -820,7 +818,7 @@
 :5:9 note: first control instruction jumped
         loop [b: $B3] {  # loop_1
         ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Continue_OutsideOfLoop) {
@@ -833,11 +831,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:8:5 error: continue: called outside of associated loop
     continue  # -> $B3
     ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Continue_InLoopInit) {
@@ -851,11 +849,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:9 error: continue: must only be called from loop body
         continue  # -> $B4
         ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Continue_InLoopBody) {
@@ -881,11 +879,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:8:9 error: continue: must only be called from loop body
         continue  # -> $B3
         ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Continue_UnexpectedValues) {
@@ -899,12 +897,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:5:9 error: continue: provides 2 values but 'loop' block $B3 expects 0 values
         continue 1i, 2.0f  # -> $B3
         ^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Continue_MissingValues) {
@@ -919,12 +917,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:5:9 error: continue: provides 0 values but 'loop' block $B3 expects 2 values
         continue  # -> $B3
         ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Continue_MismatchedInt) {
@@ -941,12 +939,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:22 error: continue: operand with type 'i32' does not match 'loop' block $B3 target type 'f32'
         continue 1i, 2i, 3u, false  # -> $B3
                      ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Continue_MismatchedFloat) {
@@ -963,12 +961,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:28 error: continue: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
         continue 1i, 2.0f, 3.0f, false  # -> $B3
                            ^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Continue_MatchedTypes) {
@@ -996,11 +994,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:8:5 error: next_iteration: called outside of associated loop
     next_iteration  # -> $B2
     ^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, NextIteration_InLoopInit) {
@@ -1027,12 +1025,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:9 error: next_iteration: must only be called from loop initializer or continuing
         next_iteration  # -> $B2
         ^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, NextIteration_InLoopContinuing) {
@@ -1060,12 +1058,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:9 error: next_iteration: provides 2 values but 'loop' block $B3 expects 0 values
         next_iteration 1i, 2.0f  # -> $B3
         ^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, NextIteration_MissingValues) {
@@ -1081,12 +1079,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:9 error: next_iteration: provides 0 values but 'loop' block $B3 expects 2 values
         next_iteration  # -> $B3
         ^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, NextIteration_MismatchedInt) {
@@ -1103,12 +1101,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:28 error: next_iteration: operand with type 'i32' does not match 'loop' block $B3 target type 'f32'
         next_iteration 1i, 2i, 3u, false  # -> $B3
                            ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, NextIteration_MismatchedFloat) {
@@ -1125,12 +1123,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:34 error: next_iteration: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
         next_iteration 1i, 2.0f, 3.0f, false  # -> $B3
                                  ^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, NextIteration_MatchedTypes) {
@@ -1159,12 +1157,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:3:5 error: loop: loop with body block parameters must have an initializer
     loop [b: $B2] {  # loop_1
     ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ContinuingUseValueBeforeContinue) {
@@ -1208,12 +1206,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:14:24 error: let: %value cannot be used in continuing block as it is declared after the first 'continue' in the loop's body
         %use:i32 = let %value
                        ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, BreakIf_NextIterUnexpectedValues) {
@@ -1227,12 +1225,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:8:9 error: break_if: provides 2 values but 'loop' block $B2 expects 0 values
         break_if true next_iteration: [ 1i, 2i ]  # -> [t: exit_loop loop_1, f: $B2]
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, BreakIf_NextIterMissingValues) {
@@ -1249,12 +1247,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:11:9 error: break_if: provides 0 values but 'loop' block $B3 expects 2 values
         break_if true  # -> [t: exit_loop loop_1, f: $B3]
         ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, BreakIf_NextIterMismatchedInt) {
@@ -1274,12 +1272,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:11:45 error: break_if: operand with type 'i32' does not match 'loop' block $B3 target type 'f32'
         break_if true next_iteration: [ 1i, 2i, 3u, false ]  # -> [t: exit_loop loop_1, f: $B3]
                                             ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, BreakIf_NextIterMismatchedFloat) {
@@ -1299,12 +1297,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:11:51 error: break_if: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
         break_if true next_iteration: [ 1i, 2.0f, 3.0f, false ]  # -> [t: exit_loop loop_1, f: $B3]
                                                   ^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, BreakIf_NextIterMatchedTypes) {
@@ -1337,11 +1335,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:8:9 error: break_if: provides 2 values but 'loop' expects 0 values
         break_if true exit_loop: [ 1i, 2i ]  # -> [t: exit_loop loop_1, f: $B2]
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, BreakIf_ExitMissingValues) {
@@ -1357,11 +1355,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:8:9 error: break_if: provides 0 values but 'loop' expects 2 values
         break_if true  # -> [t: exit_loop loop_1, f: $B2]
         ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, BreakIf_ExitMismatchedInt) {
@@ -1379,12 +1377,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:8:40 error: break_if: operand with type 'i32' does not match 'loop' target type 'f32'
         break_if true exit_loop: [ 1i, 2i, 3u, false ]  # -> [t: exit_loop loop_1, f: $B2]
                                        ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, BreakIf_ExitMismatchedTypes) {
@@ -1402,12 +1400,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:8:46 error: break_if: operand with type 'f32' does not match 'loop' target type 'u32'
         break_if true exit_loop: [ 1i, 2.0f, 3.0f, false ]  # -> [t: exit_loop loop_1, f: $B2]
                                              ^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, BreakIf_ExitMatchedTypes) {
@@ -1452,11 +1450,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:9 error: exit_loop: has no parent control instruction
         exit_loop  # undef
         ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_LessOperandsThenLoopParams) {
@@ -1476,11 +1474,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:5:9 error: exit_loop: provides 1 value but 'loop' expects 2 values
         exit_loop 1i  # loop_1
         ^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_MoreOperandsThenLoopParams) {
@@ -1500,11 +1498,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:5:9 error: exit_loop: provides 3 values but 'loop' expects 2 values
         exit_loop 1i, 2.0f, 3i  # loop_1
         ^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_WithResult) {
@@ -1542,12 +1540,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:23 error: exit_loop: operand with type 'i32' does not match 'loop' target type 'f32'
         exit_loop 1i, 2i  # loop_1
                       ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_NotInParentLoop) {
@@ -1567,11 +1565,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:13:9 error: exit_loop: loop not found in parent control instructions
         exit_loop  # loop_1
         ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_JumpsOverIfs) {
@@ -1627,12 +1625,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:7:13 error: exit_loop: loop target jumps over other control instructions
             exit_loop  # loop_1
             ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_InvalidJumpOverLoop) {
@@ -1655,12 +1653,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:7:13 error: exit_loop: loop target jumps over other control instructions
             exit_loop  # loop_1
             ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_InvalidInsideContinuing) {
@@ -1678,11 +1676,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:8:9 error: exit_loop: loop exit jumps out of continuing block
         exit_loop  # loop_1
         ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_InvalidInsideContinuingNested) {
@@ -1706,11 +1704,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:10:13 error: exit_loop: loop exit jumps out of continuing block
             exit_loop  # loop_1
             ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_InvalidInsideInitializer) {
@@ -1731,11 +1729,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:5:9 error: exit_loop: loop exit not permitted in loop initializer
         exit_loop  # loop_1
         ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_InvalidInsideInitializerNested) {
@@ -1760,11 +1758,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:7:13 error: exit_loop: loop exit not permitted in loop initializer
             exit_loop  # loop_1
             ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Return) {
@@ -1796,11 +1794,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: return: expected exactly 0 results, got 1
     ret 42i
     ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Return_NotFunction) {
@@ -1813,11 +1811,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:4:5 error: return: expected function for first operand
     ret
     ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Return_MissingFunction) {
@@ -1829,11 +1827,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: return: expected between 1 and 2 operands, got 0
     ret
     ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Return_UnexpectedValue) {
@@ -1844,11 +1842,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: return: unexpected return value
     ret 42i
     ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Return_MissingValue) {
@@ -1859,11 +1857,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: return: expected return value
     ret
     ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Return_WrongValueType) {
@@ -1875,12 +1873,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:5 error: return: return value type 'f32' does not match function return type 'i32'
     ret 42.0f
     ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Return_RootBlock) {
@@ -1890,12 +1888,12 @@
     mod.root_block->Append(b.Return(f));
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:3 error: return: root block: invalid instruction: tint::core::ir::Return
   ret
   ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Return_MissingResult) {
@@ -1907,11 +1905,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: return: expected exactly 0 results, got 1
     ret
     ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Unreachable_UnexpectedResult) {
@@ -1923,11 +1921,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: unreachable: expected exactly 0 results, got 1
     unreachable
     ^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Unreachable_UnexpectedOperand) {
@@ -1939,11 +1937,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: unreachable: expected exactly 0 operands, got 1
     unreachable
     ^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Unreachable_RootBlock) {
@@ -1954,12 +1952,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:3 error: unreachable: root block: invalid instruction: tint::core::ir::Unreachable
   unreachable
   ^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Unreachable_MissingResult) {
@@ -1970,11 +1968,11 @@
     });
 
     auto res = ir::Validate(mod);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: unreachable: expected exactly 0 results, got 1
     unreachable
     ^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Switch_ConditionPointer) {
@@ -1989,10 +1987,10 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(error: switch: condition type 'ptr<function, i32, read_write>' must be an integer scalar
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Switch_NoCases) {
@@ -2005,11 +2003,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: switch: missing default case for switch
     switch 1i [] {  # switch_1
     ^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Switch_NoDefaultCase) {
@@ -2023,11 +2021,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: switch: missing default case for switch
     switch 1i [c: (0i, $B2)] {  # switch_1
     ^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Switch_NoCondition) {
@@ -2040,9 +2038,8 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(error: switch: operand is undefined
-)")) << res.Failure().reason.Str();
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(error: switch: operand is undefined
+)")) << res.Failure().reason;
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/validator_function_test.cc b/src/tint/lang/core/ir/validator_function_test.cc
index e4af8e2..42854b6 100644
--- a/src/tint/lang/core/ir/validator_function_test.cc
+++ b/src/tint/lang/core/ir/validator_function_test.cc
@@ -71,11 +71,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:6:1 error: functions must have type '<function>'
 %invalid = func():void {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Duplicate) {
@@ -88,11 +88,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: function %my_func added to module multiple times
 %my_func = func(%2:i32, %3:f32):void {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_DuplicateEntryPointNames) {
@@ -104,11 +104,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:6:1 error: entry point name 'dup' is not unique
 %dup_1 = @fragment func():void {  # %dup_1: 'dup'
 ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_MultinBlock) {
@@ -118,11 +118,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: root block for function cannot be a multi-in block
 %my_func = func():void {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_DeadParameter) {
@@ -136,11 +136,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:17 error: destroyed parameter found in function parameter list
 %my_func = func(%my_param:f32):void {
                 ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_ParameterWithNullFunction) {
@@ -153,11 +153,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:17 error: function parameter has nullptr parent function
 %my_func = func(%my_param:f32):void {
                 ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_ParameterUsedInMultipleFunctions) {
@@ -171,11 +171,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:18 error: function parameter has incorrect parent function
 %my_func1 = func(%my_param:f32):void {
                  ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_ParameterWithNullType) {
@@ -186,11 +186,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:17 error: function parameter has nullptr type
 %my_func = func(%my_param:undef):void {
                 ^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_ParameterDuplicated) {
@@ -201,11 +201,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:17 error: function parameter is not unique
 %my_func = func(%my_param:u32%my_param:u32):void {
                 ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Param_MultipleIOAnnotations) {
@@ -221,12 +221,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:27 error: input param has more than one IO annotation, [ @location, built-in ]
 %my_func = @fragment func(%my_param:vec4<f32> [@location(0), @position]):void {
                           ^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Param_Struct_MultipleIOAnnotations) {
@@ -247,12 +247,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:27 error: input param struct member has more than one IO annotation, [ built-in, @color ]
 %my_func = @fragment func(%my_param:MyStruct):void {
                           ^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Param_MissingIOAnnotations) {
@@ -266,12 +266,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:27 error: input param must have at least one IO annotation, e.g. a binding point, a location, etc
 %my_func = @fragment func(%my_param:vec4<f32>):void {
                           ^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Param_Struct_MissingIOAnnotations) {
@@ -289,12 +289,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:54 error: input param struct members must have at least one IO annotation, e.g. a binding point, a location, etc
 %my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct):void {
                                                      ^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Param_Struct_DuplicateAnnotations) {
@@ -314,12 +314,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:54 error: input param struct member has same IO annotation, as top-level struct, '@location'
 %my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct [@location(0)]):void {
                                                      ^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Param_WorkgroupPlusOtherIOAnnotation) {
@@ -333,12 +333,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:54 error: input param has more than one IO annotation, [ @location, <workgroup> ]
 %my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:ptr<workgroup, i32, read_write> [@location(0)]):void {
                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Param_Struct_WorkgroupPlusOtherIOAnnotations) {
@@ -357,12 +357,12 @@
     auto res = ir::Validate(mod, Capabilities{Capability::kAllowPointersAndHandlesInStructures});
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:54 error: input param struct member has more than one IO annotation, [ @location, <workgroup> ]
 %my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct):void {
                                                      ^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_ParameterWithConstructibleType) {
@@ -414,12 +414,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:17 error: function parameter type, 'void', must be constructible, a pointer, or a handle
 %my_func = func(%my_param:void):void {
                 ^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Param_InvariantWithPosition) {
@@ -447,12 +447,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:17 error: invariant can only decorate a param iff it is also decorated with position
 %my_func = func(%my_param:vec4<f32> [@invariant]):void {
                 ^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Param_Struct_InvariantWithPosition) {
@@ -492,12 +492,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:17 error: invariant can only decorate a param member iff it is also decorated with position
 %my_func = func(%my_param:MyStruct):void {
                 ^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Param_BindingPointWithoutCapability) {
@@ -510,12 +510,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:17 error: input param to non-entry point function has a binding point set
 %my_func = func(%my_param:ptr<uniform, i32, read> [@binding_point(0, 0)]):void {
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Return_MultipleIOAnnotations) {
@@ -527,12 +527,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:1 error: return values has more than one IO annotation, [ @location, built-in ]
 %my_func = @vertex func():vec4<f32> [@location(0), @position] {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Return_Struct_MultipleIOAnnotations) {
@@ -549,12 +549,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:1 error: return values struct member has more than one IO annotation, [ @location, built-in ]
 %my_func = @vertex func():MyStruct {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Return_Void_IOAnnotation) {
@@ -565,11 +565,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: return values with void type should never be annotated
 %f = @fragment func():void [@location(0)] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Return_NonVoid_MissingIOAnnotations) {
@@ -580,12 +580,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:1 error: return values must have at least one IO annotation, e.g. a binding point, a location, etc
 %my_func = @fragment func():f32 {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Return_NonVoid_Struct_MissingIOAnnotations) {
@@ -599,12 +599,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:1 error: return values struct members must have at least one IO annotation, e.g. a binding point, a location, etc
 %my_func = @fragment func():MyStruct {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Return_InvariantWithPosition) {
@@ -627,12 +627,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:1 error: invariant can only decorate outputs iff they are also position builtins
 %my_func = func():vec4<f32> [@invariant] {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Return_Struct_InvariantWithPosition) {
@@ -666,12 +666,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:1 error: invariant can only decorate output members iff they are also position builtins
 %my_func = func():MyStruct {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_UnnamedEntryPoint) {
@@ -682,11 +682,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:1:1 error: entry points must have names
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:1 error: entry points must have names
 %1 = @compute @workgroup_size(1u, 1u, 1u) func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 // Parameterizing these tests is very difficult/unreadable due to the the
@@ -698,11 +697,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: function return type must be constructible
 %1 = func():texture_external {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_Sampler) {
@@ -711,11 +710,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: function return type must be constructible
 %1 = func():sampler {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_RuntimeArray) {
@@ -724,11 +723,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: function return type must be constructible
 %1 = func():array<f32> {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_Ptr) {
@@ -737,11 +736,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: function return type must be constructible
 %1 = func():ptr<function, i32, read_write> {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_Ref) {
@@ -750,11 +749,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: function return type must be constructible
 %1 = func():ref<function, u32, read_write> {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Compute_NonVoidReturn) {
@@ -766,12 +765,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:1 error: compute entry point must not have a return type, found 'f32'
 %my_func = @compute @workgroup_size(1u, 1u, 1u) func():f32 [@location(0)] {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_WorkgroupSize_MissingOnCompute) {
@@ -780,11 +779,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: compute entry point requires @workgroup_size
 %f = @compute func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_WorkgroupSize_NonCompute) {
@@ -795,11 +794,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: @workgroup_size only valid on compute entry point
 %f = @fragment @workgroup_size(1u, 1u, 1u) func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_WorkgroupSize_ParamUndefined) {
@@ -811,11 +810,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: a @workgroup_size param is undefined or missing a type
 %f = @compute @workgroup_size(undef, 2u, 3u) func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_WorkgroupSize_ParamWrongType) {
@@ -826,12 +825,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:1 error: @workgroup_size params must be an 'i32' or 'u32', received 'f32'
 %f = @compute @workgroup_size(1.0f, 2u, 3u) func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_WorkgroupSize_ParamsSameType) {
@@ -843,11 +842,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: @workgroup_size params must be all 'i32's or all 'u32's
 %f = @compute @workgroup_size(1u, 2i, 3u) func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_WorkgroupSize_ParamsTooSmall) {
@@ -858,11 +857,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: @workgroup_size params must be greater than 0
 %f = @compute @workgroup_size(-1i, 2i, 3i) func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_WorkgroupSize_OverrideWithoutAllowOverrides) {
@@ -875,12 +874,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:1 error: @workgroup_size param is not a constant value, and IR capability 'kAllowOverrides' is not set
 %f = @compute @workgroup_size(%2, %2, %2) func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_WorkgroupSize_NonRootBlockOverride) {
@@ -895,11 +894,11 @@
     auto res = ir::Validate(mod, Capabilities{Capability::kAllowOverrides});
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: @workgroup_size param defined by non-module scope value
 %f = @compute @workgroup_size(%2, %2, %2) func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Vertex_BasicPosition) {
@@ -966,11 +965,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:5:1 error: position must be declared for vertex entry point output
 %my_func = @vertex func():MyStruct {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Vertex_MissingPosition) {
@@ -982,11 +981,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:1:1 error: position must be declared for vertex entry point output
 %my_func = @vertex func():vec4<f32> [@location(0)] {
 ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_NonFragment_BoolInput) {
@@ -998,12 +997,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:1:19 error: entry point params can only be a bool for fragment shaders
 %f = @vertex func(%invalid:bool [@location(0)]):vec4<f32> [@position] {
                   ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_NonFragment_BoolOutput) {
@@ -1015,11 +1014,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:6:1 error: entry point return members can not be 'bool'
 %f = @vertex func():OutputStruct {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Fragment_BoolInputWithoutFrontFacing) {
@@ -1032,12 +1031,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:1:21 error: fragment entry point params can only be a bool if decorated with @builtin(front_facing)
 %f = @fragment func(%invalid:bool [@location(0)]):void {
                     ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_Fragment_BoolOutput) {
@@ -1049,11 +1048,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:1:1 error: entry point returns can not be 'bool'
 %f = @fragment func():bool [@location(0)] {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_BoolOutput_via_MSV) {
@@ -1074,12 +1073,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:1 error: IO address space values referenced by shader entry points can only be 'bool' if in the input space, used only by fragment shaders and decorated with @builtin(front_facing)
 %f = @compute @workgroup_size(1u, 1u, 1u) func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Function_BoolInputWithoutFrontFacing_via_MSV) {
@@ -1101,12 +1100,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:5:1 error: input address space values referenced by fragment shaders can only be 'bool' if decorated with @builtin(front_facing)
 %f = @fragment func():void {
 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 66b757b..67b9cc7 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -123,12 +123,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:3 error: loop: root block: invalid instruction: tint::core::ir::Loop
   loop [b: $B2] {  # loop_1
   ^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, RootBlock_Let) {
@@ -137,11 +137,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:2:12 error: let: root block: invalid instruction: tint::core::ir::Let
   %a:f32 = let 1.0f
            ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, RootBlock_LetWithAllowModuleScopeLets) {
@@ -157,12 +157,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:18 error: construct: root block: invalid instruction: tint::core::ir::Construct
   %1:vec2<f32> = construct 1.0f, 2.0f
                  ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, RootBlock_ConstructWithAllowModuleScopeLets) {
@@ -183,12 +183,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:38 error: var: instruction in root block does not have root block as parent
   %1:ptr<private, i32, read_write> = var undef
                                      ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Construct_Struct_ZeroValue) {
@@ -254,12 +254,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:8:19 error: construct: structure has 2 members, but construct provides 1 arguments
     %2:MyStruct = construct 1i
                   ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Construct_Struct_TooManyArgs) {
@@ -277,12 +277,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:8:19 error: construct: structure has 2 members, but construct provides 3 arguments
     %2:MyStruct = construct 1i, 2u, 3i
                   ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Construct_Struct_WrongArgType) {
@@ -300,12 +300,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:8:33 error: construct: type 'i32' of argument 1 does not match type 'u32' of struct member
     %2:MyStruct = construct 1i, 2i
                                 ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Construct_NullArg) {
@@ -322,11 +322,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:8:33 error: construct: operand is undefined
     %2:MyStruct = construct 1i, undef
                                 ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Construct_NullResult) {
@@ -344,11 +344,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:8:5 error: construct: result is undefined
     undef = construct 1i, 2u
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Construct_EmptyResult) {
@@ -366,11 +366,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:8:13 error: construct: expected exactly 1 results, got 0
     undef = construct 1i, 2u
             ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_MissingArg) {
@@ -383,11 +383,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:14 error: convert: expected exactly 1 operands, got 0
     %2:i32 = convert
              ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_NullArg) {
@@ -399,11 +399,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:22 error: convert: operand is undefined
     %2:i32 = convert undef
                      ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_MissingResult) {
@@ -416,11 +416,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:13 error: convert: expected exactly 1 results, got 0
     undef = convert 1.0f
             ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_NullResult) {
@@ -433,11 +433,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: convert: result is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: convert: result is undefined
     undef = convert 1.0f
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_ScalarToScalar) {
@@ -464,11 +463,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:14 error: convert: No defined converter for 'f32' -> 'f32'
     %3:f32 = convert %p
              ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_ScalarToVec) {
@@ -483,11 +482,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:20 error: convert: No defined converter for 'f32' -> 'vec2<f32>'
     %3:vec2<f32> = convert %p
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_ScalarToMat) {
@@ -502,11 +501,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:22 error: convert: No defined converter for 'f32' -> 'mat2x3<f32>'
     %3:mat2x3<f32> = convert %p
                      ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_VecToVec) {
@@ -533,12 +532,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:3:20 error: convert: No defined converter for 'vec3<f32>' -> 'vec3<f32>'
     %3:vec3<f32> = convert %p
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_VecToVec_WidthMismatch) {
@@ -552,12 +551,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:3:20 error: convert: No defined converter for 'vec2<u32>' -> 'vec4<f32>'
     %3:vec4<f32> = convert %p
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_VecToScalar) {
@@ -572,11 +571,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:14 error: convert: No defined converter for 'vec2<u32>' -> 'f32'
     %3:f32 = convert %p
              ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_VecToMat) {
@@ -590,12 +589,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:3:22 error: convert: No defined converter for 'vec2<u32>' -> 'mat3x2<f32>'
     %3:mat3x2<f32> = convert %p
                      ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_MatToMat) {
@@ -622,12 +621,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:3:22 error: convert: No defined converter for 'mat3x3<f32>' -> 'mat3x3<f32>'
     %3:mat3x3<f32> = convert %p
                      ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_MatToMat_ShapeMismatch) {
@@ -641,12 +640,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:3:22 error: convert: No defined converter for 'mat4x4<f32>' -> 'mat2x2<f32>'
     %3:mat2x2<f32> = convert %p
                      ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_MatToScalar) {
@@ -661,11 +660,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:14 error: convert: No defined converter for 'mat4x4<f32>' -> 'f32'
     %3:f32 = convert %p
              ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_MatToVec) {
@@ -679,12 +678,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:3:20 error: convert: No defined converter for 'mat4x4<f32>' -> 'vec4<f32>'
     %3:vec4<f32> = convert %p
                    ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_4xU8ToU32) {
@@ -699,11 +698,11 @@
     auto res = ir::Validate(mod, Capabilities{Capability::kAllow8BitIntegers});
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:14 error: convert: No defined converter for 'vec4<u8>' -> 'u32'
     %3:u32 = convert %p
              ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_U32To4xU8) {
@@ -718,11 +717,11 @@
     auto res = ir::Validate(mod, Capabilities{Capability::kAllow8BitIntegers});
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:19 error: convert: No defined converter for 'u32' -> 'vec4<u8>'
     %3:vec4<u8> = convert %p
                   ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_PtrToVal) {
@@ -736,12 +735,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:4:14 error: convert: No defined converter for 'ptr<function, u32, read_write>' -> 'u32'
     %3:u32 = convert %v
              ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Convert_PtrToPtr) {
@@ -755,12 +754,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:4:41 error: convert: not defined for result type, 'ptr<function, u32, read_write>'
     %3:ptr<function, u32, read_write> = convert %v
                                         ^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Block_NoTerminator) {
@@ -768,12 +767,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:3 error: block does not end in a terminator instruction
   $B1: {
   ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Block_VarBlockMismatch) {
@@ -791,11 +790,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:41 error: var: block instruction does not have same block as parent
     %2:ptr<function, i32, read_write> = var undef
                                         ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Block_DeadParameter) {
@@ -814,11 +813,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:7:12 error: destroyed parameter found in block parameter list
       $B3 (%my_param:f32): {  # body
            ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Block_ParameterWithNullBlock) {
@@ -837,11 +836,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:7:12 error: block parameter has nullptr parent block
       $B3 (%my_param:f32): {  # body
            ^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Block_ParameterUsedInMultipleBlocks) {
@@ -860,7 +859,7 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:7:12 error: block parameter has incorrect parent block
       $B3 (%my_param:f32): {  # body
            ^^^^^^^^^
@@ -868,7 +867,7 @@
 :10:7 note: parent block declared here
       $B4 (%my_param:f32): {  # continuing
       ^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Instruction_AppendedDead) {
@@ -906,7 +905,7 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(), expected);
+    EXPECT_EQ(res.Failure().reason, expected);
 }
 
 TEST_F(IR_ValidatorTest, Instruction_NullInstructionResultInstruction) {
@@ -920,11 +919,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: var: result instruction is undefined
     %2:ptr<function, f32, read_write> = var undef
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Instruction_WrongInstructionResultInstruction) {
@@ -940,12 +939,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:4:5 error: var: result instruction does not match instruction (possible double usage)
     %2:ptr<function, f32, read_write> = var undef
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Instruction_DeadOperand) {
@@ -961,11 +960,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:45 error: var: operand is not alive
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:45 error: var: operand is not alive
     %2:ptr<function, f32, read_write> = var %3
                                             ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Instruction_OperandUsageRemoved) {
@@ -981,11 +979,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:45 error: var: operand missing usage
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:45 error: var: operand missing usage
     %2:ptr<function, f32, read_write> = var %3
                                             ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Instruction_OrphanedInstruction) {
@@ -1000,9 +997,8 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(error: load: orphaned instruction: load
-)")) << res.Failure().reason.Str();
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(error: load: orphaned instruction: load
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Binary_LHS_Nullptr) {
@@ -1014,11 +1010,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:18 error: binary: operand is undefined
     %2:i32 = add undef, 2i
                  ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Binary_RHS_Nullptr) {
@@ -1030,11 +1026,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:22 error: binary: operand is undefined
     %2:i32 = add 2i, undef
                      ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Binary_Result_Nullptr) {
@@ -1049,11 +1045,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: binary: result is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: binary: result is undefined
     undef = add 3i, 2i
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Binary_MissingOperands) {
@@ -1066,11 +1061,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: binary: expected at least 2 operands, got 0
     %2:i32 = add
     ^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Binary_MissingResult) {
@@ -1083,11 +1078,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: binary: expected exactly 1 results, got 0
     undef = add 1i, 2i
     ^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Unary_Value_Nullptr) {
@@ -1099,11 +1094,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:23 error: unary: operand is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:23 error: unary: operand is undefined
     %2:i32 = negation undef
                       ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Unary_Result_Nullptr) {
@@ -1117,11 +1111,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: unary: result is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: unary: result is undefined
     undef = negation 2i
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Unary_ResultTypeNotMatchValueType) {
@@ -1136,12 +1129,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:5 error: unary: result value type 'f32' does not match complement result type 'i32'
     %2:f32 = complement 2i
     ^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Unary_MissingOperands) {
@@ -1155,11 +1148,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: unary: expected at least 1 operands, got 0
     %2:f32 = negation
     ^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Unary_MissingResults) {
@@ -1173,11 +1166,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: unary: expected exactly 1 results, got 0
     undef = negation 2.0f
     ^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Scoping_UseBeforeDecl) {
@@ -1192,11 +1185,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:18 error: binary: %3 is not in scope
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:18 error: binary: %3 is not in scope
     %2:i32 = add %3, 1i
                  ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, OverrideWithoutCapability) {
@@ -1205,12 +1197,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:12 error: override: root block: invalid instruction: tint::core::ir::Override
   %a:u32 = override 1u
            ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, InstructionInRootBlockWithoutOverrideCap) {
@@ -1219,12 +1211,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:3 error: binary: root block: invalid instruction: tint::core::ir::CoreBinary
   %1:u32 = add 3u, 2u
   ^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, OverrideWithCapability) {
@@ -1256,11 +1248,11 @@
     auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:2:18 error: override: override type 'vec3<u32>' is not a scalar
   %1:vec3<u32> = override undef
                  ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, OverrideWithMismatchedInitializerType) {
@@ -1273,12 +1265,12 @@
     auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:12 error: override: override type 'u32' does not match initializer type 'i32'
   %1:u32 = override 1i
            ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, OverrideDuplicateId) {
@@ -1292,11 +1284,11 @@
 
     auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:12 error: override: duplicate override id encountered: 2
   %2:i32 = override undef @id(2)
            ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, InstructionInRootBlockOnlyUsedInRootBlock) {
@@ -1317,12 +1309,12 @@
     auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:3 error: binary: root block: instruction used outside of root block tint::core::ir::CoreBinary
   %2:u32 = add %1, 2u
   ^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, OverrideArrayInvalidValue) {
@@ -1339,11 +1331,10 @@
 
     auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:2:51 error: var: %2 is not in scope
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:2:51 error: var: %2 is not in scope
   %a:ptr<workgroup, array<i32, %2>, read_write> = var undef
                                                   ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, OverrideWithoutIdOrInitializer) {
@@ -1351,11 +1342,11 @@
 
     auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:2:12 error: override: must have an id or an initializer
   %1:u32 = override undef
            ^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/validator_type_test.cc b/src/tint/lang/core/ir/validator_type_test.cc
index 8a6c77b..6dc3c40 100644
--- a/src/tint/lang/core/ir/validator_type_test.cc
+++ b/src/tint/lang/core/ir/validator_type_test.cc
@@ -88,11 +88,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
     %af:ptr<function, abstract-float, read_write> = var undef
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, AbstractInt_Scalar) {
@@ -104,11 +104,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
     %ai:ptr<function, abstract-int, read_write> = var undef
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, AbstractFloat_Vector) {
@@ -120,11 +120,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
     %af:ptr<function, vec2<abstract-float>, read_write> = var undef
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, AbstractInt_Vector) {
@@ -136,11 +136,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(3:5 error: var: abstracts are not permitted
     %ai:ptr<function, vec3<abstract-int>, read_write> = var undef
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, AbstractFloat_Matrix) {
@@ -152,11 +152,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
     %af:ptr<function, mat2x2<abstract-float>, read_write> = var undef
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, AbstractInt_Matrix) {
@@ -168,11 +168,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
     %ai:ptr<function, mat3x4<abstract-int>, read_write> = var undef
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, AbstractFloat_Struct) {
@@ -185,11 +185,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:6:3 error: var: abstracts are not permitted
   %1:ptr<private, MyStruct, read_write> = var undef
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, AbstractInt_Struct) {
@@ -202,11 +202,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:6:3 error: var: abstracts are not permitted
   %1:ptr<private, MyStruct, read_write> = var undef
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, AbstractFloat_FunctionParam) {
@@ -217,11 +217,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:1:17 error: abstracts are not permitted
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:17 error: abstracts are not permitted
 %my_func = func(%2:abstract-float):void {
                 ^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, AbstractInt_FunctionParam) {
@@ -232,11 +231,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:1:17 error: abstracts are not permitted
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:17 error: abstracts are not permitted
 %my_func = func(%2:abstract-int):void {
                 ^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 using TypeTest = IRTestParamHelper<std::tuple<
@@ -258,7 +256,7 @@
         });
 
         auto res = ir::Validate(mod);
-        ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+        ASSERT_EQ(res, Success) << res.Failure().reason;
     } else {
         auto* f = b.Function("my_func", ty.void_());
         b.Append(f->Block(), [&] {
@@ -267,11 +265,11 @@
         });
 
         auto res = ir::Validate(mod);
-        ASSERT_NE(res, Success) << res.Failure().reason.Str();
-        EXPECT_THAT(res.Failure().reason.Str(),
+        ASSERT_NE(res, Success) << res.Failure().reason;
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr(R"(:3:5 error: var: vector elements, ')" +
                                        ty.vec2(type)->FriendlyName() + R"(', must be scalars
- )")) << res.Failure().reason.Str();
+ )")) << res.Failure().reason;
     }
 }
 
@@ -305,7 +303,7 @@
         });
 
         auto res = ir::Validate(mod);
-        ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+        ASSERT_EQ(res, Success) << res.Failure().reason;
     } else {
         auto* f = b.Function("my_func", ty.void_());
         b.Append(f->Block(), [&] {
@@ -314,11 +312,11 @@
         });
 
         auto res = ir::Validate(mod);
-        ASSERT_NE(res, Success) << res.Failure().reason.Str();
-        EXPECT_THAT(res.Failure().reason.Str(),
+        ASSERT_NE(res, Success) << res.Failure().reason;
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr(R"(:3:5 error: var: matrix elements, ')" +
                                        ty.mat3x3(type)->FriendlyName() + R"(', must be float scalars
- )")) << res.Failure().reason.Str();
+ )")) << res.Failure().reason;
     }
 }
 
@@ -348,17 +346,17 @@
 
     if (allowed) {
         auto res = ir::Validate(mod);
-        ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+        ASSERT_EQ(res, Success) << res.Failure().reason;
     } else {
         auto res = ir::Validate(mod);
-        ASSERT_NE(res, Success) << res.Failure().reason.Str();
-        EXPECT_THAT(res.Failure().reason.Str(),
+        ASSERT_NE(res, Success) << res.Failure().reason;
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr(
                         dim != type::TextureDimension::kNone
                             ? R"(:2:3 error: var: dimension ')" + std::string(ToString(dim)) +
                                   R"(' for storage textures does not in WGSL yet)"
                             : R"(:2:3 error: var: invalid texture dimension 'none')"))
-            << res.Failure().reason.Str();
+            << res.Failure().reason;
     }
 }
 
@@ -399,9 +397,9 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr("3:5 error: var: reference types are not permitted"))
-            << res.Failure().reason.Str();
+            << res.Failure().reason;
     }
 }
 
@@ -423,9 +421,8 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
-                    testing::HasSubstr("reference types are not permitted"))
-            << res.Failure().reason.Str();
+        EXPECT_THAT(res.Failure().reason, testing::HasSubstr("reference types are not permitted"))
+            << res.Failure().reason;
     }
 }
 
@@ -446,9 +443,8 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
-                    testing::HasSubstr("reference types are not permitted"))
-            << res.Failure().reason.Str();
+        EXPECT_THAT(res.Failure().reason, testing::HasSubstr("reference types are not permitted"))
+            << res.Failure().reason;
     }
 }
 
@@ -479,9 +475,8 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
-                    testing::HasSubstr("reference types are not permitted"))
-            << res.Failure().reason.Str();
+        EXPECT_THAT(res.Failure().reason, testing::HasSubstr("reference types are not permitted"))
+            << res.Failure().reason;
     }
 }
 
@@ -514,9 +509,8 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr("nested pointer types are not permitted"))
-        << res.Failure().reason.Str();
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr("nested pointer types are not permitted"))
+        << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, PointerToVoid) {
@@ -529,9 +523,8 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr("pointers to void are not permitted"))
-        << res.Failure().reason.Str();
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr("pointers to void are not permitted"))
+        << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ReferenceToReference) {
@@ -547,9 +540,9 @@
 
     auto res = ir::Validate(mod, caps);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr("nested reference types are not permitted"))
-        << res.Failure().reason.Str();
+        << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, ReferenceToVoid) {
@@ -565,9 +558,8 @@
 
     auto res = ir::Validate(mod, caps);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr("references to void are not permitted"))
-        << res.Failure().reason.Str();
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr("references to void are not permitted"))
+        << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, PointerInStructure_WithoutCapability) {
@@ -582,9 +574,8 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr("nested pointer types are not permitted"))
-        << res.Failure().reason.Str();
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr("nested pointer types are not permitted"))
+        << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, PointerInStructure_WithCapability) {
@@ -625,9 +616,9 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr("3:5 error: var: 8-bit integer types are not permitted"))
-            << res.Failure().reason.Str();
+            << res.Failure().reason;
     }
 }
 
@@ -648,9 +639,9 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr("8-bit integer types are not permitted"))
-            << res.Failure().reason.Str();
+            << res.Failure().reason;
     }
 }
 
@@ -670,9 +661,9 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr("8-bit integer types are not permitted"))
-            << res.Failure().reason.Str();
+            << res.Failure().reason;
     }
 }
 
@@ -702,9 +693,9 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr("8-bit integer types are not permitted"))
-            << res.Failure().reason.Str();
+            << res.Failure().reason;
     }
 }
 
@@ -727,11 +718,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: let: 8-bit integer types are not permitted
     %l:u8 = let 1u8
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Int8Type_InstructionOperand_Allowed) {
@@ -768,9 +759,9 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr("3:5 error: var: 64-bit integer types are not permitted"))
-            << res.Failure().reason.Str();
+            << res.Failure().reason;
     }
 }
 
@@ -791,9 +782,9 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr("64-bit integer types are not permitted"))
-            << res.Failure().reason.Str();
+            << res.Failure().reason;
     }
 }
 
@@ -813,9 +804,9 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr("64-bit integer types are not permitted"))
-            << res.Failure().reason.Str();
+            << res.Failure().reason;
     }
 }
 
@@ -845,9 +836,9 @@
         ASSERT_EQ(res, Success) << res.Failure();
     } else {
         ASSERT_NE(res, Success);
-        EXPECT_THAT(res.Failure().reason.Str(),
+        EXPECT_THAT(res.Failure().reason,
                     testing::HasSubstr("64-bit integer types are not permitted"))
-            << res.Failure().reason.Str();
+            << res.Failure().reason;
     }
 }
 
@@ -869,11 +860,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:5 error: let: 64-bit integer types are not permitted
     %l:u64 = let 1u64
     ^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Int64Type_InstructionOperand_Allowed) {
diff --git a/src/tint/lang/core/ir/validator_value_test.cc b/src/tint/lang/core/ir/validator_value_test.cc
index 5712dad..81a6d48 100644
--- a/src/tint/lang/core/ir/validator_value_test.cc
+++ b/src/tint/lang/core/ir/validator_value_test.cc
@@ -66,11 +66,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:2:38 error: var: expected exactly 1 operands, got 2
   %1:ptr<private, i32, read_write> = var 0i, 1i
                                      ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_RootBlock_NullResult) {
@@ -80,11 +80,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:2:3 error: var: result is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:2:3 error: var: result is undefined
   undef = var 0i
   ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_VoidType) {
@@ -92,11 +91,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:2:3 error: var: pointers to void are not permitted
   %1:ptr<private, void, read_write> = var undef
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Function_NullResult) {
@@ -111,11 +110,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: var: result is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: var: result is undefined
     undef = var 0i
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Function_NoResult) {
@@ -130,11 +128,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:13 error: var: expected exactly 1 results, got 0
     undef = var 1i
             ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Function_NonPtrResult) {
@@ -149,11 +147,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:14 error: var: result type 'f32' must be a pointer or a reference
     %2:f32 = var undef
              ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Function_UnexpectedInputAttachmentIndex) {
@@ -167,12 +165,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:3:41 error: var: '@input_attachment_index' is not valid for non-handle var
     %2:ptr<function, f32, read_write> = var undef @input_attachment_index(0)
                                         ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Function_OutsideFunctionScope) {
@@ -182,12 +180,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:39 error: var: vars in the 'function' address space must be in a function scope
   %1:ptr<function, f32, read_write> = var undef
                                       ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_NonFunction_InsideFunctionScope) {
@@ -201,12 +199,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:40 error: var: vars in a function scope must be in the 'function' address space
     %2:ptr<private, f32, read_write> = var undef
                                        ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Private_InsideFunctionScopeWithCapability) {
@@ -228,12 +226,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:38 error: var: '@input_attachment_index' is not valid for non-handle var
   %1:ptr<private, f32, read_write> = var undef @input_attachment_index(0)
                                      ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_PushConstant_UnexpectedInputAttachmentIndex) {
@@ -243,12 +241,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:38 error: var: '@input_attachment_index' is not valid for non-handle var
   %1:ptr<push_constant, f32, read> = var undef @input_attachment_index(0)
                                      ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Storage_UnexpectedInputAttachmentIndex) {
@@ -259,12 +257,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:38 error: var: '@input_attachment_index' is not valid for non-handle var
   %1:ptr<storage, f32, read_write> = var undef @binding_point(0, 0) @input_attachment_index(0)
                                      ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Uniform_UnexpectedInputAttachmentIndex) {
@@ -275,12 +273,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:32 error: var: '@input_attachment_index' is not valid for non-handle var
   %1:ptr<uniform, f32, read> = var undef @binding_point(0, 0) @input_attachment_index(0)
                                ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Workgroup_UnexpectedInputAttachmentIndex) {
@@ -290,12 +288,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:2:40 error: var: '@input_attachment_index' is not valid for non-handle var
   %1:ptr<workgroup, f32, read_write> = var undef @input_attachment_index(0)
                                        ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Init_WrongType) {
@@ -309,12 +307,12 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(
                     R"(:3:41 error: var: initializer type 'i32' does not match store type 'f32'
     %2:ptr<function, f32, read_write> = var 1i
                                         ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Init_NullType) {
@@ -333,11 +331,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:5:45 error: var: operand type is undefined
     %j:ptr<function, f32, read_write> = var %3
                                             ^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Init_FunctionTypeInit) {
@@ -354,12 +352,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:8:41 error: var: initializer type '<function>' does not match store type 'f32'
     %i:ptr<function, f32, read_write> = var %invalid_init
                                         ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Init_InvalidAddressSpace) {
@@ -380,12 +378,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:38 error: var: only variables in the function or private address space may be initialized
   %s:ptr<storage, f32, read_write> = var 1.0f
                                      ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_HandleMissingBindingPoint) {
@@ -394,11 +392,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:2:31 error: var: a resource variable is missing binding point
   %1:ptr<handle, i32, read> = var undef
                               ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_StorageMissingBindingPoint) {
@@ -407,11 +405,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:2:38 error: var: a resource variable is missing binding point
   %1:ptr<storage, i32, read_write> = var undef
                                      ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_UniformMissingBindingPoint) {
@@ -420,11 +418,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:2:32 error: var: a resource variable is missing binding point
   %1:ptr<uniform, i32, read> = var undef
                                ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_NonResourceWithBindingPoint) {
@@ -434,11 +432,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:2:38 error: var: a non-resource variable has binding point
   %1:ptr<private, i32, read_write> = var undef @binding_point(0, 0)
                                      ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_MultipleIOAnnotations) {
@@ -452,12 +450,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:35 error: var: module scope variable has more than one IO annotation, [ @location, built-in ]
   %1:ptr<__in, vec4<f32>, read> = var undef @location(0) @builtin(position)
                                   ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Struct_MultipleIOAnnotations) {
@@ -475,12 +473,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:6:41 error: var: module scope variable struct member has more than one IO annotation, [ built-in, @color ]
   %1:ptr<__out, MyStruct, read_write> = var undef
                                         ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_MissingIOAnnotations) {
@@ -490,12 +488,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:35 error: var: module scope variable must have at least one IO annotation, e.g. a binding point, a location, etc
   %1:ptr<__in, vec4<f32>, read> = var undef
                                   ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Struct_MissingIOAnnotations) {
@@ -508,12 +506,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:6:41 error: var: module scope variable struct members must have at least one IO annotation, e.g. a binding point, a location, etc
   %1:ptr<__out, MyStruct, read_write> = var undef
                                         ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Sampler_NonHandleAddressSpace) {
@@ -523,12 +521,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:42 error: var: handle types can only be declared in the 'handle' address space
   %1:ptr<private, sampler, read_write> = var undef
                                          ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_Texture_NonHandleAddressSpace) {
@@ -538,12 +536,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:42 error: var: handle types can only be declared in the 'handle' address space
   %1:ptr<private, sampler, read_write> = var undef
                                          ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Var_BindingArray_Texture_NonHandleAddressSpace) {
@@ -556,12 +554,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:2:62 error: var: handle types can only be declared in the 'handle' address space
   %1:ptr<private, binding_array<texture_2d<f32>, 4>, read> = var undef
                                                              ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_NullResult) {
@@ -575,11 +573,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:5 error: let: result is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: let: result is undefined
     undef = let 1i
     ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_EmptyResults) {
@@ -594,11 +591,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:13 error: let: expected exactly 1 results, got 0
     undef = let 1i
             ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_NullValue) {
@@ -611,11 +608,10 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:18 error: let: operand is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:18 error: let: operand is undefined
     %2:f32 = let undef
                  ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_EmptyValue) {
@@ -630,11 +626,11 @@
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:14 error: let: expected exactly 1 operands, got 0
     %2:i32 = let
              ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_WrongType) {
@@ -649,11 +645,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:14 error: let: result type 'f32' does not match value type 'i32'
     %2:f32 = let 1i
              ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_VoidResultWithCapability) {
@@ -666,11 +662,11 @@
 
     auto res = ir::Validate(mod, Capabilities{ir::Capability::kAllowAnyLetType});
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(), testing::HasSubstr(
-                                                R"(:3:15 error: let: result type cannot be void
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(
+                                          R"(:3:15 error: let: result type cannot be void
     %2:void = let 1i
               ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_VoidResultWithoutCapability) {
@@ -684,12 +680,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:15 error: let: result type, 'void', must be concrete constructible type or a pointer type
     %2:void = let 1i
               ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_VoidValueWithCapability) {
@@ -705,11 +701,11 @@
 
     auto res = ir::Validate(mod, Capabilities{ir::Capability::kAllowAnyLetType});
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:9:14 error: let: value type cannot be void
     %4:i32 = let %3
              ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_VoidValueWithoutCapability) {
@@ -726,12 +722,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:9:14 error: let: value type, 'void', must be concrete constructible type or a pointer type
     %4:i32 = let %3
              ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_NotConstructibleResult) {
@@ -748,12 +744,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:18 error: let: result type, 'sampler', must be concrete constructible type or a pointer type
     %3:sampler = let 1i
                  ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_NotConstructibleValue) {
@@ -769,12 +765,12 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(
             R"(:3:14 error: let: value type, 'sampler', must be concrete constructible type or a pointer type
     %3:i32 = let %p
              ^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Let_CapabilityBypass) {
@@ -789,7 +785,7 @@
     });
 
     auto res = ir::Validate(mod, Capabilities{ir::Capability::kAllowAnyLetType});
-    ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+    ASSERT_EQ(res, Success) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Phony_NullValue) {
@@ -802,11 +798,10 @@
 
     auto res = ir::Validate(mod, Capabilities{ir::Capability::kAllowPhonyInstructions});
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
-                testing::HasSubstr(R"(:3:19 error: phony: operand is undefined
+    EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:19 error: phony: operand is undefined
     undef = phony undef
                   ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Phony_EmptyValue) {
@@ -821,11 +816,11 @@
 
     auto res = ir::Validate(mod, Capabilities{ir::Capability::kAllowPhonyInstructions});
     ASSERT_NE(res, Success);
-    EXPECT_THAT(res.Failure().reason.Str(),
+    EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:3:13 error: phony: expected exactly 1 operands, got 0
     undef = phony
             ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 TEST_F(IR_ValidatorTest, Phony_MissingCapability) {
@@ -840,11 +835,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         testing::HasSubstr(R"(:3:13 error: phony: missing capability 'kAllowPhonyInstructions'
     undef = phony 1i
             ^^^^^
-)")) << res.Failure().reason.Str();
+)")) << res.Failure().reason;
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/glsl/ir/member_builtin_call_test.cc b/src/tint/lang/glsl/ir/member_builtin_call_test.cc
index 4cfcfb2..2ebd087 100644
--- a/src/tint/lang/glsl/ir/member_builtin_call_test.cc
+++ b/src/tint/lang/glsl/ir/member_builtin_call_test.cc
@@ -84,7 +84,7 @@
 
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               R"(:12:17 error: length: no matching call to 'length(ptr<storage, u32, read_write>)'
 
 1 candidate function:
@@ -154,7 +154,7 @@
 
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               R"(:12:16 error: length: expected exactly 1 results, got 0
     undef = %3.length
                ^^^^^^
@@ -200,7 +200,7 @@
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         R"(:12:17 error: length: no matching call to 'length(ptr<storage, array<u32>, read_write>, u32)'
 
 1 candidate function:
diff --git a/src/tint/lang/glsl/writer/helper_test.h b/src/tint/lang/glsl/writer/helper_test.h
index a6fd3cd..d66c366 100644
--- a/src/tint/lang/glsl/writer/helper_test.h
+++ b/src/tint/lang/glsl/writer/helper_test.h
@@ -68,7 +68,7 @@
                   tint::ast::PipelineStage stage = tint::ast::PipelineStage::kCompute) {
         auto result = writer::Generate(mod, options, "");
         if (result != Success) {
-            err_ = result.Failure().reason.Str();
+            err_ = result.Failure().reason;
             return false;
         }
         output_ = result.Get();
diff --git a/src/tint/lang/glsl/writer/printer/printer.cc b/src/tint/lang/glsl/writer/printer/printer.cc
index 16ca04f..3f290ce 100644
--- a/src/tint/lang/glsl/writer/printer/printer.cc
+++ b/src/tint/lang/glsl/writer/printer/printer.cc
@@ -125,7 +125,7 @@
     Printer(core::ir::Module& module, const Options& options) : ir_(module), options_(options) {}
 
     /// @returns the generated GLSL shader
-    diag::Result<Output> Generate() {
+    tint::Result<Output> Generate() {
         auto valid = core::ir::ValidateAndDumpIfNeeded(
             ir_, "glsl.Printer",
             core::ir::Capabilities{core::ir::Capability::kAllowHandleVarsWithoutBindings});
@@ -2276,7 +2276,7 @@
 
 }  // namespace
 
-diag::Result<Output> Print(core::ir::Module& module, const Options& options) {
+Result<Output> Print(core::ir::Module& module, const Options& options) {
     return Printer{module, options}.Generate();
 }
 
diff --git a/src/tint/lang/glsl/writer/printer/printer.h b/src/tint/lang/glsl/writer/printer/printer.h
index c7ab503..7a9d786 100644
--- a/src/tint/lang/glsl/writer/printer/printer.h
+++ b/src/tint/lang/glsl/writer/printer/printer.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_GLSL_WRITER_PRINTER_PRINTER_H_
 
 #include "src/tint/lang/glsl/writer/common/output.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -44,7 +44,7 @@
 /// @returns the generated GLSL shader on success, or failure
 /// @param module the Tint IR module to generate
 /// @param options the options to use
-diag::Result<Output> Print(core::ir::Module& module, const Options& options);
+Result<Output> Print(core::ir::Module& module, const Options& options);
 
 }  // namespace tint::glsl::writer
 
diff --git a/src/tint/lang/glsl/writer/raise/binary_polyfill.cc b/src/tint/lang/glsl/writer/raise/binary_polyfill.cc
index f6aff36..7a295c2 100644
--- a/src/tint/lang/glsl/writer/raise/binary_polyfill.cc
+++ b/src/tint/lang/glsl/writer/raise/binary_polyfill.cc
@@ -215,7 +215,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BinaryPolyfill(core::ir::Module& ir) {
+Result<SuccessType> BinaryPolyfill(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "glsl.BinaryPolyfill");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/glsl/writer/raise/binary_polyfill.h b/src/tint/lang/glsl/writer/raise/binary_polyfill.h
index b196e57..387aff8 100644
--- a/src/tint/lang/glsl/writer/raise/binary_polyfill.h
+++ b/src/tint/lang/glsl/writer/raise/binary_polyfill.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_GLSL_WRITER_RAISE_BINARY_POLYFILL_H_
 #define SRC_TINT_LANG_GLSL_WRITER_RAISE_BINARY_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -43,7 +43,7 @@
 ///
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> BinaryPolyfill(core::ir::Module& module);
+Result<SuccessType> BinaryPolyfill(core::ir::Module& module);
 
 }  // namespace tint::glsl::writer::raise
 
diff --git a/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc b/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc
index cd82fac..3f87485 100644
--- a/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc
+++ b/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc
@@ -276,7 +276,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BitcastPolyfill(core::ir::Module& ir) {
+Result<SuccessType> BitcastPolyfill(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(
         ir, "glsl.BitcastPolyfill",
         core::ir::Capabilities{core::ir::Capability::kAllowHandleVarsWithoutBindings});
diff --git a/src/tint/lang/glsl/writer/raise/bitcast_polyfill.h b/src/tint/lang/glsl/writer/raise/bitcast_polyfill.h
index 4f2e813..4c592eb 100644
--- a/src/tint/lang/glsl/writer/raise/bitcast_polyfill.h
+++ b/src/tint/lang/glsl/writer/raise/bitcast_polyfill.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_GLSL_WRITER_RAISE_BITCAST_POLYFILL_H_
 #define SRC_TINT_LANG_GLSL_WRITER_RAISE_BITCAST_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// BitcastPolyfill is a transform that replaces calls to bitcasts with polyfills
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> BitcastPolyfill(core::ir::Module& module);
+Result<SuccessType> BitcastPolyfill(core::ir::Module& module);
 
 }  // namespace tint::glsl::writer::raise
 
diff --git a/src/tint/lang/glsl/writer/raise/builtin_polyfill.cc b/src/tint/lang/glsl/writer/raise/builtin_polyfill.cc
index 6dcb02b..af95dd9 100644
--- a/src/tint/lang/glsl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/glsl/writer/raise/builtin_polyfill.cc
@@ -525,7 +525,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "glsl.BuiltinPolyfill");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/glsl/writer/raise/builtin_polyfill.h b/src/tint/lang/glsl/writer/raise/builtin_polyfill.h
index 01ee6e9..90768fa 100644
--- a/src/tint/lang/glsl/writer/raise/builtin_polyfill.h
+++ b/src/tint/lang/glsl/writer/raise/builtin_polyfill.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_GLSL_WRITER_RAISE_BUILTIN_POLYFILL_H_
 #define SRC_TINT_LANG_GLSL_WRITER_RAISE_BUILTIN_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// GLSL polyfilled or backend intrinsic functions.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> BuiltinPolyfill(core::ir::Module& module);
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& module);
 
 }  // namespace tint::glsl::writer::raise
 
diff --git a/src/tint/lang/glsl/writer/raise/offset_first_index.cc b/src/tint/lang/glsl/writer/raise/offset_first_index.cc
index b731e73..4226a02 100644
--- a/src/tint/lang/glsl/writer/raise/offset_first_index.cc
+++ b/src/tint/lang/glsl/writer/raise/offset_first_index.cc
@@ -111,8 +111,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> OffsetFirstIndex(core::ir::Module& ir,
-                                           const OffsetFirstIndexConfig& config) {
+Result<SuccessType> OffsetFirstIndex(core::ir::Module& ir, const OffsetFirstIndexConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "glsl.OffsetFirstIndex",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowHandleVarsWithoutBindings,
diff --git a/src/tint/lang/glsl/writer/raise/offset_first_index.h b/src/tint/lang/glsl/writer/raise/offset_first_index.h
index 57ef01a..29fd23f 100644
--- a/src/tint/lang/glsl/writer/raise/offset_first_index.h
+++ b/src/tint/lang/glsl/writer/raise/offset_first_index.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_GLSL_WRITER_RAISE_OFFSET_FIRST_INDEX_H_
 
 #include "src/tint/lang/core/ir/transform/prepare_push_constants.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -55,8 +55,8 @@
 ///
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> OffsetFirstIndex(core::ir::Module& module,
-                                           const OffsetFirstIndexConfig& config);
+Result<SuccessType> OffsetFirstIndex(core::ir::Module& module,
+                                     const OffsetFirstIndexConfig& config);
 
 }  // namespace tint::glsl::writer::raise
 
diff --git a/src/tint/lang/glsl/writer/raise/raise.cc b/src/tint/lang/glsl/writer/raise/raise.cc
index 20f4cea..d94ebfa 100644
--- a/src/tint/lang/glsl/writer/raise/raise.cc
+++ b/src/tint/lang/glsl/writer/raise/raise.cc
@@ -63,7 +63,7 @@
 
 namespace tint::glsl::writer {
 
-diag::Result<SuccessType> Raise(core::ir::Module& module, const Options& options) {
+Result<SuccessType> Raise(core::ir::Module& module, const Options& options) {
 #define RUN_TRANSFORM(name, ...)         \
     do {                                 \
         auto result = name(__VA_ARGS__); \
diff --git a/src/tint/lang/glsl/writer/raise/raise.h b/src/tint/lang/glsl/writer/raise/raise.h
index e21e35f..6b97d00 100644
--- a/src/tint/lang/glsl/writer/raise/raise.h
+++ b/src/tint/lang/glsl/writer/raise/raise.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_GLSL_WRITER_RAISE_RAISE_H_
 
 #include "src/tint/lang/glsl/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// @param module the core IR module to raise to MSL dialect
 /// @param options the writer options
 /// @returns success or failure
-diag::Result<SuccessType> Raise(core::ir::Module& module, const Options& options);
+Result<SuccessType> Raise(core::ir::Module& module, const Options& options);
 
 }  // namespace tint::glsl::writer
 
diff --git a/src/tint/lang/glsl/writer/raise/shader_io.cc b/src/tint/lang/glsl/writer/raise/shader_io.cc
index cbae280..3403b7e 100644
--- a/src/tint/lang/glsl/writer/raise/shader_io.cc
+++ b/src/tint/lang/glsl/writer/raise/shader_io.cc
@@ -236,7 +236,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
+Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
     auto result = ValidateAndDumpIfNeeded(
         ir, "glsl.ShaderIO",
         core::ir::Capabilities{core::ir::Capability::kAllowHandleVarsWithoutBindings});
diff --git a/src/tint/lang/glsl/writer/raise/shader_io.h b/src/tint/lang/glsl/writer/raise/shader_io.h
index 9c30b63..14795c2 100644
--- a/src/tint/lang/glsl/writer/raise/shader_io.h
+++ b/src/tint/lang/glsl/writer/raise/shader_io.h
@@ -32,7 +32,7 @@
 
 #include "src/tint/lang/core/ir/transform/prepare_push_constants.h"
 #include "src/tint/lang/glsl/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -58,7 +58,7 @@
 /// @param module the module to transform
 /// @param config the configuration
 /// @returns success or failure
-diag::Result<SuccessType> ShaderIO(core::ir::Module& module, const ShaderIOConfig& config);
+Result<SuccessType> ShaderIO(core::ir::Module& module, const ShaderIOConfig& config);
 
 }  // namespace tint::glsl::writer::raise
 
diff --git a/src/tint/lang/glsl/writer/raise/texture_builtins_from_uniform.cc b/src/tint/lang/glsl/writer/raise/texture_builtins_from_uniform.cc
index c8a6e41..3db84fd 100644
--- a/src/tint/lang/glsl/writer/raise/texture_builtins_from_uniform.cc
+++ b/src/tint/lang/glsl/writer/raise/texture_builtins_from_uniform.cc
@@ -215,8 +215,8 @@
 
 }  // namespace
 
-diag::Result<SuccessType> TextureBuiltinsFromUniform(core::ir::Module& ir,
-                                                     const TextureBuiltinsFromUniformOptions& cfg) {
+Result<SuccessType> TextureBuiltinsFromUniform(core::ir::Module& ir,
+                                               const TextureBuiltinsFromUniformOptions& cfg) {
     auto result = ValidateAndDumpIfNeeded(ir, "glsl.TextureBuiltinsFromUniform");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/glsl/writer/raise/texture_builtins_from_uniform.h b/src/tint/lang/glsl/writer/raise/texture_builtins_from_uniform.h
index 7b2029c..d84f911 100644
--- a/src/tint/lang/glsl/writer/raise/texture_builtins_from_uniform.h
+++ b/src/tint/lang/glsl/writer/raise/texture_builtins_from_uniform.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_GLSL_WRITER_RAISE_TEXTURE_BUILTINS_FROM_UNIFORM_H_
 
 #include "src/tint/lang/glsl/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -45,8 +45,8 @@
 /// @param module the module to transform
 /// @param cfg the configuration
 /// @returns success or failure
-diag::Result<SuccessType> TextureBuiltinsFromUniform(core::ir::Module& module,
-                                                     const TextureBuiltinsFromUniformOptions& cfg);
+Result<SuccessType> TextureBuiltinsFromUniform(core::ir::Module& module,
+                                               const TextureBuiltinsFromUniformOptions& cfg);
 
 }  // namespace tint::glsl::writer::raise
 
diff --git a/src/tint/lang/glsl/writer/raise/texture_polyfill.cc b/src/tint/lang/glsl/writer/raise/texture_polyfill.cc
index 664d187..b2f7b0c 100644
--- a/src/tint/lang/glsl/writer/raise/texture_polyfill.cc
+++ b/src/tint/lang/glsl/writer/raise/texture_polyfill.cc
@@ -1228,7 +1228,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> TexturePolyfill(core::ir::Module& ir, const TexturePolyfillConfig& cfg) {
+Result<SuccessType> TexturePolyfill(core::ir::Module& ir, const TexturePolyfillConfig& cfg) {
     auto result = ValidateAndDumpIfNeeded(ir, "glsl.TexturePolyfill");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/glsl/writer/raise/texture_polyfill.h b/src/tint/lang/glsl/writer/raise/texture_polyfill.h
index ea02636e..d6faefc 100644
--- a/src/tint/lang/glsl/writer/raise/texture_polyfill.h
+++ b/src/tint/lang/glsl/writer/raise/texture_polyfill.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_GLSL_WRITER_RAISE_TEXTURE_POLYFILL_H_
 
 #include "src/tint/lang/glsl/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -59,8 +59,7 @@
 /// @param module the module to transform
 /// @param cfg the configuration
 /// @returns success or failure
-diag::Result<SuccessType> TexturePolyfill(core::ir::Module& module,
-                                          const TexturePolyfillConfig& cfg);
+Result<SuccessType> TexturePolyfill(core::ir::Module& module, const TexturePolyfillConfig& cfg);
 
 }  // namespace tint::glsl::writer::raise
 
diff --git a/src/tint/lang/glsl/writer/writer.cc b/src/tint/lang/glsl/writer/writer.cc
index e6f2d8d..84792a2 100644
--- a/src/tint/lang/glsl/writer/writer.cc
+++ b/src/tint/lang/glsl/writer/writer.cc
@@ -37,11 +37,11 @@
 
 namespace tint::glsl::writer {
 
-diag::Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options) {
+Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options) {
     // Check for unsupported types.
     for (auto* ty : ir.Types()) {
         if (ty->Is<core::type::SubgroupMatrix>()) {
-            return diag::Failure("subgroup matrices are not supported by the GLSL backend");
+            return Failure("subgroup matrices are not supported by the GLSL backend");
         }
     }
 
@@ -59,7 +59,7 @@
 
         // The pixel_local extension is not supported by the GLSL backend.
         if (ptr->AddressSpace() == core::AddressSpace::kPixelLocal) {
-            return diag::Failure("pixel_local address space is not supported by the GLSL backend");
+            return Failure("pixel_local address space is not supported by the GLSL backend");
         }
 
         if (ptr->StoreType()->Is<core::type::Texture>()) {
@@ -73,7 +73,7 @@
                 }
             }
             if (!found) {
-                return diag::Failure("texture missing from texture_builtins_from_uniform list");
+                return Failure("texture missing from texture_builtins_from_uniform list");
             }
 
             // Check texel formats for read-write storage textures when targeting ES.
@@ -86,8 +86,7 @@
                             case core::TexelFormat::kR32Uint:
                                 break;
                             default:
-                                return diag::Failure(
-                                    "unsupported read-write storage texture format");
+                                return Failure("unsupported read-write storage texture format");
                         }
                     }
                 }
@@ -97,7 +96,7 @@
         if (ptr->AddressSpace() == core::AddressSpace::kPushConstant) {
             if (user_push_constant_size > 0) {
                 // We've already seen a user-declared push constant.
-                return diag::Failure("multiple user-declared push constants");
+                return Failure("multiple user-declared push constants");
             }
             user_push_constant_size = tint::RoundUp(4u, ptr->StoreType()->Size());
         }
@@ -111,10 +110,10 @@
         }
 
         if (core::IsSubgroup(call->Func())) {
-            return diag::Failure("subgroups are not supported by the GLSL backend");
+            return Failure("subgroups are not supported by the GLSL backend");
         }
         if (call->Func() == core::BuiltinFn::kInputAttachmentLoad) {
-            return diag::Failure("input attachments are not supported by the GLSL backend");
+            return Failure("input attachments are not supported by the GLSL backend");
         }
     }
 
@@ -130,13 +129,13 @@
                 for (auto* member : str->Members()) {
                     if (member->Attributes().builtin == core::BuiltinValue::kSubgroupInvocationId ||
                         member->Attributes().builtin == core::BuiltinValue::kSubgroupSize) {
-                        return diag::Failure("subgroups are not supported by the GLSL backend");
+                        return Failure("subgroups are not supported by the GLSL backend");
                     }
                 }
             } else {
                 if (param->Builtin() == core::BuiltinValue::kSubgroupInvocationId ||
                     param->Builtin() == core::BuiltinValue::kSubgroupSize) {
-                    return diag::Failure("subgroups are not supported by the GLSL backend");
+                    return Failure("subgroups are not supported by the GLSL backend");
                 }
             }
         }
@@ -145,7 +144,7 @@
         if (auto* str = func->ReturnType()->As<core::type::Struct>()) {
             for (auto* member : str->Members()) {
                 if (member->Attributes().builtin == core::BuiltinValue::kClipDistances) {
-                    return diag::Failure("clip_distances is not supported by the GLSL backend");
+                    return Failure("clip_distances is not supported by the GLSL backend");
                 }
             }
         }
@@ -175,24 +174,24 @@
 
     if (options.first_instance_offset &&
         !check_push_constant_offset(*options.first_instance_offset)) {
-        return diag::Failure("invalid offset for first_instance_offset push constant");
+        return Failure("invalid offset for first_instance_offset push constant");
     }
 
     if (options.first_vertex_offset && !check_push_constant_offset(*options.first_vertex_offset)) {
-        return diag::Failure("invalid offset for first_vertex_offset push constant");
+        return Failure("invalid offset for first_vertex_offset push constant");
     }
 
     if (options.depth_range_offsets) {
         if (!check_push_constant_offset(options.depth_range_offsets->max) ||
             !check_push_constant_offset(options.depth_range_offsets->min)) {
-            return diag::Failure("invalid offsets for depth range push constants");
+            return Failure("invalid offsets for depth range push constants");
         }
     }
 
     return Success;
 }
 
-diag::Result<Output> Generate(core::ir::Module& ir, const Options& options, const std::string&) {
+Result<Output> Generate(core::ir::Module& ir, const Options& options, const std::string&) {
     // Raise from core-dialect to GLSL-dialect.
     if (auto res = Raise(ir, options); res != Success) {
         return res.Failure();
diff --git a/src/tint/lang/glsl/writer/writer.h b/src/tint/lang/glsl/writer/writer.h
index c2572f4..b8cd4c0 100644
--- a/src/tint/lang/glsl/writer/writer.h
+++ b/src/tint/lang/glsl/writer/writer.h
@@ -32,7 +32,7 @@
 
 #include "src/tint/lang/glsl/writer/common/options.h"
 #include "src/tint/lang/glsl/writer/common/output.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint {
@@ -48,7 +48,7 @@
 /// @param ir the module
 /// @param options the writer options
 /// @returns Success or a failure message indicating why GLSL generation would fail
-diag::Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options);
+Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options);
 
 /// Generate GLSL for a program, according to a set of configuration options.
 /// The result will contain the GLSL and supplementary information, or failure.
@@ -57,9 +57,9 @@
 /// @param options the configuration options to use when generating GLSL
 /// @param entry_point the entry point to generate GLSL for
 /// @returns the resulting GLSL and supplementary information, or failure
-diag::Result<Output> Generate(core::ir::Module& ir,
-                              const Options& options,
-                              const std::string& entry_point);
+Result<Output> Generate(core::ir::Module& ir,
+                        const Options& options,
+                        const std::string& entry_point);
 
 }  // namespace tint::glsl::writer
 
diff --git a/src/tint/lang/glsl/writer/writer_bench.cc b/src/tint/lang/glsl/writer/writer_bench.cc
index 64288ec..d173dc2 100644
--- a/src/tint/lang/glsl/writer/writer_bench.cc
+++ b/src/tint/lang/glsl/writer/writer_bench.cc
@@ -43,7 +43,7 @@
 void GenerateGLSL(benchmark::State& state, std::string input_name) {
     auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
-        state.SkipWithError(res.Failure().reason.Str());
+        state.SkipWithError(res.Failure().reason);
         return;
     }
 
@@ -85,7 +85,7 @@
             // Generate GLSL.
             auto gen_res = Generate(ir.Get(), gen_options, names[i]);
             if (gen_res != Success) {
-                state.SkipWithError(gen_res.Failure().reason.Str());
+                state.SkipWithError(gen_res.Failure().reason);
             }
         }
     }
diff --git a/src/tint/lang/glsl/writer/writer_fuzz.cc b/src/tint/lang/glsl/writer/writer_fuzz.cc
index 547d4c8..4a399dc 100644
--- a/src/tint/lang/glsl/writer/writer_fuzz.cc
+++ b/src/tint/lang/glsl/writer/writer_fuzz.cc
@@ -127,7 +127,7 @@
 
     auto check = CanGenerate(module, options);
     if (check != Success) {
-        return Failure{check.Failure().reason.Str()};
+        return Failure{check.Failure().reason};
     }
 
     auto output = Generate(module, options, "");
diff --git a/src/tint/lang/hlsl/ir/member_builtin_call_test.cc b/src/tint/lang/hlsl/ir/member_builtin_call_test.cc
index 92a12bf..b872ded 100644
--- a/src/tint/lang/hlsl/ir/member_builtin_call_test.cc
+++ b/src/tint/lang/hlsl/ir/member_builtin_call_test.cc
@@ -85,7 +85,7 @@
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         R"(:7:17 error: asint: no matching call to 'asint(hlsl.byte_address_buffer<read>, u32)'
 
     %3:u32 = %t.asint 2u
@@ -125,7 +125,7 @@
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         R"(:7:17 error: Store: no matching call to 'Store(hlsl.byte_address_buffer<read>, u32, u32)'
 
 1 candidate function:
@@ -184,7 +184,7 @@
 
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               R"(:7:16 error: Load: expected exactly 1 results, got 0
     undef = %t.Load 0u
                ^^^^
@@ -221,7 +221,7 @@
 
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               R"(:7:17 error: Load: no matching call to 'Load(hlsl.byte_address_buffer<read>)'
 
 24 candidate functions:
@@ -321,7 +321,7 @@
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         R"(:7:17 error: Load: no matching call to 'Load(hlsl.byte_address_buffer<read>, u32, u32, u32)'
 
 24 candidate functions:
diff --git a/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
index 1a99d1f..5cdda5b 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
@@ -41,7 +41,7 @@
     ASSERT_FALSE(program.IsValid());
     auto result = Generate(program, Options{});
     EXPECT_NE(result, Success);
-    EXPECT_EQ(result.Failure().reason.Str(), "error: make the program invalid");
+    EXPECT_EQ(result.Failure().reason, "error: make the program invalid");
 }
 
 TEST_F(HlslASTPrinterTest, UnsupportedExtension) {
diff --git a/src/tint/lang/hlsl/writer/helper_test.h b/src/tint/lang/hlsl/writer/helper_test.h
index 7e97e2a..c5dba5e 100644
--- a/src/tint/lang/hlsl/writer/helper_test.h
+++ b/src/tint/lang/hlsl/writer/helper_test.h
@@ -63,7 +63,7 @@
     bool Generate(Options options = {}) {
         auto result = writer::Generate(mod, options);
         if (result != Success) {
-            err_ = result.Failure().reason.Str();
+            err_ = result.Failure().reason;
             return false;
         }
         output_ = result.Get();
diff --git a/src/tint/lang/hlsl/writer/printer/printer.cc b/src/tint/lang/hlsl/writer/printer/printer.cc
index 01c6fa0..12770c6 100644
--- a/src/tint/lang/hlsl/writer/printer/printer.cc
+++ b/src/tint/lang/hlsl/writer/printer/printer.cc
@@ -172,7 +172,7 @@
         : ir_(module), options_(options) {}
 
     /// @returns the generated HLSL shader
-    diag::Result<Output> Generate() {
+    tint::Result<Output> Generate() {
         core::ir::Capabilities capabilities{
             core::ir::Capability::kAllowModuleScopeLets,
             core::ir::Capability::kAllowVectorElementPointer,
@@ -2372,7 +2372,7 @@
 
 }  // namespace
 
-diag::Result<Output> Print(core::ir::Module& module, const Options& options) {
+Result<Output> Print(core::ir::Module& module, const Options& options) {
     return Printer{module, options}.Generate();
 }
 
diff --git a/src/tint/lang/hlsl/writer/printer/printer.h b/src/tint/lang/hlsl/writer/printer/printer.h
index c57023b..97b5110 100644
--- a/src/tint/lang/hlsl/writer/printer/printer.h
+++ b/src/tint/lang/hlsl/writer/printer/printer.h
@@ -30,7 +30,7 @@
 
 #include "src/tint/lang/hlsl/writer/common/options.h"
 #include "src/tint/lang/hlsl/writer/common/output.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// @param module the Tint IR module to generate
 /// @param options the printer options
 /// @returns the result of printing the HLSL shader on success, or failure
-diag::Result<Output> Print(core::ir::Module& module, const Options& options);
+Result<Output> Print(core::ir::Module& module, const Options& options);
 
 }  // namespace tint::hlsl::writer
 
diff --git a/src/tint/lang/hlsl/writer/raise/binary_polyfill.cc b/src/tint/lang/hlsl/writer/raise/binary_polyfill.cc
index e67927c..542f5fb 100644
--- a/src/tint/lang/hlsl/writer/raise/binary_polyfill.cc
+++ b/src/tint/lang/hlsl/writer/raise/binary_polyfill.cc
@@ -136,7 +136,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BinaryPolyfill(core::ir::Module& ir) {
+Result<SuccessType> BinaryPolyfill(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "hlsl.BinaryPolyfill",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowClipDistancesOnF32,
diff --git a/src/tint/lang/hlsl/writer/raise/binary_polyfill.h b/src/tint/lang/hlsl/writer/raise/binary_polyfill.h
index 716bd02..327682a 100644
--- a/src/tint/lang/hlsl/writer/raise/binary_polyfill.h
+++ b/src/tint/lang/hlsl/writer/raise/binary_polyfill.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_HLSL_WRITER_RAISE_BINARY_POLYFILL_H_
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_BINARY_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -43,7 +43,7 @@
 ///
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> BinaryPolyfill(core::ir::Module& module);
+Result<SuccessType> BinaryPolyfill(core::ir::Module& module);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
index d978dcb..180c1e6 100644
--- a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
@@ -1934,7 +1934,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "hlsl.BuiltinPolyfill",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowClipDistancesOnF32,
diff --git a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.h b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.h
index c2863ac..c6c0e6c 100644
--- a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.h
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_HLSL_WRITER_RAISE_BUILTIN_POLYFILL_H_
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_BUILTIN_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// HLSL polyfilled or backend intrinsic functions.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> BuiltinPolyfill(core::ir::Module& module);
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& module);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_storage_access.cc b/src/tint/lang/hlsl/writer/raise/decompose_storage_access.cc
index 82f63f6..e461d1bb 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_storage_access.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_storage_access.cc
@@ -898,7 +898,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> DecomposeStorageAccess(core::ir::Module& ir) {
+Result<SuccessType> DecomposeStorageAccess(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "hlsl.DecomposeStorageAccess",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowClipDistancesOnF32,
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_storage_access.h b/src/tint/lang/hlsl/writer/raise/decompose_storage_access.h
index 3c5c657..5086bf9 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_storage_access.h
+++ b/src/tint/lang/hlsl/writer/raise/decompose_storage_access.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_HLSL_WRITER_RAISE_DECOMPOSE_STORAGE_ACCESS_H_
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_DECOMPOSE_STORAGE_ACCESS_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 ///
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> DecomposeStorageAccess(core::ir::Module& module);
+Result<SuccessType> DecomposeStorageAccess(core::ir::Module& module);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.cc b/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.cc
index 27c680f..57e8d3b 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.cc
@@ -616,7 +616,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> DecomposeUniformAccess(core::ir::Module& ir) {
+Result<SuccessType> DecomposeUniformAccess(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "hlsl.DecomposeUniformAccess",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowClipDistancesOnF32,
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.h b/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.h
index 9473206..c8ed021 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.h
+++ b/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_HLSL_WRITER_RAISE_DECOMPOSE_UNIFORM_ACCESS_H_
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_DECOMPOSE_UNIFORM_ACCESS_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 ///
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> DecomposeUniformAccess(core::ir::Module& module);
+Result<SuccessType> DecomposeUniformAccess(core::ir::Module& module);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/raise/localize_struct_array_assignment.cc b/src/tint/lang/hlsl/writer/raise/localize_struct_array_assignment.cc
index d82ac92..f8002b9 100644
--- a/src/tint/lang/hlsl/writer/raise/localize_struct_array_assignment.cc
+++ b/src/tint/lang/hlsl/writer/raise/localize_struct_array_assignment.cc
@@ -156,7 +156,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> LocalizeStructArrayAssignment(core::ir::Module& ir) {
+Result<SuccessType> LocalizeStructArrayAssignment(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "hlsl.LocalizeStructArrayAssignment");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/hlsl/writer/raise/localize_struct_array_assignment.h b/src/tint/lang/hlsl/writer/raise/localize_struct_array_assignment.h
index 87587ef..b5d2e98 100644
--- a/src/tint/lang/hlsl/writer/raise/localize_struct_array_assignment.h
+++ b/src/tint/lang/hlsl/writer/raise/localize_struct_array_assignment.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_HLSL_WRITER_RAISE_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -52,7 +52,7 @@
 
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> LocalizeStructArrayAssignment(core::ir::Module& module);
+Result<SuccessType> LocalizeStructArrayAssignment(core::ir::Module& module);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/raise/pixel_local.cc b/src/tint/lang/hlsl/writer/raise/pixel_local.cc
index d798847..02f64e9 100644
--- a/src/tint/lang/hlsl/writer/raise/pixel_local.cc
+++ b/src/tint/lang/hlsl/writer/raise/pixel_local.cc
@@ -241,7 +241,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> PixelLocal(core::ir::Module& ir, const PixelLocalConfig& config) {
+Result<SuccessType> PixelLocal(core::ir::Module& ir, const PixelLocalConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "hlsl.PixelLocal",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowClipDistancesOnF32,
diff --git a/src/tint/lang/hlsl/writer/raise/pixel_local.h b/src/tint/lang/hlsl/writer/raise/pixel_local.h
index 4f8e204..4a4247a 100644
--- a/src/tint/lang/hlsl/writer/raise/pixel_local.h
+++ b/src/tint/lang/hlsl/writer/raise/pixel_local.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_PIXEL_LOCAL_H_
 
 #include "src/tint/lang/hlsl/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -46,7 +46,7 @@
 /// PixelLocal is a transform that implements the PixelLocal feature for HLSL.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> PixelLocal(core::ir::Module& module, const PixelLocalConfig& config);
+Result<SuccessType> PixelLocal(core::ir::Module& module, const PixelLocalConfig& config);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/raise/promote_initializers.cc b/src/tint/lang/hlsl/writer/raise/promote_initializers.cc
index 631ca1f..b5f5132 100644
--- a/src/tint/lang/hlsl/writer/raise/promote_initializers.cc
+++ b/src/tint/lang/hlsl/writer/raise/promote_initializers.cc
@@ -237,7 +237,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> PromoteInitializers(core::ir::Module& ir) {
+Result<SuccessType> PromoteInitializers(core::ir::Module& ir) {
     auto result =
         ValidateAndDumpIfNeeded(ir, "hlsl.PromoteInitializers", kPromoteInitializersCapabilities);
     if (result != Success) {
diff --git a/src/tint/lang/hlsl/writer/raise/promote_initializers.h b/src/tint/lang/hlsl/writer/raise/promote_initializers.h
index d1845a1..c6dbde1 100644
--- a/src/tint/lang/hlsl/writer/raise/promote_initializers.h
+++ b/src/tint/lang/hlsl/writer/raise/promote_initializers.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_PROMOTE_INITIALIZERS_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -78,8 +78,8 @@
 /// ```
 ///
 /// @param module the module to transform
-/// @returns error diagnostics on failure
-diag::Result<SuccessType> PromoteInitializers(core::ir::Module& module);
+/// @returns error on failure
+Result<SuccessType> PromoteInitializers(core::ir::Module& module);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/raise/promote_initializers_fuzz.cc b/src/tint/lang/hlsl/writer/raise/promote_initializers_fuzz.cc
index 2a63ede..59d7a28 100644
--- a/src/tint/lang/hlsl/writer/raise/promote_initializers_fuzz.cc
+++ b/src/tint/lang/hlsl/writer/raise/promote_initializers_fuzz.cc
@@ -35,11 +35,7 @@
 namespace {
 
 Result<SuccessType> PromoteInitializersFuzzer(core::ir::Module& ir, const fuzz::ir::Context&) {
-    auto res = PromoteInitializers(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return PromoteInitializers(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/hlsl/writer/raise/raise.cc b/src/tint/lang/hlsl/writer/raise/raise.cc
index 06b77dc..298a7af 100644
--- a/src/tint/lang/hlsl/writer/raise/raise.cc
+++ b/src/tint/lang/hlsl/writer/raise/raise.cc
@@ -62,7 +62,7 @@
 
 namespace tint::hlsl::writer {
 
-diag::Result<SuccessType> Raise(core::ir::Module& module, const Options& options) {
+Result<SuccessType> Raise(core::ir::Module& module, const Options& options) {
 #define RUN_TRANSFORM(name, ...)         \
     do {                                 \
         auto result = name(__VA_ARGS__); \
diff --git a/src/tint/lang/hlsl/writer/raise/raise.h b/src/tint/lang/hlsl/writer/raise/raise.h
index ab9cc8b..40bd42e 100644
--- a/src/tint/lang/hlsl/writer/raise/raise.h
+++ b/src/tint/lang/hlsl/writer/raise/raise.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_RAISE_H_
 
 #include "src/tint/lang/hlsl/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// @param module the core IR module to raise to HLSL dialect
 /// @param options the printer options
 /// @returns success or failure
-diag::Result<SuccessType> Raise(core::ir::Module& module, const Options& options);
+Result<SuccessType> Raise(core::ir::Module& module, const Options& options);
 
 }  // namespace tint::hlsl::writer
 
diff --git a/src/tint/lang/hlsl/writer/raise/replace_default_only_switch.cc b/src/tint/lang/hlsl/writer/raise/replace_default_only_switch.cc
index dd60c50..558ffb3 100644
--- a/src/tint/lang/hlsl/writer/raise/replace_default_only_switch.cc
+++ b/src/tint/lang/hlsl/writer/raise/replace_default_only_switch.cc
@@ -97,7 +97,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ReplaceDefaultOnlySwitch(core::ir::Module& ir) {
+Result<SuccessType> ReplaceDefaultOnlySwitch(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "hlsl.ReplaceDefaultOnlySwitch");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/hlsl/writer/raise/replace_default_only_switch.h b/src/tint/lang/hlsl/writer/raise/replace_default_only_switch.h
index 33db4b3..fe99b81 100644
--- a/src/tint/lang/hlsl/writer/raise/replace_default_only_switch.h
+++ b/src/tint/lang/hlsl/writer/raise/replace_default_only_switch.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_HLSL_WRITER_RAISE_REPLACE_DEFAULT_ONLY_SWITCH_H_
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_REPLACE_DEFAULT_ONLY_SWITCH_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// it to miscompile default-only-switch statements. See crbug.com/tint/1188.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> ReplaceDefaultOnlySwitch(core::ir::Module& module);
+Result<SuccessType> ReplaceDefaultOnlySwitch(core::ir::Module& module);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/raise/replace_non_indexable_mat_vec_stores.cc b/src/tint/lang/hlsl/writer/raise/replace_non_indexable_mat_vec_stores.cc
index 9fb24ef..5d6083a 100644
--- a/src/tint/lang/hlsl/writer/raise/replace_non_indexable_mat_vec_stores.cc
+++ b/src/tint/lang/hlsl/writer/raise/replace_non_indexable_mat_vec_stores.cc
@@ -285,7 +285,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ReplaceNonIndexableMatVecStores(core::ir::Module& ir) {
+Result<SuccessType> ReplaceNonIndexableMatVecStores(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "hlsl.ReplaceNonIndexableMatVecStores");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/hlsl/writer/raise/replace_non_indexable_mat_vec_stores.h b/src/tint/lang/hlsl/writer/raise/replace_non_indexable_mat_vec_stores.h
index 4a9bd5e..b93e591 100644
--- a/src/tint/lang/hlsl/writer/raise/replace_non_indexable_mat_vec_stores.h
+++ b/src/tint/lang/hlsl/writer/raise/replace_non_indexable_mat_vec_stores.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_HLSL_WRITER_RAISE_REPLACE_NON_INDEXABLE_MAT_VEC_STORES_H_
 #define SRC_TINT_LANG_HLSL_WRITER_RAISE_REPLACE_NON_INDEXABLE_MAT_VEC_STORES_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -50,7 +50,7 @@
 
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> ReplaceNonIndexableMatVecStores(core::ir::Module& module);
+Result<SuccessType> ReplaceNonIndexableMatVecStores(core::ir::Module& module);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/raise/shader_io.cc b/src/tint/lang/hlsl/writer/raise/shader_io.cc
index 9b94cc7..82ba776 100644
--- a/src/tint/lang/hlsl/writer/raise/shader_io.cc
+++ b/src/tint/lang/hlsl/writer/raise/shader_io.cc
@@ -523,7 +523,7 @@
 };
 }  // namespace
 
-diag::Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
+Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "hlsl.ShaderIO");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/hlsl/writer/raise/shader_io.h b/src/tint/lang/hlsl/writer/raise/shader_io.h
index fc21687..f8e0d5a 100644
--- a/src/tint/lang/hlsl/writer/raise/shader_io.h
+++ b/src/tint/lang/hlsl/writer/raise/shader_io.h
@@ -32,7 +32,7 @@
 #include <optional>
 
 #include "src/tint/api/common/binding_point.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -70,7 +70,7 @@
 /// entry point parameter, and all outputs are wrapped in a struct and returned by the entry point.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> ShaderIO(core::ir::Module& module, const ShaderIOConfig& config);
+Result<SuccessType> ShaderIO(core::ir::Module& module, const ShaderIOConfig& config);
 
 }  // namespace tint::hlsl::writer::raise
 
diff --git a/src/tint/lang/hlsl/writer/writer.cc b/src/tint/lang/hlsl/writer/writer.cc
index 6e219b8..f14bf00 100644
--- a/src/tint/lang/hlsl/writer/writer.cc
+++ b/src/tint/lang/hlsl/writer/writer.cc
@@ -41,11 +41,11 @@
 
 namespace tint::hlsl::writer {
 
-diag::Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options) {
+Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options) {
     // Check for unsupported types.
     for (auto* ty : ir.Types()) {
         if (ty->Is<core::type::SubgroupMatrix>()) {
-            return diag::Failure("subgroup matrices are not supported by the HLSL backend");
+            return Failure("subgroup matrices are not supported by the HLSL backend");
         }
     }
 
@@ -54,26 +54,26 @@
         auto* var = inst->As<core::ir::Var>();
         auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>();
         if (ptr->AddressSpace() == core::AddressSpace::kPushConstant) {
-            return diag::Failure("push constants are not supported by the HLSL backend");
+            return Failure("push constants are not supported by the HLSL backend");
         }
         if (ptr->AddressSpace() == core::AddressSpace::kPixelLocal) {
             // Check the pixel_local variables have corresponding entries in the PLS attachment map.
             auto* str = ptr->StoreType()->As<core::type::Struct>();
             for (uint32_t i = 0; i < str->Members().Length(); i++) {
                 if (options.pixel_local.attachments.count(i) == 0) {
-                    return diag::Failure("missing pixel local attachment for member index " +
-                                         std::to_string(i));
+                    return Failure("missing pixel local attachment for member index " +
+                                   std::to_string(i));
                 }
             }
         }
         if (ptr->StoreType()->Is<core::type::InputAttachment>()) {
-            return diag::Failure("input attachments are not supported by the HLSL backend");
+            return Failure("input attachments are not supported by the HLSL backend");
         }
     }
     return Success;
 }
 
-diag::Result<Output> Generate(core::ir::Module& ir, const Options& options) {
+Result<Output> Generate(core::ir::Module& ir, const Options& options) {
     // Raise the core-dialect to HLSL-dialect
     auto res = Raise(ir, options);
     if (res != Success) {
@@ -83,21 +83,21 @@
     return Print(ir, options);
 }
 
-diag::Result<Output> Generate(const Program& program, const Options& options) {
+Result<Output> Generate(const Program& program, const Options& options) {
     if (!program.IsValid()) {
-        return diag::Failure{program.Diagnostics()};
+        return Failure{program.Diagnostics().Str()};
     }
 
     // Sanitize the program.
     auto sanitized_result = Sanitize(program, options);
     if (!sanitized_result.program.IsValid()) {
-        return diag::Failure{sanitized_result.program.Diagnostics()};
+        return Failure{sanitized_result.program.Diagnostics().Str()};
     }
 
     // Generate the HLSL code.
     auto impl = std::make_unique<ASTPrinter>(sanitized_result.program);
     if (!impl->Generate()) {
-        return diag::Failure{impl->Diagnostics()};
+        return Failure{impl->Diagnostics().Str()};
     }
 
     Output output;
diff --git a/src/tint/lang/hlsl/writer/writer.h b/src/tint/lang/hlsl/writer/writer.h
index 706a493..bb1a7f2 100644
--- a/src/tint/lang/hlsl/writer/writer.h
+++ b/src/tint/lang/hlsl/writer/writer.h
@@ -30,7 +30,7 @@
 
 #include "src/tint/lang/hlsl/writer/common/options.h"
 #include "src/tint/lang/hlsl/writer/common/output.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint {
@@ -46,21 +46,21 @@
 /// @param ir the module
 /// @param options the writer options
 /// @returns Success or a failure message indicating why HLSL generation would fail
-diag::Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options);
+Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options);
 
 /// Generate HLSL for a program, according to a set of configuration options.
 /// The result will contain the HLSL and supplementary information, or failure.
 /// @param ir the IR module to translate to HLSL
 /// @param options the configuration options to use when generating HLSL
 /// @returns the resulting HLSL and supplementary information, or failure
-diag::Result<Output> Generate(core::ir::Module& ir, const Options& options);
+Result<Output> Generate(core::ir::Module& ir, const Options& options);
 
 /// Generate HLSL for a program, according to a set of configuration options.
 /// The result will contain the HLSL and supplementary information, or failure.
 /// @param program the program to translate to HLSL
 /// @param options the configuration options to use when generating HLSL
 /// @returns the resulting HLSL and supplementary information, or failure
-diag::Result<Output> Generate(const Program& program, const Options& options);
+Result<Output> Generate(const Program& program, const Options& options);
 
 }  // namespace tint::hlsl::writer
 
diff --git a/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc b/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
index 4c8579d..222c64e 100644
--- a/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
+++ b/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
@@ -85,7 +85,7 @@
     }
     auto dxc = tint::Command::LookPath(dxc_path);
 
-    diag::Result<tint::hlsl::writer::Output> res;
+    Result<tint::hlsl::writer::Output> res;
     if (dxc.Found()) {
         // If validating with DXC, run renamer transform first to avoid DXC validation failures.
         ast::transform::DataMap inputs, outputs;
diff --git a/src/tint/lang/hlsl/writer/writer_bench.cc b/src/tint/lang/hlsl/writer/writer_bench.cc
index aca4ee5..331c00d 100644
--- a/src/tint/lang/hlsl/writer/writer_bench.cc
+++ b/src/tint/lang/hlsl/writer/writer_bench.cc
@@ -38,7 +38,7 @@
 void GenerateHLSL(benchmark::State& state, std::string input_name) {
     auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
-        state.SkipWithError(res.Failure().reason.Str());
+        state.SkipWithError(res.Failure().reason);
         return;
     }
     for (auto _ : state) {
@@ -53,7 +53,7 @@
         gen_options.bindings = GenerateBindings(res->program);
         auto gen_res = Generate(ir.Get(), gen_options);
         if (gen_res != Success) {
-            state.SkipWithError(gen_res.Failure().reason.Str());
+            state.SkipWithError(gen_res.Failure().reason);
         }
     }
 }
@@ -61,7 +61,7 @@
 void GenerateHLSL_AST(benchmark::State& state, std::string input_name) {
     auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
-        state.SkipWithError(res.Failure().reason.Str());
+        state.SkipWithError(res.Failure().reason);
         return;
     }
     for (auto _ : state) {
@@ -69,7 +69,7 @@
         gen_options.bindings = GenerateBindings(res->program);
         auto gen_res = Generate(res->program, gen_options);
         if (gen_res != Success) {
-            state.SkipWithError(gen_res.Failure().reason.Str());
+            state.SkipWithError(gen_res.Failure().reason);
         }
     }
 }
diff --git a/src/tint/lang/hlsl/writer/writer_fuzz.cc b/src/tint/lang/hlsl/writer/writer_fuzz.cc
index 9fb66b9..954a856 100644
--- a/src/tint/lang/hlsl/writer/writer_fuzz.cc
+++ b/src/tint/lang/hlsl/writer/writer_fuzz.cc
@@ -93,7 +93,7 @@
 
     auto check = CanGenerate(module, options);
     if (check != Success) {
-        return Failure{check.Failure().reason.Str()};
+        return check.Failure();
     }
 
     auto output = Generate(module, options);
diff --git a/src/tint/lang/msl/ir/binary_test.cc b/src/tint/lang/msl/ir/binary_test.cc
index 6410911..d51aa01 100644
--- a/src/tint/lang/msl/ir/binary_test.cc
+++ b/src/tint/lang/msl/ir/binary_test.cc
@@ -74,7 +74,7 @@
     core::ir::Capabilities caps;
     caps.Add(core::ir::Capability::kAllow8BitIntegers);
     auto res = core::ir::Validate(mod, caps);
-    EXPECT_EQ(res, Success) << res.Failure().reason.Str();
+    EXPECT_EQ(res, Success) << res.Failure().reason;
 }
 
 TEST_F(IR_MslBinaryTest, DoesNotMatchOverloadFromCore) {
@@ -88,7 +88,7 @@
 
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               R"(:3:5 error: binary: no matching overload for 'operator + (i32, i32)'
 
 1 candidate operator:
diff --git a/src/tint/lang/msl/ir/builtin_call_test.cc b/src/tint/lang/msl/ir/builtin_call_test.cc
index e962621..548d637 100644
--- a/src/tint/lang/msl/ir/builtin_call_test.cc
+++ b/src/tint/lang/msl/ir/builtin_call_test.cc
@@ -75,7 +75,7 @@
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         R"(:3:20 error: msl.sample: no matching call to 'msl.sample(texture_2d<f32>, sampler, vec2<f32>)'
 
     %5:vec4<f32> = msl.sample %t, %s, %coords
diff --git a/src/tint/lang/msl/ir/member_builtin_call_test.cc b/src/tint/lang/msl/ir/member_builtin_call_test.cc
index 043b8ca..533f6ee 100644
--- a/src/tint/lang/msl/ir/member_builtin_call_test.cc
+++ b/src/tint/lang/msl/ir/member_builtin_call_test.cc
@@ -83,7 +83,7 @@
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         R"(:3:17 error: atomic_load_explicit: no matching call to 'atomic_load_explicit(ptr<workgroup, atomic<u32>, read_write>, u32)'
 
     %3:u32 = %t.atomic_load_explicit 0u
@@ -128,7 +128,7 @@
 
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               R"(:3:16 error: get_width: expected exactly 1 results, got 0
     undef = %t.get_width 0u
                ^^^^^^^^^
@@ -158,7 +158,7 @@
 
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
-    EXPECT_EQ(res.Failure().reason.Str(),
+    EXPECT_EQ(res.Failure().reason,
               R"(:3:17 error: get_width: no matching call to 'get_width(texture_2d<f32>)'
 
 16 candidate functions:
@@ -215,7 +215,7 @@
     auto res = core::ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(
-        res.Failure().reason.Str(),
+        res.Failure().reason,
         R"(:3:17 error: get_width: no matching call to 'get_width(texture_2d<f32>, u32, u32, u32)'
 
 16 candidate functions:
diff --git a/src/tint/lang/msl/writer/common/option_helpers.cc b/src/tint/lang/msl/writer/common/option_helpers.cc
index d4d184d..b21bf98 100644
--- a/src/tint/lang/msl/writer/common/option_helpers.cc
+++ b/src/tint/lang/msl/writer/common/option_helpers.cc
@@ -30,13 +30,14 @@
 #include <utility>
 
 #include "src/tint/utils/containers/hashmap.h"
+#include "src/tint/utils/diagnostic/diagnostic.h"
 
 namespace tint::msl::writer {
 
 /// binding::BindingInfo to tint::BindingPoint map
 using InfoToPointMap = tint::Hashmap<binding::BindingInfo, tint::BindingPoint, 8>;
 
-diag::Result<SuccessType> ValidateBindingOptions(const Options& options) {
+Result<SuccessType> ValidateBindingOptions(const Options& options) {
     diag::List diagnostics;
 
     tint::Hashmap<tint::BindingPoint, binding::BindingInfo, 8> seen_wgsl_bindings{};
@@ -88,27 +89,27 @@
     // Storage and uniform are both [[buffer()]]
     if (!valid(seen_msl_buffer_bindings, options.bindings.uniform)) {
         diagnostics.AddNote(Source{}) << "when processing uniform";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
     if (!valid(seen_msl_buffer_bindings, options.bindings.storage)) {
         diagnostics.AddNote(Source{}) << "when processing storage";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
 
     // Sampler is [[sampler()]]
     if (!valid(seen_msl_sampler_bindings, options.bindings.sampler)) {
         diagnostics.AddNote(Source{}) << "when processing sampler";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
 
     // Texture and storage texture are [[texture()]]
     if (!valid(seen_msl_texture_bindings, options.bindings.texture)) {
         diagnostics.AddNote(Source{}) << "when processing texture";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
     if (!valid(seen_msl_texture_bindings, options.bindings.storage_texture)) {
         diagnostics.AddNote(Source{}) << "when processing storage_texture";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
 
     for (const auto& it : options.bindings.external_texture) {
@@ -120,22 +121,22 @@
         // Validate with the actual source regardless of what the remapper will do
         if (wgsl_seen(src_binding, plane0)) {
             diagnostics.AddNote(Source{}) << "when processing external_texture";
-            return diag::Failure{std::move(diagnostics)};
+            return Failure{diagnostics.Str()};
         }
 
         // Plane0 & Plane1 are [[texture()]]
         if (msl_seen(seen_msl_texture_bindings, plane0, src_binding)) {
             diagnostics.AddNote(Source{}) << "when processing external_texture";
-            return diag::Failure{std::move(diagnostics)};
+            return Failure{diagnostics.Str()};
         }
         if (msl_seen(seen_msl_texture_bindings, plane1, src_binding)) {
             diagnostics.AddNote(Source{}) << "when processing external_texture";
-            return diag::Failure{std::move(diagnostics)};
+            return Failure{diagnostics.Str()};
         }
         // Metadata is [[buffer()]]
         if (msl_seen(seen_msl_buffer_bindings, metadata, src_binding)) {
             diagnostics.AddNote(Source{}) << "when processing external_texture";
-            return diag::Failure{std::move(diagnostics)};
+            return Failure{diagnostics.Str()};
         }
     }
 
diff --git a/src/tint/lang/msl/writer/common/option_helpers.h b/src/tint/lang/msl/writer/common/option_helpers.h
index 570915e..a0a1c06 100644
--- a/src/tint/lang/msl/writer/common/option_helpers.h
+++ b/src/tint/lang/msl/writer/common/option_helpers.h
@@ -33,7 +33,7 @@
 #include "src/tint/api/common/binding_point.h"
 #include "src/tint/lang/core/common/multiplanar_options.h"
 #include "src/tint/lang/msl/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 namespace tint::msl::writer {
 
@@ -42,7 +42,7 @@
 
 /// @param options the options
 /// @returns success or failure
-diag::Result<SuccessType> ValidateBindingOptions(const Options& options);
+Result<SuccessType> ValidateBindingOptions(const Options& options);
 
 /// Populates binding-related option from the writer options
 /// @param options the writer options
diff --git a/src/tint/lang/msl/writer/helper_test.h b/src/tint/lang/msl/writer/helper_test.h
index 38fc1f4..4c06e96 100644
--- a/src/tint/lang/msl/writer/helper_test.h
+++ b/src/tint/lang/msl/writer/helper_test.h
@@ -86,7 +86,7 @@
         [[maybe_unused]] validate::MslVersion msl_version = validate::MslVersion::kMsl_2_3) {
         auto result = writer::Generate(mod, options);
         if (result != Success) {
-            err_ = result.Failure().reason.Str();
+            err_ = result.Failure().reason;
             return false;
         }
         output_ = result.Get();
@@ -101,7 +101,7 @@
                [[maybe_unused]] validate::MslVersion msl_version = validate::MslVersion::kMsl_2_3) {
         auto result = writer::Print(mod, options);
         if (result != Success) {
-            err_ = result.Failure().reason.Str();
+            err_ = result.Failure().reason;
             return false;
         }
         output_ = result.Get();
diff --git a/src/tint/lang/msl/writer/printer/printer.cc b/src/tint/lang/msl/writer/printer/printer.cc
index 6285604..ddd1054 100644
--- a/src/tint/lang/msl/writer/printer/printer.cc
+++ b/src/tint/lang/msl/writer/printer/printer.cc
@@ -123,7 +123,7 @@
         : ir_(module), options_(options) {}
 
     /// @returns the generated MSL shader
-    diag::Result<Output> Generate() {
+    tint::Result<Output> Generate() {
         auto valid = core::ir::ValidateAndDumpIfNeeded(
             ir_, "msl.Printer",
             core::ir::Capabilities{
@@ -2114,7 +2114,7 @@
 
 }  // namespace
 
-diag::Result<Output> Print(core::ir::Module& module, const Options& options) {
+Result<Output> Print(core::ir::Module& module, const Options& options) {
     return Printer{module, options}.Generate();
 }
 
diff --git a/src/tint/lang/msl/writer/printer/printer.h b/src/tint/lang/msl/writer/printer/printer.h
index 9d89a948..956722b 100644
--- a/src/tint/lang/msl/writer/printer/printer.h
+++ b/src/tint/lang/msl/writer/printer/printer.h
@@ -30,7 +30,7 @@
 
 #include "src/tint/lang/msl/writer/common/options.h"
 #include "src/tint/lang/msl/writer/common/output.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 
 /// @param module the Tint IR module to generate
 /// @returns the result of printing the MSL shader on success, or failure
-diag::Result<Output> Print(core::ir::Module& module, const Options& options);
+Result<Output> Print(core::ir::Module& module, const Options& options);
 
 }  // namespace tint::msl::writer
 
diff --git a/src/tint/lang/msl/writer/raise/binary_polyfill.cc b/src/tint/lang/msl/writer/raise/binary_polyfill.cc
index d555e93..c8912ab 100644
--- a/src/tint/lang/msl/writer/raise/binary_polyfill.cc
+++ b/src/tint/lang/msl/writer/raise/binary_polyfill.cc
@@ -154,7 +154,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BinaryPolyfill(core::ir::Module& ir) {
+Result<SuccessType> BinaryPolyfill(core::ir::Module& ir) {
     auto result =
         ValidateAndDumpIfNeeded(ir, "msl.BinaryPolyfill",
                                 core::ir::Capabilities{
diff --git a/src/tint/lang/msl/writer/raise/binary_polyfill.h b/src/tint/lang/msl/writer/raise/binary_polyfill.h
index 7aac7cf..016e1aa 100644
--- a/src/tint/lang/msl/writer/raise/binary_polyfill.h
+++ b/src/tint/lang/msl/writer/raise/binary_polyfill.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_MSL_WRITER_RAISE_BINARY_POLYFILL_H_
 #define SRC_TINT_LANG_MSL_WRITER_RAISE_BINARY_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// MSL backend intrinsic functions.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> BinaryPolyfill(core::ir::Module& module);
+Result<SuccessType> BinaryPolyfill(core::ir::Module& module);
 
 }  // namespace tint::msl::writer::raise
 
diff --git a/src/tint/lang/msl/writer/raise/builtin_polyfill.cc b/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
index c40e988..a61b20a 100644
--- a/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
@@ -1058,7 +1058,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
     auto result =
         ValidateAndDumpIfNeeded(ir, "msl.BuiltinPolyfill",
                                 core::ir::Capabilities{
diff --git a/src/tint/lang/msl/writer/raise/builtin_polyfill.h b/src/tint/lang/msl/writer/raise/builtin_polyfill.h
index 9748d97..ae34360 100644
--- a/src/tint/lang/msl/writer/raise/builtin_polyfill.h
+++ b/src/tint/lang/msl/writer/raise/builtin_polyfill.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_MSL_WRITER_RAISE_BUILTIN_POLYFILL_H_
 #define SRC_TINT_LANG_MSL_WRITER_RAISE_BUILTIN_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// MSL backend intrinsic functions.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> BuiltinPolyfill(core::ir::Module& module);
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& module);
 
 }  // namespace tint::msl::writer::raise
 
diff --git a/src/tint/lang/msl/writer/raise/module_scope_vars.cc b/src/tint/lang/msl/writer/raise/module_scope_vars.cc
index a76faf3..c79eaea 100644
--- a/src/tint/lang/msl/writer/raise/module_scope_vars.cc
+++ b/src/tint/lang/msl/writer/raise/module_scope_vars.cc
@@ -341,7 +341,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ModuleScopeVars(core::ir::Module& ir) {
+Result<SuccessType> ModuleScopeVars(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "msl.ModuleScopeVars");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/msl/writer/raise/module_scope_vars.h b/src/tint/lang/msl/writer/raise/module_scope_vars.h
index c422141..8df51b4 100644
--- a/src/tint/lang/msl/writer/raise/module_scope_vars.h
+++ b/src/tint/lang/msl/writer/raise/module_scope_vars.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_MSL_WRITER_RAISE_MODULE_SCOPE_VARS_H_
 
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// declarations that are wrapped in a structure and passed to functions that need them.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> ModuleScopeVars(core::ir::Module& module);
+Result<SuccessType> ModuleScopeVars(core::ir::Module& module);
 
 }  // namespace tint::msl::writer::raise
 
diff --git a/src/tint/lang/msl/writer/raise/packed_vec3.cc b/src/tint/lang/msl/writer/raise/packed_vec3.cc
index cb34b06..9e1fc3f 100644
--- a/src/tint/lang/msl/writer/raise/packed_vec3.cc
+++ b/src/tint/lang/msl/writer/raise/packed_vec3.cc
@@ -639,7 +639,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> PackedVec3(core::ir::Module& ir) {
+Result<SuccessType> PackedVec3(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "msl.PackedVec3");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/msl/writer/raise/packed_vec3.h b/src/tint/lang/msl/writer/raise/packed_vec3.h
index 2a980ed..437102c 100644
--- a/src/tint/lang/msl/writer/raise/packed_vec3.h
+++ b/src/tint/lang/msl/writer/raise/packed_vec3.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_MSL_WRITER_RAISE_PACKED_VEC3_H_
 #define SRC_TINT_LANG_MSL_WRITER_RAISE_PACKED_VEC3_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -52,7 +52,7 @@
 ///
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> PackedVec3(core::ir::Module& module);
+Result<SuccessType> PackedVec3(core::ir::Module& module);
 
 }  // namespace tint::msl::writer::raise
 
diff --git a/src/tint/lang/msl/writer/raise/raise.cc b/src/tint/lang/msl/writer/raise/raise.cc
index 88145d6..8758166 100644
--- a/src/tint/lang/msl/writer/raise/raise.cc
+++ b/src/tint/lang/msl/writer/raise/raise.cc
@@ -56,7 +56,7 @@
 
 namespace tint::msl::writer {
 
-diag::Result<RaiseResult> Raise(core::ir::Module& module, const Options& options) {
+Result<RaiseResult> Raise(core::ir::Module& module, const Options& options) {
 #define RUN_TRANSFORM(name, ...)         \
     do {                                 \
         auto result = name(__VA_ARGS__); \
diff --git a/src/tint/lang/msl/writer/raise/raise.h b/src/tint/lang/msl/writer/raise/raise.h
index 97a24e5..cc7b664 100644
--- a/src/tint/lang/msl/writer/raise/raise.h
+++ b/src/tint/lang/msl/writer/raise/raise.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_MSL_WRITER_RAISE_RAISE_H_
 
 #include "src/tint/lang/msl/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -48,7 +48,7 @@
 /// @param module the core IR module to raise to MSL dialect
 /// @param options the printer options
 /// @returns success or failure
-diag::Result<RaiseResult> Raise(core::ir::Module& module, const Options& options);
+Result<RaiseResult> Raise(core::ir::Module& module, const Options& options);
 
 }  // namespace tint::msl::writer
 
diff --git a/src/tint/lang/msl/writer/raise/shader_io.cc b/src/tint/lang/msl/writer/raise/shader_io.cc
index 6aa2fcf..3d4603c 100644
--- a/src/tint/lang/msl/writer/raise/shader_io.cc
+++ b/src/tint/lang/msl/writer/raise/shader_io.cc
@@ -241,7 +241,7 @@
 };
 }  // namespace
 
-diag::Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
+Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "msl.ShaderIO");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/msl/writer/raise/shader_io.h b/src/tint/lang/msl/writer/raise/shader_io.h
index 24a6071..5d1f13a 100644
--- a/src/tint/lang/msl/writer/raise/shader_io.h
+++ b/src/tint/lang/msl/writer/raise/shader_io.h
@@ -28,7 +28,9 @@
 #ifndef SRC_TINT_LANG_MSL_WRITER_RAISE_SHADER_IO_H_
 #define SRC_TINT_LANG_MSL_WRITER_RAISE_SHADER_IO_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include <cstdint>
+
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -49,7 +51,7 @@
 /// @param module the module to transform
 /// @param config the configuration
 /// @returns success or failure
-diag::Result<SuccessType> ShaderIO(core::ir::Module& module, const ShaderIOConfig& config);
+Result<SuccessType> ShaderIO(core::ir::Module& module, const ShaderIOConfig& config);
 
 }  // namespace tint::msl::writer::raise
 
diff --git a/src/tint/lang/msl/writer/raise/simd_ballot.cc b/src/tint/lang/msl/writer/raise/simd_ballot.cc
index 1f13ecf..27283c6 100644
--- a/src/tint/lang/msl/writer/raise/simd_ballot.cc
+++ b/src/tint/lang/msl/writer/raise/simd_ballot.cc
@@ -157,7 +157,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> SimdBallot(core::ir::Module& ir) {
+Result<SuccessType> SimdBallot(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "msl.SimdBallot");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/msl/writer/raise/simd_ballot.h b/src/tint/lang/msl/writer/raise/simd_ballot.h
index 7db9b2d..641d673 100644
--- a/src/tint/lang/msl/writer/raise/simd_ballot.h
+++ b/src/tint/lang/msl/writer/raise/simd_ballot.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_MSL_WRITER_RAISE_SIMD_BALLOT_H_
 #define SRC_TINT_LANG_MSL_WRITER_RAISE_SIMD_BALLOT_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// adding conversion and masking operations to produce the correct result.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> SimdBallot(core::ir::Module& module);
+Result<SuccessType> SimdBallot(core::ir::Module& module);
 
 }  // namespace tint::msl::writer::raise
 
diff --git a/src/tint/lang/msl/writer/raise/unary_polyfill.cc b/src/tint/lang/msl/writer/raise/unary_polyfill.cc
index b012394..c777c66 100644
--- a/src/tint/lang/msl/writer/raise/unary_polyfill.cc
+++ b/src/tint/lang/msl/writer/raise/unary_polyfill.cc
@@ -85,7 +85,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> UnaryPolyfill(core::ir::Module& ir) {
+Result<SuccessType> UnaryPolyfill(core::ir::Module& ir) {
     auto result =
         ValidateAndDumpIfNeeded(ir, "msl.UnaryPolyfill",
                                 core::ir::Capabilities{
diff --git a/src/tint/lang/msl/writer/raise/unary_polyfill.h b/src/tint/lang/msl/writer/raise/unary_polyfill.h
index 29f3d32..6d2820a 100644
--- a/src/tint/lang/msl/writer/raise/unary_polyfill.h
+++ b/src/tint/lang/msl/writer/raise/unary_polyfill.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_MSL_WRITER_RAISE_UNARY_POLYFILL_H_
 #define SRC_TINT_LANG_MSL_WRITER_RAISE_UNARY_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// backend intrinsic functions.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> UnaryPolyfill(core::ir::Module& module);
+Result<SuccessType> UnaryPolyfill(core::ir::Module& module);
 
 }  // namespace tint::msl::writer::raise
 
diff --git a/src/tint/lang/msl/writer/writer.cc b/src/tint/lang/msl/writer/writer.cc
index 8901448..e8bff0e 100644
--- a/src/tint/lang/msl/writer/writer.cc
+++ b/src/tint/lang/msl/writer/writer.cc
@@ -39,13 +39,12 @@
 
 namespace tint::msl::writer {
 
-diag::Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options) {
+Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options) {
     // Check for unsupported types.
     for (auto* ty : ir.Types()) {
         if (auto* m = ty->As<core::type::SubgroupMatrix>()) {
             if (!m->Type()->IsAnyOf<core::type::F16, core::type::F32>()) {
-                return diag::Failure(
-                    "non-float subgroup matrices are not supported by the MSL backend");
+                return Failure("non-float subgroup matrices are not supported by the MSL backend");
             }
         }
     }
@@ -55,13 +54,13 @@
         auto* var = inst->As<core::ir::Var>();
         auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>();
         if (ptr->AddressSpace() == core::AddressSpace::kPushConstant) {
-            return diag::Failure("push constants are not supported by the MSL backend");
+            return Failure("push constants are not supported by the MSL backend");
         }
         if (ptr->AddressSpace() == core::AddressSpace::kPixelLocal) {
-            return diag::Failure("pixel_local address space is not supported by the MSL backend");
+            return Failure("pixel_local address space is not supported by the MSL backend");
         }
         if (ptr->StoreType()->Is<core::type::InputAttachment>()) {
-            return diag::Failure("input attachments are not supported by the MSL backend");
+            return Failure("input attachments are not supported by the MSL backend");
         }
     }
 
@@ -72,27 +71,25 @@
         for (auto& func : ir.functions) {
             if (func->IsVertex()) {
                 if (ep) {
-                    return diag::Failure(
-                        "vertex pulling config provided with multiple vertex shaders");
+                    return Failure("vertex pulling config provided with multiple vertex shaders");
                 }
                 ep = func;
             }
         }
         if (!ep) {
-            return diag::Failure("vertex pulling config provided without a vertex shader");
+            return Failure("vertex pulling config provided without a vertex shader");
         }
 
         // Gather all of the vertex attribute locations in the config.
         Hashset<uint32_t, 4> locations;
         for (auto& buffer : options.vertex_pulling_config->vertex_state) {
             if (buffer.array_stride & 3) {
-                return diag::Failure(
+                return Failure(
                     "vertex pulling config contains array stride that is not a multiple of 4");
             }
             for (auto& attr : buffer.attributes) {
                 if (!locations.Add(attr.shader_location)) {
-                    return diag::Failure(
-                        "vertex pulling config contains duplicate shader locations");
+                    return Failure("vertex pulling config contains duplicate shader locations");
                 }
             }
         }
@@ -103,15 +100,15 @@
                 for (auto* member : str->Members()) {
                     if (auto loc = member->Attributes().location) {
                         if (!locations.Contains(*loc)) {
-                            return diag::Failure("shader location " + std::to_string(*loc) +
-                                                 " missing from vertex pulling map");
+                            return Failure("shader location " + std::to_string(*loc) +
+                                           " missing from vertex pulling map");
                         }
                     }
                 }
             } else if (auto loc = param->Location()) {
                 if (!locations.Contains(*loc)) {
-                    return diag::Failure("shader location " + std::to_string(*loc) +
-                                         " missing from vertex pulling map");
+                    return Failure("shader location " + std::to_string(*loc) +
+                                   " missing from vertex pulling map");
                 }
             }
         }
@@ -120,7 +117,7 @@
     return Success;
 }
 
-diag::Result<Output> Generate(core::ir::Module& ir, const Options& options) {
+Result<Output> Generate(core::ir::Module& ir, const Options& options) {
     {
         auto res = ValidateBindingOptions(options);
         if (res != Success) {
diff --git a/src/tint/lang/msl/writer/writer.h b/src/tint/lang/msl/writer/writer.h
index 8f63cf4f..6e49c69 100644
--- a/src/tint/lang/msl/writer/writer.h
+++ b/src/tint/lang/msl/writer/writer.h
@@ -30,7 +30,7 @@
 
 #include "src/tint/lang/msl/writer/common/options.h"
 #include "src/tint/lang/msl/writer/common/output.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -43,14 +43,14 @@
 /// @param ir the module
 /// @param options the writer options
 /// @returns Success or a failure message indicating why MSL generation would fail
-diag::Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options);
+Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options);
 
 /// Generate MSL for a program, according to a set of configuration options.
 /// The result will contain the MSL and supplementary information, or failure.
 /// @param ir the IR module to translate to MSL
 /// @param options the configuration options to use when generating MSL
 /// @returns the resulting MSL and supplementary information, or failure
-diag::Result<Output> Generate(core::ir::Module& ir, const Options& options);
+Result<Output> Generate(core::ir::Module& ir, const Options& options);
 
 }  // namespace tint::msl::writer
 
diff --git a/src/tint/lang/msl/writer/writer_bench.cc b/src/tint/lang/msl/writer/writer_bench.cc
index fcf6866..2ceb366 100644
--- a/src/tint/lang/msl/writer/writer_bench.cc
+++ b/src/tint/lang/msl/writer/writer_bench.cc
@@ -41,7 +41,7 @@
 void GenerateMSL(benchmark::State& state, std::string input_name) {
     auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
-        state.SkipWithError(res.Failure().reason.Str());
+        state.SkipWithError(res.Failure().reason);
         return;
     }
 
@@ -90,7 +90,7 @@
 
         auto gen_res = Generate(ir.Get(), gen_options);
         if (gen_res != Success) {
-            state.SkipWithError(gen_res.Failure().reason.Str());
+            state.SkipWithError(gen_res.Failure().reason);
         }
     }
 }
diff --git a/src/tint/lang/msl/writer/writer_fuzz.cc b/src/tint/lang/msl/writer/writer_fuzz.cc
index 31c620b..ec397fe 100644
--- a/src/tint/lang/msl/writer/writer_fuzz.cc
+++ b/src/tint/lang/msl/writer/writer_fuzz.cc
@@ -59,7 +59,7 @@
 
     auto check = CanGenerate(module, options);
     if (check != Success) {
-        return Failure{check.Failure().reason.Str()};
+        return Failure{check.Failure().reason};
     }
 
     auto output = Generate(module, options);
diff --git a/src/tint/lang/spirv/reader/helper_test.h b/src/tint/lang/spirv/reader/helper_test.h
index 6997a61..bd17468 100644
--- a/src/tint/lang/spirv/reader/helper_test.h
+++ b/src/tint/lang/spirv/reader/helper_test.h
@@ -42,12 +42,12 @@
 
 // Helper macro to run the parser and compare the disassembled IR to a string.
 // Automatically prefixes the IR disassembly with a newline to improve formatting of tests.
-#define EXPECT_IR(asm, ir)                                           \
-    do {                                                             \
-        auto result = Run(asm);                                      \
-        ASSERT_EQ(result, Success) << result.Failure().reason.Str(); \
-        auto got = "\n" + result.Get();                              \
-        ASSERT_THAT(got, testing::HasSubstr(ir)) << got;             \
+#define EXPECT_IR(asm, ir)                                     \
+    do {                                                       \
+        auto result = Run(asm);                                \
+        ASSERT_EQ(result, Success) << result.Failure().reason; \
+        auto got = "\n" + result.Get();                        \
+        ASSERT_THAT(got, testing::HasSubstr(ir)) << got;       \
     } while (false)
 
 /// Base helper class for testing the SPIR-V parser implementation.
@@ -57,7 +57,7 @@
     /// Run the parser on a SPIR-V module and return the Tint IR or an error string.
     /// @param spirv_asm the SPIR-V assembly to parse
     /// @returns the disassembled Tint IR or an error
-    diag::Result<std::string> Run(std::string spirv_asm) {
+    Result<std::string> Run(std::string spirv_asm) {
         // Assemble the SPIR-V input.
         auto binary = Assemble(spirv_asm);
         if (binary != Success) {
diff --git a/src/tint/lang/spirv/reader/lower/builtins.cc b/src/tint/lang/spirv/reader/lower/builtins.cc
index 81cba14..5fe41de 100644
--- a/src/tint/lang/spirv/reader/lower/builtins.cc
+++ b/src/tint/lang/spirv/reader/lower/builtins.cc
@@ -1098,7 +1098,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> Builtins(core::ir::Module& ir) {
+Result<SuccessType> Builtins(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.Builtins",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowOverrides,
diff --git a/src/tint/lang/spirv/reader/lower/builtins.h b/src/tint/lang/spirv/reader/lower/builtins.h
index d56556f..eed84a1 100644
--- a/src/tint/lang/spirv/reader/lower/builtins.h
+++ b/src/tint/lang/spirv/reader/lower/builtins.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_READER_LOWER_BUILTINS_H_
 #define SRC_TINT_LANG_SPIRV_READER_LOWER_BUILTINS_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// core IR.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> Builtins(core::ir::Module& module);
+Result<SuccessType> Builtins(core::ir::Module& module);
 
 }  // namespace tint::spirv::reader::lower
 
diff --git a/src/tint/lang/spirv/reader/lower/lower.cc b/src/tint/lang/spirv/reader/lower/lower.cc
index bcff629..86d7241 100644
--- a/src/tint/lang/spirv/reader/lower/lower.cc
+++ b/src/tint/lang/spirv/reader/lower/lower.cc
@@ -35,7 +35,7 @@
 
 namespace tint::spirv::reader {
 
-diag::Result<SuccessType> Lower(core::ir::Module& mod) {
+Result<SuccessType> Lower(core::ir::Module& mod) {
 #define RUN_TRANSFORM(name, ...)         \
     do {                                 \
         auto result = name(__VA_ARGS__); \
diff --git a/src/tint/lang/spirv/reader/lower/lower.h b/src/tint/lang/spirv/reader/lower/lower.h
index dfb21ee..01c9b13 100644
--- a/src/tint/lang/spirv/reader/lower/lower.h
+++ b/src/tint/lang/spirv/reader/lower/lower.h
@@ -29,14 +29,14 @@
 #define SRC_TINT_LANG_SPIRV_READER_LOWER_LOWER_H_
 
 #include "src/tint/lang/core/ir/module.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 namespace tint::spirv::reader {
 
 /// Lower converts a SPIR-V-dialect IR module to a core-dialect IR module
 /// @param  mod the IR module
 /// @return the result of the operation
-diag::Result<SuccessType> Lower(core::ir::Module& mod);
+Result<SuccessType> Lower(core::ir::Module& mod);
 
 }  // namespace tint::spirv::reader
 
diff --git a/src/tint/lang/spirv/reader/lower/shader_io.cc b/src/tint/lang/spirv/reader/lower/shader_io.cc
index 73f54cd..38faf72 100644
--- a/src/tint/lang/spirv/reader/lower/shader_io.cc
+++ b/src/tint/lang/spirv/reader/lower/shader_io.cc
@@ -463,7 +463,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ShaderIO(core::ir::Module& ir) {
+Result<SuccessType> ShaderIO(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.ShaderIO",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowOverrides,
diff --git a/src/tint/lang/spirv/reader/lower/shader_io.h b/src/tint/lang/spirv/reader/lower/shader_io.h
index 1b028ab..58f83de 100644
--- a/src/tint/lang/spirv/reader/lower/shader_io.h
+++ b/src/tint/lang/spirv/reader/lower/shader_io.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_READER_LOWER_SHADER_IO_H_
 #define SRC_TINT_LANG_SPIRV_READER_LOWER_SHADER_IO_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// the form expected by Tint's core IR (using function parameters and return values).
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> ShaderIO(core::ir::Module& module);
+Result<SuccessType> ShaderIO(core::ir::Module& module);
 
 }  // namespace tint::spirv::reader::lower
 
diff --git a/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc b/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc
index 339ddcd..50c5140 100644
--- a/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc
+++ b/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc
@@ -150,7 +150,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> VectorElementPointer(core::ir::Module& ir) {
+Result<SuccessType> VectorElementPointer(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.VectorElementPointer",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowOverrides,
diff --git a/src/tint/lang/spirv/reader/lower/vector_element_pointer.h b/src/tint/lang/spirv/reader/lower/vector_element_pointer.h
index 538a600..397c710 100644
--- a/src/tint/lang/spirv/reader/lower/vector_element_pointer.h
+++ b/src/tint/lang/spirv/reader/lower/vector_element_pointer.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_READER_LOWER_VECTOR_ELEMENT_POINTER_H_
 #define SRC_TINT_LANG_SPIRV_READER_LOWER_VECTOR_ELEMENT_POINTER_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// instructions and their uses.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> VectorElementPointer(core::ir::Module& module);
+Result<SuccessType> VectorElementPointer(core::ir::Module& module);
 
 }  // namespace tint::spirv::reader::lower
 
diff --git a/src/tint/lang/spirv/reader/parser/helper_test.h b/src/tint/lang/spirv/reader/parser/helper_test.h
index 98bc003..1ce4ad3 100644
--- a/src/tint/lang/spirv/reader/parser/helper_test.h
+++ b/src/tint/lang/spirv/reader/parser/helper_test.h
@@ -46,7 +46,7 @@
 #define EXPECT_IR(asm, ir)                                                                  \
     do {                                                                                    \
         auto result = Run(asm);                                                             \
-        ASSERT_EQ(result, Success) << result.Failure().reason.Str();                        \
+        ASSERT_EQ(result, Success) << result.Failure().reason;                              \
         auto got = "\n" + result.Get();                                                     \
         ASSERT_THAT(got, testing::HasSubstr(ir)) << "GOT:\n" << got << "EXPECTED:\n" << ir; \
     } while (false)
@@ -58,7 +58,7 @@
     /// Run the parser on a SPIR-V module and return the Tint IR or an error string.
     /// @param spirv_asm the SPIR-V assembly to parse
     /// @returns the disassembled Tint IR or an error
-    diag::Result<std::string> Run(std::string spirv_asm) {
+    Result<std::string> Run(std::string spirv_asm) {
         // Assemble the SPIR-V input.
         auto binary = Assemble(spirv_asm);
         if (binary != Success) {
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 12aab47..32dd6f9 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -71,7 +71,7 @@
   public:
     /// @param spirv the SPIR-V binary data
     /// @returns the generated SPIR-V IR module on success, or failure
-    diag::Result<core::ir::Module> Run(Slice<const uint32_t> spirv) {
+    Result<core::ir::Module> Run(Slice<const uint32_t> spirv) {
         // Validate the incoming SPIR-V binary.
         auto result = validate::Validate(spirv, kTargetEnv);
         if (result != Success) {
@@ -83,7 +83,7 @@
         spirv_context_ =
             spvtools::BuildModule(kTargetEnv, context.CContext()->consumer, spirv.data, spirv.len);
         if (!spirv_context_) {
-            return diag::Failure("failed to build the internal representation of the module");
+            return Failure("failed to build the internal representation of the module");
         }
 
         // Check for unsupported extensions.
@@ -91,7 +91,7 @@
             auto name = ext.GetOperand(0).AsString();
             if (name != "SPV_KHR_storage_buffer_storage_class" &&
                 name != "SPV_KHR_non_semantic_info") {
-                return diag::Failure("SPIR-V extension '" + name + "' is not supported");
+                return Failure("SPIR-V extension '" + name + "' is not supported");
             }
         }
 
@@ -105,7 +105,7 @@
             } else if (name.find("NonSemantic.") == 0) {
                 ignored_imports_.insert(import.result_id());
             } else {
-                return diag::Failure("Unrecognized extended instruction set: " + name);
+                return Failure("Unrecognized extended instruction set: " + name);
             }
         }
 
@@ -2142,7 +2142,7 @@
 
 }  // namespace
 
-diag::Result<core::ir::Module> Parse(Slice<const uint32_t> spirv) {
+Result<core::ir::Module> Parse(Slice<const uint32_t> spirv) {
     return Parser{}.Run(spirv);
 }
 
diff --git a/src/tint/lang/spirv/reader/parser/parser.h b/src/tint/lang/spirv/reader/parser/parser.h
index 99ce561..d4c77f1 100644
--- a/src/tint/lang/spirv/reader/parser/parser.h
+++ b/src/tint/lang/spirv/reader/parser/parser.h
@@ -28,7 +28,10 @@
 #ifndef SRC_TINT_LANG_SPIRV_READER_PARSER_PARSER_H_
 #define SRC_TINT_LANG_SPIRV_READER_PARSER_PARSER_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include <cstdint>
+
+#include "src/tint/utils/containers/slice.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -40,7 +43,7 @@
 /// Parse a SPIR-V binary to produce a SPIR-V IR module.
 /// @param spirv the SPIR-V binary data
 /// @returns the SPIR-V IR module on success, or failure
-diag::Result<core::ir::Module> Parse(Slice<const uint32_t> spirv);
+Result<core::ir::Module> Parse(Slice<const uint32_t> spirv);
 
 }  // namespace tint::spirv::reader
 
diff --git a/src/tint/lang/spirv/reader/reader.cc b/src/tint/lang/spirv/reader/reader.cc
index 8038d4e..d70c16c 100644
--- a/src/tint/lang/spirv/reader/reader.cc
+++ b/src/tint/lang/spirv/reader/reader.cc
@@ -36,7 +36,7 @@
 
 namespace tint::spirv::reader {
 
-diag::Result<core::ir::Module> ReadIR(const std::vector<uint32_t>& input) {
+Result<core::ir::Module> ReadIR(const std::vector<uint32_t>& input) {
     // Parse the input SPIR-V to the SPIR-V dialect of the IR.
     auto mod = Parse(Slice(input.data(), input.size()));
     if (mod != Success) {
diff --git a/src/tint/lang/spirv/reader/reader.h b/src/tint/lang/spirv/reader/reader.h
index b74f503..71aae7a 100644
--- a/src/tint/lang/spirv/reader/reader.h
+++ b/src/tint/lang/spirv/reader/reader.h
@@ -41,11 +41,11 @@
 namespace tint::spirv::reader {
 
 /// Reads the SPIR-V source data, returning a core IR module.
-/// If the SPIR-V binary fails to parse then the result will contain diagnostic error messages.
+/// If the SPIR-V binary fails to parse then the result will contain error messages.
 /// TODO(crbug.com/tint/1907): Rename when we remove the AST path.
 /// @param input the SPIR-V binary data
 /// @returns the Tint IR module
-diag::Result<core::ir::Module> ReadIR(const std::vector<uint32_t>& input);
+Result<core::ir::Module> ReadIR(const std::vector<uint32_t>& input);
 
 /// Reads the SPIR-V source data, returning the parsed program.
 /// If the source data fails to parse then the returned
diff --git a/src/tint/lang/spirv/reader/reader_test.cc b/src/tint/lang/spirv/reader/reader_test.cc
index 5faa674..4a1ef8a 100644
--- a/src/tint/lang/spirv/reader/reader_test.cc
+++ b/src/tint/lang/spirv/reader/reader_test.cc
@@ -55,8 +55,8 @@
                OpFunctionEnd
 )");
     ASSERT_NE(got, Success);
-    EXPECT_EQ(got.Failure().reason.Str(),
-              "error: SPIR-V extension 'SPV_KHR_variable_pointers' is not supported");
+    EXPECT_EQ(got.Failure().reason,
+              "SPIR-V extension 'SPV_KHR_variable_pointers' is not supported");
 }
 
 TEST_F(SpirvReaderTest, Load_VectorComponent) {
diff --git a/src/tint/lang/spirv/validate/BUILD.bazel b/src/tint/lang/spirv/validate/BUILD.bazel
index e177a9f..42fd2c8 100644
--- a/src/tint/lang/spirv/validate/BUILD.bazel
+++ b/src/tint/lang/spirv/validate/BUILD.bazel
@@ -73,13 +73,11 @@
   deps = [
     "//src/tint/utils",
     "//src/tint/utils/containers",
-    "//src/tint/utils/diagnostic",
     "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
     "//src/tint/utils/memory",
     "//src/tint/utils/rtti",
-    "//src/tint/utils/text",
     "@gtest",
     "//src/utils",
   ] + select({
diff --git a/src/tint/lang/spirv/validate/BUILD.cmake b/src/tint/lang/spirv/validate/BUILD.cmake
index a073646..3ab6911 100644
--- a/src/tint/lang/spirv/validate/BUILD.cmake
+++ b/src/tint/lang/spirv/validate/BUILD.cmake
@@ -81,13 +81,11 @@
 tint_target_add_dependencies(tint_lang_spirv_validate_test test
   tint_utils
   tint_utils_containers
-  tint_utils_diagnostic
   tint_utils_ice
   tint_utils_macros
   tint_utils_math
   tint_utils_memory
   tint_utils_rtti
-  tint_utils_text
 )
 
 tint_target_add_external_dependencies(tint_lang_spirv_validate_test test
diff --git a/src/tint/lang/spirv/validate/BUILD.gn b/src/tint/lang/spirv/validate/BUILD.gn
index 77700e2..dadb222 100644
--- a/src/tint/lang/spirv/validate/BUILD.gn
+++ b/src/tint/lang/spirv/validate/BUILD.gn
@@ -78,13 +78,11 @@
         "${tint_src_dir}:gmock_and_gtest",
         "${tint_src_dir}/utils",
         "${tint_src_dir}/utils/containers",
-        "${tint_src_dir}/utils/diagnostic",
         "${tint_src_dir}/utils/ice",
         "${tint_src_dir}/utils/macros",
         "${tint_src_dir}/utils/math",
         "${tint_src_dir}/utils/memory",
         "${tint_src_dir}/utils/rtti",
-        "${tint_src_dir}/utils/text",
       ]
 
       if (tint_build_spv_reader || tint_build_spv_writer) {
diff --git a/src/tint/lang/spirv/validate/validate.cc b/src/tint/lang/spirv/validate/validate.cc
index 516d729..237e125 100644
--- a/src/tint/lang/spirv/validate/validate.cc
+++ b/src/tint/lang/spirv/validate/validate.cc
@@ -32,10 +32,11 @@
 #include <utility>
 
 #include "spirv-tools/libspirv.hpp"
+#include "src/tint/utils/diagnostic/diagnostic.h"
 
 namespace tint::spirv::validate {
 
-diag::Result<SuccessType> Validate(Slice<const uint32_t> spirv, spv_target_env target_env) {
+Result<SuccessType> Validate(Slice<const uint32_t> spirv, spv_target_env target_env) {
     Vector<diag::Diagnostic, 4> diags;
     diags.Push(diag::Diagnostic{});  // Filled in on error
 
@@ -90,7 +91,8 @@
         diag.source.file = file.get();
         diag.owned_file = file;
     }
-    return diag::Failure{diag::List{std::move(diags)}};
+    auto list = diag::List(diags);
+    return Failure{list.Str()};
 }
 
 }  // namespace tint::spirv::validate
diff --git a/src/tint/lang/spirv/validate/validate.h b/src/tint/lang/spirv/validate/validate.h
index 1983298..6e88423 100644
--- a/src/tint/lang/spirv/validate/validate.h
+++ b/src/tint/lang/spirv/validate/validate.h
@@ -29,7 +29,8 @@
 #define SRC_TINT_LANG_SPIRV_VALIDATE_VALIDATE_H_
 
 #include "spirv-tools/libspirv.hpp"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/containers/slice.h"
+#include "src/tint/utils/result.h"
 
 namespace tint::spirv::validate {
 
@@ -37,7 +38,7 @@
 /// @param spirv the SPIR-V binary data
 /// @param target_env the target environment to validate against
 /// @return success or failure(s)
-diag::Result<SuccessType> Validate(Slice<const uint32_t> spirv, spv_target_env target_env);
+Result<SuccessType> Validate(Slice<const uint32_t> spirv, spv_target_env target_env);
 
 }  // namespace tint::spirv::validate
 
diff --git a/src/tint/lang/spirv/validate/validate_test.cc b/src/tint/lang/spirv/validate/validate_test.cc
index 83dc3af..722ee4f 100644
--- a/src/tint/lang/spirv/validate/validate_test.cc
+++ b/src/tint/lang/spirv/validate/validate_test.cc
@@ -70,7 +70,7 @@
     };
     auto res = Validate(spirv, SPV_ENV_VULKAN_1_3);
     ASSERT_NE(res, Success);
-    auto got = res.Failure().reason.Str();
+    auto got = res.Failure().reason;
     EXPECT_THAT(got, testing::HasSubstr("spirv error: SPIR-V failed validation."));
     EXPECT_THAT(got, testing::HasSubstr("error: Initializer type must match"));
 }
diff --git a/src/tint/lang/spirv/writer/common/helper_test.h b/src/tint/lang/spirv/writer/common/helper_test.h
index b7ebaf8..68949cb 100644
--- a/src/tint/lang/spirv/writer/common/helper_test.h
+++ b/src/tint/lang/spirv/writer/common/helper_test.h
@@ -111,7 +111,7 @@
     bool Generate(Options options = {}) {
         auto result = writer::Generate(mod, options);
         if (result != Success) {
-            err_ = result.Failure().reason.Str();
+            err_ = result.Failure().reason;
             return false;
         }
 
diff --git a/src/tint/lang/spirv/writer/common/option_helper.cc b/src/tint/lang/spirv/writer/common/option_helper.cc
index 0380ecb..59b26c2 100644
--- a/src/tint/lang/spirv/writer/common/option_helper.cc
+++ b/src/tint/lang/spirv/writer/common/option_helper.cc
@@ -30,10 +30,11 @@
 #include <utility>
 
 #include "src/tint/utils/containers/hashmap.h"
+#include "src/tint/utils/diagnostic/diagnostic.h"
 
 namespace tint::spirv::writer {
 
-diag::Result<SuccessType> ValidateBindingOptions(const Options& options) {
+Result<SuccessType> ValidateBindingOptions(const Options& options) {
     diag::List diagnostics;
 
     tint::Hashmap<tint::BindingPoint, binding::BindingInfo, 8> seen_wgsl_bindings{};
@@ -92,27 +93,27 @@
 
     if (!valid(options.bindings.uniform)) {
         diagnostics.AddNote(Source{}) << "when processing uniform";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
     if (!valid(options.bindings.storage)) {
         diagnostics.AddNote(Source{}) << "when processing storage";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
     if (!valid(options.bindings.texture)) {
         diagnostics.AddNote(Source{}) << "when processing texture";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
     if (!valid(options.bindings.storage_texture)) {
         diagnostics.AddNote(Source{}) << "when processing storage_texture";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
     if (!valid(options.bindings.sampler)) {
         diagnostics.AddNote(Source{}) << "when processing sampler";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
     if (!valid(options.bindings.input_attachment)) {
         diagnostics.AddNote(Source{}) << "when processing input_attachment";
-        return diag::Failure{std::move(diagnostics)};
+        return Failure{diagnostics.Str()};
     }
 
     for (const auto& it : options.bindings.external_texture) {
@@ -124,20 +125,20 @@
         // Validate with the actual source regardless of what the remapper will do
         if (wgsl_seen(src_binding, plane0)) {
             diagnostics.AddNote(Source{}) << "when processing external_texture";
-            return diag::Failure{std::move(diagnostics)};
+            return Failure{diagnostics.Str()};
         }
 
         if (spirv_seen(plane0, src_binding)) {
             diagnostics.AddNote(Source{}) << "when processing external_texture";
-            return diag::Failure{std::move(diagnostics)};
+            return Failure{diagnostics.Str()};
         }
         if (spirv_seen(plane1, src_binding)) {
             diagnostics.AddNote(Source{}) << "when processing external_texture";
-            return diag::Failure{std::move(diagnostics)};
+            return Failure{diagnostics.Str()};
         }
         if (spirv_seen(metadata, src_binding)) {
             diagnostics.AddNote(Source{}) << "when processing external_texture";
-            return diag::Failure{std::move(diagnostics)};
+            return Failure{diagnostics.Str()};
         }
     }
 
diff --git a/src/tint/lang/spirv/writer/common/option_helpers.h b/src/tint/lang/spirv/writer/common/option_helpers.h
index 4eb2768..82c1e6c 100644
--- a/src/tint/lang/spirv/writer/common/option_helpers.h
+++ b/src/tint/lang/spirv/writer/common/option_helpers.h
@@ -33,7 +33,7 @@
 #include "src/tint/api/common/binding_point.h"
 #include "src/tint/lang/core/common/multiplanar_options.h"
 #include "src/tint/lang/spirv/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 namespace tint::spirv::writer {
 
@@ -41,7 +41,7 @@
 
 /// @param options the options
 /// @return success or failure
-diag::Result<SuccessType> ValidateBindingOptions(const Options& options);
+Result<SuccessType> ValidateBindingOptions(const Options& options);
 
 /// Populates data from the writer options for the remapper and external texture.
 /// @param options the writer options
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index 0806a34..94f8208 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -196,7 +196,7 @@
     }
 
     /// @returns the generated SPIR-V code on success, or failure
-    diag::Result<Output> Code() {
+    Result<Output> Code() {
         if (auto res = Generate(); res != Success) {
             return res.Failure();
         }
@@ -285,7 +285,7 @@
     bool zero_init_workgroup_memory_ = false;
 
     /// Builds the SPIR-V from the IR
-    diag::Result<SuccessType> Generate() {
+    Result<SuccessType> Generate() {
         auto valid = core::ir::ValidateAndDumpIfNeeded(ir_, "spirv.Printer");
         if (valid != Success) {
             return valid.Failure();
@@ -734,7 +734,7 @@
 
     /// Emit a function.
     /// @param func the function to emit
-    diag::Result<SuccessType> EmitFunction(core::ir::Function* func) {
+    Result<SuccessType> EmitFunction(core::ir::Function* func) {
         if (func->Params().Length() > 255) {
             // Tint transforms may add additional function parameters which can cause a valid input
             // shader to exceed SPIR-V's function parameter limit. There isn't much we can do about
@@ -742,7 +742,7 @@
             StringStream ss;
             ss << "Function '" << ir_.NameOf(func).Name()
                << "' has more than 255 parameters after running Tint transforms";
-            return diag::Failure{ss.str()};
+            return Failure{ss.str()};
         }
 
         auto id = Value(func);
@@ -2795,7 +2795,7 @@
 
 }  // namespace
 
-diag::Result<Output> Print(core::ir::Module& module, const Options& options) {
+Result<Output> Print(core::ir::Module& module, const Options& options) {
     return Printer{module, options}.Code();
 }
 
diff --git a/src/tint/lang/spirv/writer/printer/printer.h b/src/tint/lang/spirv/writer/printer/printer.h
index b63251e..8c6cbfc 100644
--- a/src/tint/lang/spirv/writer/printer/printer.h
+++ b/src/tint/lang/spirv/writer/printer/printer.h
@@ -30,7 +30,7 @@
 
 #include "src/tint/lang/spirv/writer/common/options.h"
 #include "src/tint/lang/spirv/writer/common/output.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// @returns the generated SPIR-V instructions on success, or failure
 /// @param module the Tint IR module to generate
 /// @param options the printer options
-diag::Result<Output> Print(core::ir::Module& module, const Options& options);
+Result<Output> Print(core::ir::Module& module, const Options& options);
 
 }  // namespace tint::spirv::writer
 
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
index c18c04f..d803f59 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
@@ -1087,7 +1087,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir, bool use_vulkan_memory_model) {
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir, bool use_vulkan_memory_model) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.BuiltinPolyfill");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.h b/src/tint/lang/spirv/writer/raise/builtin_polyfill.h
index 084f023..8fff29e 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.h
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_RAISE_BUILTIN_POLYFILL_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_BUILTIN_POLYFILL_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -43,7 +43,7 @@
 /// @param module the module to transform
 /// @param use_vulkan_memory_model set `true` to use the vulkan memory model
 /// @returns success or failure
-diag::Result<SuccessType> BuiltinPolyfill(core::ir::Module& module, bool use_vulkan_memory_model);
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& module, bool use_vulkan_memory_model);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
index 97fa861..c0d2f34 100644
--- a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
+++ b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
@@ -134,7 +134,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ExpandImplicitSplats(core::ir::Module& ir) {
+Result<SuccessType> ExpandImplicitSplats(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.ExpandImplicitSplats");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.h b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.h
index 7694e84..ccc67d8 100644
--- a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.h
+++ b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_RAISE_EXPAND_IMPLICIT_SPLATS_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_EXPAND_IMPLICIT_SPLATS_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// instructions and binary instructions where not supported by SPIR-V.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> ExpandImplicitSplats(core::ir::Module& module);
+Result<SuccessType> ExpandImplicitSplats(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.cc b/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.cc
index 18f0eb3..e3f084b 100644
--- a/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.cc
+++ b/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.cc
@@ -356,7 +356,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ForkExplicitLayoutTypes(core::ir::Module& ir) {
+Result<SuccessType> ForkExplicitLayoutTypes(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.ForkExplicitLayoutTypes");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.h b/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.h
index 3d1b363..678bfa3 100644
--- a/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.h
+++ b/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_RAISE_FORK_EXPLICIT_LAYOUT_TYPES_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_FORK_EXPLICIT_LAYOUT_TYPES_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 ///
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> ForkExplicitLayoutTypes(core::ir::Module& module);
+Result<SuccessType> ForkExplicitLayoutTypes(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
index 20c9ac5..679f4e0 100644
--- a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
+++ b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
@@ -162,7 +162,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> HandleMatrixArithmetic(core::ir::Module& ir) {
+Result<SuccessType> HandleMatrixArithmetic(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.HandleMatrixArithmetic");
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.h b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.h
index 09a1abc..dc0ab72 100644
--- a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.h
+++ b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_RAISE_HANDLE_MATRIX_ARITHMETIC_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_HANDLE_MATRIX_ARITHMETIC_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// SPIR-V intrinsics or polyfills.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> HandleMatrixArithmetic(core::ir::Module& module);
+Result<SuccessType> HandleMatrixArithmetic(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/merge_return.cc b/src/tint/lang/spirv/writer/raise/merge_return.cc
index a969bd5..359e149 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return.cc
+++ b/src/tint/lang/spirv/writer/raise/merge_return.cc
@@ -321,7 +321,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> MergeReturn(core::ir::Module& ir) {
+Result<SuccessType> MergeReturn(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.MergeReturn");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/spirv/writer/raise/merge_return.h b/src/tint/lang/spirv/writer/raise/merge_return.h
index fcc1dcd..5503503 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return.h
+++ b/src/tint/lang/spirv/writer/raise/merge_return.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_RAISE_MERGE_RETURN_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_MERGE_RETURN_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// at the end of the function.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> MergeReturn(core::ir::Module& module);
+Result<SuccessType> MergeReturn(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.cc b/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.cc
index 504e43d..85b5d04 100644
--- a/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.cc
+++ b/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.cc
@@ -127,7 +127,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> PassMatrixByPointer(core::ir::Module& ir) {
+Result<SuccessType> PassMatrixByPointer(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.PassMatrixByPointer");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.h b/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.h
index de56456..87bd4b1 100644
--- a/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.h
+++ b/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_RAISE_PASS_MATRIX_BY_POINTER_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_PASS_MATRIX_BY_POINTER_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// This is used to workaround bugs in some Qualcomm drivers (see crbug.com/tint/2045).
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> PassMatrixByPointer(core::ir::Module& module);
+Result<SuccessType> PassMatrixByPointer(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/raise.cc b/src/tint/lang/spirv/writer/raise/raise.cc
index 44fb351..0fc09ba 100644
--- a/src/tint/lang/spirv/writer/raise/raise.cc
+++ b/src/tint/lang/spirv/writer/raise/raise.cc
@@ -60,7 +60,7 @@
 
 namespace tint::spirv::writer {
 
-diag::Result<SuccessType> Raise(core::ir::Module& module, const Options& options) {
+Result<SuccessType> Raise(core::ir::Module& module, const Options& options) {
 #define RUN_TRANSFORM(name, ...)         \
     do {                                 \
         auto result = name(__VA_ARGS__); \
diff --git a/src/tint/lang/spirv/writer/raise/raise.h b/src/tint/lang/spirv/writer/raise/raise.h
index 6eccfc9..c5c0e8b 100644
--- a/src/tint/lang/spirv/writer/raise/raise.h
+++ b/src/tint/lang/spirv/writer/raise/raise.h
@@ -29,7 +29,7 @@
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_RAISE_H_
 
 #include "src/tint/lang/spirv/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint::core::ir {
@@ -42,7 +42,7 @@
 /// @param module the core IR module to raise to SPIR-V dialect
 /// @param options the SPIR-V writer options
 /// @returns success or failure
-diag::Result<SuccessType> Raise(core::ir::Module& module, const Options& options);
+Result<SuccessType> Raise(core::ir::Module& module, const Options& options);
 
 }  // namespace tint::spirv::writer
 
diff --git a/src/tint/lang/spirv/writer/raise/remove_unreachable_in_loop_continuing.cc b/src/tint/lang/spirv/writer/raise/remove_unreachable_in_loop_continuing.cc
index 6b75085..5f534b4 100644
--- a/src/tint/lang/spirv/writer/raise/remove_unreachable_in_loop_continuing.cc
+++ b/src/tint/lang/spirv/writer/raise/remove_unreachable_in_loop_continuing.cc
@@ -93,7 +93,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> RemoveUnreachableInLoopContinuing(core::ir::Module& ir) {
+Result<SuccessType> RemoveUnreachableInLoopContinuing(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.RemoveUnreachableInLoopContinuing");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/spirv/writer/raise/remove_unreachable_in_loop_continuing.h b/src/tint/lang/spirv/writer/raise/remove_unreachable_in_loop_continuing.h
index fe59d18..31742fd 100644
--- a/src/tint/lang/spirv/writer/raise/remove_unreachable_in_loop_continuing.h
+++ b/src/tint/lang/spirv/writer/raise/remove_unreachable_in_loop_continuing.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_RAISE_REMOVE_UNREACHABLE_IN_LOOP_CONTINUING_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_REMOVE_UNREACHABLE_IN_LOOP_CONTINUING_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// nested inside a loop continuing block, as SPIR-V's structured control flow rules prohibit this.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> RemoveUnreachableInLoopContinuing(core::ir::Module& module);
+Result<SuccessType> RemoveUnreachableInLoopContinuing(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.cc b/src/tint/lang/spirv/writer/raise/shader_io.cc
index 0ae5a62..beb2870 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io.cc
@@ -206,7 +206,7 @@
 };
 }  // namespace
 
-diag::Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
+Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.ShaderIO");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.h b/src/tint/lang/spirv/writer/raise/shader_io.h
index c770854..e8c42f4 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.h
+++ b/src/tint/lang/spirv/writer/raise/shader_io.h
@@ -30,7 +30,7 @@
 
 #include "src/tint/lang/core/ir/transform/prepare_push_constants.h"
 #include "src/tint/lang/spirv/writer/common/options.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -57,7 +57,7 @@
 /// @param module the module to transform
 /// @param config the configuration
 /// @returns success or failure
-diag::Result<SuccessType> ShaderIO(core::ir::Module& module, const ShaderIOConfig& config);
+Result<SuccessType> ShaderIO(core::ir::Module& module, const ShaderIOConfig& config);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
index 97368fe..e69dccd 100644
--- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
+++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
@@ -248,7 +248,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> VarForDynamicIndex(core::ir::Module& ir) {
+Result<SuccessType> VarForDynamicIndex(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.VarForDynamicIndex");
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h
index e819e01..e3e1b8c 100644
--- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h
+++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_RAISE_VAR_FOR_DYNAMIC_INDEX_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_VAR_FOR_DYNAMIC_INDEX_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -43,7 +43,7 @@
 /// composite.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> VarForDynamicIndex(core::ir::Module& module);
+Result<SuccessType> VarForDynamicIndex(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/writer.cc b/src/tint/lang/spirv/writer/writer.cc
index 6a10786..041fe08 100644
--- a/src/tint/lang/spirv/writer/writer.cc
+++ b/src/tint/lang/spirv/writer/writer.cc
@@ -41,12 +41,12 @@
 
 namespace tint::spirv::writer {
 
-diag::Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options) {
+Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options) {
     // Check optionally supported types against their required options.
     for (auto* ty : ir.Types()) {
         if (ty->Is<core::type::SubgroupMatrix>()) {
             if (!options.use_vulkan_memory_model) {
-                return diag::Failure("using subgroup matrices requires the Vulkan Memory Model");
+                return Failure("using subgroup matrices requires the Vulkan Memory Model");
             }
         }
     }
@@ -55,7 +55,7 @@
     // embedded null characters.
     if (!options.remapped_entry_point_name.empty()) {
         if (options.remapped_entry_point_name.find('\0') != std::string::npos) {
-            return diag::Failure("remapped entry point name contains null character");
+            return Failure("remapped entry point name contains null character");
         }
 
         // Check for multiple entry points.
@@ -64,7 +64,7 @@
         for (auto& func : ir.functions) {
             if (func->IsEntryPoint()) {
                 if (has_entry_point) {
-                    return diag::Failure("module must only contain a single entry point");
+                    return Failure("module must only contain a single entry point");
                 }
                 has_entry_point = true;
             }
@@ -78,14 +78,13 @@
         auto* var = inst->As<core::ir::Var>();
         auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>();
         if (ptr->AddressSpace() == core::AddressSpace::kPixelLocal) {
-            return diag::Failure(
-                "pixel_local address space is not supported by the SPIR-V backend");
+            return Failure("pixel_local address space is not supported by the SPIR-V backend");
         }
 
         if (ptr->AddressSpace() == core::AddressSpace::kPushConstant) {
             if (user_push_constant_size > 0) {
                 // We've already seen a user-declared push constant.
-                return diag::Failure("module contains multiple user-declared push constants");
+                return Failure("module contains multiple user-declared push constants");
             }
             user_push_constant_size = tint::RoundUp(4u, ptr->StoreType()->Size());
         }
@@ -116,14 +115,14 @@
     if (options.depth_range_offsets) {
         if (!check_push_constant_offset(options.depth_range_offsets->max) ||
             !check_push_constant_offset(options.depth_range_offsets->min)) {
-            return diag::Failure("invalid offsets for depth range push constants");
+            return Failure("invalid offsets for depth range push constants");
         }
     }
 
     return Success;
 }
 
-diag::Result<Output> Generate(core::ir::Module& ir, const Options& options) {
+Result<Output> Generate(core::ir::Module& ir, const Options& options) {
     {
         auto res = ValidateBindingOptions(options);
         if (res != Success) {
diff --git a/src/tint/lang/spirv/writer/writer.h b/src/tint/lang/spirv/writer/writer.h
index afa6747..33c7873 100644
--- a/src/tint/lang/spirv/writer/writer.h
+++ b/src/tint/lang/spirv/writer/writer.h
@@ -31,7 +31,7 @@
 #include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/spirv/writer/common/options.h"
 #include "src/tint/lang/spirv/writer/common/output.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 namespace tint::spirv::writer {
 
@@ -39,14 +39,14 @@
 /// @param ir the module
 /// @param options the writer options
 /// @returns Success or a failure message indicating why SPIR-V generation would fail
-diag::Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options);
+Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options);
 
 /// Generate SPIR-V for a program, according to a set of configuration options.
 /// The result will contain the SPIR-V or failure.
 /// @param ir the IR module to translate to SPIR-V
 /// @param options the configuration options to use when generating SPIR-V
 /// @returns the resulting SPIR-V and supplementary information, or failure.
-diag::Result<Output> Generate(core::ir::Module& ir, const Options& options);
+Result<Output> Generate(core::ir::Module& ir, const Options& options);
 
 }  // namespace tint::spirv::writer
 
diff --git a/src/tint/lang/spirv/writer/writer_bench.cc b/src/tint/lang/spirv/writer/writer_bench.cc
index 813f32f..f93a762 100644
--- a/src/tint/lang/spirv/writer/writer_bench.cc
+++ b/src/tint/lang/spirv/writer/writer_bench.cc
@@ -45,7 +45,7 @@
 void GenerateSPIRV(benchmark::State& state, std::string input_name) {
     auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
-        state.SkipWithError(res.Failure().reason.Str());
+        state.SkipWithError(res.Failure().reason);
         return;
     }
     for (auto _ : state) {
@@ -58,7 +58,7 @@
 
         auto gen_res = Generate(ir.Get(), {});
         if (gen_res != Success) {
-            state.SkipWithError(gen_res.Failure().reason.Str());
+            state.SkipWithError(gen_res.Failure().reason);
         }
     }
 }
diff --git a/src/tint/lang/spirv/writer/writer_fuzz.cc b/src/tint/lang/spirv/writer/writer_fuzz.cc
index a41ef78..94fa09a 100644
--- a/src/tint/lang/spirv/writer/writer_fuzz.cc
+++ b/src/tint/lang/spirv/writer/writer_fuzz.cc
@@ -38,13 +38,13 @@
 Result<SuccessType> IRFuzzer(core::ir::Module& module, const fuzz::ir::Context&, Options options) {
     auto check = CanGenerate(module, options);
     if (check != Success) {
-        return Failure{check.Failure().reason.Str()};
+        return Failure{check.Failure().reason};
     }
 
     options.bindings = GenerateBindings(module);
     auto output = Generate(module, options);
     if (output != Success) {
-        return Failure{output.Failure().reason.Str()};
+        return Failure{output.Failure().reason};
     }
 
     auto& spirv = output->spirv;
diff --git a/src/tint/lang/spirv/writer/writer_test.cc b/src/tint/lang/spirv/writer/writer_test.cc
index 2c60b31..da107c7 100644
--- a/src/tint/lang/spirv/writer/writer_test.cc
+++ b/src/tint/lang/spirv/writer/writer_test.cc
@@ -59,7 +59,7 @@
     options.use_vulkan_memory_model = false;
     auto result = CanGenerate(mod, options);
     ASSERT_NE(result, Success);
-    EXPECT_THAT(result.Failure().reason.Str(),
+    EXPECT_THAT(result.Failure().reason,
                 testing::HasSubstr("using subgroup matrices requires the Vulkan Memory Model"));
 }
 
diff --git a/src/tint/lang/wgsl/ast/transform/helper_test.h b/src/tint/lang/wgsl/ast/transform/helper_test.h
index 15a1ef5..493db53 100644
--- a/src/tint/lang/wgsl/ast/transform/helper_test.h
+++ b/src/tint/lang/wgsl/ast/transform/helper_test.h
@@ -52,7 +52,7 @@
     wgsl::writer::Options options;
     auto result = wgsl::writer::Generate(program, options);
     if (result != Success) {
-        return result.Failure().reason.Str();
+        return result.Failure().reason;
     }
 
     auto res = result->wgsl;
diff --git a/src/tint/lang/wgsl/ir_roundtrip_test.cc b/src/tint/lang/wgsl/ir_roundtrip_test.cc
index e29c22c..7f7771b 100644
--- a/src/tint/lang/wgsl/ir_roundtrip_test.cc
+++ b/src/tint/lang/wgsl/ir_roundtrip_test.cc
@@ -76,7 +76,7 @@
         result.ir_pre_raise = core::ir::Disassembler(ir_module.Get()).Plain();
 
         if (auto res = tint::wgsl::writer::Raise(ir_module.Get()); res != Success) {
-            result.err = res.Failure().reason.Str();
+            result.err = res.Failure().reason;
             return result;
         }
 
diff --git a/src/tint/lang/wgsl/reader/lower/lower.cc b/src/tint/lang/wgsl/reader/lower/lower.cc
index 6458b6a..269c737 100644
--- a/src/tint/lang/wgsl/reader/lower/lower.cc
+++ b/src/tint/lang/wgsl/reader/lower/lower.cc
@@ -35,7 +35,6 @@
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/wgsl/builtin_fn.h"
 #include "src/tint/lang/wgsl/ir/builtin_call.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/ice/ice.h"
 
 namespace tint::wgsl::reader {
@@ -208,7 +207,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> Lower(core::ir::Module& mod) {
+Result<SuccessType> Lower(core::ir::Module& mod) {
     auto res = core::ir::ValidateAndDumpIfNeeded(
         mod, "wgsl.Lower", core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
     if (res != Success) {
diff --git a/src/tint/lang/wgsl/reader/lower/lower.h b/src/tint/lang/wgsl/reader/lower/lower.h
index 38678f3..146cba9 100644
--- a/src/tint/lang/wgsl/reader/lower/lower.h
+++ b/src/tint/lang/wgsl/reader/lower/lower.h
@@ -29,14 +29,14 @@
 #define SRC_TINT_LANG_WGSL_READER_LOWER_LOWER_H_
 
 #include "src/tint/lang/core/ir/module.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 namespace tint::wgsl::reader {
 
 /// Lower converts a WGSL-dialect IR module to a core-dialect IR module
 /// @param  mod the IR module
 /// @return the result of the operation
-diag::Result<SuccessType> Lower(core::ir::Module& mod);
+Result<SuccessType> Lower(core::ir::Module& mod);
 
 }  // namespace tint::wgsl::reader
 
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h b/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h
index ad33c3c5..975f412 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h
+++ b/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h
@@ -50,15 +50,15 @@
 
     /// Builds a core-dialect module from this ProgramBuilder.
     /// @returns the generated core-dialect module
-    diag::Result<core::ir::Module> Build() {
+    Result<core::ir::Module> Build() {
         Program program{resolver::Resolve(*this)};
         if (!program.IsValid()) {
-            return diag::Failure{program.Diagnostics()};
+            return Failure{program.Diagnostics().Str()};
         }
 
         auto result = wgsl::reader::ProgramToIR(program);
         if (result != Success) {
-            return result.Failure();
+            return Failure{result.Failure().reason.Str()};
         }
 
         // WGSL-dialect -> core-dialect
@@ -70,22 +70,24 @@
         if (validate != Success) {
             return validate.Failure();
         }
-        return result;
+        return result.Move();
     }
 
     /// Build the module from the given WGSL.
     /// @param wgsl the WGSL to convert to IR
     /// @returns the generated module
-    diag::Result<core::ir::Module> Build(std::string wgsl) {
+    Result<core::ir::Module> Build(std::string wgsl) {
         Source::File file("test.wgsl", std::move(wgsl));
         auto result = wgsl::reader::WgslToIR(&file);
-        if (result == Success) {
-            auto validated = core::ir::Validate(result.Get(), kCapabilities);
-            if (validated != Success) {
-                return validated.Failure();
-            }
+        if (result != Success) {
+            return Failure{result.Failure().reason.Str()};
         }
-        return result;
+        auto validated = core::ir::Validate(result.Get(), kCapabilities);
+        if (validated != Success) {
+            return validated.Failure();
+        }
+
+        return result.Move();
     }
 
     core::ir::Capabilities kCapabilities =
diff --git a/src/tint/lang/wgsl/reader/reader.cc b/src/tint/lang/wgsl/reader/reader.cc
index 026a202..35ca046 100644
--- a/src/tint/lang/wgsl/reader/reader.cc
+++ b/src/tint/lang/wgsl/reader/reader.cc
@@ -63,7 +63,7 @@
     // Lower from WGSL-dialect to core-dialect
     auto res = Lower(ir.Get());
     if (res != Success) {
-        return res.Failure();
+        return diag::Failure{res.Failure().reason};
     }
     return ir;
 }
diff --git a/src/tint/lang/wgsl/reader/reader_bench.cc b/src/tint/lang/wgsl/reader/reader_bench.cc
index 39e1aa6..78d2373 100644
--- a/src/tint/lang/wgsl/reader/reader_bench.cc
+++ b/src/tint/lang/wgsl/reader/reader_bench.cc
@@ -36,7 +36,7 @@
 void ParseWGSL(benchmark::State& state, std::string input_name) {
     auto res = bench::GetWgslFile(input_name);
     if (res != Success) {
-        state.SkipWithError(res.Failure().reason.Str());
+        state.SkipWithError(res.Failure().reason);
         return;
     }
     for (auto _ : state) {
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
index e2d8b67..bc73b38 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
@@ -119,7 +119,7 @@
                                     core::ir::Capability::kAllowPhonyInstructions};
         if (auto res = core::ir::Validate(mod, caps); res != Success) {
             // IR module failed validation.
-            b.Diagnostics() = res.Failure().reason;
+            b.Diagnostics().AddError(Source{}) << res.Failure().reason;
             return Program{resolver::Resolve(b)};
         }
 
diff --git a/src/tint/lang/wgsl/writer/raise/ptr_to_ref.cc b/src/tint/lang/wgsl/writer/raise/ptr_to_ref.cc
index 92d8036..cb68fc9 100644
--- a/src/tint/lang/wgsl/writer/raise/ptr_to_ref.cc
+++ b/src/tint/lang/wgsl/writer/raise/ptr_to_ref.cc
@@ -31,6 +31,7 @@
 #include "src/tint/lang/core/ir/let.h"
 #include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/phony.h"
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/core/ir/var.h"
 #include "src/tint/lang/core/type/pointer.h"
 #include "src/tint/lang/core/type/reference.h"
@@ -138,7 +139,19 @@
 
 }  // namespace
 
-diag::Result<SuccessType> PtrToRef(core::ir::Module& mod) {
+Result<SuccessType> PtrToRef(core::ir::Module& mod) {
+    auto result =
+        core::ir::ValidateAndDumpIfNeeded(mod, "wgsl.PtrToRef",
+                                          core::ir::Capabilities{
+                                              core::ir::Capability::kAllowOverrides,
+                                              core::ir::Capability::kAllowPhonyInstructions,
+                                          }
+
+        );
+    if (result != Success) {
+        return result;
+    }
+
     Impl{mod}.Run();
 
     return Success;
diff --git a/src/tint/lang/wgsl/writer/raise/ptr_to_ref.h b/src/tint/lang/wgsl/writer/raise/ptr_to_ref.h
index bad2ebe..3be16a3 100644
--- a/src/tint/lang/wgsl/writer/raise/ptr_to_ref.h
+++ b/src/tint/lang/wgsl/writer/raise/ptr_to_ref.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_WGSL_WRITER_RAISE_PTR_TO_REF_H_
 #define SRC_TINT_LANG_WGSL_WRITER_RAISE_PTR_TO_REF_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -41,7 +41,7 @@
 /// reference types.
 /// @param module the module to transform
 /// @returns success or failure
-diag::Result<SuccessType> PtrToRef(core::ir::Module& module);
+Result<SuccessType> PtrToRef(core::ir::Module& module);
 
 }  // namespace tint::wgsl::writer::raise
 
diff --git a/src/tint/lang/wgsl/writer/raise/ptr_to_ref_fuzz.cc b/src/tint/lang/wgsl/writer/raise/ptr_to_ref_fuzz.cc
index d72bc00..9141041 100644
--- a/src/tint/lang/wgsl/writer/raise/ptr_to_ref_fuzz.cc
+++ b/src/tint/lang/wgsl/writer/raise/ptr_to_ref_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> PtrToRefFuzzer(core::ir::Module& ir, const fuzz::ir::Context&) {
-    auto res = PtrToRef(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return PtrToRef(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/wgsl/writer/raise/raise.cc b/src/tint/lang/wgsl/writer/raise/raise.cc
index 153aed2..9c4126f 100644
--- a/src/tint/lang/wgsl/writer/raise/raise.cc
+++ b/src/tint/lang/wgsl/writer/raise/raise.cc
@@ -255,7 +255,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> Raise(core::ir::Module& mod) {
+Result<SuccessType> Raise(core::ir::Module& mod) {
     core::ir::Builder b{mod};
     for (auto* inst : mod.Instructions()) {
         if (auto* call = inst->As<core::ir::CoreBuiltinCall>()) {
diff --git a/src/tint/lang/wgsl/writer/raise/raise.h b/src/tint/lang/wgsl/writer/raise/raise.h
index 2a91c4f..c4a60ae 100644
--- a/src/tint/lang/wgsl/writer/raise/raise.h
+++ b/src/tint/lang/wgsl/writer/raise/raise.h
@@ -29,14 +29,14 @@
 #define SRC_TINT_LANG_WGSL_WRITER_RAISE_RAISE_H_
 
 #include "src/tint/lang/core/ir/module.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 namespace tint::wgsl::writer {
 
 /// Raise converts a core-dialect IR module to a WGSL-dialect IR module
 /// @param  mod the IR module
 /// @return the result of the operation
-diag::Result<SuccessType> Raise(core::ir::Module& mod);
+Result<SuccessType> Raise(core::ir::Module& mod);
 
 }  // namespace tint::wgsl::writer
 
diff --git a/src/tint/lang/wgsl/writer/raise/raise_fuzz.cc b/src/tint/lang/wgsl/writer/raise/raise_fuzz.cc
index d1561ed..ead8f89 100644
--- a/src/tint/lang/wgsl/writer/raise/raise_fuzz.cc
+++ b/src/tint/lang/wgsl/writer/raise/raise_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> RaiseFuzzer(core::ir::Module& ir, const fuzz::ir::Context&) {
-    auto res = Raise(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return Raise(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/wgsl/writer/raise/value_to_let.cc b/src/tint/lang/wgsl/writer/raise/value_to_let.cc
index 38c98ee..4c08108 100644
--- a/src/tint/lang/wgsl/writer/raise/value_to_let.cc
+++ b/src/tint/lang/wgsl/writer/raise/value_to_let.cc
@@ -192,7 +192,7 @@
 
 }  // namespace
 
-diag::Result<SuccessType> ValueToLet(core::ir::Module& ir) {
+Result<SuccessType> ValueToLet(core::ir::Module& ir) {
     auto result = core::ir::ValidateAndDumpIfNeeded(ir, "wgsl.ValueToLet",
                                                     core::ir::Capabilities{
                                                         core::ir::Capability::kAllowOverrides,
diff --git a/src/tint/lang/wgsl/writer/raise/value_to_let.h b/src/tint/lang/wgsl/writer/raise/value_to_let.h
index 2518f66..b3a7246 100644
--- a/src/tint/lang/wgsl/writer/raise/value_to_let.h
+++ b/src/tint/lang/wgsl/writer/raise/value_to_let.h
@@ -28,7 +28,7 @@
 #ifndef SRC_TINT_LANG_WGSL_WRITER_RAISE_VALUE_TO_LET_H_
 #define SRC_TINT_LANG_WGSL_WRITER_RAISE_VALUE_TO_LET_H_
 
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations.
 namespace tint::core::ir {
@@ -46,8 +46,8 @@
 /// * The value is used in a block different to the value's instruction.
 ///
 /// @param module the module to transform
-/// @returns error diagnostics on failure
-diag::Result<SuccessType> ValueToLet(core::ir::Module& module);
+/// @returns error on failure
+Result<SuccessType> ValueToLet(core::ir::Module& module);
 
 }  // namespace tint::wgsl::writer::raise
 
diff --git a/src/tint/lang/wgsl/writer/raise/value_to_let_fuzz.cc b/src/tint/lang/wgsl/writer/raise/value_to_let_fuzz.cc
index 1db5b55..166c3c7 100644
--- a/src/tint/lang/wgsl/writer/raise/value_to_let_fuzz.cc
+++ b/src/tint/lang/wgsl/writer/raise/value_to_let_fuzz.cc
@@ -34,11 +34,7 @@
 namespace {
 
 Result<SuccessType> ValueToLetFuzzer(core::ir::Module& ir, const fuzz::ir::Context&) {
-    auto res = ValueToLet(ir);
-    if (res != Success) {
-        return Failure{res.Failure().reason.Str()};
-    }
-    return Success;
+    return ValueToLet(ir);
 }
 
 }  // namespace
diff --git a/src/tint/lang/wgsl/writer/writer.cc b/src/tint/lang/wgsl/writer/writer.cc
index cd4c44c..5b80391 100644
--- a/src/tint/lang/wgsl/writer/writer.cc
+++ b/src/tint/lang/wgsl/writer/writer.cc
@@ -40,7 +40,7 @@
 
 namespace tint::wgsl::writer {
 
-diag::Result<Output> Generate(const Program& program, const Options& options) {
+Result<Output> Generate(const Program& program, const Options& options) {
     (void)options;
 
     Output output;
@@ -49,7 +49,7 @@
         // Generate the WGSL code.
         auto impl = std::make_unique<SyntaxTreePrinter>(program);
         if (!impl->Generate()) {
-            return diag::Failure{impl->Diagnostics()};
+            return Failure{impl->Diagnostics().Str()};
         }
         output.wgsl = impl->Result();
     } else  // NOLINT(readability/braces)
@@ -58,7 +58,7 @@
         // Generate the WGSL code.
         auto impl = std::make_unique<ASTPrinter>(program);
         if (!impl->Generate()) {
-            return diag::Failure{impl->Diagnostics()};
+            return Failure{impl->Diagnostics().Str()};
         }
         output.wgsl = impl->Result();
     }
@@ -66,7 +66,7 @@
     return output;
 }
 
-diag::Result<Output> WgslFromIR(core::ir::Module& module, const ProgramOptions& options) {
+Result<Output> WgslFromIR(core::ir::Module& module, const ProgramOptions& options) {
     auto res = ProgramFromIR(module, options);
     if (res != Success) {
         return res.Failure();
@@ -74,7 +74,7 @@
     return Generate(res.Move(), Options{});
 }
 
-diag::Result<Program> ProgramFromIR(core::ir::Module& module, const ProgramOptions& options) {
+Result<Program> ProgramFromIR(core::ir::Module& module, const ProgramOptions& options) {
     // core-dialect -> WGSL-dialect
     if (auto res = Raise(module); res != Success) {
         return res.Failure();
@@ -82,7 +82,7 @@
 
     auto program = IRToProgram(module, options);
     if (!program.IsValid()) {
-        return diag::Failure{program.Diagnostics()};
+        return Failure{program.Diagnostics().Str()};
     }
 
     return program;
diff --git a/src/tint/lang/wgsl/writer/writer.h b/src/tint/lang/wgsl/writer/writer.h
index a877bad..28d1de8 100644
--- a/src/tint/lang/wgsl/writer/writer.h
+++ b/src/tint/lang/wgsl/writer/writer.h
@@ -31,7 +31,7 @@
 #include "src/tint/lang/wgsl/writer/ir_to_program/program_options.h"
 #include "src/tint/lang/wgsl/writer/options.h"
 #include "src/tint/lang/wgsl/writer/output.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/result.h"
 
 // Forward declarations
 namespace tint {
@@ -48,18 +48,18 @@
 /// @param program the program to translate to WGSL
 /// @param options the configuration options to use when generating WGSL
 /// @returns the resulting WGSL, or failure
-diag::Result<Output> Generate(const Program& program, const Options& options);
+Result<Output> Generate(const Program& program, const Options& options);
 
 /// Generate WGSL from a core-dialect ir::Module.
 /// @param module the core-dialect ir::Module.
 /// @param options the configuration options to use when generating WGSL
 /// @returns the resulting WGSL, or failure
-diag::Result<Output> WgslFromIR(core::ir::Module& module, const ProgramOptions& options);
+Result<Output> WgslFromIR(core::ir::Module& module, const ProgramOptions& options);
 
 /// Generate a Program from a core-dialect ir::Module.
 /// @param module the core-dialect ir::Module.
 /// @returns the resulting Program, or failure
-diag::Result<Program> ProgramFromIR(core::ir::Module& module, const ProgramOptions& options);
+Result<Program> ProgramFromIR(core::ir::Module& module, const ProgramOptions& options);
 
 }  // namespace tint::wgsl::writer
 
diff --git a/src/tint/lang/wgsl/writer/writer_bench.cc b/src/tint/lang/wgsl/writer/writer_bench.cc
index 1f9cbfc..a93b587 100644
--- a/src/tint/lang/wgsl/writer/writer_bench.cc
+++ b/src/tint/lang/wgsl/writer/writer_bench.cc
@@ -36,13 +36,13 @@
 void GenerateWGSL(benchmark::State& state, std::string input_name) {
     auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
-        state.SkipWithError(res.Failure().reason.Str());
+        state.SkipWithError(res.Failure().reason);
         return;
     }
     for (auto _ : state) {
         auto gen_res = Generate(res->program, {});
         if (gen_res != Success) {
-            state.SkipWithError(gen_res.Failure().reason.Str());
+            state.SkipWithError(gen_res.Failure().reason);
         }
     }
 }
diff --git a/src/tint/lang/wgsl/writer/writer_test.cc b/src/tint/lang/wgsl/writer/writer_test.cc
index 092c1a9..153c621 100644
--- a/src/tint/lang/wgsl/writer/writer_test.cc
+++ b/src/tint/lang/wgsl/writer/writer_test.cc
@@ -71,7 +71,7 @@
         result.ir_pre_raise = core::ir::Disassembler(mod).Plain();
 
         if (auto res = tint::wgsl::writer::Raise(mod); res != Success) {
-            result.err = res.Failure().reason.Str();
+            result.err = res.Failure().reason;
             return result;
         }