tint: Move "suggest alternatives" logic to utils

This will be used for suggesting alternative diagnostic rule names in
the Resolver.

Bug: tint:1809
Change-Id: Icc9af02937326f6f774fbaf2aeaa9314c88fdea6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/117565
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
James Price 2023-01-25 14:10:04 +00:00
parent 2cf9764e45
commit f629770bcd
11 changed files with 74 additions and 38 deletions

View File

@ -1240,40 +1240,15 @@ Expect<ENUM> 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());

View File

@ -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();

View File

@ -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;
^^^^^^^^^^

View File

@ -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')");
}

View File

@ -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')");
}

View File

@ -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')");
}

View File

@ -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

View File

@ -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')");
}

View File

@ -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')");
}

View File

@ -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 <size_t N>
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_

View File

@ -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