Import Tint changes from Dawn

Changes:
  - bfced8b81864de13fff25d48a50d09a5d87d99be [tint][wgsl] Hide 'chromium' extensions in diagnostics by Ben Clayton <bclayton@google.com>
  - 0b255d2da6884122ffced19bddffcb87510c1a3b [tint] Use std::string_view for generated enum strings by Ben Clayton <bclayton@google.com>
  - 1d6f3543b9e5a5dcce74a7b94db867649d82253e [tint][wgsl] Generate kAllExtensions, kAllLanguageFeatures by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: bfced8b81864de13fff25d48a50d09a5d87d99be
Change-Id: Ifce700708546f2ca59aa9471cac7827d9a0d60a6
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/159700
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/core/access.h b/src/tint/lang/core/access.h
index 2dfea06..9ace935 100644
--- a/src/tint/lang/core/access.h
+++ b/src/tint/lang/core/access.h
@@ -68,7 +68,7 @@
 /// @returns the parsed enum, or Access::kUndefined if the string could not be parsed.
 Access ParseAccess(std::string_view str);
 
-constexpr const char* kAccessStrings[] = {
+constexpr std::string_view kAccessStrings[] = {
     "read",
     "read_write",
     "write",
diff --git a/src/tint/lang/core/address_space.h b/src/tint/lang/core/address_space.h
index bb2ac7a..8418574 100644
--- a/src/tint/lang/core/address_space.h
+++ b/src/tint/lang/core/address_space.h
@@ -75,7 +75,7 @@
 /// @returns the parsed enum, or AddressSpace::kUndefined if the string could not be parsed.
 AddressSpace ParseAddressSpace(std::string_view str);
 
-constexpr const char* kAddressSpaceStrings[] = {
+constexpr std::string_view kAddressSpaceStrings[] = {
     "__in",          "__out",   "function", "pixel_local", "private",
     "push_constant", "storage", "uniform",  "workgroup",
 };
diff --git a/src/tint/lang/core/attribute.h b/src/tint/lang/core/attribute.h
index 606f4b6..3e7b087 100644
--- a/src/tint/lang/core/attribute.h
+++ b/src/tint/lang/core/attribute.h
@@ -84,7 +84,7 @@
 /// @returns the parsed enum, or Attribute::kUndefined if the string could not be parsed.
 Attribute ParseAttribute(std::string_view str);
 
-constexpr const char* kAttributeStrings[] = {
+constexpr std::string_view kAttributeStrings[] = {
     "align",    "binding", "builtin", "compute",        "diagnostic", "fragment",
     "group",    "id",      "index",   "interpolate",    "invariant",  "location",
     "must_use", "size",    "vertex",  "workgroup_size",
diff --git a/src/tint/lang/core/builtin_type.h b/src/tint/lang/core/builtin_type.h
index 6817791..ac0a22e 100644
--- a/src/tint/lang/core/builtin_type.h
+++ b/src/tint/lang/core/builtin_type.h
@@ -161,7 +161,7 @@
 /// @returns the parsed enum, or BuiltinType::kUndefined if the string could not be parsed.
 BuiltinType ParseBuiltinType(std::string_view str);
 
-constexpr const char* kBuiltinTypeStrings[] = {
+constexpr std::string_view kBuiltinTypeStrings[] = {
     "__atomic_compare_exchange_result_i32",
     "__atomic_compare_exchange_result_u32",
     "__frexp_result_abstract",
diff --git a/src/tint/lang/core/builtin_value.h b/src/tint/lang/core/builtin_value.h
index 917ba14..1af2585 100644
--- a/src/tint/lang/core/builtin_value.h
+++ b/src/tint/lang/core/builtin_value.h
@@ -80,7 +80,7 @@
 /// @returns the parsed enum, or BuiltinValue::kUndefined if the string could not be parsed.
 BuiltinValue ParseBuiltinValue(std::string_view str);
 
-constexpr const char* kBuiltinValueStrings[] = {
+constexpr std::string_view kBuiltinValueStrings[] = {
     "__point_size",           "frag_depth",     "front_facing",
     "global_invocation_id",   "instance_index", "local_invocation_id",
     "local_invocation_index", "num_workgroups", "position",
diff --git a/src/tint/lang/core/interpolation_sampling.h b/src/tint/lang/core/interpolation_sampling.h
index ebec530..ca7e075 100644
--- a/src/tint/lang/core/interpolation_sampling.h
+++ b/src/tint/lang/core/interpolation_sampling.h
@@ -70,7 +70,7 @@
 /// parsed.
 InterpolationSampling ParseInterpolationSampling(std::string_view str);
 
-constexpr const char* kInterpolationSamplingStrings[] = {
+constexpr std::string_view kInterpolationSamplingStrings[] = {
     "center",
     "centroid",
     "sample",
diff --git a/src/tint/lang/core/interpolation_type.h b/src/tint/lang/core/interpolation_type.h
index e1eeb15..a71af66 100644
--- a/src/tint/lang/core/interpolation_type.h
+++ b/src/tint/lang/core/interpolation_type.h
@@ -69,7 +69,7 @@
 /// @returns the parsed enum, or InterpolationType::kUndefined if the string could not be parsed.
 InterpolationType ParseInterpolationType(std::string_view str);
 
-constexpr const char* kInterpolationTypeStrings[] = {
+constexpr std::string_view kInterpolationTypeStrings[] = {
     "flat",
     "linear",
     "perspective",
diff --git a/src/tint/lang/core/texel_format.h b/src/tint/lang/core/texel_format.h
index f99504b..da6791d 100644
--- a/src/tint/lang/core/texel_format.h
+++ b/src/tint/lang/core/texel_format.h
@@ -82,7 +82,7 @@
 /// @returns the parsed enum, or TexelFormat::kUndefined if the string could not be parsed.
 TexelFormat ParseTexelFormat(std::string_view str);
 
-constexpr const char* kTexelFormatStrings[] = {
+constexpr std::string_view kTexelFormatStrings[] = {
     "bgra8unorm", "r32float",    "r32sint",    "r32uint",    "rg32float",   "rg32sint",
     "rg32uint",   "rgba16float", "rgba16sint", "rgba16uint", "rgba32float", "rgba32sint",
     "rgba32uint", "rgba8sint",   "rgba8snorm", "rgba8uint",  "rgba8unorm",
diff --git a/src/tint/lang/wgsl/ast/transform/renamer_test.cc b/src/tint/lang/wgsl/ast/transform/renamer_test.cc
index d31f0a6..fca0b22 100644
--- a/src/tint/lang/wgsl/ast/transform/renamer_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/renamer_test.cc
@@ -1791,22 +1791,21 @@
     return std::string(name);
 }
 
-std::vector<const char*> ConstructableTypes() {
-    std::vector<const char*> out;
-    for (auto* ty : core::kBuiltinTypeStrings) {
-        std::string_view type(ty);
+std::vector<std::string_view> ConstructableTypes() {
+    std::vector<std::string_view> out;
+    for (auto type : core::kBuiltinTypeStrings) {
         if (type != "ptr" && type != "atomic" && !tint::HasPrefix(type, "sampler") &&
             !tint::HasPrefix(type, "texture") && !tint::HasPrefix(type, "__")) {
-            out.push_back(ty);
+            out.push_back(type);
         }
     }
     return out;
 }
 
-using RenamerBuiltinTypeTest = TransformTestWithParam<const char*>;
+using RenamerBuiltinTypeTest = TransformTestWithParam<std::string_view>;
 
 TEST_P(RenamerBuiltinTypeTest, PreserveTypeUsage) {
-    auto expand = [&](const char* source) {
+    auto expand = [&](std::string_view source) {
         return tint::ReplaceAll(source, "$type", ExpandBuiltinType(GetParam()));
     };
 
@@ -1845,7 +1844,7 @@
     EXPECT_EQ(expect, str(got));
 }
 TEST_P(RenamerBuiltinTypeTest, PreserveTypeInitializer) {
-    auto expand = [&](const char* source) {
+    auto expand = [&](std::string_view source) {
         return tint::ReplaceAll(source, "$type", ExpandBuiltinType(GetParam()));
     };
 
@@ -1877,7 +1876,7 @@
         return;  // Cannot value convert arrays.
     }
 
-    auto expand = [&](const char* source) {
+    auto expand = [&](std::string_view source) {
         return tint::ReplaceAll(source, "$type", ExpandBuiltinType(GetParam()));
     };
 
@@ -1929,7 +1928,7 @@
 }
 
 TEST_P(RenamerBuiltinTypeTest, RenameShadowedByAlias) {
-    auto expand = [&](const char* source) {
+    auto expand = [&](std::string_view source) {
         std::string_view ty = GetParam();
         auto out = tint::ReplaceAll(source, "$name", ty);
         out = tint::ReplaceAll(out, "$type", ExpandBuiltinType(ty));
@@ -1961,7 +1960,7 @@
 }
 
 TEST_P(RenamerBuiltinTypeTest, RenameShadowedByStruct) {
-    auto expand = [&](const char* source) {
+    auto expand = [&](std::string_view source) {
         std::string_view ty = GetParam();
         auto out = tint::ReplaceAll(source, "$name", ty);
         out = tint::ReplaceAll(out, "$type", ExpandBuiltinType(ty));
@@ -2003,31 +2002,33 @@
                          testing::ValuesIn(ConstructableTypes()));
 
 /// @return WGSL builtin identifier keywords
-std::vector<const char*> Identifiers() {
-    std::vector<const char*> out;
-    for (auto* ident : core::kBuiltinTypeStrings) {
+std::vector<std::string_view> Identifiers() {
+    std::vector<std::string_view> out;
+    for (auto ident : core::kBuiltinTypeStrings) {
         if (!tint::HasPrefix(ident, "__")) {
             out.push_back(ident);
         }
     }
-    for (auto* ident : core::kAddressSpaceStrings) {
+    for (auto ident : core::kAddressSpaceStrings) {
         if (!tint::HasPrefix(ident, "_")) {
             out.push_back(ident);
         }
     }
-    for (auto* ident : core::kTexelFormatStrings) {
+    for (auto ident : core::kTexelFormatStrings) {
         out.push_back(ident);
     }
-    for (auto* ident : core::kAccessStrings) {
+    for (auto ident : core::kAccessStrings) {
         out.push_back(ident);
     }
     return out;
 }
 
-using RenamerBuiltinIdentifierTest = TransformTestWithParam<const char*>;
+using RenamerBuiltinIdentifierTest = TransformTestWithParam<std::string_view>;
 
 TEST_P(RenamerBuiltinIdentifierTest, GlobalConstName) {
-    auto expand = [&](const char* source) { return tint::ReplaceAll(source, "$name", GetParam()); };
+    auto expand = [&](std::string_view source) {
+        return tint::ReplaceAll(source, "$name", GetParam());
+    };
 
     auto src = expand(R"(
 const $name = 42;
@@ -2051,7 +2052,9 @@
 }
 
 TEST_P(RenamerBuiltinIdentifierTest, LocalVarName) {
-    auto expand = [&](const char* source) { return tint::ReplaceAll(source, "$name", GetParam()); };
+    auto expand = [&](std::string_view source) {
+        return tint::ReplaceAll(source, "$name", GetParam());
+    };
 
     auto src = expand(R"(
 fn f() {
@@ -2071,7 +2074,9 @@
 }
 
 TEST_P(RenamerBuiltinIdentifierTest, FunctionName) {
-    auto expand = [&](const char* source) { return tint::ReplaceAll(source, "$name", GetParam()); };
+    auto expand = [&](std::string_view source) {
+        return tint::ReplaceAll(source, "$name", GetParam());
+    };
 
     auto src = expand(R"(
 fn $name() {
@@ -2097,7 +2102,7 @@
 }
 
 TEST_P(RenamerBuiltinIdentifierTest, StructName) {
-    auto expand = [&](const char* source) {
+    auto expand = [&](std::string_view source) {
         std::string_view name = GetParam();
         auto out = tint::ReplaceAll(source, "$name", name);
         return tint::ReplaceAll(out, "$other_type", name == "i32" ? "u32" : "i32");
diff --git a/src/tint/lang/wgsl/common/allowed_features.h b/src/tint/lang/wgsl/common/allowed_features.h
index 080e3c8..d494352 100644
--- a/src/tint/lang/wgsl/common/allowed_features.h
+++ b/src/tint/lang/wgsl/common/allowed_features.h
@@ -50,21 +50,14 @@
         AllowedFeatures allowed_features;
 
         // Allow all extensions.
-        allowed_features.extensions.insert(wgsl::Extension::kChromiumDisableUniformityAnalysis);
-        allowed_features.extensions.insert(wgsl::Extension::kChromiumExperimentalDp4A);
-        allowed_features.extensions.insert(wgsl::Extension::kChromiumExperimentalFullPtrParameters);
-        allowed_features.extensions.insert(wgsl::Extension::kChromiumExperimentalPixelLocal);
-        allowed_features.extensions.insert(wgsl::Extension::kChromiumExperimentalPushConstant);
-        allowed_features.extensions.insert(
-            wgsl::Extension::kChromiumExperimentalReadWriteStorageTexture);
-        allowed_features.extensions.insert(wgsl::Extension::kChromiumExperimentalSubgroups);
-        allowed_features.extensions.insert(wgsl::Extension::kChromiumInternalDualSourceBlending);
-        allowed_features.extensions.insert(wgsl::Extension::kChromiumInternalRelaxedUniformLayout);
-        allowed_features.extensions.insert(wgsl::Extension::kF16);
+        for (auto extension : wgsl::kAllExtensions) {
+            allowed_features.extensions.insert(extension);
+        }
 
         // Allow all language features.
-        allowed_features.features.insert(
-            wgsl::LanguageFeature::kReadonlyAndReadwriteStorageTextures);
+        for (auto feature : wgsl::kAllLanguageFeatures) {
+            allowed_features.features.insert(feature);
+        }
 
         return allowed_features;
     }
diff --git a/src/tint/lang/wgsl/diagnostic_rule.h b/src/tint/lang/wgsl/diagnostic_rule.h
index db19783..cf2d8f0 100644
--- a/src/tint/lang/wgsl/diagnostic_rule.h
+++ b/src/tint/lang/wgsl/diagnostic_rule.h
@@ -68,7 +68,7 @@
 /// @returns the parsed enum, or CoreDiagnosticRule::kUndefined if the string could not be parsed.
 CoreDiagnosticRule ParseCoreDiagnosticRule(std::string_view str);
 
-constexpr const char* kCoreDiagnosticRuleStrings[] = {
+constexpr std::string_view kCoreDiagnosticRuleStrings[] = {
     "derivative_uniformity",
 };
 
@@ -96,7 +96,7 @@
 /// parsed.
 ChromiumDiagnosticRule ParseChromiumDiagnosticRule(std::string_view str);
 
-constexpr const char* kChromiumDiagnosticRuleStrings[] = {
+constexpr std::string_view kChromiumDiagnosticRuleStrings[] = {
     "unreachable_code",
 };
 
diff --git a/src/tint/lang/wgsl/diagnostic_severity.h b/src/tint/lang/wgsl/diagnostic_severity.h
index 026b421..dfccb5d 100644
--- a/src/tint/lang/wgsl/diagnostic_severity.h
+++ b/src/tint/lang/wgsl/diagnostic_severity.h
@@ -72,7 +72,7 @@
 /// @returns the parsed enum, or DiagnosticSeverity::kUndefined if the string could not be parsed.
 DiagnosticSeverity ParseDiagnosticSeverity(std::string_view str);
 
-constexpr const char* kDiagnosticSeverityStrings[] = {
+constexpr std::string_view kDiagnosticSeverityStrings[] = {
     "error",
     "info",
     "off",
diff --git a/src/tint/lang/wgsl/extension.h b/src/tint/lang/wgsl/extension.h
index dfcb7e1..931c1ad 100644
--- a/src/tint/lang/wgsl/extension.h
+++ b/src/tint/lang/wgsl/extension.h
@@ -75,7 +75,7 @@
 /// @returns the parsed enum, or Extension::kUndefined if the string could not be parsed.
 Extension ParseExtension(std::string_view str);
 
-constexpr const char* kExtensionStrings[] = {
+constexpr std::string_view kExtensionStrings[] = {
     "chromium_disable_uniformity_analysis",      "chromium_experimental_dp4a",
     "chromium_experimental_full_ptr_parameters", "chromium_experimental_pixel_local",
     "chromium_experimental_push_constant",       "chromium_experimental_read_write_storage_texture",
@@ -83,7 +83,21 @@
     "chromium_internal_relaxed_uniform_layout",  "f16",
 };
 
-// A unique vector of extensions
+/// All extensions
+static constexpr Extension kAllExtensions[] = {
+    Extension::kChromiumDisableUniformityAnalysis,
+    Extension::kChromiumExperimentalDp4A,
+    Extension::kChromiumExperimentalFullPtrParameters,
+    Extension::kChromiumExperimentalPixelLocal,
+    Extension::kChromiumExperimentalPushConstant,
+    Extension::kChromiumExperimentalReadWriteStorageTexture,
+    Extension::kChromiumExperimentalSubgroups,
+    Extension::kChromiumInternalDualSourceBlending,
+    Extension::kChromiumInternalRelaxedUniformLayout,
+    Extension::kF16,
+};
+
+/// A unique vector of extensions
 using Extensions = UniqueVector<Extension, 4>;
 
 }  // namespace tint::wgsl
diff --git a/src/tint/lang/wgsl/extension.h.tmpl b/src/tint/lang/wgsl/extension.h.tmpl
index 1c69ee1..3ea8b69 100644
--- a/src/tint/lang/wgsl/extension.h.tmpl
+++ b/src/tint/lang/wgsl/extension.h.tmpl
@@ -24,7 +24,14 @@
 /// @see src/tint/lang/wgsl/intrinsics.def for extension descriptions
 {{ Eval "DeclareEnum" $enum}}
 
-// A unique vector of extensions
+/// All extensions
+static constexpr Extension kAllExtensions[] = {
+{{-   range $entry := $enum.Entries }}
+    Extension::k{{PascalCase $entry.Name}},
+{{-   end }}
+};
+
+/// A unique vector of extensions
 using Extensions = UniqueVector<Extension, 4>;
 
 }  // namespace tint::wgsl
diff --git a/src/tint/lang/wgsl/language_feature.h b/src/tint/lang/wgsl/language_feature.h
index 0a5573a..afdbdba 100644
--- a/src/tint/lang/wgsl/language_feature.h
+++ b/src/tint/lang/wgsl/language_feature.h
@@ -66,11 +66,16 @@
 /// @returns the parsed enum, or LanguageFeature::kUndefined if the string could not be parsed.
 LanguageFeature ParseLanguageFeature(std::string_view str);
 
-constexpr const char* kLanguageFeatureStrings[] = {
+constexpr std::string_view kLanguageFeatureStrings[] = {
     "readonly_and_readwrite_storage_textures",
 };
 
-// A unique vector of language features
+/// All features
+static constexpr LanguageFeature kAllLanguageFeatures[] = {
+    LanguageFeature::kReadonlyAndReadwriteStorageTextures,
+};
+
+/// A unique vector of language features
 using LanguageFeatures = UniqueVector<LanguageFeature, 4>;
 
 }  // namespace tint::wgsl
diff --git a/src/tint/lang/wgsl/language_feature.h.tmpl b/src/tint/lang/wgsl/language_feature.h.tmpl
index cbe9f78..7c88975 100644
--- a/src/tint/lang/wgsl/language_feature.h.tmpl
+++ b/src/tint/lang/wgsl/language_feature.h.tmpl
@@ -24,7 +24,14 @@
 /// @see src/tint/lang/wgsl/intrinsics.def for language feature descriptions
 {{ Eval "DeclareEnum" $enum}}
 
-// A unique vector of language features
+/// All features
+static constexpr LanguageFeature kAllLanguageFeatures[] = {
+{{-   range $entry := $enum.Entries }}
+    LanguageFeature::k{{PascalCase $entry.Name}},
+{{-   end }}
+};
+
+/// A unique vector of language features
 using LanguageFeatures = UniqueVector<LanguageFeature, 4>;
 
 }  // namespace tint::wgsl
diff --git a/src/tint/lang/wgsl/reader/parser/enable_directive_test.cc b/src/tint/lang/wgsl/reader/parser/enable_directive_test.cc
index e43af72..26bd8dc 100644
--- a/src/tint/lang/wgsl/reader/parser/enable_directive_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/enable_directive_test.cc
@@ -171,26 +171,40 @@
 }
 
 // Test an unknown extension identifier.
-TEST_F(EnableDirectiveTest, InvalidIdentifier) {
+TEST_F(EnableDirectiveTest, InvalidExtension) {
     auto p = parser("enable NotAValidExtensionName;");
     p->enable_directive();
     // Error when unknown extension found
     EXPECT_TRUE(p->has_error());
     EXPECT_EQ(p->error(), R"(1:8: expected extension
-Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_full_ptr_parameters', 'chromium_experimental_pixel_local', 'chromium_experimental_push_constant', 'chromium_experimental_read_write_storage_texture', 'chromium_experimental_subgroups', 'chromium_internal_dual_source_blending', 'chromium_internal_relaxed_uniform_layout', 'f16')");
+Possible values: 'f16')");
     auto program = p->program();
     auto& ast = program.AST();
     EXPECT_EQ(ast.Enables().Length(), 0u);
     EXPECT_EQ(ast.GlobalDeclarations().Length(), 0u);
 }
 
-TEST_F(EnableDirectiveTest, InvalidIdentifierSuggest) {
+TEST_F(EnableDirectiveTest, InvalidExtensionSuggest) {
     auto p = parser("enable f15;");
     p->enable_directive();
     // Error when unknown extension found
     EXPECT_TRUE(p->has_error());
     EXPECT_EQ(p->error(), R"(1:8: expected extension
 Did you mean 'f16'?
+Possible values: 'f16')");
+    auto program = p->program();
+    auto& ast = program.AST();
+    EXPECT_EQ(ast.Enables().Length(), 0u);
+    EXPECT_EQ(ast.GlobalDeclarations().Length(), 0u);
+}
+
+// Test an unknown extension identifier, starting with 'chromium'
+TEST_F(EnableDirectiveTest, InvalidChromiumExtension) {
+    auto p = parser("enable chromium_blah;");
+    p->enable_directive();
+    // Error when unknown extension found
+    EXPECT_TRUE(p->has_error());
+    EXPECT_EQ(p->error(), R"(1:8: expected extension
 Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_full_ptr_parameters', 'chromium_experimental_pixel_local', 'chromium_experimental_push_constant', 'chromium_experimental_read_write_storage_texture', 'chromium_experimental_subgroups', 'chromium_internal_dual_source_blending', 'chromium_internal_relaxed_uniform_layout', 'f16')");
     auto program = p->program();
     auto& ast = program.AST();
@@ -239,7 +253,7 @@
         p->translation_unit();
         EXPECT_TRUE(p->has_error());
         EXPECT_EQ(p->error(), R"(1:8: expected extension
-Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_full_ptr_parameters', 'chromium_experimental_pixel_local', 'chromium_experimental_push_constant', 'chromium_experimental_read_write_storage_texture', 'chromium_experimental_subgroups', 'chromium_internal_dual_source_blending', 'chromium_internal_relaxed_uniform_layout', 'f16')");
+Possible values: 'f16')");
         auto program = p->program();
         auto& ast = program.AST();
         EXPECT_EQ(ast.Enables().Length(), 0u);
@@ -250,7 +264,7 @@
         p->translation_unit();
         EXPECT_TRUE(p->has_error());
         EXPECT_EQ(p->error(), R"(1:8: expected extension
-Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_full_ptr_parameters', 'chromium_experimental_pixel_local', 'chromium_experimental_push_constant', 'chromium_experimental_read_write_storage_texture', 'chromium_experimental_subgroups', 'chromium_internal_dual_source_blending', 'chromium_internal_relaxed_uniform_layout', 'f16')");
+Possible values: 'f16')");
         auto program = p->program();
         auto& ast = program.AST();
         EXPECT_EQ(ast.Enables().Length(), 0u);
@@ -262,7 +276,7 @@
         EXPECT_TRUE(p->has_error());
         EXPECT_EQ(p->error(), R"(1:8: expected extension
 Did you mean 'f16'?
-Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_full_ptr_parameters', 'chromium_experimental_pixel_local', 'chromium_experimental_push_constant', 'chromium_experimental_read_write_storage_texture', 'chromium_experimental_subgroups', 'chromium_internal_dual_source_blending', 'chromium_internal_relaxed_uniform_layout', 'f16')");
+Possible values: 'f16')");
         auto program = p->program();
         auto& ast = program.AST();
         EXPECT_EQ(ast.Enables().Length(), 0u);
diff --git a/src/tint/lang/wgsl/reader/parser/lexer.cc b/src/tint/lang/wgsl/reader/parser/lexer.cc
index ebc5522..7b19d4c 100644
--- a/src/tint/lang/wgsl/reader/parser/lexer.cc
+++ b/src/tint/lang/wgsl/reader/parser/lexer.cc
@@ -132,7 +132,7 @@
     return tokens;
 }
 
-const std::string_view Lexer::line() const {
+std::string_view Lexer::line() const {
     if (file_->content.lines.size() == 0) {
         static const char* empty_string = "";
         return empty_string;
diff --git a/src/tint/lang/wgsl/reader/parser/lexer.h b/src/tint/lang/wgsl/reader/parser/lexer.h
index 23676c2..7d10dd9 100644
--- a/src/tint/lang/wgsl/reader/parser/lexer.h
+++ b/src/tint/lang/wgsl/reader/parser/lexer.h
@@ -87,7 +87,7 @@
     void end_source(Source&) const;
 
     /// @returns view of current line
-    const std::string_view line() const;
+    std::string_view line() const;
     /// @returns position in current line
     size_t pos() const;
     /// @returns length of current line
diff --git a/src/tint/lang/wgsl/reader/parser/parser.cc b/src/tint/lang/wgsl/reader/parser/parser.cc
index 54aeb27..6a10c72 100644
--- a/src/tint/lang/wgsl/reader/parser/parser.cc
+++ b/src/tint/lang/wgsl/reader/parser/parser.cc
@@ -907,14 +907,15 @@
     return builder_.ty(builder_.Ident(source.Source(), ident.to_str(), std::move(args.value)));
 }
 
-template <typename ENUM, size_t N>
+template <typename ENUM>
 Expect<ENUM> Parser::expect_enum(std::string_view name,
                                  ENUM (*parse)(std::string_view str),
-                                 const char* const (&strings)[N],
+                                 Slice<const std::string_view> strings,
                                  std::string_view use) {
     auto& t = peek();
+    auto ident = t.to_str();
     if (t.IsIdentifier()) {
-        auto val = parse(t.to_str());
+        auto val = parse(ident);
         if (val != ENUM::kUndefined) {
             synchronized_ = true;
             next();
@@ -936,7 +937,20 @@
     }
     err << "\n";
 
-    tint::SuggestAlternatives(t.to_str(), strings, err);
+    if (strings == wgsl::kExtensionStrings && !HasPrefix(ident, "chromium")) {
+        // Filter out 'chromium' prefixed extensions. We don't want to advertise experimental
+        // extensions to end users (unless it looks like they've actually mis-typed a chromium
+        // extension name)
+        Vector<std::string_view, 8> filtered;
+        for (auto str : strings) {
+            if (!HasPrefix(str, "chromium")) {
+                filtered.Push(str);
+            }
+        }
+        tint::SuggestAlternatives(ident, filtered.Slice(), err);
+    } else {
+        tint::SuggestAlternatives(ident, strings, err);
+    }
 
     synchronized_ = false;
     return add_error(t.source(), err.str());
diff --git a/src/tint/lang/wgsl/reader/parser/parser.h b/src/tint/lang/wgsl/reader/parser/parser.h
index c489700..0e6c152 100644
--- a/src/tint/lang/wgsl/reader/parser/parser.h
+++ b/src/tint/lang/wgsl/reader/parser/parser.h
@@ -868,10 +868,10 @@
     /// @param parse the optimized function used to parse the enum
     /// @param strings the list of possible strings in the enum
     /// @param use an optional description of what was being parsed if an error was raised.
-    template <typename ENUM, size_t N>
+    template <typename ENUM>
     Expect<ENUM> expect_enum(std::string_view name,
                              ENUM (*parse)(std::string_view str),
-                             const char* const (&strings)[N],
+                             Slice<const std::string_view> strings,
                              std::string_view use = "");
 
     Expect<ast::Type> expect_type(std::string_view use);
diff --git a/src/tint/lang/wgsl/reader/parser/token.cc b/src/tint/lang/wgsl/reader/parser/token.cc
index 89d58e8..9ed9460 100644
--- a/src/tint/lang/wgsl/reader/parser/token.cc
+++ b/src/tint/lang/wgsl/reader/parser/token.cc
@@ -213,7 +213,7 @@
 
 Token::Token() : type_(Type::kUninitialized) {}
 
-Token::Token(Type type, const Source& source, const std::string_view& view)
+Token::Token(Type type, const Source& source, std::string_view view)
     : type_(type), source_(source), value_(view) {}
 
 Token::Token(Type type, const Source& source, const std::string& str)
diff --git a/src/tint/lang/wgsl/reader/parser/token.h b/src/tint/lang/wgsl/reader/parser/token.h
index 8ea96f9..b3cdebe 100644
--- a/src/tint/lang/wgsl/reader/parser/token.h
+++ b/src/tint/lang/wgsl/reader/parser/token.h
@@ -238,7 +238,7 @@
     /// @param type the Token::Type of the token
     /// @param source the source of the token
     /// @param view the source string view for the token
-    Token(Type type, const Source& source, const std::string_view& view);
+    Token(Type type, const Source& source, std::string_view view);
     /// Create a string Token
     /// @param type the Token::Type of the token
     /// @param source the source of the token
diff --git a/src/tint/lang/wgsl/resolver/builtin_enum_test.cc b/src/tint/lang/wgsl/resolver/builtin_enum_test.cc
index 265cab8..cc7bbeb 100644
--- a/src/tint/lang/wgsl/resolver/builtin_enum_test.cc
+++ b/src/tint/lang/wgsl/resolver/builtin_enum_test.cc
@@ -46,7 +46,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // access
 ////////////////////////////////////////////////////////////////////////////////
-using ResolverAccessUsedWithTemplateArgs = ResolverTestWithParam<const char*>;
+using ResolverAccessUsedWithTemplateArgs = ResolverTestWithParam<std::string_view>;
 
 TEST_P(ResolverAccessUsedWithTemplateArgs, Test) {
     // @group(0) @binding(0) var t : texture_storage_2d<rgba8unorm, ACCESS<i32>>;
@@ -65,7 +65,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // address space
 ////////////////////////////////////////////////////////////////////////////////
-using ResolverAddressSpaceUsedWithTemplateArgs = ResolverTestWithParam<const char*>;
+using ResolverAddressSpaceUsedWithTemplateArgs = ResolverTestWithParam<std::string_view>;
 
 TEST_P(ResolverAddressSpaceUsedWithTemplateArgs, Test) {
     // fn f(p : ptr<ADDRESS_SPACE<T>, f32) {}
@@ -85,7 +85,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // builtin value
 ////////////////////////////////////////////////////////////////////////////////
-using ResolverBuiltinValueUsedWithTemplateArgs = ResolverTestWithParam<const char*>;
+using ResolverBuiltinValueUsedWithTemplateArgs = ResolverTestWithParam<std::string_view>;
 
 TEST_P(ResolverBuiltinValueUsedWithTemplateArgs, Test) {
     // fn f(@builtin(BUILTIN<T>) p : vec4<f32>) {}
@@ -104,7 +104,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // interpolation sampling
 ////////////////////////////////////////////////////////////////////////////////
-using ResolverInterpolationSamplingUsedWithTemplateArgs = ResolverTestWithParam<const char*>;
+using ResolverInterpolationSamplingUsedWithTemplateArgs = ResolverTestWithParam<std::string_view>;
 
 TEST_P(ResolverInterpolationSamplingUsedWithTemplateArgs, Test) {
     // @fragment
@@ -132,7 +132,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // interpolation type
 ////////////////////////////////////////////////////////////////////////////////
-using ResolverInterpolationTypeUsedWithTemplateArgs = ResolverTestWithParam<const char*>;
+using ResolverInterpolationTypeUsedWithTemplateArgs = ResolverTestWithParam<std::string_view>;
 
 TEST_P(ResolverInterpolationTypeUsedWithTemplateArgs, Test) {
     // @fragment
@@ -160,7 +160,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // texel format
 ////////////////////////////////////////////////////////////////////////////////
-using ResolverTexelFormatUsedWithTemplateArgs = ResolverTestWithParam<const char*>;
+using ResolverTexelFormatUsedWithTemplateArgs = ResolverTestWithParam<std::string_view>;
 
 TEST_P(ResolverTexelFormatUsedWithTemplateArgs, Test) {
     // @group(0) @binding(0) var t : texture_storage_2d<TEXEL_FORMAT<T>, write>
diff --git a/src/tint/lang/wgsl/resolver/dependency_graph_test.cc b/src/tint/lang/wgsl/resolver/dependency_graph_test.cc
index 8a1f136..05a83d4 100644
--- a/src/tint/lang/wgsl/resolver/dependency_graph_test.cc
+++ b/src/tint/lang/wgsl/resolver/dependency_graph_test.cc
@@ -1233,7 +1233,7 @@
 namespace resolve_to_builtin_type {
 
 using ResolverDependencyGraphResolveToBuiltinType =
-    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, std::string_view>>;
 
 TEST_P(ResolverDependencyGraphResolveToBuiltinType, Resolve) {
     const auto use = std::get<0>(GetParam());
@@ -1272,7 +1272,7 @@
 namespace resolve_to_access {
 
 using ResolverDependencyGraphResolveToAccess =
-    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, std::string_view>>;
 
 TEST_P(ResolverDependencyGraphResolveToAccess, Resolve) {
     const auto use = std::get<0>(GetParam());
@@ -1311,7 +1311,7 @@
 namespace resolve_to_address_space {
 
 using ResolverDependencyGraphResolveToAddressSpace =
-    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, std::string_view>>;
 
 TEST_P(ResolverDependencyGraphResolveToAddressSpace, Resolve) {
     const auto use = std::get<0>(GetParam());
@@ -1350,7 +1350,7 @@
 namespace resolve_to_builtin_value {
 
 using ResolverDependencyGraphResolveToBuiltinValue =
-    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, std::string_view>>;
 
 TEST_P(ResolverDependencyGraphResolveToBuiltinValue, Resolve) {
     const auto use = std::get<0>(GetParam());
@@ -1389,7 +1389,7 @@
 namespace resolve_to_interpolation_sampling {
 
 using ResolverDependencyGraphResolveToInterpolationSampling =
-    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, std::string_view>>;
 
 TEST_P(ResolverDependencyGraphResolveToInterpolationSampling, Resolve) {
     const auto use = std::get<0>(GetParam());
@@ -1429,7 +1429,7 @@
 namespace resolve_to_interpolation_sampling {
 
 using ResolverDependencyGraphResolveToInterpolationType =
-    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, std::string_view>>;
 
 TEST_P(ResolverDependencyGraphResolveToInterpolationType, Resolve) {
     const auto use = std::get<0>(GetParam());
@@ -1469,7 +1469,7 @@
 namespace resolve_to_texel_format {
 
 using ResolverDependencyGraphResolveToTexelFormat =
-    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, std::string_view>>;
 
 TEST_P(ResolverDependencyGraphResolveToTexelFormat, Resolve) {
     const auto use = std::get<0>(GetParam());
@@ -1546,7 +1546,7 @@
                                                           SymbolDeclKind::NestedLocalLet)));
 
 using ResolverDependencyGraphShadowKindTest =
-    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, std::string_view>>;
 
 TEST_P(ResolverDependencyGraphShadowKindTest, ShadowedByGlobalVar) {
     const auto use = std::get<0>(GetParam());
@@ -1667,10 +1667,10 @@
 
     Vector<SymbolUse, 64> symbol_uses;
 
-    auto add_use = [&](const ast::Node* decl, auto use, int line, const char* kind) {
-        symbol_uses.Push(
-            SymbolUse{decl, IdentifierOf(use),
-                      std::string(__FILE__) + ":" + std::to_string(line) + ": " + kind});
+    auto add_use = [&](const ast::Node* decl, auto use, int line, std::string_view kind) {
+        symbol_uses.Push(SymbolUse{
+            decl, IdentifierOf(use),
+            std::string(__FILE__) + ":" + std::to_string(line) + ": " + std::string(kind)});
         return use;
     };
 
diff --git a/src/tint/lang/wgsl/resolver/sem_helper.cc b/src/tint/lang/wgsl/resolver/sem_helper.cc
index adb384e..a68734a 100644
--- a/src/tint/lang/wgsl/resolver/sem_helper.cc
+++ b/src/tint/lang/wgsl/resolver/sem_helper.cc
@@ -136,21 +136,21 @@
 void SemHelper::ErrorUnexpectedExprKind(
     const sem::Expression* expr,
     std::string_view wanted,
-    tint::Slice<char const* const> suggestions /* = Empty */) const {
+    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("unresolved " + std::string(wanted) + " '" + name + "'", ident->source);
         if (!suggestions.IsEmpty()) {
             // Filter out suggestions that have a leading underscore.
-            Vector<const char*, 8> filtered;
-            for (auto* str : suggestions) {
+            Vector<std::string_view, 8> filtered;
+            for (auto str : suggestions) {
                 if (str[0] != '_') {
                     filtered.Push(str);
                 }
             }
             StringStream msg;
-            tint::SuggestAlternatives(name, filtered.Slice().Reinterpret<char const* const>(), msg);
+            tint::SuggestAlternatives(name, filtered.Slice(), msg);
             AddNote(msg.str(), ident->source);
         }
         return;
diff --git a/src/tint/lang/wgsl/resolver/sem_helper.h b/src/tint/lang/wgsl/resolver/sem_helper.h
index bda2b0d..ad63ac2 100644
--- a/src/tint/lang/wgsl/resolver/sem_helper.h
+++ b/src/tint/lang/wgsl/resolver/sem_helper.h
@@ -278,7 +278,7 @@
     /// @param suggestions suggested valid identifiers
     void ErrorUnexpectedExprKind(const sem::Expression* expr,
                                  std::string_view wanted,
-                                 tint::Slice<char const* const> suggestions = Empty) const;
+                                 tint::Slice<const std::string_view> suggestions = Empty) const;
 
     /// If @p node is a module-scope type, variable or function declaration, then appends a note
     /// diagnostic where this declaration was declared, otherwise the function does nothing.
diff --git a/src/tint/utils/containers/slice.h b/src/tint/utils/containers/slice.h
index b88863a..f2cf7c3 100644
--- a/src/tint/utils/containers/slice.h
+++ b/src/tint/utils/containers/slice.h
@@ -265,6 +265,18 @@
 
     /// @returns the end for a reverse iterator
     auto rend() const { return std::reverse_iterator<const T*>(begin()); }
+
+    /// Equality operator.
+    /// @param other the other slice to compare against
+    /// @returns true if all fields of this slice are equal to the fields of @p other
+    bool operator==(const Slice& other) {
+        return data == other.data && len == other.len && cap == other.cap;
+    }
+
+    /// Inequality operator.
+    /// @param other the other slice to compare against
+    /// @returns false if any fields of this slice are not equal to the fields of @p other
+    bool operator!=(const Slice& other) { return !(*this == other); }
 };
 
 /// Deduction guide for Slice from c-array
diff --git a/src/tint/utils/containers/slice_test.cc b/src/tint/utils/containers/slice_test.cc
index 99f2f99..2432aeb 100644
--- a/src/tint/utils/containers/slice_test.cc
+++ b/src/tint/utils/containers/slice_test.cc
@@ -194,5 +194,33 @@
     EXPECT_EQ(truncated[2], 3);
 }
 
+TEST(TintSliceTest, Equality) {
+    int elements[] = {1, 2, 3};
+    auto a = Slice{elements};
+    {
+        auto b = a;
+        EXPECT_TRUE(a == b);
+        EXPECT_FALSE(a != b);
+    }
+    {
+        auto b = a;
+        b.data++;
+        EXPECT_FALSE(a == b);
+        EXPECT_TRUE(a != b);
+    }
+    {
+        auto b = a;
+        b.len++;
+        EXPECT_FALSE(a == b);
+        EXPECT_TRUE(a != b);
+    }
+    {
+        auto b = a;
+        b.cap++;
+        EXPECT_FALSE(a == b);
+        EXPECT_TRUE(a != b);
+    }
+}
+
 }  // namespace
 }  // namespace tint
diff --git a/src/tint/utils/templates/enums.tmpl.inc b/src/tint/utils/templates/enums.tmpl.inc
index 5bd899e..dd848a3 100644
--- a/src/tint/utils/templates/enums.tmpl.inc
+++ b/src/tint/utils/templates/enums.tmpl.inc
@@ -64,7 +64,7 @@
 /// @returns the parsed enum, or {{$enum}}::kUndefined if the string could not be parsed.
 {{$enum}} Parse{{$enum}}(std::string_view str);
 
-constexpr const char* k{{$enum}}Strings[] = {
+constexpr std::string_view k{{$enum}}Strings[] = {
 {{-   range $entry := $.Entries }}
 {{-     if not $entry.IsInternal}}
     "{{$entry.Name}}",
diff --git a/src/tint/utils/text/string.cc b/src/tint/utils/text/string.cc
index a84a5b1..0812dad 100644
--- a/src/tint/utils/text/string.cc
+++ b/src/tint/utils/text/string.cc
@@ -63,15 +63,7 @@
 }
 
 void SuggestAlternatives(std::string_view got,
-                         Slice<char const* const> strings,
-                         StringStream& ss,
-                         const SuggestAlternativeOptions& options /* = {} */) {
-    auto views = Transform<8>(strings, [](char const* const str) { return std::string_view(str); });
-    SuggestAlternatives(got, views.Slice(), ss, options);
-}
-
-void SuggestAlternatives(std::string_view got,
-                         Slice<std::string_view> strings,
+                         Slice<const std::string_view> strings,
                          StringStream& ss,
                          const SuggestAlternativeOptions& options /* = {} */) {
     // If the string typed was within kSuggestionDistance of one of the possible enum values,
diff --git a/src/tint/utils/text/string.h b/src/tint/utils/text/string.h
index f98f4b6..6ae2894 100644
--- a/src/tint/utils/text/string.h
+++ b/src/tint/utils/text/string.h
@@ -52,6 +52,20 @@
     return str;
 }
 
+/// @copydoc ReplaceAll(std::string, std::string_view, std::string_view)
+[[nodiscard]] inline std::string ReplaceAll(std::string_view str,
+                                            std::string_view substr,
+                                            std::string_view replacement) {
+    return ReplaceAll(std::string(str), substr, replacement);
+}
+
+/// @copydoc ReplaceAll(std::string, std::string_view, std::string_view)
+[[nodiscard]] inline std::string ReplaceAll(const char* str,
+                                            std::string_view substr,
+                                            std::string_view replacement) {
+    return ReplaceAll(std::string(str), substr, replacement);
+}
+
 /// @param value the boolean value to be printed as a string
 /// @returns value printed as a string via the stream `<<` operator
 inline std::string ToString(bool value) {
@@ -109,17 +123,7 @@
 /// @param ss the stream to write the suggest and list of possible values to
 /// @param options options for the suggestion
 void SuggestAlternatives(std::string_view got,
-                         Slice<char const* const> strings,
-                         StringStream& ss,
-                         const SuggestAlternativeOptions& options = {});
-
-/// Suggest alternatives for an unrecognized string from a list of possible values.
-/// @param got the unrecognized string
-/// @param strings the list of possible values
-/// @param ss the stream to write the suggest and list of possible values to
-/// @param options options for the suggestion
-void SuggestAlternatives(std::string_view got,
-                         Slice<std::string_view> strings,
+                         Slice<const std::string_view> strings,
                          StringStream& ss,
                          const SuggestAlternativeOptions& options = {});
 
diff --git a/src/tint/utils/text/string_test.cc b/src/tint/utils/text/string_test.cc
index fccf0fa..b14aaac 100644
--- a/src/tint/utils/text/string_test.cc
+++ b/src/tint/utils/text/string_test.cc
@@ -94,20 +94,20 @@
 
 TEST(StringTest, SuggestAlternatives) {
     {
-        const char* alternatives[] = {"hello world", "Hello World"};
+        std::string_view alternatives[] = {"hello world", "Hello World"};
         StringStream ss;
         SuggestAlternatives("hello wordl", alternatives, ss);
         EXPECT_EQ(ss.str(), R"(Did you mean 'hello world'?
 Possible values: 'hello world', 'Hello World')");
     }
     {
-        const char* alternatives[] = {"foobar", "something else"};
+        std::string_view alternatives[] = {"foobar", "something else"};
         StringStream ss;
         SuggestAlternatives("hello world", alternatives, ss);
         EXPECT_EQ(ss.str(), R"(Possible values: 'foobar', 'something else')");
     }
     {
-        const char* alternatives[] = {"hello world", "Hello World"};
+        std::string_view alternatives[] = {"hello world", "Hello World"};
         StringStream ss;
         SuggestAlternativeOptions opts;
         opts.prefix = "$";
@@ -116,7 +116,7 @@
 Possible values: '$hello world', '$Hello World')");
     }
     {
-        const char* alternatives[] = {"hello world", "Hello World"};
+        std::string_view alternatives[] = {"hello world", "Hello World"};
         StringStream ss;
         SuggestAlternativeOptions opts;
         opts.list_possible_values = false;