[tint] Add support for scoped text spans

Add operator() to TextStyle to produce a span of styled text. This span
restores the old style after printing the values.
Also change StyledText<<(StyledText) to restore the old style after
printing the RHS. This removes a bunch of manual state restoration
logic.

Change-Id: Ib2f48dd4d65a31254e69fcdefa10fd0a3b56c959
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/176460
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/core/intrinsic/data.cc b/src/tint/lang/core/intrinsic/data.cc
index 46637b1..0fc6fe4 100644
--- a/src/tint/lang/core/intrinsic/data.cc
+++ b/src/tint/lang/core/intrinsic/data.cc
@@ -81,7 +81,7 @@
     return BuildBool(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "bool";
+    out << style::Type("bool");
   }
 };
 
@@ -95,7 +95,7 @@
     return BuildI32(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "i32";
+    out << style::Type("i32");
   }
 };
 
@@ -109,7 +109,7 @@
     return BuildU32(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "u32";
+    out << style::Type("u32");
   }
 };
 
@@ -123,7 +123,7 @@
     return BuildF32(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "f32";
+    out << style::Type("f32");
   }
 };
 
@@ -137,7 +137,7 @@
     return BuildF16(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "f16";
+    out << style::Type("f16");
   }
 };
 
@@ -157,7 +157,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "vec2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("vec2", "<", T, ">");
   }
 };
 
@@ -177,7 +177,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "vec3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("vec3", "<", T, ">");
   }
 };
 
@@ -197,7 +197,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "vec4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("vec4", "<", T, ">");
   }
 };
 
@@ -217,7 +217,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat2x2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat2x2", "<", T, ">");
   }
 };
 
@@ -237,7 +237,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat2x3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat2x3", "<", T, ">");
   }
 };
 
@@ -257,7 +257,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat2x4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat2x4", "<", T, ">");
   }
 };
 
@@ -277,7 +277,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat3x2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat3x2", "<", T, ">");
   }
 };
 
@@ -297,7 +297,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat3x3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat3x3", "<", T, ">");
   }
 };
 
@@ -317,7 +317,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat3x4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat3x4", "<", T, ">");
   }
 };
 
@@ -337,7 +337,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat4x2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat4x2", "<", T, ">");
   }
 };
 
@@ -357,7 +357,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat4x3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat4x3", "<", T, ">");
   }
 };
 
@@ -377,7 +377,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat4x4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat4x4", "<", T, ">");
   }
 };
 
@@ -403,7 +403,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText N;
   state->PrintNum(N);StyledText T;
   state->PrintType(T);
-    out  << style::Type << "vec" << style::Type << N << style::Type << "<" << style::Type << T << style::Type << ">";
+    out << style::Type("vec", N, "<", T, ">");
   }
 };
 
@@ -435,7 +435,7 @@
   state->PrintNum(N);StyledText M;
   state->PrintNum(M);StyledText T;
   state->PrintType(T);
-    out  << style::Type << "mat" << style::Type << N << style::Type << "x" << style::Type << M << style::Type << "<" << style::Type << T << style::Type << ">";
+    out << style::Type("mat", N, "x", M, "<", T, ">");
   }
 };
 
@@ -467,7 +467,7 @@
   state->PrintNum(S);StyledText T;
   state->PrintType(T);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "ptr" << style::Code << "<" << style::Type << S << style::Code << ", " << style::Type << T << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("ptr", "<", S, ", ", T, ", ", A, ">");
   }
 };
 
@@ -487,7 +487,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "atomic" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("atomic", "<", T, ">");
   }
 };
 
@@ -507,7 +507,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "array" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("array", "<", T, ">");
   }
 };
 
@@ -521,7 +521,7 @@
     return BuildSampler(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "sampler";
+    out << style::Type("sampler");
   }
 };
 
@@ -535,7 +535,7 @@
     return BuildSamplerComparison(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "sampler_comparison";
+    out << style::Type("sampler_comparison");
   }
 };
 
@@ -555,7 +555,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_1d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_1d", "<", T, ">");
   }
 };
 
@@ -575,7 +575,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_2d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_2d", "<", T, ">");
   }
 };
 
@@ -595,7 +595,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_2d_array" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_2d_array", "<", T, ">");
   }
 };
 
@@ -615,7 +615,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_3d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_3d", "<", T, ">");
   }
 };
 
@@ -635,7 +635,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_cube" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_cube", "<", T, ">");
   }
 };
 
@@ -655,7 +655,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_cube_array" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_cube_array", "<", T, ">");
   }
 };
 
@@ -675,7 +675,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_multisampled_2d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_multisampled_2d", "<", T, ">");
   }
 };
 
@@ -689,7 +689,7 @@
     return BuildTextureDepth2D(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_2d";
+    out << style::Type("texture_depth_2d");
   }
 };
 
@@ -703,7 +703,7 @@
     return BuildTextureDepth2DArray(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_2d_array";
+    out << style::Type("texture_depth_2d_array");
   }
 };
 
@@ -717,7 +717,7 @@
     return BuildTextureDepthCube(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_cube";
+    out << style::Type("texture_depth_cube");
   }
 };
 
@@ -731,7 +731,7 @@
     return BuildTextureDepthCubeArray(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_cube_array";
+    out << style::Type("texture_depth_cube_array");
   }
 };
 
@@ -745,7 +745,7 @@
     return BuildTextureDepthMultisampled2D(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_multisampled_2d";
+    out << style::Type("texture_depth_multisampled_2d");
   }
 };
 
@@ -771,7 +771,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_1d" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_1d", "<", F, ", ", A, ">");
   }
 };
 
@@ -797,7 +797,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_2d" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_2d", "<", F, ", ", A, ">");
   }
 };
 
@@ -823,7 +823,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_2d_array" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_2d_array", "<", F, ", ", A, ">");
   }
 };
 
@@ -849,7 +849,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_3d" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_3d", "<", F, ", ", A, ">");
   }
 };
 
@@ -863,7 +863,7 @@
     return BuildTextureExternal(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_external";
+    out << style::Type("texture_external");
   }
 };
 
@@ -883,7 +883,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out  << style::Type << "__modf_result_" << style::Type << T;
+    out << style::Type("__modf_result_", T);
   }
 };
 
@@ -909,7 +909,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText N;
   state->PrintNum(N);StyledText T;
   state->PrintType(T);
-    out  << style::Type << "__modf_result_vec" << style::Type << N << style::Type << "_" << style::Type << T;
+    out << style::Type("__modf_result_vec", N, "_", T);
   }
 };
 
@@ -929,7 +929,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out  << style::Type << "__frexp_result_" << style::Type << T;
+    out << style::Type("__frexp_result_", T);
   }
 };
 
@@ -955,7 +955,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText N;
   state->PrintNum(N);StyledText T;
   state->PrintType(T);
-    out  << style::Type << "__frexp_result_vec" << style::Type << N << style::Type << "_" << style::Type << T;
+    out << style::Type("__frexp_result_vec", N, "_", T);
   }
 };
 
@@ -975,7 +975,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "__atomic_compare_exchange_result" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("__atomic_compare_exchange_result", "<", T, ">");
   }
 };
 
@@ -1003,7 +1003,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar_no_f32'
@@ -1026,7 +1026,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kI32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar_no_f16'
@@ -1049,7 +1049,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar_no_i32'
@@ -1072,7 +1072,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar_no_u32'
@@ -1095,7 +1095,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar_no_bool'
@@ -1118,7 +1118,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kU32Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fiu32_f16'
@@ -1141,7 +1141,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kF16Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kF16Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fiu32'
@@ -1161,7 +1161,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kU32Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fi32_f16'
@@ -1181,7 +1181,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kF16Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kF16Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fi32'
@@ -1198,7 +1198,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kI32Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(" or "); kI32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match f32_f16'
@@ -1215,7 +1215,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kF16Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(" or "); kF16Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match iu32'
@@ -1232,7 +1232,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kU32Matcher.print(nullptr, out);}
+ kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
 };
 
 /// EnumMatcher for 'match f32_texel_format'
@@ -1252,7 +1252,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "bgra8unorm"<< TextStyle{} << ", " << style::Enum << "rgba8unorm"<< TextStyle{} << ", " << style::Enum << "rgba8snorm"<< TextStyle{} << ", " << style::Enum << "rgba16float"<< TextStyle{} << ", " << style::Enum << "r32float"<< TextStyle{} << ", " << style::Enum << "rg32float"<< TextStyle{} << " or " << style::Enum << "rgba32float";
+  out<< style::Enum("bgra8unorm")<< style::Plain(", ") << style::Enum("rgba8unorm")<< style::Plain(", ") << style::Enum("rgba8snorm")<< style::Plain(", ") << style::Enum("rgba16float")<< style::Plain(", ") << style::Enum("r32float")<< style::Plain(", ") << style::Enum("rg32float")<< style::Plain(" or ") << style::Enum("rgba32float");
   }
 };
 
@@ -1271,7 +1271,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "rgba8sint"<< TextStyle{} << ", " << style::Enum << "rgba16sint"<< TextStyle{} << ", " << style::Enum << "r32sint"<< TextStyle{} << ", " << style::Enum << "rg32sint"<< TextStyle{} << " or " << style::Enum << "rgba32sint";
+  out<< style::Enum("rgba8sint")<< style::Plain(", ") << style::Enum("rgba16sint")<< style::Plain(", ") << style::Enum("r32sint")<< style::Plain(", ") << style::Enum("rg32sint")<< style::Plain(" or ") << style::Enum("rgba32sint");
   }
 };
 
@@ -1290,7 +1290,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "rgba8uint"<< TextStyle{} << ", " << style::Enum << "rgba16uint"<< TextStyle{} << ", " << style::Enum << "r32uint"<< TextStyle{} << ", " << style::Enum << "rg32uint"<< TextStyle{} << " or " << style::Enum << "rgba32uint";
+  out<< style::Enum("rgba8uint")<< style::Plain(", ") << style::Enum("rgba16uint")<< style::Plain(", ") << style::Enum("r32uint")<< style::Plain(", ") << style::Enum("rg32uint")<< style::Plain(" or ") << style::Enum("rgba32uint");
   }
 };
 
@@ -1303,7 +1303,7 @@
     return Number::invalid;
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "write";
+  out<< style::Enum("write");
   }
 };
 
@@ -1316,7 +1316,7 @@
     return Number::invalid;
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "read_write";
+  out<< style::Enum("read_write");
   }
 };
 
@@ -1332,7 +1332,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "read"<< TextStyle{} << " or " << style::Enum << "read_write";
+  out<< style::Enum("read")<< style::Plain(" or ") << style::Enum("read_write");
   }
 };
 
@@ -1348,7 +1348,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "write"<< TextStyle{} << " or " << style::Enum << "read_write";
+  out<< style::Enum("write")<< style::Plain(" or ") << style::Enum("read_write");
   }
 };
 
@@ -1365,7 +1365,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "function"<< TextStyle{} << ", " << style::Enum << "private"<< TextStyle{} << " or " << style::Enum << "workgroup";
+  out<< style::Enum("function")<< style::Plain(", ") << style::Enum("private")<< style::Plain(" or ") << style::Enum("workgroup");
   }
 };
 
@@ -1381,7 +1381,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "workgroup"<< TextStyle{} << " or " << style::Enum << "storage";
+  out<< style::Enum("workgroup")<< style::Plain(" or ") << style::Enum("storage");
   }
 };
 
@@ -1394,7 +1394,7 @@
     return Number::invalid;
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "storage";
+  out<< style::Enum("storage");
   }
 };
 
@@ -1407,7 +1407,7 @@
     return Number::invalid;
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "workgroup";
+  out<< style::Enum("workgroup");
   }
 };
 
diff --git a/src/tint/lang/core/intrinsic/table.cc b/src/tint/lang/core/intrinsic/table.cc
index b888362..1a9dae9 100644
--- a/src/tint/lang/core/intrinsic/table.cc
+++ b/src/tint/lang/core/intrinsic/table.cc
@@ -97,17 +97,16 @@
                      [&](const Candidate& a, const Candidate& b) { return a.score < b.score; });
 }
 
-/// Prints a list of types. Ends with the style of @p ss set to style::Code.
+/// Prints a list of types.
 static void PrintTypeList(StyledText& ss, VectorRef<const core::type::Type*> types) {
     bool first = true;
     for (auto* arg : types) {
         if (!first) {
-            ss << style::Code << ", ";
+            ss << ", ";
         }
         first = false;
-        ss << style::Type << arg->FriendlyName();
+        ss << style::Type(arg->FriendlyName());
     }
-    ss << style::Code;
 }
 
 /// Attempts to find a single intrinsic overload that matches the provided argument types.
@@ -191,13 +190,12 @@
                                 VectorRef<const core::type::Type*> args,
                                 VectorRef<Candidate> candidates);
 
-/// @return a string representing a call to a builtin with the given argument
-/// types.
+/// @return a string representing a call to a builtin with the given argument types.
 StyledText CallSignature(std::string_view intrinsic_name,
                          VectorRef<const core::type::Type*> template_args,
                          VectorRef<const core::type::Type*> args) {
     StyledText out;
-    out << style::Function << intrinsic_name << style::Code;
+    out << style::Code << style::Function(intrinsic_name);
     if (!template_args.IsEmpty()) {
         out << "<";
         PrintTypeList(out, template_args);
@@ -207,7 +205,7 @@
     PrintTypeList(out, args);
     out << ")";
 
-    return out << style::Plain;
+    return out;
 }
 
 Result<Overload, StyledText> MatchIntrinsic(Context& context,
@@ -541,22 +539,24 @@
                    Context& context,
                    const OverloadInfo& overload,
                    std::string_view intrinsic_name) {
-    TINT_DEFER(ss << style::Plain);
+    // Restore old style before returning.
+    auto prev_style = ss.Style();
+    TINT_DEFER(ss << prev_style);
 
     TemplateState templates;
 
     // TODO(crbug.com/tint/1730): Use input evaluation stage to output only relevant overloads.
     auto earliest_eval_stage = EvaluationStage::kConstant;
 
-    ss << style::Function << intrinsic_name << style::Code;
+    ss << style::Code << style::Function(intrinsic_name);
 
     if (overload.num_explicit_templates > 0) {
         ss << "<";
         for (size_t i = 0; i < overload.num_explicit_templates; i++) {
             if (i > 0) {
-                ss << style::Code << ", ";
+                ss << ", ";
             }
-            ss << style::Type << context.data[overload.templates + i].name;
+            ss << style::Type(context.data[overload.templates + i].name);
         }
         ss << ">";
     }
@@ -564,15 +564,15 @@
     for (size_t p = 0; p < overload.num_parameters; p++) {
         auto& parameter = context.data[overload.parameters + p];
         if (p > 0) {
-            ss << style::Code << ", ";
+            ss << ", ";
         }
         if (parameter.usage != ParameterUsage::kNone) {
-            ss << style::Variable << ToString(parameter.usage) << style::Code << ": ";
+            ss << style::Variable(parameter.usage, ": ");
         }
         auto* matcher_indices = context.data[parameter.matcher_indices];
         Match(context, templates, overload, matcher_indices, earliest_eval_stage).PrintType(ss);
     }
-    ss << style::Code << ")";
+    ss << ")";
     if (overload.return_matcher_indices.IsValid()) {
         ss << " -> ";
         auto* matcher_indices = context.data[overload.return_matcher_indices];
@@ -581,7 +581,7 @@
 
     bool first = true;
     auto separator = [&] {
-        ss << style::Plain << (first ? "  where: " : ", ");
+        ss << style::Plain(first ? "  where: " : ", ");
         first = false;
     };
 
@@ -592,7 +592,7 @@
                 Match(context, templates, overload, matcher_indices, earliest_eval_stage);
 
             separator();
-            ss << style::Type << tmpl.name << style::Plain << " is ";
+            ss << style::Type(tmpl.name) << style::Plain(" is ");
             if (tmpl.kind == TemplateInfo::Kind::kType) {
                 matcher.PrintType(ss);
             } else {
diff --git a/src/tint/lang/core/intrinsic/table_data.h b/src/tint/lang/core/intrinsic/table_data.h
index 1ed619d..b5496a2 100644
--- a/src/tint/lang/core/intrinsic/table_data.h
+++ b/src/tint/lang/core/intrinsic/table_data.h
@@ -634,7 +634,7 @@
         },
         /* print */
         [](MatchState* state, StyledText& out) {
-            out << style::Type << state->data[state->overload.templates + INDEX].name;
+            out << style::Type(state->data[state->overload.templates + INDEX].name);
         },
     };
 };
@@ -655,7 +655,7 @@
         },
         /* print */
         [](MatchState* state, StyledText& out) {
-            out << style::Variable << state->data[state->overload.templates + INDEX].name;
+            out << style::Variable(state->data[state->overload.templates + INDEX].name);
         },
     };
 };
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 3fd6ae4..3b96695 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -308,7 +308,7 @@
 
     for (auto& func : mod_.functions) {
         if (!all_functions_.Add(func.Get())) {
-            AddError(Source{}) << "function " << style::Function << Name(func.Get()) << style::Plain
+            AddError(Source{}) << "function " << style::Function(Name(func.Get()))
                                << " added to module multiple times";
         }
     }
diff --git a/src/tint/lang/msl/intrinsic/data.cc b/src/tint/lang/msl/intrinsic/data.cc
index c4f7487..3baa716 100644
--- a/src/tint/lang/msl/intrinsic/data.cc
+++ b/src/tint/lang/msl/intrinsic/data.cc
@@ -83,7 +83,7 @@
     return BuildU32(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "u32";
+    out << style::Type("u32");
   }
 };
 
diff --git a/src/tint/lang/spirv/intrinsic/data.cc b/src/tint/lang/spirv/intrinsic/data.cc
index 691263a..27b7ab3 100644
--- a/src/tint/lang/spirv/intrinsic/data.cc
+++ b/src/tint/lang/spirv/intrinsic/data.cc
@@ -84,7 +84,7 @@
     return BuildBool(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "bool";
+    out << style::Type("bool");
   }
 };
 
@@ -98,7 +98,7 @@
     return BuildF32(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "f32";
+    out << style::Type("f32");
   }
 };
 
@@ -112,7 +112,7 @@
     return BuildF16(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "f16";
+    out << style::Type("f16");
   }
 };
 
@@ -126,7 +126,7 @@
     return BuildI32(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "i32";
+    out << style::Type("i32");
   }
 };
 
@@ -140,7 +140,7 @@
     return BuildU32(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "u32";
+    out << style::Type("u32");
   }
 };
 
@@ -160,7 +160,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "vec2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("vec2", "<", T, ">");
   }
 };
 
