diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc index 37c3ac3d99..f7aa4b8aa3 100644 --- a/src/tint/reader/wgsl/parser_impl.cc +++ b/src/tint/reader/wgsl/parser_impl.cc @@ -1240,40 +1240,15 @@ Expect ParserImpl::expect_enum(std::string_view name, } /// Create a sensible error message - std::stringstream err; + std::ostringstream err; err << "expected " << name; if (!use.empty()) { err << " for " << use; } + err << "\n"; - // If the string typed was within kSuggestionDistance of one of the possible enum values, - // suggest that. Don't bother with suggestions if the string was extremely long. - constexpr size_t kSuggestionDistance = 5; - constexpr size_t kSuggestionMaxLength = 64; - if (auto got = t.to_str(); !got.empty() && got.size() < kSuggestionMaxLength) { - size_t candidate_dist = kSuggestionDistance; - const char* candidate = nullptr; - for (auto* str : strings) { - auto dist = utils::Distance(str, got); - if (dist < candidate_dist) { - candidate = str; - candidate_dist = dist; - } - } - if (candidate) { - err << ". Did you mean '" << candidate << "'?"; - } - } - - // List all the possible enumerator values - err << "\nPossible values: "; - for (auto* str : strings) { - if (str != strings[0]) { - err << ", "; - } - err << "'" << str << "'"; - } + utils::SuggestAlternatives(t.to_str(), strings, err); synchronized_ = false; return add_error(t.source(), err.str()); diff --git a/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc b/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc index 23a8cb2076..260175da36 100644 --- a/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc +++ b/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc @@ -74,7 +74,8 @@ TEST_F(EnableDirectiveTest, InvalidIdentifierSuggest) { 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'? + 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_push_constant', 'f16')"); auto program = p->program(); auto& ast = program.AST(); diff --git a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc index f458834c96..5337c3cdb4 100644 --- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc +++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc @@ -1110,7 +1110,8 @@ Possible values: 'frag_depth', 'front_facing', 'global_invocation_id', 'instance TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinInvalidValue) { EXPECT("@builtin(frag_d3pth) var i : i32;", - R"(test.wgsl:1:10 error: expected builtin. Did you mean 'frag_depth'? + R"(test.wgsl:1:10 error: expected builtin +Did you mean 'frag_depth'? Possible values: 'frag_depth', 'front_facing', 'global_invocation_id', 'instance_index', 'local_invocation_id', 'local_invocation_index', 'num_workgroups', 'position', 'sample_index', 'sample_mask', 'vertex_index', 'workgroup_id' @builtin(frag_d3pth) var i : i32; ^^^^^^^^^^ diff --git a/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc b/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc index 299bd333b9..7b051d3274 100644 --- a/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc +++ b/src/tint/reader/wgsl/parser_impl_texture_sampler_test.cc @@ -230,7 +230,8 @@ TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidTypeSuggest) { EXPECT_FALSE(t.matched); EXPECT_TRUE(t.errored); EXPECT_EQ(p->error(), - R"(1:20: expected texel format for storage texture type. Did you mean 'rg32float'? + R"(1:20: expected texel format for storage texture type +Did you mean 'rg32float'? Possible values: 'bgra8unorm', 'r32float', 'r32sint', 'r32uint', 'rg32float', 'rg32sint', 'rg32uint', 'rgba16float', 'rgba16sint', 'rgba16uint', 'rgba32float', 'rgba32sint', 'rgba32uint', 'rgba8sint', 'rgba8snorm', 'rgba8uint', 'rgba8unorm')"); } @@ -241,7 +242,8 @@ TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidAccess) { EXPECT_FALSE(t.matched); EXPECT_TRUE(t.errored); EXPECT_EQ(p->error(), - R"(1:30: expected access control for storage texture type. Did you mean 'read'? + R"(1:30: expected access control for storage texture type +Did you mean 'read'? Possible values: 'read', 'read_write', 'write')"); } diff --git a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc index 5e18b00402..0a4198ebef 100644 --- a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc +++ b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc @@ -316,7 +316,8 @@ TEST_F(ParserImplTest, TypeDecl_Ptr_BadAddressSpace) { ASSERT_EQ(t.value, nullptr); ASSERT_TRUE(p->has_error()); ASSERT_EQ(p->error(), - R"(1:5: expected address space for ptr declaration. Did you mean 'uniform'? + R"(1:5: expected address space for ptr declaration +Did you mean 'uniform'? Possible values: 'function', 'private', 'push_constant', 'storage', 'uniform', 'workgroup')"); } diff --git a/src/tint/reader/wgsl/parser_impl_type_decl_without_ident_test.cc b/src/tint/reader/wgsl/parser_impl_type_decl_without_ident_test.cc index ea146c918c..409062a84b 100644 --- a/src/tint/reader/wgsl/parser_impl_type_decl_without_ident_test.cc +++ b/src/tint/reader/wgsl/parser_impl_type_decl_without_ident_test.cc @@ -307,7 +307,8 @@ TEST_F(ParserImplTest, TypeDeclWithoutIdent_Ptr_BadAddressSpace) { ASSERT_EQ(t.value, nullptr); ASSERT_TRUE(p->has_error()); ASSERT_EQ(p->error(), - R"(1:5: expected address space for ptr declaration. Did you mean 'uniform'? + R"(1:5: expected address space for ptr declaration +Did you mean 'uniform'? Possible values: 'function', 'private', 'push_constant', 'storage', 'uniform', 'workgroup')"); } diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc index 79574a8bdf..9de2a3d5b1 100644 --- a/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc +++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_list_test.cc @@ -69,7 +69,8 @@ TEST_F(ParserImplTest, AttributeList_InvalidValueSuggest) { EXPECT_TRUE(attrs.errored); EXPECT_FALSE(attrs.matched); EXPECT_TRUE(attrs.value.IsEmpty()); - EXPECT_EQ(p->error(), R"(1:10: expected builtin. Did you mean 'instance_index'? + EXPECT_EQ(p->error(), R"(1:10: expected builtin +Did you mean 'instance_index'? Possible values: 'frag_depth', 'front_facing', 'global_invocation_id', 'instance_index', 'local_invocation_id', 'local_invocation_index', 'num_workgroups', 'position', 'sample_index', 'sample_mask', 'vertex_index', 'workgroup_id')"); } } // namespace diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc index 1b538a27c9..602cc20014 100644 --- a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc +++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc @@ -323,7 +323,8 @@ TEST_F(ParserImplTest, Attribute_Builtin_InvalidValueSuggest) { EXPECT_TRUE(attr.errored); EXPECT_EQ(attr.value, nullptr); EXPECT_TRUE(p->has_error()); - EXPECT_EQ(p->error(), R"(1:9: expected builtin. Did you mean 'front_facing'? + EXPECT_EQ(p->error(), R"(1:9: expected builtin +Did you mean 'front_facing'? Possible values: 'frag_depth', 'front_facing', 'global_invocation_id', 'instance_index', 'local_invocation_id', 'local_invocation_index', 'num_workgroups', 'position', 'sample_index', 'sample_mask', 'vertex_index', 'workgroup_id')"); } @@ -494,7 +495,8 @@ TEST_F(ParserImplTest, Attribute_Interpolate_InvalidSecondValue) { EXPECT_TRUE(attr.errored); EXPECT_EQ(attr.value, nullptr); EXPECT_TRUE(p->has_error()); - EXPECT_EQ(p->error(), R"(1:26: expected interpolation sampling. Did you mean 'sample'? + EXPECT_EQ(p->error(), R"(1:26: expected interpolation sampling +Did you mean 'sample'? Possible values: 'center', 'centroid', 'sample')"); } diff --git a/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc b/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc index ba4a8727ca..7e2920304b 100644 --- a/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc +++ b/src/tint/reader/wgsl/parser_impl_variable_decl_test.cc @@ -106,7 +106,8 @@ TEST_F(ParserImplTest, VariableDecl_InvalidAddressSpace) { EXPECT_TRUE(v.errored); EXPECT_TRUE(p->has_error()); EXPECT_EQ(p->error(), - R"(1:5: expected address space for variable declaration. Did you mean 'uniform'? + R"(1:5: expected address space for variable declaration +Did you mean 'uniform'? Possible values: 'function', 'private', 'push_constant', 'storage', 'uniform', 'workgroup')"); } diff --git a/src/tint/utils/string.h b/src/tint/utils/string.h index abfe51a4bb..5c05ebfe04 100644 --- a/src/tint/utils/string.h +++ b/src/tint/utils/string.h @@ -66,6 +66,43 @@ inline size_t HasPrefix(std::string_view str, std::string_view prefix) { /// @returns the Levenshtein distance between @p a and @p b size_t Distance(std::string_view a, std::string_view b); +/// Suggest alternatives for an unrecognized string from a list of expected values. +/// @param got the unrecognized string +/// @param strings the list of expected values +/// @param ss the stream to write the suggest and list of possible values to +template +void SuggestAlternatives(std::string_view got, + const char* const (&strings)[N], + std::ostringstream& ss) { + // If the string typed was within kSuggestionDistance of one of the possible enum values, + // suggest that. Don't bother with suggestions if the string was extremely long. + constexpr size_t kSuggestionDistance = 5; + constexpr size_t kSuggestionMaxLength = 64; + if (!got.empty() && got.size() < kSuggestionMaxLength) { + size_t candidate_dist = kSuggestionDistance; + const char* candidate = nullptr; + for (auto* str : strings) { + auto dist = utils::Distance(str, got); + if (dist < candidate_dist) { + candidate = str; + candidate_dist = dist; + } + } + if (candidate) { + ss << "Did you mean '" << candidate << "'?\n"; + } + } + + // List all the possible enumerator values + ss << "Possible values: "; + for (auto* str : strings) { + if (str != strings[0]) { + ss << ", "; + } + ss << "'" << str << "'"; + } +} + } // namespace tint::utils #endif // SRC_TINT_UTILS_STRING_H_ diff --git a/src/tint/utils/string_test.cc b/src/tint/utils/string_test.cc index bbf8e0ff37..6b17dfb409 100644 --- a/src/tint/utils/string_test.cc +++ b/src/tint/utils/string_test.cc @@ -58,5 +58,19 @@ TEST(StringTest, Distance) { EXPECT_EQ(Distance("", "Hello world"), 11u); } +TEST(StringTest, SuggestAlternatives) { + { + std::ostringstream ss; + SuggestAlternatives("hello wordl", {"hello world", "Hello World"}, ss); + EXPECT_EQ(ss.str(), R"(Did you mean 'hello world'? +Possible values: 'hello world', 'Hello World')"); + } + { + std::ostringstream ss; + SuggestAlternatives("hello world", {"foobar", "something else"}, ss); + EXPECT_EQ(ss.str(), R"(Possible values: 'foobar', 'something else')"); + } +} + } // namespace } // namespace tint::utils