@@ -180,7 +180,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "vec3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("vec3", "<", T, ">");
   }
 };
 
@@ -200,7 +200,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "vec4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("vec4", "<", T, ">");
   }
 };
 
@@ -220,7 +220,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat2x2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat2x2", "<", T, ">");
   }
 };
 
@@ -240,7 +240,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat2x3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat2x3", "<", T, ">");
   }
 };
 
@@ -260,7 +260,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat2x4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat2x4", "<", T, ">");
   }
 };
 
@@ -280,7 +280,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat3x2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat3x2", "<", T, ">");
   }
 };
 
@@ -300,7 +300,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat3x3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat3x3", "<", T, ">");
   }
 };
 
@@ -320,7 +320,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat3x4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat3x4", "<", T, ">");
   }
 };
 
@@ -340,7 +340,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat4x2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat4x2", "<", T, ">");
   }
 };
 
@@ -360,7 +360,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat4x3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat4x3", "<", T, ">");
   }
 };
 
@@ -380,7 +380,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat4x4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat4x4", "<", T, ">");
   }
 };
 
@@ -406,7 +406,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText N;
   state->PrintNum(N);StyledText T;
   state->PrintType(T);
-    out  << style::Type << "vec" << style::Type << N << style::Type << "<" << style::Type << T << style::Type << ">";
+    out << style::Type("vec", N, "<", T, ">");
   }
 };
 
@@ -438,7 +438,7 @@
   state->PrintNum(N);StyledText M;
   state->PrintNum(M);StyledText T;
   state->PrintType(T);
-    out  << style::Type << "mat" << style::Type << N << style::Type << "x" << style::Type << M << style::Type << "<" << style::Type << T << style::Type << ">";
+    out << style::Type("mat", N, "x", M, "<", T, ">");
   }
 };
 
@@ -458,7 +458,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "atomic" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("atomic", "<", T, ">");
   }
 };
 
@@ -472,7 +472,7 @@
     return BuildSampler(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "sampler";
+    out << style::Type("sampler");
   }
 };
 
@@ -486,7 +486,7 @@
     return BuildSamplerComparison(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "sampler_comparison";
+    out << style::Type("sampler_comparison");
   }
 };
 
@@ -506,7 +506,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_1d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_1d", "<", T, ">");
   }
 };
 
@@ -526,7 +526,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_2d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_2d", "<", T, ">");
   }
 };
 
@@ -546,7 +546,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_2d_array" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_2d_array", "<", T, ">");
   }
 };
 
@@ -566,7 +566,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_3d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_3d", "<", T, ">");
   }
 };
 
@@ -586,7 +586,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_cube" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_cube", "<", T, ">");
   }
 };
 
@@ -606,7 +606,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_cube_array" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_cube_array", "<", T, ">");
   }
 };
 
@@ -626,7 +626,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_multisampled_2d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_multisampled_2d", "<", T, ">");
   }
 };
 
@@ -640,7 +640,7 @@
     return BuildTextureDepth2D(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_2d";
+    out << style::Type("texture_depth_2d");
   }
 };
 
@@ -654,7 +654,7 @@
     return BuildTextureDepth2DArray(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_2d_array";
+    out << style::Type("texture_depth_2d_array");
   }
 };
 
@@ -668,7 +668,7 @@
     return BuildTextureDepthCube(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_cube";
+    out << style::Type("texture_depth_cube");
   }
 };
 
@@ -682,7 +682,7 @@
     return BuildTextureDepthCubeArray(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_cube_array";
+    out << style::Type("texture_depth_cube_array");
   }
 };
 
@@ -696,7 +696,7 @@
     return BuildTextureDepthMultisampled2D(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_multisampled_2d";
+    out << style::Type("texture_depth_multisampled_2d");
   }
 };
 
@@ -722,7 +722,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_1d" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_1d", "<", F, ", ", A, ">");
   }
 };
 
@@ -748,7 +748,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_2d" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_2d", "<", F, ", ", A, ">");
   }
 };
 
@@ -774,7 +774,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_2d_array" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_2d_array", "<", F, ", ", A, ">");
   }
 };
 
@@ -800,7 +800,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_3d" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_3d", "<", F, ", ", A, ">");
   }
 };
 
@@ -832,7 +832,7 @@
   state->PrintNum(S);StyledText T;
   state->PrintType(T);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "ptr" << style::Code << "<" << style::Type << S << style::Code << ", " << style::Type << T << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("ptr", "<", S, ", ", T, ", ", A, ">");
   }
 };
 
@@ -846,7 +846,7 @@
     return BuildStructWithRuntimeArray(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "struct_with_runtime_array";
+    out << style::Type("struct_with_runtime_array");
   }
 };
 
@@ -866,7 +866,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "sampled_image" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("sampled_image", "<", T, ">");
   }
 };
 
@@ -885,7 +885,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kF16Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(" or "); kF16Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match iu32'
@@ -902,7 +902,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kU32Matcher.print(nullptr, out);}
+ kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fiu32'
@@ -922,7 +922,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kU32Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar'
@@ -948,7 +948,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match samplers'
@@ -965,7 +965,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kSamplerMatcher.print(nullptr, out); out << TextStyle{} << " or "; kSamplerComparisonMatcher.print(nullptr, out);}
+ kSamplerMatcher.print(nullptr, out); out << style::Plain(" or "); kSamplerComparisonMatcher.print(nullptr, out);}
 };
 
 /// EnumMatcher for 'match read_write'
@@ -977,7 +977,7 @@
     return Number::invalid;
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "read_write";
+  out<< style::Enum("read_write");
   }
 };
 
@@ -990,7 +990,7 @@
     return Number::invalid;
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "storage";
+  out<< style::Enum("storage");
   }
 };
 
@@ -1006,7 +1006,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "workgroup"<< TextStyle{} << " or " << style::Enum << "storage";
+  out<< style::Enum("workgroup")<< style::Plain(" or ") << style::Enum("storage");
   }
 };
 
@@ -1027,7 +1027,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "bgra8unorm"<< TextStyle{} << ", " << style::Enum << "rgba8unorm"<< TextStyle{} << ", " << style::Enum << "rgba8snorm"<< TextStyle{} << ", " << style::Enum << "rgba16float"<< TextStyle{} << ", " << style::Enum << "r32float"<< TextStyle{} << ", " << style::Enum << "rg32float"<< TextStyle{} << " or " << style::Enum << "rgba32float";
+  out<< style::Enum("bgra8unorm")<< style::Plain(", ") << style::Enum("rgba8unorm")<< style::Plain(", ") << style::Enum("rgba8snorm")<< style::Plain(", ") << style::Enum("rgba16float")<< style::Plain(", ") << style::Enum("r32float")<< style::Plain(", ") << style::Enum("rg32float")<< style::Plain(" or ") << style::Enum("rgba32float");
   }
 };
 
@@ -1046,7 +1046,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "rgba8sint"<< TextStyle{} << ", " << style::Enum << "rgba16sint"<< TextStyle{} << ", " << style::Enum << "r32sint"<< TextStyle{} << ", " << style::Enum << "rg32sint"<< TextStyle{} << " or " << style::Enum << "rgba32sint";
+  out<< style::Enum("rgba8sint")<< style::Plain(", ") << style::Enum("rgba16sint")<< style::Plain(", ") << style::Enum("r32sint")<< style::Plain(", ") << style::Enum("rg32sint")<< style::Plain(" or ") << style::Enum("rgba32sint");
   }
 };
 
@@ -1065,7 +1065,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "rgba8uint"<< TextStyle{} << ", " << style::Enum << "rgba16uint"<< TextStyle{} << ", " << style::Enum << "r32uint"<< TextStyle{} << ", " << style::Enum << "rg32uint"<< TextStyle{} << " or " << style::Enum << "rgba32uint";
+  out<< style::Enum("rgba8uint")<< style::Plain(", ") << style::Enum("rgba16uint")<< style::Plain(", ") << style::Enum("r32uint")<< style::Plain(", ") << style::Enum("rg32uint")<< style::Plain(" or ") << style::Enum("rgba32uint");
   }
 };
 
@@ -1081,7 +1081,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "read"<< TextStyle{} << " or " << style::Enum << "read_write";
+  out<< style::Enum("read")<< style::Plain(" or ") << style::Enum("read_write");
   }
 };
 
@@ -1097,7 +1097,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "write"<< TextStyle{} << " or " << style::Enum << "read_write";
+  out<< style::Enum("write")<< style::Plain(" or ") << style::Enum("read_write");
   }
 };
 
diff --git a/src/tint/lang/wgsl/helpers/check_supported_extensions.cc b/src/tint/lang/wgsl/helpers/check_supported_extensions.cc
index 72f65c6..5a4372c 100644
--- a/src/tint/lang/wgsl/helpers/check_supported_extensions.cc
+++ b/src/tint/lang/wgsl/helpers/check_supported_extensions.cc
@@ -49,8 +49,8 @@
         for (auto* ext : enable->extensions) {
             if (!set.Contains(ext->name)) {
                 diags.AddError(diag::System::Writer, ext->source)
-                    << writer_name << " backend does not support extension " << style::Code
-                    << ext->name;
+                    << writer_name << " backend does not support extension "
+                    << style::Code(ext->name);
                 return false;
             }
         }
diff --git a/src/tint/lang/wgsl/intrinsic/data.cc b/src/tint/lang/wgsl/intrinsic/data.cc
index 96b5ad0..f5ec9db 100644
--- a/src/tint/lang/wgsl/intrinsic/data.cc
+++ b/src/tint/lang/wgsl/intrinsic/data.cc
@@ -106,7 +106,7 @@
     return BuildBool(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "bool";
+    out << style::Type("bool");
   }
 };
 
@@ -120,7 +120,7 @@
     return BuildIa(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out  << style::Type << "abstract-int";
+    out << style::Type("abstract-int");
   }
 };
 
@@ -134,7 +134,7 @@
     return BuildFa(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out  << style::Type << "abstract-float";
+    out << style::Type("abstract-float");
   }
 };
 
@@ -148,7 +148,7 @@
     return BuildI32(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "i32";
+    out << style::Type("i32");
   }
 };
 
@@ -162,7 +162,7 @@
     return BuildU32(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "u32";
+    out << style::Type("u32");
   }
 };
 
@@ -176,7 +176,7 @@
     return BuildF32(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "f32";
+    out << style::Type("f32");
   }
 };
 
@@ -190,7 +190,7 @@
     return BuildF16(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "f16";
+    out << style::Type("f16");
   }
 };
 
@@ -210,7 +210,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "vec2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("vec2", "<", T, ">");
   }
 };
 
@@ -230,7 +230,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "vec3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("vec3", "<", T, ">");
   }
 };
 
@@ -250,7 +250,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "vec4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("vec4", "<", T, ">");
   }
 };
 
@@ -270,7 +270,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat2x2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat2x2", "<", T, ">");
   }
 };
 
@@ -290,7 +290,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat2x3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat2x3", "<", T, ">");
   }
 };
 
@@ -310,7 +310,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat2x4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat2x4", "<", T, ">");
   }
 };
 
@@ -330,7 +330,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat3x2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat3x2", "<", T, ">");
   }
 };
 
@@ -350,7 +350,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat3x3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat3x3", "<", T, ">");
   }
 };
 
@@ -370,7 +370,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat3x4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat3x4", "<", T, ">");
   }
 };
 
@@ -390,7 +390,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat4x2" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat4x2", "<", T, ">");
   }
 };
 
@@ -410,7 +410,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat4x3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat4x3", "<", T, ">");
   }
 };
 
@@ -430,7 +430,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "mat4x4" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("mat4x4", "<", T, ">");
   }
 };
 
@@ -456,7 +456,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText N;
   state->PrintNum(N);StyledText T;
   state->PrintType(T);
-    out  << style::Type << "vec" << style::Type << N << style::Type << "<" << style::Type << T << style::Type << ">";
+    out << style::Type("vec", N, "<", T, ">");
   }
 };
 
@@ -488,7 +488,7 @@
   state->PrintNum(N);StyledText M;
   state->PrintNum(M);StyledText T;
   state->PrintType(T);
-    out  << style::Type << "mat" << style::Type << N << style::Type << "x" << style::Type << M << style::Type << "<" << style::Type << T << style::Type << ">";
+    out << style::Type("mat", N, "x", M, "<", T, ">");
   }
 };
 
@@ -520,7 +520,7 @@
   state->PrintNum(S);StyledText T;
   state->PrintType(T);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "ptr" << style::Code << "<" << style::Type << S << style::Code << ", " << style::Type << T << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("ptr", "<", S, ", ", T, ", ", A, ">");
   }
 };
 
@@ -540,7 +540,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "atomic" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("atomic", "<", T, ">");
   }
 };
 
@@ -560,7 +560,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "array" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("array", "<", T, ">");
   }
 };
 
@@ -574,7 +574,7 @@
     return BuildSampler(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "sampler";
+    out << style::Type("sampler");
   }
 };
 
@@ -588,7 +588,7 @@
     return BuildSamplerComparison(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "sampler_comparison";
+    out << style::Type("sampler_comparison");
   }
 };
 
@@ -608,7 +608,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_1d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_1d", "<", T, ">");
   }
 };
 
@@ -628,7 +628,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_2d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_2d", "<", T, ">");
   }
 };
 
@@ -648,7 +648,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_2d_array" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_2d_array", "<", T, ">");
   }
 };
 
@@ -668,7 +668,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_3d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_3d", "<", T, ">");
   }
 };
 
@@ -688,7 +688,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_cube" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_cube", "<", T, ">");
   }
 };
 
@@ -708,7 +708,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_cube_array" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_cube_array", "<", T, ">");
   }
 };
 
@@ -728,7 +728,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "texture_multisampled_2d" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("texture_multisampled_2d", "<", T, ">");
   }
 };
 
@@ -742,7 +742,7 @@
     return BuildTextureDepth2D(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_2d";
+    out << style::Type("texture_depth_2d");
   }
 };
 
@@ -756,7 +756,7 @@
     return BuildTextureDepth2DArray(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_2d_array";
+    out << style::Type("texture_depth_2d_array");
   }
 };
 
@@ -770,7 +770,7 @@
     return BuildTextureDepthCube(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_cube";
+    out << style::Type("texture_depth_cube");
   }
 };
 
@@ -784,7 +784,7 @@
     return BuildTextureDepthCubeArray(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_cube_array";
+    out << style::Type("texture_depth_cube_array");
   }
 };
 
@@ -798,7 +798,7 @@
     return BuildTextureDepthMultisampled2D(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_depth_multisampled_2d";
+    out << style::Type("texture_depth_multisampled_2d");
   }
 };
 
@@ -824,7 +824,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_1d" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_1d", "<", F, ", ", A, ">");
   }
 };
 
@@ -850,7 +850,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_2d" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_2d", "<", F, ", ", A, ">");
   }
 };
 
@@ -876,7 +876,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_2d_array" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_2d_array", "<", F, ", ", A, ">");
   }
 };
 
@@ -902,7 +902,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText F;
   state->PrintNum(F);StyledText A;
   state->PrintNum(A);
-    out << style::Type << "texture_storage_3d" << style::Code << "<" << style::Type << F << style::Code << ", " << style::Type << A << style::Code << ">";
+    out << style::Type("texture_storage_3d", "<", F, ", ", A, ">");
   }
 };
 
@@ -916,7 +916,7 @@
     return BuildTextureExternal(state, ty);
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {
-    out << style::Type << "texture_external";
+    out << style::Type("texture_external");
   }
 };
 
@@ -936,7 +936,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "packedVec3" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("packedVec3", "<", T, ">");
   }
 };
 
@@ -956,7 +956,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out  << style::Type << "__modf_result_" << style::Type << T;
+    out << style::Type("__modf_result_", T);
   }
 };
 
@@ -982,7 +982,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText N;
   state->PrintNum(N);StyledText T;
   state->PrintType(T);
-    out  << style::Type << "__modf_result_vec" << style::Type << N << style::Type << "_" << style::Type << T;
+    out << style::Type("__modf_result_vec", N, "_", T);
   }
 };
 
@@ -1002,7 +1002,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out  << style::Type << "__frexp_result_" << style::Type << T;
+    out << style::Type("__frexp_result_", T);
   }
 };
 
@@ -1028,7 +1028,7 @@
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText N;
   state->PrintNum(N);StyledText T;
   state->PrintType(T);
-    out  << style::Type << "__frexp_result_vec" << style::Type << N << style::Type << "_" << style::Type << T;
+    out << style::Type("__frexp_result_vec", N, "_", T);
   }
 };
 
@@ -1048,7 +1048,7 @@
   },
 /* print */ []([[maybe_unused]] MatchState* state, StyledText& out) {StyledText T;
   state->PrintType(T);
-    out << style::Type << "__atomic_compare_exchange_result" << style::Code << "<" << style::Type << T << style::Code << ">";
+    out << style::Type("__atomic_compare_exchange_result", "<", T, ">");
   }
 };
 
@@ -1082,7 +1082,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kIaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kFaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kIaMatcher.print(nullptr, out); out << style::Plain(", "); kFaMatcher.print(nullptr, out); out << style::Plain(", "); kF32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match concrete_scalar'
@@ -1108,7 +1108,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar_no_f32'
@@ -1137,7 +1137,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kIaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kFaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kIaMatcher.print(nullptr, out); out << style::Plain(", "); kFaMatcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar_no_f16'
@@ -1166,7 +1166,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kIaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kFaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kIaMatcher.print(nullptr, out); out << style::Plain(", "); kFaMatcher.print(nullptr, out); out << style::Plain(", "); kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar_no_i32'
@@ -1195,7 +1195,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kIaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kFaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kIaMatcher.print(nullptr, out); out << style::Plain(", "); kFaMatcher.print(nullptr, out); out << style::Plain(", "); kF32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar_no_u32'
@@ -1224,7 +1224,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kIaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kFaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kBoolMatcher.print(nullptr, out);}
+ kIaMatcher.print(nullptr, out); out << style::Plain(", "); kFaMatcher.print(nullptr, out); out << style::Plain(", "); kF32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kBoolMatcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match scalar_no_bool'
@@ -1253,7 +1253,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kIaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kFaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kF16Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kU32Matcher.print(nullptr, out);}
+ kIaMatcher.print(nullptr, out); out << style::Plain(", "); kFaMatcher.print(nullptr, out); out << style::Plain(", "); kF32Matcher.print(nullptr, out); out << style::Plain(", "); kF16Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fia_fiu32_f16'
@@ -1282,7 +1282,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kFaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kIaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kF16Matcher.print(nullptr, out);}
+ kFaMatcher.print(nullptr, out); out << style::Plain(", "); kIaMatcher.print(nullptr, out); out << style::Plain(", "); kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kF16Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fia_fi32_f16'
@@ -1308,7 +1308,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kFaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kIaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kF16Matcher.print(nullptr, out);}
+ kFaMatcher.print(nullptr, out); out << style::Plain(", "); kIaMatcher.print(nullptr, out); out << style::Plain(", "); kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kF16Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fia_fiu32'
@@ -1334,7 +1334,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kFaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kIaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kU32Matcher.print(nullptr, out);}
+ kFaMatcher.print(nullptr, out); out << style::Plain(", "); kIaMatcher.print(nullptr, out); out << style::Plain(", "); kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fa_f32'
@@ -1351,7 +1351,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kFaMatcher.print(nullptr, out); out << TextStyle{} << " or "; kF32Matcher.print(nullptr, out);}
+ kFaMatcher.print(nullptr, out); out << style::Plain(" or "); kF32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fa_f32_f16'
@@ -1371,7 +1371,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kFaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kF32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kF16Matcher.print(nullptr, out);}
+ kFaMatcher.print(nullptr, out); out << style::Plain(", "); kF32Matcher.print(nullptr, out); out << style::Plain(" or "); kF16Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match ia_iu32'
@@ -1391,7 +1391,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kIaMatcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kU32Matcher.print(nullptr, out);}
+ kIaMatcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match ia_i32'
@@ -1408,7 +1408,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kIaMatcher.print(nullptr, out); out << TextStyle{} << " or "; kI32Matcher.print(nullptr, out);}
+ kIaMatcher.print(nullptr, out); out << style::Plain(" or "); kI32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fiu32_f16'
@@ -1431,7 +1431,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kU32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kF16Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(", "); kU32Matcher.print(nullptr, out); out << style::Plain(" or "); kF16Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fiu32'
@@ -1451,7 +1451,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kU32Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fi32_f16'
@@ -1471,7 +1471,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << ", "; kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kF16Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(", "); kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kF16Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match fi32'
@@ -1488,7 +1488,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kI32Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(" or "); kI32Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match f32_f16'
@@ -1505,7 +1505,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kF32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kF16Matcher.print(nullptr, out);}
+ kF32Matcher.print(nullptr, out); out << style::Plain(" or "); kF16Matcher.print(nullptr, out);}
 };
 
 /// TypeMatcher for 'match iu32'
@@ -1522,7 +1522,7 @@
 /* print */ [](MatchState*, StyledText& out) {
     // Note: We pass nullptr to the Matcher.print() functions, as matchers do not support
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
- kI32Matcher.print(nullptr, out); out << TextStyle{} << " or "; kU32Matcher.print(nullptr, out);}
+ kI32Matcher.print(nullptr, out); out << style::Plain(" or "); kU32Matcher.print(nullptr, out);}
 };
 
 /// EnumMatcher for 'match f32_texel_format'
@@ -1542,7 +1542,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "bgra8unorm"<< TextStyle{} << ", " << style::Enum << "rgba8unorm"<< TextStyle{} << ", " << style::Enum << "rgba8snorm"<< TextStyle{} << ", " << style::Enum << "rgba16float"<< TextStyle{} << ", " << style::Enum << "r32float"<< TextStyle{} << ", " << style::Enum << "rg32float"<< TextStyle{} << " or " << style::Enum << "rgba32float";
+  out<< style::Enum("bgra8unorm")<< style::Plain(", ") << style::Enum("rgba8unorm")<< style::Plain(", ") << style::Enum("rgba8snorm")<< style::Plain(", ") << style::Enum("rgba16float")<< style::Plain(", ") << style::Enum("r32float")<< style::Plain(", ") << style::Enum("rg32float")<< style::Plain(" or ") << style::Enum("rgba32float");
   }
 };
 
@@ -1561,7 +1561,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "rgba8sint"<< TextStyle{} << ", " << style::Enum << "rgba16sint"<< TextStyle{} << ", " << style::Enum << "r32sint"<< TextStyle{} << ", " << style::Enum << "rg32sint"<< TextStyle{} << " or " << style::Enum << "rgba32sint";
+  out<< style::Enum("rgba8sint")<< style::Plain(", ") << style::Enum("rgba16sint")<< style::Plain(", ") << style::Enum("r32sint")<< style::Plain(", ") << style::Enum("rg32sint")<< style::Plain(" or ") << style::Enum("rgba32sint");
   }
 };
 
@@ -1580,7 +1580,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "rgba8uint"<< TextStyle{} << ", " << style::Enum << "rgba16uint"<< TextStyle{} << ", " << style::Enum << "r32uint"<< TextStyle{} << ", " << style::Enum << "rg32uint"<< TextStyle{} << " or " << style::Enum << "rgba32uint";
+  out<< style::Enum("rgba8uint")<< style::Plain(", ") << style::Enum("rgba16uint")<< style::Plain(", ") << style::Enum("r32uint")<< style::Plain(", ") << style::Enum("rg32uint")<< style::Plain(" or ") << style::Enum("rgba32uint");
   }
 };
 
@@ -1593,7 +1593,7 @@
     return Number::invalid;
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "write";
+  out<< style::Enum("write");
   }
 };
 
@@ -1606,7 +1606,7 @@
     return Number::invalid;
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "read_write";
+  out<< style::Enum("read_write");
   }
 };
 
@@ -1622,7 +1622,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "read"<< TextStyle{} << " or " << style::Enum << "read_write";
+  out<< style::Enum("read")<< style::Plain(" or ") << style::Enum("read_write");
   }
 };
 
@@ -1638,7 +1638,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "write"<< TextStyle{} << " or " << style::Enum << "read_write";
+  out<< style::Enum("write")<< style::Plain(" or ") << style::Enum("read_write");
   }
 };
 
@@ -1655,7 +1655,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "function"<< TextStyle{} << ", " << style::Enum << "private"<< TextStyle{} << " or " << style::Enum << "workgroup";
+  out<< style::Enum("function")<< style::Plain(", ") << style::Enum("private")<< style::Plain(" or ") << style::Enum("workgroup");
   }
 };
 
@@ -1671,7 +1671,7 @@
     }
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "workgroup"<< TextStyle{} << " or " << style::Enum << "storage";
+  out<< style::Enum("workgroup")<< style::Plain(" or ") << style::Enum("storage");
   }
 };
 
@@ -1684,7 +1684,7 @@
     return Number::invalid;
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "storage";
+  out<< style::Enum("storage");
   }
 };
 
@@ -1697,7 +1697,7 @@
     return Number::invalid;
   },
 /* print */ [](MatchState*, StyledText& out) {
-  out<< style::Enum << "workgroup";
+  out<< style::Enum("workgroup");
   }
 };
 
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index 20ec2a4..945fd29 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -31,6 +31,7 @@
 #include <cmath>
 #include <iomanip>
 #include <limits>
+#include <string_view>
 #include <utility>
 
 #include "src/tint/lang/core/builtin_type.h"
@@ -271,8 +272,8 @@
             attribute,  //
             [&](const ast::InternalAttribute* attr) -> bool { return InternalAttribute(attr); },
             [&](Default) {
-                ErrorInvalidAttribute(attribute, StyledText{} << style::Keyword << "let"
-                                                              << style::Plain << " declaration");
+                ErrorInvalidAttribute(attribute,
+                                      StyledText{} << style::Keyword("let") << " declaration");
                 return false;
             });
         if (!ok) {
@@ -281,8 +282,7 @@
     }
 
     if (TINT_UNLIKELY(!v->initializer)) {
-        AddError(v->source) << style::Keyword << "let" << style::Plain
-                            << " declaration must have an initializer";
+        AddError(v->source) << style::Keyword("let") << " declaration must have an initializer";
         return nullptr;
     }
 
@@ -303,8 +303,8 @@
 
     if (!ApplyAddressSpaceUsageToType(core::AddressSpace::kUndefined,
                                       const_cast<core::type::Type*>(sem->Type()), v->source)) {
-        AddNote(v->source) << "while instantiating " << style::Keyword << "let " << style::Variable
-                           << v->name->symbol.Name();
+        AddNote(v->source) << "while instantiating " << style::Keyword("let ")
+                           << style::Variable(v->name->symbol.NameView());
         return nullptr;
     }
 
@@ -362,8 +362,8 @@
 
     if (!ApplyAddressSpaceUsageToType(core::AddressSpace::kUndefined,
                                       const_cast<core::type::Type*>(ty), v->source)) {
-        AddNote(v->source) << "while instantiating " << style::Keyword << "override "
-                           << style::Variable << v->name->symbol.Name();
+        AddNote(v->source) << "while instantiating " << style::Keyword("override ")
+                           << style::Variable(v->name->symbol.NameView());
         return nullptr;
     }
 
@@ -381,23 +381,21 @@
                 }
                 if (!materialized->Type()->IsAnyOf<core::type::I32, core::type::U32>()) {
                     AddError(attr->source)
-                        << style::Attribute << "@id" << style::Plain << " must be an "
-                        << style::Type << "i32" << style::Plain << " or " << style::Type << "u32"
-                        << style::Plain << " value";
+                        << style::Attribute("@id") << " must be an " << style::Type("i32") << " or "
+                        << style::Type("u32") << " value";
                     return false;
                 }
 
                 auto const_value = materialized->ConstantValue();
                 auto value = const_value->ValueAs<AInt>();
                 if (value < 0) {
-                    AddError(attr->source) << style::Attribute << "@id" << style::Plain
-                                           << " value must be non-negative";
+                    AddError(attr->source)
+                        << style::Attribute("@id") << " value must be non-negative";
                     return false;
                 }
                 if (value > std::numeric_limits<decltype(OverrideId::value)>::max()) {
                     AddError(attr->source)
-                        << style::Attribute << "@id" << style::Plain
-                        << " value must be between 0 and "
+                        << style::Attribute("@id") << " value must be between 0 and "
                         << std::numeric_limits<decltype(OverrideId::value)>::max();
                     return false;
                 }
@@ -410,8 +408,8 @@
                 return true;
             },
             [&](Default) {
-                ErrorInvalidAttribute(attribute, StyledText{} << style::Keyword << "override"
-                                                              << style::Plain << " declaration");
+                ErrorInvalidAttribute(attribute,
+                                      StyledText{} << style::Keyword("override") << " declaration");
                 return false;
             });
         if (!ok) {
@@ -435,13 +433,13 @@
 
     for (auto* attribute : c->attributes) {
         Mark(attribute);
-        bool ok = Switch(attribute,  //
-                         [&](Default) {
-                             ErrorInvalidAttribute(attribute,
-                                                   StyledText{} << style::Keyword << "const"
-                                                                << style::Plain << " declaration");
-                             return false;
-                         });
+        bool ok =
+            Switch(attribute,  //
+                   [&](Default) {
+                       ErrorInvalidAttribute(
+                           attribute, StyledText{} << style::Keyword("const") << " declaration");
+                       return false;
+                   });
         if (!ok) {
             return nullptr;
         }
@@ -493,7 +491,7 @@
 
     if (!ApplyAddressSpaceUsageToType(core::AddressSpace::kUndefined,
                                       const_cast<core::type::Type*>(ty), c->source)) {
-        AddNote(c->source) << "while instantiating 'const' " << c->name->symbol.Name();
+        AddNote(c->source) << "while instantiating 'const' " << c->name->symbol.NameView();
         return nullptr;
     }
 
@@ -606,7 +604,7 @@
     if (!ApplyAddressSpaceUsageToType(sem->AddressSpace(),
                                       const_cast<core::type::Type*>(sem->Type()),
                                       var->type ? var->type->source : var->source)) {
-        AddNote(var->source) << "while instantiating 'var' " << var->name->symbol.Name();
+        AddNote(var->source) << "while instantiating 'var' " << var->name->symbol.NameView();
         return nullptr;
     }
 
@@ -698,8 +696,8 @@
                 case kErrored:
                     return nullptr;
                 case kInvalid:
-                    ErrorInvalidAttribute(
-                        attribute, StyledText{} << "module-scope " << style::Keyword << "var");
+                    ErrorInvalidAttribute(attribute,
+                                          StyledText{} << "module-scope " << style::Keyword("var"));
                     return nullptr;
             }
         }
@@ -716,7 +714,7 @@
                 [&](const ast::InternalAttribute* attr) { return InternalAttribute(attr); },
                 [&](Default) {
                     ErrorInvalidAttribute(
-                        attribute, StyledText{} << "function-scope " << style::Keyword << "var");
+                        attribute, StyledText{} << "function-scope " << style::Keyword("var"));
                     return false;
                 });
             if (!ok) {
@@ -737,7 +735,8 @@
     b.Sem().Add(param, sem);
 
     auto add_note = [&] {
-        AddNote(param->source) << "while instantiating parameter " << param->name->symbol.Name();
+        AddNote(param->source) << "while instantiating parameter "
+                               << param->name->symbol.NameView();
     };
 
     if (func->IsEntryPoint()) {
@@ -1032,7 +1031,7 @@
 
         {  // Check the parameter name is unique for the function
             if (auto added = parameter_names.Add(param->name->symbol, param->source); !added) {
-                auto name = param->name->symbol.Name();
+                auto name = param->name->symbol.NameView();
                 AddError(param->source) << "redefinition of parameter '" << name << "'";
                 AddNote(added.value) << "previous definition is here";
                 return nullptr;
@@ -1158,7 +1157,7 @@
     if (auto* str = return_type->As<core::type::Struct>()) {
         if (!ApplyAddressSpaceUsageToType(core::AddressSpace::kUndefined, str, decl->source)) {
             AddNote(decl->source) << "while instantiating return type for "
-                                  << decl->name->symbol.Name();
+                                  << decl->name->symbol.NameView();
             return nullptr;
         }
 
@@ -1725,7 +1724,7 @@
                 break;
             case Alias::ModuleScope: {
                 auto* func = var.expr->Stmt()->Function();
-                auto func_name = func->Declaration()->name->symbol.Name();
+                auto func_name = func->Declaration()->name->symbol.NameView();
                 AddNote(var.expr->Declaration()->source)
                     << "aliases with module-scope variable " << var.access << " in '" << func_name
                     << "'";
@@ -2721,7 +2720,7 @@
             break;
     }
 
-    auto name = ident->symbol.Name();
+    auto name = ident->symbol.NameView();
     StringStream err;
     err << " unhandled builtin type '" << name << "'";
     AddICE(err.str(), ident->source);
@@ -3004,9 +3003,8 @@
     auto* tmpl_ident = ident->As<ast::TemplatedIdentifier>();
     if (!tmpl_ident) {
         if (TINT_UNLIKELY(min_args != 0)) {
-            AddError(Source{ident->source.range.end})
-                << "expected " << style::Code << "<" << style::Plain << " for " << style::Code
-                << ident->symbol.Name();
+            AddError(Source{ident->source.range.end}) << "expected " << style::Code("<") << " for "
+                                                      << style::Code(ident->symbol.NameView());
         }
         return nullptr;
     }
@@ -3021,19 +3019,19 @@
     }
     if (min_args == max_args) {
         if (TINT_UNLIKELY(ident->arguments.Length() != min_args)) {
-            AddError(ident->source) << style::Code << ident->symbol.Name() << style::Plain
-                                    << " requires " << min_args << " template arguments";
+            AddError(ident->source) << style::Code(ident->symbol.NameView()) << " requires "
+                                    << min_args << " template arguments";
             return false;
         }
     } else {
         if (TINT_UNLIKELY(ident->arguments.Length() < min_args)) {
-            AddError(ident->source) << style::Code << ident->symbol.Name() << style::Plain
+            AddError(ident->source) << style::Code(ident->symbol.NameView())
                                     << " requires at least " << min_args << " template arguments";
             return false;
         }
         if (TINT_UNLIKELY(ident->arguments.Length() > max_args)) {
-            AddError(ident->source) << style::Code << ident->symbol.Name() << style::Plain
-                                    << " requires at most " << max_args << " template arguments";
+            AddError(ident->source) << style::Code(ident->symbol.NameView()) << " requires at most "
+                                    << max_args << " template arguments";
             return false;
         }
     }
@@ -3257,7 +3255,7 @@
     auto resolved = dependencies_.resolved_identifiers.Get(ident);
     if (!resolved) {
         StringStream err;
-        err << "identifier '" << ident->symbol.Name() << "' was not resolved";
+        err << "identifier '" << ident->symbol.NameView() << "' was not resolved";
         AddICE(err.str(), expr->source);
         return nullptr;
     }
@@ -3299,11 +3297,11 @@
                                 if (decl->order >= loop_block->NumDeclsAtFirstContinue()) {
                                     AddError(loop_block->FirstContinue()->source)
                                         << "continue statement bypasses declaration of '"
-                                        << symbol.Name() << "'";
+                                        << symbol.NameView() << "'";
                                     AddNote(decl->variable->Declaration()->source)
-                                        << "identifier '" << symbol.Name() << "' declared here";
+                                        << "identifier '" << symbol.NameView() << "' declared here";
                                     AddNote(expr->source)
-                                        << "identifier '" << symbol.Name()
+                                        << "identifier '" << symbol.NameView()
                                         << "' referenced in continuing block here";
                                     return nullptr;
                                 }
@@ -3319,8 +3317,8 @@
                     if (!current_function_ && variable->Declaration()->Is<ast::Var>()) {
                         // Use of a module-scope 'var' outside of a function.
                         AddError(expr->source)
-                            << style::Keyword << "var " << style::Variable << ident->symbol.Name()
-                            << style::Plain << " cannot be referenced at module-scope";
+                            << style::Keyword("var ") << style::Variable(ident->symbol.NameView())
+                            << " cannot be referenced at module-scope";
                         sem_.NoteDeclarationSource(variable->Declaration());
                         return nullptr;
                     }
@@ -3462,7 +3460,7 @@
             }
 
             if (member == nullptr) {
-                AddError(expr->source) << "struct member " << symbol.Name() << " not found";
+                AddError(expr->source) << "struct member " << symbol.NameView() << " not found";
                 return nullptr;
             }
 
@@ -3483,7 +3481,7 @@
         },
 
         [&](const core::type::Vector* vec) -> sem::ValueExpression* {
-            std::string s = expr->member->symbol.Name();
+            std::string_view s = expr->member->symbol.NameView();
             auto size = s.size();
             Vector<uint32_t, 4> swizzle;
             swizzle.Reserve(s.size());
@@ -3707,7 +3705,7 @@
                 root_ident = expr->RootIdentifier();
             } else {
                 AddError(unary->expr->source) << "cannot dereference expression of type "
-                                              << style::Type << sem_.TypeNameOf(expr_ty);
+                                              << style::Type(sem_.TypeNameOf(expr_ty));
                 return nullptr;
             }
             break;
@@ -3767,17 +3765,15 @@
     }
 
     if (!materialized->Type()->IsAnyOf<core::type::I32, core::type::U32>()) {
-        AddError(attr->source) << style::Attribute << "@location" << style::Plain << " must be an "
-                               << style::Type << "i32" << style::Plain << " or " << style::Type
-                               << "u32" << style::Plain << " value";
+        AddError(attr->source) << style::Attribute("@location") << " must be an "
+                               << style::Type("i32") << " or " << style::Type("u32") << " value";
         return Failure{};
     }
 
     auto const_value = materialized->ConstantValue();
     auto value = const_value->ValueAs<AInt>();
     if (value < 0) {
-        AddError(attr->source) << style::Attribute << "@location" << style::Plain
-                               << " value must be non-negative";
+        AddError(attr->source) << style::Attribute("@location") << " value must be non-negative";
         return Failure{};
     }
 
@@ -3794,17 +3790,15 @@
     }
 
     if (!materialized->Type()->IsAnyOf<core::type::I32, core::type::U32>()) {
-        AddError(attr->source) << style::Attribute << "@color" << style::Plain << " must be an "
-                               << style::Type << "i32" << style::Plain << " or " << style::Type
-                               << "u32" << style::Plain << " value";
+        AddError(attr->source) << style::Attribute("@color") << " must be an " << style::Type("i32")
+                               << " or " << style::Type("u32") << " value";
         return Failure{};
     }
 
     auto const_value = materialized->ConstantValue();
     auto value = const_value->ValueAs<AInt>();
     if (value < 0) {
-        AddError(attr->source) << style::Attribute << "@color" << style::Plain
-                               << " value must be non-negative";
+        AddError(attr->source) << style::Attribute("@color") << " value must be non-negative";
         return Failure{};
     }
 
@@ -3820,17 +3814,15 @@
     }
 
     if (!materialized->Type()->IsAnyOf<core::type::I32, core::type::U32>()) {
-        AddError(attr->source) << style::Attribute << "@blend_src" << style::Plain << style::Type
-                               << "i32" << style::Plain << " or " << style::Type << "u32"
-                               << style::Plain << " value";
+        AddError(attr->source) << style::Attribute("@blend_src") << style::Type("i32") << " or "
+                               << style::Type("u32") << " value";
         return Failure{};
     }
 
     auto const_value = materialized->ConstantValue();
     auto value = const_value->ValueAs<AInt>();
     if (value != 0 && value != 1) {
-        AddError(attr->source) << style::Attribute << "@blend_src" << style::Plain
-                               << " value must be zero or one";
+        AddError(attr->source) << style::Attribute("@blend_src") << " value must be zero or one";
         return Failure{};
     }
 
@@ -3846,17 +3838,15 @@
         return Failure{};
     }
     if (!materialized->Type()->IsAnyOf<core::type::I32, core::type::U32>()) {
-        AddError(attr->source) << style::Attribute << "@binding" << style::Plain << " must be an "
-                               << style::Type << "i32" << style::Plain << " or " << style::Type
-                               << "u32" << style::Plain << " value";
+        AddError(attr->source) << style::Attribute("@binding") << " must be an "
+                               << style::Type("i32") << " or " << style::Type("u32") << " value";
         return Failure{};
     }
 
     auto const_value = materialized->ConstantValue();
     auto value = const_value->ValueAs<AInt>();
     if (value < 0) {
-        AddError(attr->source) << style::Attribute << "@binding" << style::Plain
-                               << " value must be non-negative";
+        AddError(attr->source) << style::Attribute("@binding") << " value must be non-negative";
         return Failure{};
     }
     return static_cast<uint32_t>(value);
@@ -3871,17 +3861,15 @@
         return Failure{};
     }
     if (!materialized->Type()->IsAnyOf<core::type::I32, core::type::U32>()) {
-        AddError(attr->source) << style::Attribute << "@group" << style::Plain << " must be an "
-                               << style::Type << "i32" << style::Plain << " or " << style::Type
-                               << "u32" << style::Plain << " value";
+        AddError(attr->source) << style::Attribute("@group") << " must be an " << style::Type("i32")
+                               << " or " << style::Type("u32") << " value";
         return Failure{};
     }
 
     auto const_value = materialized->ConstantValue();
     auto value = const_value->ValueAs<AInt>();
     if (value < 0) {
-        AddError(attr->source) << style::Attribute << "@group" << style::Plain
-                               << " value must be non-negative";
+        AddError(attr->source) << style::Attribute("@group") << " value must be non-negative";
         return Failure{};
     }
     return static_cast<uint32_t>(value);
@@ -3899,11 +3887,10 @@
     Vector<const core::type::Type*, 3> arg_tys;
 
     auto err_bad_expr = [&](const ast::Expression* value) {
-        AddError(value->source) << style::Attribute << "@workgroup_size" << style::Plain
+        AddError(value->source) << style::Attribute("@workgroup_size")
                                 << " argument must be a constant or override-expression of type "
-                                << style::Type << "abstract-integer" << style::Plain << ", "
-                                << style::Type << "i32" << style::Plain << " or " << style::Type
-                                << "u32";
+                                << style::Type("abstract-integer") << ", " << style::Type("i32")
+                                << " or " << style::Type("u32");
     };
 
     for (size_t i = 0; i < 3; i++) {
@@ -3935,9 +3922,9 @@
 
     auto* common_ty = core::type::Type::Common(arg_tys);
     if (!common_ty) {
-        AddError(attr->source) << style::Attribute << "@workgroup_size" << style::Plain
-                               << " arguments must be of the same type, either " << style::Type
-                               << "i32" << style::Plain << " or " << style::Type << "u32";
+        AddError(attr->source) << style::Attribute("@workgroup_size")
+                               << " arguments must be of the same type, either "
+                               << style::Type("i32") << " or " << style::Type("u32");
         return Failure{};
     }
 
@@ -3953,8 +3940,8 @@
         }
         if (auto* value = materialized->ConstantValue()) {
             if (value->ValueAs<AInt>() < 1) {
-                AddError(values[i]->source) << style::Attribute << "@workgroup_size" << style::Plain
-                                            << " argument must be at least 1";
+                AddError(values[i]->source)
+                    << style::Attribute("@workgroup_size") << " argument must be at least 1";
                 return Failure{};
             }
             ws[i] = value->ValueAs<u32>();
@@ -4037,18 +4024,18 @@
 bool Resolver::DiagnosticControl(const ast::DiagnosticControl& control) {
     Mark(control.rule_name);
     Mark(control.rule_name->name);
-    auto name = control.rule_name->name->symbol.Name();
+    auto name = control.rule_name->name->symbol.NameView();
 
     if (control.rule_name->category) {
         Mark(control.rule_name->category);
-        if (control.rule_name->category->symbol.Name() == "chromium") {
+        if (control.rule_name->category->symbol.NameView() == "chromium") {
             auto rule = wgsl::ParseChromiumDiagnosticRule(name);
             if (rule != wgsl::ChromiumDiagnosticRule::kUndefined) {
                 validator_.DiagnosticFilters().Set(rule, control.severity);
             } else {
                 auto& warning = AddWarning(control.rule_name->source)
-                                << "unrecognized diagnostic rule " << style::Code << "chromium."
-                                << name << style::Plain << "\n";
+                                << "unrecognized diagnostic rule " << style::Code("chromium.", name)
+                                << "\n";
                 tint::SuggestAlternativeOptions opts;
                 opts.prefix = "chromium.";
                 tint::SuggestAlternatives(name, wgsl::kChromiumDiagnosticRuleStrings,
@@ -4063,8 +4050,7 @@
         validator_.DiagnosticFilters().Set(rule, control.severity);
     } else {
         auto& warning = AddWarning(control.rule_name->source)
-                        << "unrecognized diagnostic rule " << style::Code << name << style::Plain
-                        << "\n";
+                        << "unrecognized diagnostic rule " << style::Code(name) << "\n";
         tint::SuggestAlternatives(name, wgsl::kCoreDiagnosticRuleStrings, warning.message);
     }
     return true;
@@ -4075,7 +4061,7 @@
         Mark(ext);
         enabled_extensions_.Add(ext->name);
         if (!allowed_features_.extensions.count(ext->name)) {
-            AddError(ext->source) << "extension " << style::Code << ext->name << style::Plain
+            AddError(ext->source) << "extension " << style::Code(ext->name)
                                   << " is not allowed in the current environment";
             return false;
         }
@@ -4086,8 +4072,8 @@
 bool Resolver::Requires(const ast::Requires* req) {
     for (auto feature : req->features) {
         if (!allowed_features_.features.count(feature)) {
-            AddError(req->source) << "language feature " << style::Code << wgsl::ToString(feature)
-                                  << style::Plain << " is not allowed in the current environment";
+            AddError(req->source) << "language feature " << style::Code(wgsl::ToString(feature))
+                                  << " is not allowed in the current environment";
             return false;
         }
     }
@@ -4144,7 +4130,7 @@
             if (auto* ty = count_val->Type(); !ty->is_integer_scalar()) {
                 AddError(count_expr->source)
                     << "array count must evaluate to a constant integer expression, but is type "
-                    << style::Type << ty->FriendlyName();
+                    << style::Type(ty->FriendlyName());
                 return nullptr;
             }
 
@@ -4192,8 +4178,7 @@
                 return true;
             },
             [&](Default) {
-                ErrorInvalidAttribute(
-                    attribute, StyledText{} << style::Type << "array" << style::Plain << " types");
+                ErrorInvalidAttribute(attribute, StyledText{} << style::Type("array") << " types");
                 return false;
             });
         if (!ok) {
@@ -4260,7 +4245,7 @@
 
 sem::Struct* Resolver::Structure(const ast::Struct* str) {
     auto struct_name = [&] {  //
-        return str->name->symbol.Name();
+        return str->name->symbol.NameView();
     };
 
     if (validator_.IsValidationEnabled(str->attributes,
@@ -4269,9 +4254,9 @@
         // https://gpuweb.github.io/gpuweb/wgsl/#limits
         const size_t kMaxNumStructMembers = 16383;
         if (str->members.Length() > kMaxNumStructMembers) {
-            AddError(str->source) << style::Keyword << "struct " << style::Type << struct_name()
-                                  << style::Plain << " has " << str->members.Length()
-                                  << " members, maximum is " << kMaxNumStructMembers;
+            AddError(str->source) << style::Keyword("struct ") << style::Type(struct_name())
+                                  << " has " << str->members.Length() << " members, maximum is "
+                                  << kMaxNumStructMembers;
             return nullptr;
         }
     }
@@ -4285,8 +4270,8 @@
         bool ok = Switch(
             attribute, [&](const ast::InternalAttribute* attr) { return InternalAttribute(attr); },
             [&](Default) {
-                ErrorInvalidAttribute(attribute, StyledText{} << style::Keyword << "struct"
-                                                              << style::Plain << " declarations");
+                ErrorInvalidAttribute(attribute,
+                                      StyledText{} << style::Keyword("struct") << " declarations");
                 return false;
             });
         if (!ok) {
@@ -4313,7 +4298,7 @@
         Mark(member->name);
         if (auto added = member_map.Add(member->name->symbol, member); !added) {
             AddError(member->source)
-                << "redefinition of " << style::Code << member->name->symbol.Name();
+                << "redefinition of " << style::Code(member->name->symbol.NameView());
             AddNote(added.value->source) << "previous definition is here";
             return nullptr;
         }
@@ -4384,22 +4369,22 @@
                         return false;
                     }
                     if (!materialized->Type()->IsAnyOf<core::type::I32, core::type::U32>()) {
-                        AddError(attr->source) << style::Attribute << "@align" << style::Plain
-                                               << " value must be an " << style::Type << "i32"
-                                               << style::Plain << " or " << style::Type << "u32";
+                        AddError(attr->source)
+                            << style::Attribute("@align") << " value must be an "
+                            << style::Type("i32") << " or " << style::Type("u32");
                         return false;
                     }
 
                     auto const_value = materialized->ConstantValue();
                     if (!const_value) {
-                        AddError(attr->source) << style::Attribute << "@align" << style::Plain
-                                               << " value must be constant expression";
+                        AddError(attr->source)
+                            << style::Attribute("@align") << " value must be constant expression";
                         return false;
                     }
                     auto value = const_value->ValueAs<AInt>();
 
                     if (value <= 0 || !tint::IsPowerOfTwo(value)) {
-                        AddError(attr->source) << style::Attribute << "@align" << style::Plain
+                        AddError(attr->source) << style::Attribute("@align")
                                                << " value must be a positive, power-of-two integer";
                         return false;
                     }
@@ -4416,30 +4401,30 @@
                         return false;
                     }
                     if (!materialized->Type()->IsAnyOf<core::type::U32, core::type::I32>()) {
-                        AddError(attr->source) << style::Attribute << "@size" << style::Plain
-                                               << " value must be an " << style::Type << "i32"
-                                               << style::Plain << " or " << style::Type << "u32";
+                        AddError(attr->source)
+                            << style::Attribute("@size") << " value must be an "
+                            << style::Type("i32") << " or " << style::Type("u32");
                         return false;
                     }
 
                     auto const_value = materialized->ConstantValue();
                     if (!const_value) {
-                        AddError(attr->expr->source) << style::Attribute << "@size" << style::Plain
-                                                     << " value must be constant expression";
+                        AddError(attr->expr->source)
+                            << style::Attribute("@size") << " value must be constant expression";
                         return false;
                     }
                     {
                         auto value = const_value->ValueAs<AInt>();
                         if (value <= 0) {
-                            AddError(attr->source) << style::Attribute << "@size" << style::Plain
-                                                   << " value must be a positive integer";
+                            AddError(attr->source)
+                                << style::Attribute("@size") << " value must be a positive integer";
                             return false;
                         }
                     }
                     auto value = const_value->ValueAs<uint64_t>();
                     if (value < size) {
                         AddError(attr->source)
-                            << style::Attribute << "@size" << style::Plain
+                            << style::Attribute("@size")
                             << " must be at least as big as the type's size (" << size << ")";
                         return false;
                     }
@@ -4497,17 +4482,16 @@
                 [&](const ast::StrideAttribute* attr) {
                     if (validator_.IsValidationEnabled(
                             member->attributes, ast::DisabledValidation::kIgnoreStrideAttribute)) {
-                        ErrorInvalidAttribute(attribute, StyledText{} << style::Keyword << "struct"
-                                                                      << style::Plain
-                                                                      << " members");
+                        ErrorInvalidAttribute(
+                            attribute, StyledText{} << style::Keyword("struct") << " members");
                         return false;
                     }
                     return StrideAttribute(attr);
                 },
                 [&](const ast::InternalAttribute* attr) { return InternalAttribute(attr); },
                 [&](Default) {
-                    ErrorInvalidAttribute(attribute, StyledText{} << style::Keyword << "struct"
-                                                                  << style::Plain << " members");
+                    ErrorInvalidAttribute(attribute,
+                                          StyledText{} << style::Keyword("struct") << " members");
                     return false;
                 });
             if (!ok) {
@@ -4516,9 +4500,9 @@
         }
 
         if (has_offset_attr && (has_align_attr || has_size_attr)) {
-            AddError(member->source) << style::Attribute << "@offset" << style::Plain
-                                     << " cannot be used with " << style::Attribute << "@align"
-                                     << style::Plain << " or " << style::Attribute << "@size";
+            AddError(member->source)
+                << style::Attribute("@offset") << " cannot be used with "
+                << style::Attribute("@align") << " or " << style::Attribute("@size");
             return nullptr;
         }
 
@@ -4583,9 +4567,9 @@
     //  https://gpuweb.github.io/gpuweb/wgsl/#limits
     const size_t nest_depth = 1 + members_nest_depth;
     if (nest_depth > kMaxNestDepthOfCompositeType) {
-        AddError(str->source) << style::Keyword << "struct " << style::Type << struct_name()
-                              << style::Plain << " has nesting depth of " << nest_depth
-                              << ", maximum is " << kMaxNestDepthOfCompositeType;
+        AddError(str->source) << style::Keyword("struct ") << style::Type(struct_name())
+                              << " has nesting depth of " << nest_depth << ", maximum is "
+                              << kMaxNestDepthOfCompositeType;
         return nullptr;
     }
     nest_depth_.Add(out, nest_depth);
@@ -4673,8 +4657,8 @@
                 attribute,
                 [&](const ast::DiagnosticAttribute* attr) { return DiagnosticAttribute(attr); },
                 [&](Default) {
-                    ErrorInvalidAttribute(attribute, StyledText{} << style::Keyword << "switch"
-                                                                  << style::Plain << " body");
+                    ErrorInvalidAttribute(attribute,
+                                          StyledText{} << style::Keyword("switch") << " body");
                     return false;
                 });
             if (!ok) {
@@ -4930,9 +4914,9 @@
     }
 
     if (core::IsHostShareable(address_space) && !validator_.IsHostShareable(ty)) {
-        AddError(usage) << "type " << style::Type << sem_.TypeNameOf(ty) << style::Plain
-                        << " cannot be used in address space " << style::Enum << address_space
-                        << style::Plain << " as it is non-host-shareable";
+        AddError(usage) << "type " << style::Type(sem_.TypeNameOf(ty))
+                        << " cannot be used in address space " << style::Enum(address_space)
+                        << " as it is non-host-shareable";
         return false;
     }
 
@@ -5040,7 +5024,7 @@
 
 bool Resolver::CheckNotTemplated(const char* use, const ast::Identifier* ident) {
     if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
-        AddError(ident->source) << use << " " << style::Code << ident->symbol.Name() << style::Plain
+        AddError(ident->source) << use << " " << style::Code(ident->symbol.NameView())
                                 << " does not take template arguments";
         if (auto resolved = dependencies_.resolved_identifiers.Get(ident)) {
             if (auto* ast_node = resolved->Node()) {
@@ -5053,8 +5037,7 @@
 }
 
 void Resolver::ErrorInvalidAttribute(const ast::Attribute* attr, StyledText use) {
-    AddError(attr->source) << style::Attribute << "@" << attr->Name() << style::Plain
-                           << " is not valid for " << use;
+    AddError(attr->source) << style::Attribute("@", attr->Name()) << " is not valid for " << use;
 }
 
 void Resolver::AddICE(std::string_view msg, const Source& source) const {
diff --git a/src/tint/lang/wgsl/resolver/sem_helper.cc b/src/tint/lang/wgsl/resolver/sem_helper.cc
index 5164284..494e4f8 100644
--- a/src/tint/lang/wgsl/resolver/sem_helper.cc
+++ b/src/tint/lang/wgsl/resolver/sem_helper.cc
@@ -71,8 +71,7 @@
     auto* type = ty_expr->Type();
     if (auto* incomplete = type->As<IncompleteType>(); TINT_UNLIKELY(incomplete)) {
         AddError(expr->Declaration()->source.End())
-            << "expected " << style::Code << "<" << style::Plain << " for " << style::Type
-            << incomplete->builtin;
+            << "expected " << style::Code("<") << " for " << style::Type(incomplete->builtin);
         return nullptr;
     }
 
@@ -86,55 +85,57 @@
         expr,  //
         [&](const sem::VariableUser* var_expr) {
             auto* variable = var_expr->Variable()->Declaration();
-            auto name = variable->name->symbol.Name();
+            auto name = variable->name->symbol.NameView();
             Switch(
-                variable,                                                             //
-                [&](const ast::Var*) { text << style::Keyword << "var"; },            //
-                [&](const ast::Let*) { text << style::Keyword << "let"; },            //
-                [&](const ast::Const*) { text << style::Keyword << "const"; },        //
-                [&](const ast::Parameter*) { text << "parameter"; },                  //
-                [&](const ast::Override*) { text << style::Keyword << "override"; },  //
-                [&](Default) { text << "variable"; });
-            text << " " << style::Variable << name;
+                variable,                                                                         //
+                [&](const ast::Var*) { text << style::Keyword("var") << style::Code(" "); },      //
+                [&](const ast::Let*) { text << style::Keyword("let") << style::Code(" "); },      //
+                [&](const ast::Const*) { text << style::Keyword("const") << style::Code(" "); },  //
+                [&](const ast::Parameter*) { text << "parameter "; },                             //
+                [&](const ast::Override*) {
+                    text << style::Keyword("override") << style::Code(" ");
+                },  //
+                [&](Default) { text << "variable "; });
+            text << style::Variable(name);
         },
         [&](const sem::ValueExpression* val_expr) {
-            text << "value of type " << style::Type << val_expr->Type()->FriendlyName();
+            text << "value of type " << style::Type(val_expr->Type()->FriendlyName());
         },
         [&](const sem::TypeExpression* ty_expr) {
-            text << "type " << style::Type << ty_expr->Type()->FriendlyName();
+            text << "type " << style::Type(ty_expr->Type()->FriendlyName());
         },
         [&](const sem::FunctionExpression* fn_expr) {
             auto* fn = fn_expr->Function()->Declaration();
-            text << "function " << style::Function << fn->name->symbol.Name();
+            text << "function " << style::Function(fn->name->symbol.NameView());
         },
         [&](const sem::BuiltinEnumExpression<wgsl::BuiltinFn>* fn) {
-            text << "builtin function " << style::Function << fn->Value();
+            text << "builtin function " << style::Function(fn->Value());
         },
         [&](const sem::BuiltinEnumExpression<core::Access>* access) {
-            text << "access " << style::Enum << access->Value();
+            text << "access " << style::Enum(access->Value());
         },
         [&](const sem::BuiltinEnumExpression<core::AddressSpace>* addr) {
-            text << "address space " << style::Enum << addr->Value();
+            text << "address space " << style::Enum(addr->Value());
         },
         [&](const sem::BuiltinEnumExpression<core::BuiltinValue>* builtin) {
-            text << "builtin value " << style::Enum << builtin->Value();
+            text << "builtin value " << style::Enum(builtin->Value());
         },
         [&](const sem::BuiltinEnumExpression<core::InterpolationSampling>* fmt) {
-            text << "interpolation sampling " << style::Enum << fmt->Value();
+            text << "interpolation sampling " << style::Enum(fmt->Value());
         },
         [&](const sem::BuiltinEnumExpression<core::InterpolationType>* fmt) {
-            text << "interpolation type " << style::Enum << fmt->Value();
+            text << "interpolation type " << style::Enum(fmt->Value());
         },
         [&](const sem::BuiltinEnumExpression<core::TexelFormat>* fmt) {
-            text << "texel format " << style::Enum << fmt->Value();
+            text << "texel format " << style::Enum(fmt->Value());
         },
         [&](const UnresolvedIdentifier* ui) {
-            auto name = ui->Identifier()->identifier->symbol.Name();
-            text << "unresolved identifier " << style::Code << name;
+            auto name = ui->Identifier()->identifier->symbol.NameView();
+            text << "unresolved identifier " << style::Code(name);
         },  //
         TINT_ICE_ON_NO_MATCH);
 
-    return text << style::Plain;
+    return text;
 }
 
 void SemHelper::ErrorUnexpectedExprKind(
@@ -143,8 +144,8 @@
     tint::Slice<const std::string_view> suggestions /* = Empty */) const {
     if (auto* ui = expr->As<UnresolvedIdentifier>()) {
         auto* ident = ui->Identifier();
-        auto name = ident->identifier->symbol.Name();
-        AddError(ident->source) << "unresolved " << wanted << " " << style::Code << name;
+        auto name = ident->identifier->symbol.NameView();
+        AddError(ident->source) << "unresolved " << wanted << " " << style::Code(name);
         if (!suggestions.IsEmpty()) {
             // Filter out suggestions that have a leading underscore.
             Vector<std::string_view, 8> filtered;
@@ -169,7 +170,7 @@
         if (expr->IsAnyOf<sem::FunctionExpression, sem::TypeExpression,
                           sem::BuiltinEnumExpression<wgsl::BuiltinFn>>()) {
             AddNote(ident->source.End())
-                << "are you missing " << style::Code << "()" << style::Plain << "?";
+                << "are you missing " << style::Code("()") << style::Plain("?");
         }
     }
 }
@@ -191,36 +192,36 @@
     Switch(
         node,
         [&](const ast::Struct* n) {
-            AddNote(n->source) << style::Keyword << "struct " << style::Type
-                               << n->name->symbol.Name() << style::Plain << " declared here";
+            AddNote(n->source) << style::Keyword("struct ")
+                               << style::Type(n->name->symbol.NameView()) << " declared here";
         },
         [&](const ast::Alias* n) {
-            AddNote(n->source) << style::Keyword << "alias " << style::Type
-                               << n->name->symbol.Name() << style::Plain << " declared here";
+            AddNote(n->source) << style::Keyword("alias ")
+                               << style::Type(n->name->symbol.NameView()) << " declared here";
         },
         [&](const ast::Var* n) {
-            AddNote(n->source) << style::Keyword << "var " << style::Variable
-                               << n->name->symbol.Name() << style::Plain << " declared here";
+            AddNote(n->source) << style::Keyword("var ")
+                               << style::Variable(n->name->symbol.NameView()) << " declared here";
         },
         [&](const ast::Let* n) {
-            AddNote(n->source) << style::Keyword << "let " << style::Variable
-                               << n->name->symbol.Name() << style::Plain << " declared here";
+            AddNote(n->source) << style::Keyword("let ")
+                               << style::Variable(n->name->symbol.NameView()) << " declared here";
         },
         [&](const ast::Override* n) {
-            AddNote(n->source) << style::Keyword << "override " << style::Variable
-                               << n->name->symbol.Name() << style::Plain << " declared here";
+            AddNote(n->source) << style::Keyword("override ")
+                               << style::Variable(n->name->symbol.NameView()) << " declared here";
         },
         [&](const ast::Const* n) {
-            AddNote(n->source) << style::Keyword << "const " << style::Variable
-                               << n->name->symbol.Name() << style::Plain << " declared here";
+            AddNote(n->source) << style::Keyword("const ")
+                               << style::Variable(n->name->symbol.NameView()) << " declared here";
         },
         [&](const ast::Parameter* n) {
-            AddNote(n->source) << "parameter " << style::Variable << n->name->symbol.Name()
-                               << style::Plain << " declared here";
+            AddNote(n->source) << "parameter " << style::Variable(n->name->symbol.NameView())
+                               << " declared here";
         },
         [&](const ast::Function* n) {
-            AddNote(n->source) << "function " << style::Function << n->name->symbol.Name()
-                               << style::Plain << " declared here";
+            AddNote(n->source) << "function " << style::Function(n->name->symbol.NameView())
+                               << " declared here";
         });
 }
 
diff --git a/src/tint/lang/wgsl/resolver/validation_test.cc b/src/tint/lang/wgsl/resolver/validation_test.cc
index 809ade9..db92d22 100644
--- a/src/tint/lang/wgsl/resolver/validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/validation_test.cc
@@ -315,7 +315,7 @@
     EXPECT_FALSE(r()->Resolve());
 
     EXPECT_EQ(r()->error(),
-              R"(12:34 error: variables of type 'sampler' must not specifiy an address space)");
+              R"(12:34 error: variables of type 'sampler' must not specify an address space)");
 }
 
 TEST_F(ResolverValidationTest, AddressSpace_TextureExplicitAddressSpace) {
@@ -326,7 +326,7 @@
 
     EXPECT_EQ(
         r()->error(),
-        R"(12:34 error: variables of type 'texture_1d<f32>' must not specifiy an address space)");
+        R"(12:34 error: variables of type 'texture_1d<f32>' must not specify an address space)");
 }
 
 TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadChar) {
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index 9546c27..c852f7f 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -29,6 +29,7 @@
 
 #include <algorithm>
 #include <limits>
+#include <string_view>
 #include <tuple>
 #include <utility>
 
@@ -309,10 +310,9 @@
     for (auto pair : incompatible) {
         if (enabled_extensions_.Contains(pair.first) && enabled_extensions_.Contains(pair.second)) {
             AddError(source_of(pair.first))
-                << "extension " << style::Code << pair.first << style::Plain
-                << " cannot be used with extension " << style::Code << pair.second;
-            AddNote(source_of(pair.second))
-                << style::Code << pair.second << style::Plain << " enabled here";
+                << "extension " << style::Code(pair.first) << " cannot be used with extension "
+                << style::Code(pair.second);
+            AddNote(source_of(pair.second)) << style::Code(pair.second) << " enabled here";
             return false;
         }
     }
@@ -429,8 +429,8 @@
                             const core::type::Type* from,
                             const Source& source) const {
     if (core::type::Type::ConversionRank(from, to) == core::type::Type::kNoConversion) {
-        AddError(source) << "cannot convert value of type " << style::Type << sem_.TypeNameOf(from)
-                         << style::Plain << " to type " << style::Type << sem_.TypeNameOf(to);
+        AddError(source) << "cannot convert value of type " << style::Type(sem_.TypeNameOf(from))
+                         << " to type " << style::Type(sem_.TypeNameOf(to));
         return false;
     }
     return true;
@@ -444,10 +444,9 @@
 
     // Value type has to match storage type
     if (storage_ty != value_type) {
-        AddError(v->source) << "cannot initialize " << style::Keyword << v->Kind() << style::Plain
-                            << " of type " << style::Type << sem_.TypeNameOf(storage_ty)
-                            << style::Plain << " with value of type " << style::Type
-                            << sem_.TypeNameOf(initializer_ty);
+        AddError(v->source) << "cannot initialize " << style::Keyword(v->Kind()) << " of type "
+                            << style::Type(sem_.TypeNameOf(storage_ty)) << " with value of type "
+                            << style::Type(sem_.TypeNameOf(initializer_ty));
         return false;
     }
 
@@ -489,17 +488,15 @@
     }
 
     auto note_usage = [&] {
-        AddNote(source) << style::Type << store_ty->FriendlyName() << style::Plain
-                        << " used in address space " << style::Enum << address_space << style::Plain
-                        << " here";
+        AddNote(source) << style::Type(store_ty->FriendlyName()) << " used in address space "
+                        << style::Enum(address_space) << " here";
     };
 
     // Among three host-shareable address spaces, f16 is supported in "uniform" and
     // "storage" address space, but not "push_constant" address space yet.
     if (Is<core::type::F16>(store_ty->DeepestElement()) &&
         address_space == core::AddressSpace::kPushConstant) {
-        AddError(source) << "using " << style::Type << "f16" << style::Plain << " in "
-                         << style::Enum << "push_constant" << style::Plain
+        AddError(source) << "using " << style::Type("f16") << " in " << style::Enum("push_constant")
                          << " address space is not implemented yet";
         return false;
     }
@@ -521,14 +518,13 @@
                 !enabled_extensions_.Contains(
                     wgsl::Extension::kChromiumInternalRelaxedUniformLayout)) {
                 AddError(m->Declaration()->source)
-                    << "the offset of a struct member of type " << style::Type
-                    << m->Type()->UnwrapRef()->FriendlyName() << style::Plain
-                    << " in address space " << style::Enum << address_space << style::Plain
-                    << " must be a multiple of " << required_align << " bytes, but "
-                    << style::Variable << member_name_of(m) << style::Plain
+                    << "the offset of a struct member of type "
+                    << style::Type(m->Type()->UnwrapRef()->FriendlyName()) << " in address space "
+                    << style::Enum(address_space) << " must be a multiple of " << required_align
+                    << " bytes, but " << style::Variable(member_name_of(m))
                     << " is currently at offset " << m->Offset() << ". Consider setting "
-                    << style::Attribute << "@align" << style::Code << "(" << required_align << ")"
-                    << style::Plain << " on this member";
+                    << style::Attribute("@align") << style::Code("(", required_align, ")")
+                    << " on this member";
 
                 AddNote(str->Declaration()->source) << "see layout of struct:\n" << str->Layout();
 
@@ -550,15 +546,14 @@
                     !enabled_extensions_.Contains(
                         wgsl::Extension::kChromiumInternalRelaxedUniformLayout)) {
                     AddError(m->Declaration()->source)
-                        << style::Enum << "uniform" << style::Plain
+                        << style::Enum("uniform")
                         << " storage requires that the number of bytes between the start of the "
                            "previous member of type struct and the current member be a "
                            "multiple of 16 bytes, but there are currently "
-                        << prev_to_curr_offset << " bytes between " << style::Variable
-                        << member_name_of(prev_member) << style::Plain << " and " << style::Variable
-                        << member_name_of(m) << style::Plain << ". Consider setting "
-                        << style::Attribute << "@align" << style::Code << "(16)" << style::Plain
-                        << " on this member";
+                        << prev_to_curr_offset << " bytes between "
+                        << style::Variable(member_name_of(prev_member)) << " and "
+                        << style::Variable(member_name_of(m)) << ". Consider setting "
+                        << style::Attribute("@align") << style::Code("(16)") << " on this member";
 
                     AddNote(str->Declaration()->source) << "see layout of struct:\n"
                                                         << str->Layout();
@@ -598,16 +593,16 @@
                            vec && vec->type()->Size() == 4) {
                     hint << "Consider using a vec4 instead.";
                 } else if (arr->ElemType()->Is<sem::Struct>()) {
-                    hint << "Consider using the " << style::Attribute << "@size" << style::Plain
+                    hint << "Consider using the " << style::Attribute("@size")
                          << " attribute on the last struct member.";
                 } else {
                     hint << "Consider wrapping the element type in a struct and using the "
-                         << style::Attribute << "@size" << style::Plain << " attribute.";
+                         << style::Attribute("@size") << " attribute.";
                 }
-                AddError(source) << style::Enum << "uniform" << style::Plain
-                                 << " storage requires that array elements are aligned to "
-                                    "16 bytes, but array element of type "
-                                 << style::Type << arr->ElemType()->FriendlyName() << style::Plain
+                AddError(source) << style::Enum("uniform")
+                                 << " storage requires that array elements are aligned to 16 "
+                                    "bytes, but array element of type "
+                                 << style::Type(arr->ElemType()->FriendlyName())
                                  << " has a stride of " << arr->Stride() << " bytes. " << hint;
                 return false;
             }
@@ -631,7 +626,7 @@
                                     ast::DisabledValidation::kIgnoreAddressSpace)) {
                 if (!local->Type()->UnwrapRef()->IsConstructible()) {
                     AddError(var->type ? var->type->source : var->source)
-                        << "function-scope " << style::Keyword << "var" << style::Plain
+                        << "function-scope " << style::Keyword("var")
                         << " must have a constructible type";
                     return false;
                 }
@@ -659,17 +654,15 @@
             if (auto* init = global->Initializer();
                 init && init->Stage() > core::EvaluationStage::kOverride) {
                 AddError(init->Declaration()->source)
-                    << "module-scope " << style::Keyword << "var" << style::Plain
-                    << " initializer must be a constant or "
-                       "override-expression";
+                    << "module-scope " << style::Keyword("var")
+                    << " initializer must be a constant or override-expression";
                 return false;
             }
 
             if (!var->declared_address_space && !global->Type()->UnwrapRef()->is_handle()) {
-                AddError(decl->source) << "module-scope " << style::Keyword << "var" << style::Plain
-                                       << " declarations that are not of texture "
-                                          "or sampler types must "
-                                          "provide an address space";
+                AddError(decl->source) << "module-scope " << style::Keyword("var")
+                                       << " declarations that are not of texture or sampler types "
+                                          "must provide an address space";
                 return false;
             }
 
@@ -684,8 +677,8 @@
     }
 
     if (global->AddressSpace() == core::AddressSpace::kFunction) {
-        AddError(decl->source) << "module-scope " << style::Keyword << "var" << style::Plain
-                               << " must not use address space " << style::Enum << "function";
+        AddError(decl->source) << "module-scope " << style::Keyword("var")
+                               << " must not use address space " << style::Enum("function");
         return false;
     }
 
@@ -697,8 +690,8 @@
             // Each resource variable must be declared with both group and binding attributes.
             if (!decl->HasBindingPoint()) {
                 AddError(decl->source)
-                    << "resource variables require " << style::Attribute << "@group" << style::Plain
-                    << " and " << style::Attribute << "@binding" << style::Plain << " attributes";
+                    << "resource variables require " << style::Attribute("@group") << " and "
+                    << style::Attribute("@binding") << " attributes";
                 return false;
             }
             break;
@@ -710,9 +703,8 @@
                 // https://gpuweb.github.io/gpuweb/wgsl/#attribute-binding
                 // Must only be applied to a resource variable
                 AddError(decl->source)
-                    << "non-resource variables must not have " << style::Attribute << "@group"
-                    << style::Plain << " or " << style::Attribute << "@binding" << style::Plain
-                    << " attributes";
+                    << "non-resource variables must not have " << style::Attribute("@group")
+                    << " or " << style::Attribute("@binding") << " attributes";
                 return false;
             }
         }
@@ -735,8 +727,8 @@
         // https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
         // If the store type is a texture type or a sampler type, then the variable declaration must
         // not have a address space attribute. The address space will always be handle.
-        AddError(var->source) << "variables of type " << style::Type << sem_.TypeNameOf(store_ty)
-                              << style::Plain << " must not specifiy an address space";
+        AddError(var->source) << "variables of type " << style::Type(sem_.TypeNameOf(store_ty))
+                              << " must not specify an address space";
         return false;
     }
 
@@ -762,11 +754,10 @@
                 // Optionally has an initializer expression, if the variable is in the private or
                 // function address spaces.
                 AddError(var->source)
-                    << "var of address space " << style::Enum << v->AddressSpace() << style::Plain
-                    << " cannot have an initializer. var initializers are only supported for "
-                       "the address spaces "
-                    << style::Enum << "private" << style::Plain << " and " << style::Enum
-                    << "function";
+                    << "var of address space " << style::Enum(v->AddressSpace())
+                    << " cannot have an initializer. var initializers are only supported for the "
+                       "address spaces "
+                    << style::Enum("private") << " and " << style::Enum("function");
                 return false;
         }
     }
@@ -791,7 +782,7 @@
 
     if (!(storage_ty->IsConstructible() || storage_ty->Is<core::type::Pointer>())) {
         AddError(decl->source) << sem_.TypeNameOf(storage_ty) << " cannot be used as the type of a "
-                               << style::Keyword << "let";
+                               << style::Keyword("let");
         return false;
     }
     return true;
@@ -803,16 +794,15 @@
     auto* storage_ty = v->Type()->UnwrapRef();
 
     if (auto* init = v->Initializer(); init && init->Stage() > core::EvaluationStage::kOverride) {
-        AddError(init->Declaration()->source) << style::Keyword << "override" << style::Plain
-                                              << " initializer must be an override-expression";
+        AddError(init->Declaration()->source)
+            << style::Keyword("override") << " initializer must be an override-expression";
         return false;
     }
 
     if (auto id = v->Attributes().override_id) {
         if (auto var = override_ids.Get(*id); var && *var != v) {
             auto* attr = ast::GetAttribute<ast::IdAttribute>(v->Declaration()->attributes);
-            AddError(attr->source)
-                << style::Attribute << "@id" << style::Plain << " values must be unique";
+            AddError(attr->source) << style::Attribute("@id") << " values must be unique";
             AddNote(ast::GetAttribute<ast::IdAttribute>((*var)->Declaration()->attributes)->source)
                 << "a override with an ID of " << id->value << " was previously declared here";
             return false;
@@ -821,7 +811,7 @@
 
     if (!storage_ty->Is<core::type::Scalar>()) {
         AddError(decl->source) << sem_.TypeNameOf(storage_ty) << " cannot be used as the type of a "
-                               << style::Keyword << "override";
+                               << style::Keyword("override");
         return false;
     }
 
@@ -860,7 +850,7 @@
             }
             if (!ok) {
                 AddError(decl->source) << "function parameter of pointer type cannot be in "
-                                       << style::Enum << sc << style::Plain << " address space";
+                                       << style::Enum(sc) << " address space";
                 return false;
             }
         }
@@ -886,16 +876,14 @@
                                  ast::PipelineStage stage,
                                  const bool is_input) const {
     auto* type = storage_ty->UnwrapRef();
-    StringStream stage_name;
-    stage_name << stage;
     bool is_stage_mismatch = false;
     bool is_output = !is_input;
     auto builtin = sem_.Get(attr)->Value();
 
-    auto err_builtin_type = [&](const char* required) {
-        AddError(attr->source) << "store type of " << style::Attribute << "@builtin" << style::Code
-                               << "(" << style::Enum << builtin << style::Code << ")"
-                               << style::Plain << " must be " << style::Type << required;
+    auto err_builtin_type = [&](std::string_view required) {
+        AddError(attr->source) << "store type of " << style::Attribute("@builtin")
+                               << style::Code("(", style::Enum(builtin), ")") << " must be "
+                               << style::Type(required);
     };
 
     switch (builtin) {
@@ -989,10 +977,10 @@
         case core::BuiltinValue::kSubgroupInvocationId:
         case core::BuiltinValue::kSubgroupSize:
             if (!enabled_extensions_.Contains(wgsl::Extension::kChromiumExperimentalSubgroups)) {
-                AddError(attr->source) << "use of " << style::Attribute << "@builtin" << style::Code
-                                       << "(" << style::Enum << builtin << style::Code << ")"
-                                       << style::Plain << " attribute requires enabling extension "
-                                       << style::Code << "chromium_experimental_subgroups";
+                AddError(attr->source) << "use of " << style::Attribute("@builtin")
+                                       << style::Code("(", style::Enum(builtin), ")")
+                                       << " attribute requires enabling extension "
+                                       << style::Code("chromium_experimental_subgroups");
                 return false;
             }
             if (!type->Is<core::type::U32>()) {
@@ -1001,8 +989,7 @@
             }
             if (stage != ast::PipelineStage::kNone && stage != ast::PipelineStage::kCompute) {
                 AddError(attr->source)
-                    << style::Attribute << "@builtin" << style::Code << "(" << style::Enum
-                    << builtin << style::Code << ")" << style::Plain
+                    << style::Attribute("@builtin") << style::Code("(", style::Enum(builtin), ")")
                     << " is only valid as a compute shader input";
                 return false;
             }
@@ -1012,9 +999,9 @@
     }
 
     if (is_stage_mismatch) {
-        AddError(attr->source) << style::Attribute << "@builtin" << style::Code << "("
-                               << style::Enum << builtin << style::Code << ")" << style::Plain
-                               << " cannot be used for " << stage_name.str() << " shader "
+        AddError(attr->source) << style::Attribute("@builtin")
+                               << style::Code("(", style::Enum(builtin), ")")
+                               << " cannot be used for " << stage << " shader "
                                << (is_input ? "input" : "output");
         return false;
     }
@@ -1026,7 +1013,7 @@
                                      const core::type::Type* storage_ty,
                                      const ast::PipelineStage stage) const {
     if (stage == ast::PipelineStage::kCompute) {
-        AddError(attr->source) << style::Attribute << "@" << attr->Name() << style::Plain
+        AddError(attr->source) << style::Attribute("@", attr->Name())
                                << " cannot be used by compute shaders";
         return false;
     }
@@ -1039,8 +1026,8 @@
     }
 
     if (type->is_integer_scalar_or_vector() && i_type->Value() != core::InterpolationType::kFlat) {
-        AddError(attr->source) << "interpolation type must be " << style::Enum << "flat"
-                               << style::Plain << " for integral user-defined IO types";
+        AddError(attr->source) << "interpolation type must be " << style::Enum("flat")
+                               << " for integral user-defined IO types";
         return false;
     }
 
@@ -1055,7 +1042,7 @@
 bool Validator::InvariantAttribute(const ast::InvariantAttribute* attr,
                                    const ast::PipelineStage stage) const {
     if (stage == ast::PipelineStage::kCompute) {
-        AddError(attr->source) << style::Attribute << "@" << attr->Name() << style::Plain
+        AddError(attr->source) << style::Attribute("@", attr->Name())
                                << " cannot be used by compute shaders";
         return false;
     }
@@ -1070,7 +1057,7 @@
             attr,  //
             [&](const ast::WorkgroupAttribute*) {
                 if (decl->PipelineStage() != ast::PipelineStage::kCompute) {
-                    AddError(attr->source) << style::Attribute << "@workgroup_size" << style::Plain
+                    AddError(attr->source) << style::Attribute("@workgroup_size")
                                            << " is only valid for compute stages";
                     return false;
                 }
@@ -1079,7 +1066,7 @@
             [&](const ast::MustUseAttribute*) {
                 if (func->ReturnType()->Is<core::type::Void>()) {
                     AddError(attr->source)
-                        << style::Attribute << "@must_use" << style::Plain
+                        << style::Attribute("@must_use")
                         << " can only be applied to functions that return a value";
                     return false;
                 }
@@ -1115,7 +1102,7 @@
             }
         } else if (TINT_UNLIKELY(IsValidationEnabled(
                        decl->attributes, ast::DisabledValidation::kFunctionHasNoBody))) {
-            TINT_ICE() << "function " << decl->name->symbol.Name() << " has no body";
+            TINT_ICE() << "function " << decl->name->symbol.NameView() << " has no body";
         }
     }
 
@@ -1129,7 +1116,7 @@
     // a function behavior is always one of {}, or {Next}.
     if (TINT_UNLIKELY(func->Behaviors() != sem::Behaviors{} &&
                       func->Behaviors() != sem::Behavior::kNext)) {
-        auto name = decl->name->symbol.Name();
+        auto name = decl->name->symbol.NameView();
         TINT_ICE() << "function '" << name << "' behaviors are: " << func->Behaviors();
     }
 
@@ -1180,16 +1167,16 @@
                     if (pipeline_io_attribute) {
                         AddError(attr->source) << "multiple entry point IO attributes";
                         AddNote(pipeline_io_attribute->source)
-                            << "previously consumed " << style::Attribute << "@"
-                            << pipeline_io_attribute->Name();
+                            << "previously consumed "
+                            << style::Attribute("@", pipeline_io_attribute->Name());
                         return false;
                     }
                     pipeline_io_attribute = attr;
 
                     if (builtins.Contains(builtin)) {
                         AddError(decl->source)
-                            << style::Attribute << "@builtin" << style::Code << "(" << style::Enum
-                            << builtin << style::Code << ")" << style::Plain
+                            << style::Attribute("@builtin")
+                            << style::Code("(", style::Enum(builtin), ")")
                             << " appears multiple times as pipeline "
                             << (param_or_ret == ParamOrRetType::kParameter ? "input" : "output");
                         return false;
@@ -1208,8 +1195,8 @@
                     if (pipeline_io_attribute) {
                         AddError(attr->source) << "multiple entry point IO attributes";
                         AddNote(pipeline_io_attribute->source)
-                            << "previously consumed " << style::Attribute << "@"
-                            << pipeline_io_attribute->Name();
+                            << "previously consumed "
+                            << style::Attribute("@", pipeline_io_attribute->Name());
                         return false;
                     }
                     pipeline_io_attribute = attr;
@@ -1236,8 +1223,8 @@
                     if (pipeline_io_attribute) {
                         AddError(attr->source) << "multiple entry point IO attributes";
                         AddNote(pipeline_io_attribute->source)
-                            << "previously consumed " << style::Attribute << "@"
-                            << pipeline_io_attribute->Name();
+                            << "previously consumed "
+                            << style::Attribute("@", pipeline_io_attribute->Name());
                         return false;
                     }
                     pipeline_io_attribute = attr;
@@ -1285,18 +1272,18 @@
                 if (ty->is_integer_scalar_or_vector() && !interpolate_attribute) {
                     if (decl->PipelineStage() == ast::PipelineStage::kVertex &&
                         param_or_ret == ParamOrRetType::kReturnType) {
-                        AddError(source) << "integral user-defined vertex outputs must have a "
-                                         << style::Attribute << "@interpolate" << style::Code << "("
-                                         << style::Enum << "flat" << style::Code << ")"
-                                         << style::Plain << " attribute";
+                        AddError(source)
+                            << "integral user-defined vertex outputs must have a "
+                            << style::Attribute("@interpolate")
+                            << style::Code("(", style::Enum("flat"), ")") << " attribute";
                         return false;
                     }
                     if (decl->PipelineStage() == ast::PipelineStage::kFragment &&
                         param_or_ret == ParamOrRetType::kParameter) {
-                        AddError(source) << "integral user-defined fragment inputs must have a "
-                                         << style::Attribute << "@interpolate" << style::Code << "("
-                                         << style::Enum << "flat" << style::Code << ")"
-                                         << style::Plain << " attribute";
+                        AddError(source)
+                            << "integral user-defined fragment inputs must have a "
+                            << style::Attribute("@interpolate")
+                            << style::Code("(", style::Enum("flat"), ")") << " attribute";
                         return false;
                     }
                 }
@@ -1308,9 +1295,9 @@
                 // in the backend writers.
                 if (location.value_or(1) != 0) {
                     AddError(blend_src_attribute->source)
-                        << style::Attribute << "@blend_src" << style::Plain
-                        << " can only be used with " << style::Attribute << "@location"
-                        << style::Code << "(" << style::Literal << "0" << style::Code << ")";
+                        << style::Attribute("@blend_src") << " can only be used with "
+                        << style::Attribute("@location")
+                        << style::Code("(", style::Literal("0"), ")");
                     return false;
                 }
             }
@@ -1323,12 +1310,12 @@
 
             if (first_blend_src && first_location_without_blend_src) {
                 AddError(first_location_without_blend_src->source)
-                    << "use of " << style::Attribute << "@blend_src" << style::Plain
-                    << " requires all the output " << style::Attribute << "@location"
-                    << style::Plain << " attributes of the entry point to be paired with a "
-                    << style::Attribute << "@blend_src" << style::Plain << " attribute";
+                    << "use of " << style::Attribute("@blend_src") << " requires all the output "
+                    << style::Attribute("@location")
+                    << " attributes of the entry point to be paired with a "
+                    << style::Attribute("@blend_src") << " attribute";
                 AddNote(first_blend_src->source)
-                    << "use of " << style::Attribute << "@blend_src" << style::Plain << " here";
+                    << "use of " << style::Attribute("@blend_src") << " here";
                 return false;
             }
 
@@ -1338,11 +1325,10 @@
                 }
                 if (first_nonzero_location && first_blend_src) {
                     AddError(first_blend_src->source)
-                        << "pipeline cannot use both a " << style::Attribute << "@blend_src"
-                        << style::Plain << " and non-zero " << style::Attribute << "@location";
+                        << "pipeline cannot use both a " << style::Attribute("@blend_src")
+                        << " and non-zero " << style::Attribute("@location");
                     AddNote(first_nonzero_location->source)
-                        << "non-zero " << style::Attribute << "@location" << style::Plain
-                        << " declared here";
+                        << "non-zero " << style::Attribute("@location") << " declared here";
                     return false;
                 }
 
@@ -1350,21 +1336,21 @@
                                                                      blend_src.value_or(0));
                 if (!locations_and_blend_srcs.Add(location_and_blend_src)) {
                     auto& err = AddError(location_attribute->source)
-                                << style::Attribute << "@location" << style::Code << "("
-                                << style::Literal << location.value() << style::Code << ")";
+                                << style::Attribute("@location")
+                                << style::Code("(", style::Literal(location.value()), ")");
                     if (blend_src_attribute) {
-                        err << style::Attribute << " @blend_src" << style::Code << "("
-                            << style::Literal << blend_src.value() << style::Code << ")";
+                        err << style::Attribute(" @blend_src")
+                            << style::Code("(", style::Literal(blend_src.value()), ")");
                     }
-                    err << style::Plain << " appears multiple times";
+                    err << " appears multiple times";
                     return false;
                 }
             }
 
             if (color_attribute && !colors.Add(color.value())) {
                 AddError(color_attribute->source)
-                    << style::Attribute << "@color" << style::Code << "(" << style::Literal
-                    << color.value() << style::Code << ")" << style::Plain
+                    << style::Attribute("@color")
+                    << style::Code("(", style::Literal(color.value()), ")")
                     << " appears multiple times";
                 return false;
             }
@@ -1373,8 +1359,8 @@
                 if (!pipeline_io_attribute ||
                     !pipeline_io_attribute->Is<ast::LocationAttribute>()) {
                     AddError(interpolate_attribute->source)
-                        << style::Attribute << "@interpolate" << style::Plain
-                        << " can only be used with " << style::Attribute << "@location";
+                        << style::Attribute("@interpolate") << " can only be used with "
+                        << style::Attribute("@location");
                     return false;
                 }
             }
@@ -1389,9 +1375,9 @@
                 }
                 if (!has_position) {
                     AddError(invariant_attribute->source)
-                        << style::Attribute << "@invariant" << style::Plain
-                        << " must be applied to a " << style::Attribute << "@builtin" << style::Code
-                        << "(" << style::Enum << "position" << style::Code << ")";
+                        << style::Attribute("@invariant") << " must be applied to a "
+                        << style::Attribute("@builtin")
+                        << style::Code("(", style::Enum("position"), ")");
                     return false;
                 }
             }
@@ -1417,8 +1403,8 @@
                             member->Declaration()->source, param_or_ret,
                             /*is_struct_member*/ true, member->Attributes().location,
                             member->Attributes().blend_src, member->Attributes().color)) {
-                        AddNote(decl->source) << "while analyzing entry point " << style::Function
-                                              << decl->name->symbol.Name();
+                        AddNote(decl->source) << "while analyzing entry point "
+                                              << style::Function(decl->name->symbol.NameView());
                         return false;
                     }
                 }
@@ -1469,16 +1455,16 @@
             }
         }
         if (!found) {
-            AddError(decl->source) << "a vertex shader must include the " << style::Enum
-                                   << "position" << style::Plain << " builtin in its return type";
+            AddError(decl->source) << "a vertex shader must include the " << style::Enum("position")
+                                   << " builtin in its return type";
             return false;
         }
     }
 
     if (decl->PipelineStage() == ast::PipelineStage::kCompute) {
         if (!ast::HasAttribute<ast::WorkgroupAttribute>(decl->attributes)) {
-            AddError(decl->source) << "a compute shader must include " << style::Attribute
-                                   << "@workgroup_size" << style::Plain << " in its attributes";
+            AddError(decl->source) << "a compute shader must include "
+                                   << style::Attribute("@workgroup_size") << " in its attributes";
             return false;
         }
     }
@@ -1504,13 +1490,13 @@
             // Bindings must not alias within a shader stage: two different variables in the
             // resource interface of a given shader must not have the same group and binding values,
             // when considered as a pair of values.
-            auto func_name = decl->name->symbol.Name();
+            auto func_name = decl->name->symbol.NameView();
             AddError(var_decl->source)
-                << "entry point " << style::Function << func_name << style::Plain
+                << "entry point " << style::Function(func_name)
                 << " references multiple variables that use the same resource binding "
-                << style::Attribute << "@group" << style::Code << "(" << style::Literal << bp->group
-                << style::Code << ")" << style::Plain << ", " << style::Attribute << "@binding"
-                << style::Code << "(" << style::Literal << bp->binding << style::Code << ")";
+                << style::Attribute("@group") << style::Code("(", style::Literal(bp->group), ")")
+                << ", " << style::Attribute("@binding")
+                << style::Code("(", style::Literal(bp->binding), ")");
             AddNote(added.value->source) << "first resource binding usage declared here";
             return false;
         }
@@ -1547,8 +1533,8 @@
         if (auto* stmt = expr->Stmt()) {
             if (auto* decl = As<ast::VariableDeclStatement>(stmt->Declaration())) {
                 if (decl->variable->Is<ast::Const>()) {
-                    AddNote(decl->source) << "consider changing " << style::Keyword << "const"
-                                          << style::Plain << " to " << style::Keyword << "let";
+                    AddNote(decl->source) << "consider changing " << style::Keyword("const")
+                                          << " to " << style::Keyword("let");
                 }
             }
         }
@@ -1624,14 +1610,14 @@
             call->Target(),  //
             [&](const sem::Function* fn) {
                 AddError(call->Declaration()->source)
-                    << "ignoring return value of function " << style::Function
-                    << fn->Declaration()->name->symbol.Name() << style::Plain << " annotated with "
-                    << style::Attribute << "@must_use";
+                    << "ignoring return value of function "
+                    << style::Function(fn->Declaration()->name->symbol.NameView())
+                    << " annotated with " << style::Attribute("@must_use");
                 sem_.NoteDeclarationSource(fn->Declaration());
             },
             [&](const sem::BuiltinFn* b) {
                 AddError(call->Declaration()->source)
-                    << "ignoring return value of builtin " << style::Function << b->Fn();
+                    << "ignoring return value of builtin " << style::Function(b->Fn());
             },
             [&](const sem::ValueConversion*) {
                 AddError(call->Declaration()->source) << "value conversion evaluated but not used";
@@ -1745,7 +1731,7 @@
             // used instead.
             auto* builtin = call->Target()->As<sem::BuiltinFn>();
             AddError(call->Declaration()->source)
-                << "builtin function " << style::Function << builtin->Fn() << style::Plain
+                << "builtin function " << style::Function(builtin->Fn())
                 << " does not return a value";
             return false;
         }
@@ -1851,8 +1837,8 @@
     if (extension != wgsl::Extension::kUndefined) {
         if (!enabled_extensions_.Contains(extension)) {
             AddError(call->Declaration()->source)
-                << "cannot call built-in function " << style::Function << builtin->Fn()
-                << style::Plain << " without extension " << extension;
+                << "cannot call built-in function " << style::Function(builtin->Fn())
+                << " without extension " << extension;
             return false;
         }
     }
@@ -1861,8 +1847,8 @@
     if (feature != wgsl::LanguageFeature::kUndefined) {
         if (!allowed_features_.features.count(feature)) {
             AddError(call->Declaration()->source)
-                << "built-in function " << style::Function << builtin->Fn() << style::Plain
-                << " requires the " << style::Code << wgsl::ToString(feature) << style::Plain
+                << "built-in function " << style::Function(builtin->Fn()) << " requires the "
+                << style::Code(wgsl::ToString(feature))
                 << " language feature, which is not allowed in the current environment";
             return false;
         }
@@ -1874,8 +1860,8 @@
 bool Validator::CheckF16Enabled(const Source& source) const {
     // Validate if f16 type is allowed.
     if (!enabled_extensions_.Contains(wgsl::Extension::kF16)) {
-        AddError(source) << style::Type << "f16" << style::Plain << " type used without "
-                         << style::Code << "f16" << style::Plain << " extension enabled";
+        AddError(source) << style::Type("f16") << " type used without " << style::Code("f16")
+                         << " extension enabled";
         return false;
     }
     return true;
@@ -1904,7 +1890,7 @@
         AddError(decl->source) << "too "
                                << (more ? std::string("many") : std::string("few")) +
                                       " arguments in call to "
-                               << style::Function << name << style::Plain << ", expected "
+                               << style::Function(name) << ", expected "
                                << target->Parameters().Length() << ", got "
                                << call->Arguments().Length();
         return false;
@@ -1918,9 +1904,9 @@
 
         if (param_type != arg_type) {
             AddError(arg_expr->source) << "type mismatch for argument " << (i + 1) << " in call to "
-                                       << style::Function << name << style::Plain << ", expected "
-                                       << style::Type << sem_.TypeNameOf(param_type) << style::Plain
-                                       << ", got " << style::Type << sem_.TypeNameOf(arg_type);
+                                       << style::Function(name) << ", expected "
+                                       << style::Type(sem_.TypeNameOf(param_type)) << ", got "
+                                       << style::Type(sem_.TypeNameOf(arg_type));
             return false;
         }
 
@@ -1964,8 +1950,8 @@
             // https://gpuweb.github.io/gpuweb/wgsl/#function-call-expr
             // If the called function does not return a value, a function call
             // statement should be used instead.
-            AddError(decl->source) << "function " << style::Function << name << style::Plain
-                                   << " does not return a value";
+            AddError(decl->source)
+                << "function " << style::Function(name) << " does not return a value";
             return false;
         }
     }
@@ -1994,8 +1980,8 @@
             if (member->Type() != value_ty->UnwrapRef()) {
                 AddError(value->source)
                     << "type in structure constructor does not match struct member type: expected "
-                    << style::Type << sem_.TypeNameOf(member->Type()) << style::Plain << ", found "
-                    << style::Type << sem_.TypeNameOf(value_ty);
+                    << style::Type(sem_.TypeNameOf(member->Type())) << ", found "
+                    << style::Type(sem_.TypeNameOf(value_ty));
                 return false;
             }
         }
@@ -2011,9 +1997,9 @@
         auto* value_ty = sem_.TypeOf(value)->UnwrapRef();
         if (core::type::Type::ConversionRank(value_ty, elem_ty) ==
             core::type::Type::kNoConversion) {
-            AddError(value->source) << style::Type << sem_.TypeNameOf(value_ty) << style::Plain
-                                    << " cannot be used to construct an array of " << style::Type
-                                    << sem_.TypeNameOf(elem_ty);
+            AddError(value->source) << style::Type(sem_.TypeNameOf(value_ty))
+                                    << " cannot be used to construct an array of "
+                                    << style::Type(sem_.TypeNameOf(elem_ty));
             return false;
         }
     }
@@ -2051,10 +2037,9 @@
 
 bool Validator::Vector(const core::type::Type* el_ty, const Source& source) const {
     if (!el_ty->Is<core::type::Scalar>()) {
-        AddError(source) << "vector element type must be " << style::Type << "bool" << style::Plain
-                         << ", " << style::Type << "f32" << style::Plain << ", " << style::Type
-                         << "f16" << style::Plain << ", " << style::Type << "i32" << style::Plain
-                         << " or " << style::Type << "u32";
+        AddError(source) << "vector element type must be " << style::Type("bool") << ", "
+                         << style::Type("f32") << ", " << style::Type("f16") << ", "
+                         << style::Type("i32") << " or " << style::Type("u32");
         return false;
     }
     return true;
@@ -2062,8 +2047,8 @@
 
 bool Validator::Matrix(const core::type::Type* el_ty, const Source& source) const {
     if (!el_ty->is_float_scalar()) {
-        AddError(source) << "matrix element type must be " << style::Type << "f32" << style::Plain
-                         << " or " << style::Type << "f16";
+        AddError(source) << "matrix element type must be " << style::Type("f32") << " or "
+                         << style::Type("f16");
         return false;
     }
     return true;
@@ -2073,12 +2058,13 @@
     auto backtrace = [&](const sem::Function* func, const sem::Function* entry_point) {
         if (func != entry_point) {
             TraverseCallChain(entry_point, func, [&](const sem::Function* f) {
-                AddNote(f->Declaration()->source) << "called by function " << style::Function
-                                                  << f->Declaration()->name->symbol.Name();
+                AddNote(f->Declaration()->source)
+                    << "called by function "
+                    << style::Function(f->Declaration()->name->symbol.NameView());
             });
             AddNote(entry_point->Declaration()->source)
-                << "called by entry point " << style::Function
-                << entry_point->Declaration()->name->symbol.Name();
+                << "called by entry point "
+                << style::Function(entry_point->Declaration()->name->symbol.NameView());
         }
     };
 
@@ -2091,7 +2077,7 @@
                     break;
                 }
             }
-            AddError(source) << "var with " << style::Enum << var->AddressSpace() << style::Plain
+            AddError(source) << "var with " << style::Enum(var->AddressSpace())
                              << " address space cannot be used by " << stage << " pipeline stage";
             AddNote(var->Declaration()->source) << "variable is declared here";
             backtrace(func, entry_point);
@@ -2299,7 +2285,7 @@
                 [&](const ast::StructMemberSizeAttribute*) {
                     if (!member->Type()->HasCreationFixedFootprint()) {
                         AddError(attr->source)
-                            << style::Attribute << "@size" << style::Plain
+                            << style::Attribute("@size")
                             << " can only be applied to members where the member's type size can "
                                "be fully determined at shader creation time";
                         return false;
@@ -2314,8 +2300,7 @@
 
         if (invariant_attribute && !has_position) {
             AddError(invariant_attribute->source)
-                << style::Attribute << "@invariant" << style::Plain
-                << " must be applied to a position builtin";
+                << style::Attribute("@invariant") << " must be applied to a position builtin";
             return false;
         }
 
@@ -2325,17 +2310,16 @@
             // backend writers.
             if (member->Attributes().location.value_or(1) != 0) {
                 AddError(blend_src_attribute->source)
-                    << style::Attribute << "@blend_src" << style::Plain << " can only be used with "
-                    << style::Attribute << "@location" << style::Code << "(" << style::Literal
-                    << "0" << style::Code << ")";
+                    << style::Attribute("@blend_src") << " can only be used with "
+                    << style::Attribute("@location") << style::Code("(", style::Literal("0"), ")");
                 return false;
             }
         }
 
         if (interpolate_attribute && !location_attribute) {
             AddError(interpolate_attribute->source)
-                << style::Attribute << "@interpolate" << style::Plain << " can only be used with "
-                << style::Attribute << "@location";
+                << style::Attribute("@interpolate") << " can only be used with "
+                << style::Attribute("@location");
             return false;
         }
 
@@ -2346,13 +2330,13 @@
 
             if (!locations_and_blend_srcs.Add(std::make_pair(location, blend_src))) {
                 auto& err = AddError(location_attribute->source)
-                            << style::Attribute << "@location" << style::Code << "("
-                            << style::Literal << location << style::Code << ")";
+                            << style::Attribute("@location")
+                            << style::Code("(", style::Literal(location), ")");
                 if (blend_src) {
-                    err << style::Attribute << " @blend_src" << style::Code << "(" << style::Literal
-                        << blend_src.value() << style::Code << ")";
+                    err << style::Attribute(" @blend_src")
+                        << style::Code("(", style::Literal(blend_src.value()), ")");
                 }
-                err << style::Plain << " appears multiple times";
+                err << " appears multiple times";
                 return false;
             }
         }
@@ -2361,8 +2345,8 @@
             uint32_t color = member->Attributes().color.value();
             if (!colors.Add(color)) {
                 AddError(color_attribute->source)
-                    << style::Attribute << "@color" << style::Code << "(" << style::Literal << color
-                    << style::Code << ")" << style::Plain << " appears multiple times";
+                    << style::Attribute("@color") << style::Code("(", style::Literal(color), ")")
+                    << " appears multiple times";
                 return false;
             }
         }
@@ -2376,17 +2360,17 @@
                                   ast::PipelineStage stage,
                                   const Source& source) const {
     if (stage == ast::PipelineStage::kCompute) {
-        AddError(attr->source) << style::Attribute << "@" << attr->Name() << style::Plain
+        AddError(attr->source) << style::Attribute("@", attr->Name())
                                << " cannot be used by compute shaders";
         return false;
     }
 
     if (!type->is_numeric_scalar_or_vector()) {
         std::string invalid_type = sem_.TypeNameOf(type);
-        AddError(source) << "cannot apply " << style::Attribute << "@location" << style::Plain
-                         << " to declaration of type " << style::Type << invalid_type;
+        AddError(source) << "cannot apply " << style::Attribute("@location")
+                         << " to declaration of type " << style::Type(invalid_type);
         AddNote(attr->source)
-            << style::Attribute << "@location" << style::Plain
+            << style::Attribute("@location")
             << " must only be applied to declarations of numeric scalar or numeric vector type";
         return false;
     }
@@ -2400,9 +2384,9 @@
                                const Source& source,
                                const std::optional<bool> is_input) const {
     if (!enabled_extensions_.Contains(wgsl::Extension::kChromiumExperimentalFramebufferFetch)) {
-        AddError(attr->source) << "use of " << style::Attribute << "@color" << style::Plain
-                               << " requires enabling extension " << style::Code
-                               << "chromium_experimental_framebuffer_fetch";
+        AddError(attr->source) << "use of " << style::Attribute("@color")
+                               << " requires enabling extension "
+                               << style::Code("chromium_experimental_framebuffer_fetch");
         return false;
     }
 
@@ -2410,17 +2394,17 @@
         stage != ast::PipelineStage::kNone && stage != ast::PipelineStage::kFragment;
     bool is_output = !is_input.value_or(true);
     if (is_stage_non_fragment || is_output) {
-        AddError(attr->source) << style::Attribute << "@color" << style::Plain
+        AddError(attr->source) << style::Attribute("@color")
                                << " can only be used for fragment shader input";
         return false;
     }
 
     if (!type->is_numeric_scalar_or_vector()) {
         std::string invalid_type = sem_.TypeNameOf(type);
-        AddError(source) << "cannot apply " << style::Attribute << "@color" << style::Plain
-                         << " to declaration of type " << style::Type << invalid_type;
+        AddError(source) << "cannot apply " << style::Attribute("@color")
+                         << " to declaration of type " << style::Type(invalid_type);
         AddNote(attr->source)
-            << style::Attribute << "@color" << style::Plain
+            << style::Attribute("@color")
             << " must only be applied to declarations of numeric scalar or numeric vector type";
         return false;
     }
@@ -2432,9 +2416,9 @@
                                   ast::PipelineStage stage,
                                   const std::optional<bool> is_input) const {
     if (!enabled_extensions_.Contains(wgsl::Extension::kChromiumInternalDualSourceBlending)) {
-        AddError(attr->source) << "use of " << style::Attribute << "@blend_src" << style::Plain
-                               << " requires enabling extension " << style::Code
-                               << "chromium_internal_dual_source_blending";
+        AddError(attr->source) << "use of " << style::Attribute("@blend_src")
+                               << " requires enabling extension "
+                               << style::Code("chromium_internal_dual_source_blending");
         return false;
     }
 
@@ -2442,7 +2426,7 @@
         stage != ast::PipelineStage::kNone && stage != ast::PipelineStage::kFragment;
     bool is_output = is_input.value_or(false);
     if (is_stage_non_fragment || is_output) {
-        AddError(attr->source) << style::Attribute << "@" << attr->Name() << style::Plain
+        AddError(attr->source) << style::Attribute("@", attr->Name())
                                << " can only be used for fragment shader output";
         return false;
     }
@@ -2456,9 +2440,9 @@
                        sem::Statement* current_statement) const {
     if (func_type->UnwrapRef() != ret_type) {
         AddError(ret->source)
-            << "return statement type must match its function return type, returned " << style::Type
-            << sem_.TypeNameOf(ret_type) << style::Plain << ", expected " << style::Type
-            << sem_.TypeNameOf(func_type);
+            << "return statement type must match its function return type, returned "
+            << style::Type(sem_.TypeNameOf(ret_type)) << ", expected "
+            << style::Type(sem_.TypeNameOf(func_type));
         return false;
     }
 
@@ -2561,9 +2545,8 @@
             !ty->IsAnyOf<core::type::Pointer, core::type::Texture, core::type::Sampler,
                          core::type::AbstractNumeric>()) {
             AddError(rhs->source)
-                << "cannot assign " << style::Type << sem_.TypeNameOf(rhs_ty) << style::Plain
-                << " to " << style::Code << "_" << style::Plain << ". " << style::Code << "_"
-                << style::Plain
+                << "cannot assign " << style::Type(sem_.TypeNameOf(rhs_ty)) << " to "
+                << style::Code("_") << ". " << style::Code("_")
                 << " can only be assigned a constructible, pointer, texture or sampler type";
             return false;
         }
@@ -2590,20 +2573,17 @@
                             user->Variable()->Declaration(),  //
                             [&](const ast::Let* v) {
                                 AddNote(user->Declaration()->source)
-                                    << style::Variable << "let" << style::Plain
-                                    << " variables are immutable";
+                                    << style::Variable("let") << " variables are immutable";
                                 sem_.NoteDeclarationSource(v);
                             },
                             [&](const ast::Const* v) {
                                 AddNote(user->Declaration()->source)
-                                    << style::Variable << "const" << style::Plain
-                                    << " variables are immutable";
+                                    << style::Variable("const") << " variables are immutable";
                                 sem_.NoteDeclarationSource(v);
                             },
                             [&](const ast::Override* v) {
                                 AddNote(user->Declaration()->source)
-                                    << style::Variable << "override" << style::Plain
-                                    << " variables are immutable";
+                                    << style::Variable("override") << " variables are immutable";
                                 sem_.NoteDeclarationSource(v);
                             },
                             [&](const ast::Parameter* v) {
@@ -2623,8 +2603,8 @@
 
     // Value type has to match storage type
     if (storage_ty != value_type) {
-        AddError(a->source) << "cannot assign " << style::Type << sem_.TypeNameOf(rhs_ty)
-                            << style::Plain << " to " << style::Type << sem_.TypeNameOf(lhs_ty);
+        AddError(a->source) << "cannot assign " << style::Type(sem_.TypeNameOf(rhs_ty)) << " to "
+                            << style::Type(sem_.TypeNameOf(lhs_ty));
         return false;
     }
     if (!storage_ty->IsConstructible()) {
@@ -2632,8 +2612,8 @@
         return false;
     }
     if (lhs_ref->Access() == core::Access::kRead) {
-        AddError(a->source) << "cannot store into a read-only type " << style::Type
-                            << sem_.RawTypeNameOf(lhs_ty);
+        AddError(a->source) << "cannot store into a read-only type "
+                            << style::Type(sem_.RawTypeNameOf(lhs_ty));
         return false;
     }
     return true;
@@ -2653,11 +2633,11 @@
                 return true;
             },
             [&](const ast::Let*) {
-                AddError(lhs->source) << "cannot modify " << style::Keyword << "let";
+                AddError(lhs->source) << "cannot modify " << style::Keyword("let");
                 return true;
             },
             [&](const ast::Override*) {
-                AddError(lhs->source) << "cannot modify " << style::Keyword << "override";
+                AddError(lhs->source) << "cannot modify " << style::Keyword("override");
                 return true;
             });
         if (errored) {
@@ -2670,8 +2650,8 @@
     auto* lhs_ref = lhs_ty->As<core::type::Reference>();
     if (!lhs_ref) {
         // LHS is not a reference, so it has no storage.
-        AddError(lhs->source) << "cannot modify value of type " << style::Type
-                              << sem_.TypeNameOf(lhs_ty);
+        AddError(lhs->source) << "cannot modify value of type "
+                              << style::Type(sem_.TypeNameOf(lhs_ty));
         return false;
     }
 
@@ -2682,8 +2662,8 @@
     }
 
     if (lhs_ref->Access() == core::Access::kRead) {
-        AddError(inc->source) << "cannot modify read-only type " << style::Type
-                              << sem_.RawTypeNameOf(lhs_ty);
+        AddError(inc->source) << "cannot modify read-only type "
+                              << style::Type(sem_.RawTypeNameOf(lhs_ty));
         return false;
     }
     return true;
@@ -2721,8 +2701,8 @@
         if (!diag_added && diag_added.value->severity != dc->severity) {
             AddError(dc->rule_name->source) << "conflicting diagnostic " << use;
             AddNote(diag_added.value->rule_name->source)
-                << "severity of " << style::Code << dc->rule_name->String() << style::Plain
-                << " set to " << style::Code << dc->severity << style::Plain << " here";
+                << "severity of " << style::Code(dc->rule_name->String()) << " set to "
+                << style::Code(dc->severity) << " here";
             return false;
         }
     }
@@ -2756,11 +2736,9 @@
 }
 
 void Validator::RaiseArrayWithOverrideCountError(const Source& source) const {
-    AddError(source) << style::Type << "array" << style::Plain << " with an " << style::Keyword
-                     << "override" << style::Plain
-                     << " element count can only be used as the store type of a " << style::Keyword
-                     << "var" << style::Code << "<" << style::Enum << "workgroup" << style::Code
-                     << ">";
+    AddError(source) << style::Type("array") << " with an " << style::Keyword("override")
+                     << " element count can only be used as the store type of a "
+                     << style::Keyword("var") << style::Code("<", style::Enum("workgroup"), ">");
 }
 
 std::string Validator::VectorPretty(uint32_t size, const core::type::Type* element_type) const {
@@ -2784,20 +2762,18 @@
                     using Allowed = std::tuple<core::type::I32, core::type::U32, core::type::F32>;
                     if (TINT_UNLIKELY(!member->Type()->TypeInfo().IsAnyOfTuple<Allowed>())) {
                         AddError(member->Declaration()->source)
-                            << style::Keyword << "struct" << style::Plain << " members used in the "
-                            << style::Enum << "pixel_local" << style::Plain
-                            << " address space can only be of the type " << style::Type << "i32"
-                            << style::Plain << ", " << style::Type << "u32" << style::Plain
-                            << " or " << style::Type << "f32";
-                        AddNote(source)
-                            << style::Keyword << "struct " << style::Type << str->Name().Name()
-                            << style::Plain << " used in the " << style::Enum << "pixel_local"
-                            << style::Plain << " address space here";
+                            << style::Keyword("struct") << " members used in the "
+                            << style::Enum("pixel_local")
+                            << " address space can only be of the type " << style::Type("i32")
+                            << ", " << style::Type("u32") << " or " << style::Type("f32");
+                        AddNote(source) << style::Keyword("struct ")
+                                        << style::Type(str->Name().Name()) << " used in the "
+                                        << style::Enum("pixel_local") << " address space here";
                         return false;
                     }
                 }
             } else if (TINT_UNLIKELY(!store_ty->TypeInfo().Is<core::type::Struct>())) {
-                AddError(source) << style::Enum << "pixel_local" << style::Plain
+                AddError(source) << style::Enum("pixel_local")
                                  << " variable only support struct storage types";
                 return false;
             }
@@ -2807,19 +2783,18 @@
                                   wgsl::Extension::kChromiumExperimentalPushConstant) &&
                               IsValidationEnabled(attributes,
                                                   ast::DisabledValidation::kIgnoreAddressSpace))) {
-                AddError(source) << "use of variable address space " << style::Enum
-                                 << "push_constant" << style::Plain
-                                 << " requires enabling extension " << style::Code
-                                 << "chromium_experimental_push_constant";
+                AddError(source) << "use of variable address space " << style::Enum("push_constant")
+                                 << " requires enabling extension "
+                                 << style::Code("chromium_experimental_push_constant");
                 return false;
             }
             break;
         case core::AddressSpace::kStorage:
             if (TINT_UNLIKELY(access == core::Access::kWrite)) {
                 // The access mode for the storage address space can only be 'read' or 'read_write'.
-                AddError(source) << "access mode " << style::Enum << "write" << style::Plain
-                                 << " is not valid for the " << style::Enum << "storage"
-                                 << style::Plain << " address space";
+                AddError(source) << "access mode " << style::Enum("write")
+                                 << " is not valid for the " << style::Enum("storage")
+                                 << " address space";
                 return false;
             }
             break;
@@ -2831,15 +2806,15 @@
         StyledText err;
         if (address_space != core::AddressSpace::kStorage &&
             address_space != core::AddressSpace::kWorkgroup) {
-            AddError(source) << style::Type << "atomic" << style::Plain << " variables must have "
-                             << style::Enum << "storage" << style::Plain << " or " << style::Enum
-                             << "workgroup" << style::Plain << " address space";
+            AddError(source) << style::Type("atomic") << " variables must have "
+                             << style::Enum("storage") << " or " << style::Enum("workgroup")
+                             << " address space";
             return true;
         }
         if (address_space == core::AddressSpace::kStorage && access != core::Access::kReadWrite) {
-            AddError(source) << "atomic variables in " << style::Enum << "storage" << style::Plain
-                             << " address space must have " << style::Enum << "read_write"
-                             << style::Plain << " access mode";
+            AddError(source) << "atomic variables in " << style::Enum("storage")
+                             << " address space must have " << style::Enum("read_write")
+                             << " access mode";
             return true;
         }
         return false;
@@ -2849,8 +2824,8 @@
         if (auto atomic_use = atomic_composite_info_.Get(store_ty)) {
             if (TINT_UNLIKELY(atomic_error())) {
                 AddNote(**atomic_use)
-                    << "atomic sub-type of " << style::Type << sem_.TypeNameOf(store_ty)
-                    << style::Plain << " is declared here";
+                    << "atomic sub-type of " << style::Type(sem_.TypeNameOf(store_ty))
+                    << " is declared here";
                 return false;
             }
         }
@@ -2890,29 +2865,31 @@
             }
 
             AddError(ep->Declaration()->source)
-                << "entry point " << style::Function << ep->Declaration()->name->symbol.Name()
-                << style::Plain << " uses two different " << style::Enum << space << style::Plain
-                << " variables.";
-            AddNote(var->Declaration()->source) << "first " << style::Enum << space << style::Plain
-                                                << " variable declaration is here";
+                << "entry point " << style::Function(ep->Declaration()->name->symbol.NameView())
+                << " uses two different " << style::Enum(space) << " variables.";
+            AddNote(var->Declaration()->source)
+                << "first " << style::Enum(space) << " variable declaration is here";
             if (func != ep) {
                 TraverseCallChain(ep, func, [&](const sem::Function* f) {
-                    AddNote(f->Declaration()->source) << "called by function " << style::Function
-                                                      << f->Declaration()->name->symbol.Name();
+                    AddNote(f->Declaration()->source)
+                        << "called by function "
+                        << style::Function(f->Declaration()->name->symbol.NameView());
                 });
-                AddNote(ep->Declaration()->source) << "called by entry point " << style::Function
-                                                   << ep->Declaration()->name->symbol.Name();
+                AddNote(ep->Declaration()->source)
+                    << "called by entry point "
+                    << style::Function(ep->Declaration()->name->symbol.NameView());
             }
             AddNote(seen_var->Declaration()->source)
-                << "second " << style::Enum << space << style::Plain
-                << " variable declaration is here";
+                << "second " << style::Enum(space) << " variable declaration is here";
             if (seen_func != ep) {
                 TraverseCallChain(ep, seen_func, [&](const sem::Function* f) {
-                    AddNote(f->Declaration()->source) << "called by function " << style::Function
-                                                      << f->Declaration()->name->symbol.Name();
+                    AddNote(f->Declaration()->source)
+                        << "called by function "
+                        << style::Function(f->Declaration()->name->symbol.NameView());
                 });
-                AddNote(ep->Declaration()->source) << "called by entry point " << style::Function
-                                                   << ep->Declaration()->name->symbol.Name();
+                AddNote(ep->Declaration()->source)
+                    << "called by entry point "
+                    << style::Function(ep->Declaration()->name->symbol.NameView());
             }
             return false;
         }
diff --git a/src/tint/utils/diagnostic/formatter.cc b/src/tint/utils/diagnostic/formatter.cc
index 664c4f8..9fd279b 100644
--- a/src/tint/utils/diagnostic/formatter.cc
+++ b/src/tint/utils/diagnostic/formatter.cc
@@ -143,12 +143,12 @@
     }
 
     if (!prefix.IsEmpty()) {
-        text << style::Plain << ": ";
+        text << style::Plain(": ");
     }
     text << style::Bold << diag.message;
 
     if (style_.print_line && src.file && rng.begin.line > 0) {
-        text << style::Plain << "\n";
+        text << style::Plain("\n");
 
         for (size_t line_num = rng.begin.line;
              (line_num <= rng.end.line) && (line_num <= src.file->content.lines.size());
@@ -168,7 +168,7 @@
                 }
             }
 
-            text << style::Plain << "\n";
+            text << style::Plain("\n");
 
             // If the line contains non-ascii characters, then we cannot assume that
             // a single utf8 code unit represents a single glyph, so don't attempt to
@@ -206,7 +206,7 @@
                 // Middle of multi-line
                 text.Repeat('^', num_glyphs(1, line_len + 1));
             }
-            text << style::Plain << "\n";
+            text << style::Plain("\n");
         }
     }
 }
diff --git a/src/tint/utils/templates/intrinsic_table_data.tmpl.inc b/src/tint/utils/templates/intrinsic_table_data.tmpl.inc
index 0308082..9ea5a45 100644
--- a/src/tint/utils/templates/intrinsic_table_data.tmpl.inc
+++ b/src/tint/utils/templates/intrinsic_table_data.tmpl.inc
@@ -291,11 +291,11 @@
 {{- end  }}
 
 {{- if .DisplayName }}
-    out {{range SplitDisplayName .DisplayName}} << style::Type << {{.}}{{end}};
+    out << style::Type({{range $i, $n := SplitDisplayName .DisplayName}}{{if $i}}, {{end}}{{$n}}{{end}});
 {{- else if .TemplateParams }}
-    out << style::Type << "{{.Name}}" << style::Code << "<"{{template "AppendTemplateParamNames" .TemplateParams}} << style::Code << ">";
+    out << style::Type("{{.Name}}", "<", {{template "AppendTemplateParamNames" .TemplateParams}}, ">");
 {{- else }}
-    out << style::Type << "{{.Name}}";
+    out << style::Type("{{.Name}}");
 {{- end  }}
   }
 };
@@ -322,8 +322,8 @@
     // template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
 {{  range .Types -}}
 {{-   if      IsFirstIn . $.Types }} k{{PascalCase .Name}}Matcher.print(nullptr, out);
-{{-   else if IsLastIn  . $.Types }} out << TextStyle{} << " or "; k{{PascalCase .Name}}Matcher.print(nullptr, out);
-{{-   else                        }} out << TextStyle{} << ", "; k{{PascalCase .Name}}Matcher.print(nullptr, out);
+{{-   else if IsLastIn  . $.Types }} out << style::Plain(" or "); k{{PascalCase .Name}}Matcher.print(nullptr, out);
+{{-   else                        }} out << style::Plain(", "); k{{PascalCase .Name}}Matcher.print(nullptr, out);
 {{-   end -}}
 {{- end -}}
   }
@@ -362,9 +362,9 @@
 /* print */ [](MatchState*, StyledText& out) {
   out
 {{- range .Options -}}
-{{-   if      IsFirstIn . $.Options }}<< style::Enum << "{{.Name}}"
-{{-   else if IsLastIn  . $.Options }}<< TextStyle{} << " or " << style::Enum << "{{.Name}}"
-{{-   else                          }}<< TextStyle{} << ", " << style::Enum << "{{.Name}}"
+{{-   if      IsFirstIn . $.Options }}<< style::Enum("{{.Name}}")
+{{-   else if IsLastIn  . $.Options }}<< style::Plain(" or ") << style::Enum("{{.Name}}")
+{{-   else                          }}<< style::Plain(", ") << style::Enum("{{.Name}}")
 {{-   end -}}
 {{- end -}};
   }
@@ -479,8 +479,8 @@
 {{-                      define "AppendTemplateParamNames"                   -}}
 {{- /* ------------------------------------------------------------------ */ -}}
 {{-   range $i, $ := . -}}
-{{-     if $i }} << style::Code << ", " << style::Type << {{.Name}}
-{{-     else  }} << style::Type << {{.Name}}
+{{-     if $i }}, ", ", {{.Name}}
+{{-     else  }}{{.Name}}
 {{-     end  -}}
 {{-   end -}}
 {{- end -}}
diff --git a/src/tint/utils/text/styled_text.cc b/src/tint/utils/text/styled_text.cc
index 448ecdf..76d33cf 100644
--- a/src/tint/utils/text/styled_text.cc
+++ b/src/tint/utils/text/styled_text.cc
@@ -58,12 +58,14 @@
 }
 
 StyledText& StyledText::SetStyle(TextStyle style) {
-    if (spans_.Back().style != style) {
-        if (spans_.Back().length == 0) {
-            spans_.Back().style = style;
-        } else {
-            spans_.Push(Span{style});
-        }
+    if (spans_.Back().style == style) {
+        return *this;
+    }
+    if (spans_.Back().length == 0) {
+        spans_.Pop();
+    }
+    if (spans_.IsEmpty() || spans_.Back().style != style) {
+        spans_.Push(Span{style});
     }
     return *this;
 }
diff --git a/src/tint/utils/text/styled_text.h b/src/tint/utils/text/styled_text.h
index f46ced1..21a82aa 100644
--- a/src/tint/utils/text/styled_text.h
+++ b/src/tint/utils/text/styled_text.h
@@ -30,6 +30,8 @@
 
 #include <string>
 #include <string_view>
+#include <tuple>
+#include <type_traits>
 #include <utility>
 
 #include "src/tint/utils/containers/vector.h"
@@ -70,6 +72,9 @@
     /// Clears the text and restore the default style
     void Clear();
 
+    /// @returns the TextStyle of all future writes to this StyledText
+    TextStyle Style() const { return spans_.Back().style; }
+
     /// Sets the style for all future writes to this StyledText
     StyledText& SetStyle(TextStyle style);
 
@@ -77,6 +82,8 @@
     std::string Plain() const;
 
     /// Appends the styled text of @p other to this StyledText.
+    /// @note: Unlike `operator<<(const StyledText&)`, this StyledText's previous style will *not*
+    /// be automatically restored.
     void Append(const StyledText& other);
 
     /// repeat queues the character @p c to be written to the StyledText n times.
@@ -85,15 +92,30 @@
     /// @returns this StyledText so calls can be chained.
     StyledText& Repeat(char c, size_t n);
 
-    /// operator<<() appends @p value to the StyledText.
-    /// @p value can be a StyledText to change the style of future appends.
+    /// operator<<() appends @p value to this StyledText.
+    /// @p value which is one of:
+    ///  * a TextStyle, which sets the style of all future appends. Note that this fully replaces
+    ///    the old style (no style combinations / overlays).
+    ///  * a ScopedTextStyle, which will will use the span's style for each value, and then this
+    ///    StyledText's previous style will be restored.
+    ///  * a StyledText, which will be appended to this StyledText, and then this StyledText's
+    ///    previous style will be restored.
+    ///  * any other type will be stringified and appended to this StyledText using the current
+    ///    TextStyle.
     template <typename VALUE>
     StyledText& operator<<(VALUE&& value) {
         using T = std::decay_t<VALUE>;
         if constexpr (std::is_same_v<T, TextStyle>) {
             SetStyle(std::forward<VALUE>(value));
         } else if constexpr (std::is_same_v<T, StyledText>) {
+            auto old_style = Style();
             Append(value);
+            *this << old_style;
+        } else if constexpr (IsScopedTextStyle<T>) {
+            auto old_style = Style();
+            std::apply([&](auto&&... values) { ((*this << value.style << values), ...); },
+                       value.values);
+            *this << old_style;
         } else {
             uint32_t offset = stream_.tellp();
             stream_ << value;
diff --git a/src/tint/utils/text/text_style.h b/src/tint/utils/text/text_style.h
index 84deef9..a9ce9e1 100644
--- a/src/tint/utils/text/text_style.h
+++ b/src/tint/utils/text/text_style.h
@@ -31,9 +31,18 @@
 #define SRC_TINT_UTILS_TEXT_TEXT_STYLE_H_
 
 #include <cstdint>
+#include <string_view>
+#include <tuple>
+#include <type_traits>
 
 #include "src/tint/utils/containers/enum_set.h"
 
+// Forward declarations
+namespace tint {
+template <typename... VALUES>
+struct ScopedTextStyle;
+}
+
 namespace tint {
 
 /// TextStyle is a styling that can be applied to span of a StyledText.
@@ -120,10 +129,37 @@
         return TextStyle{out};
     }
 
+    /// @returns a new ScopedTextStyle of @p values using with this TextStyle
+    template <typename... VALUES>
+    inline ScopedTextStyle<VALUES...> operator()(VALUES&&... values) const;
+
     /// The style bit pattern
     Bits bits = 0;
 };
 
+/// ScopedTextStyle is a span of text, styled with a TextStyle
+template <typename... VALUES>
+struct ScopedTextStyle {
+    std::tuple<VALUES...> values;
+    TextStyle style;
+};
+
+template <typename... VALUES>
+ScopedTextStyle<VALUES...> TextStyle::operator()(VALUES&&... values) const {
+    return ScopedTextStyle<VALUES...>{std::forward_as_tuple(values...), *this};
+}
+
+namespace detail {
+template <typename T>
+struct IsScopedTextStyle : std::false_type {};
+template <typename... VALUES>
+struct IsScopedTextStyle<ScopedTextStyle<VALUES...> > : std::true_type {};
+}  // namespace detail
+
+/// Resolves to true iff T is a ScopedTextStyle.
+template <typename T>
+static constexpr bool IsScopedTextStyle = detail::IsScopedTextStyle<T>::value;
+
 }  // namespace tint
 
 namespace tint::style {
diff --git a/tools/src/gerrit/gerrit.go b/tools/src/gerrit/gerrit.go
index d208539..9d4d310 100644
--- a/tools/src/gerrit/gerrit.go
+++ b/tools/src/gerrit/gerrit.go
@@ -279,11 +279,9 @@
 type CommentSide int
 
 const (
-	// Left is used to specifiy that code comments should appear on the parent
-	// change
+	// Left is used to specify that code comments should appear on the parent change
 	Left CommentSide = iota
-	// Right is used to specifiy that code comments should appear on the new
-	// change
+	// Right is used to specify that code comments should appear on the new change
 	Right
 )