tint/reader/wgsl: Improve errors when parsing builtin

If the builtin doesn't parse, then generate an error message that includes the list of possible values, and a suggestion if there was a close match.

Fixed: tint:1629
Change-Id: I8f575a2ffcef2af308b9566ae7832702e76085ef
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/105323
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
This commit is contained in:
Ben Clayton 2022-10-11 19:32:19 +00:00 committed by Dawn LUCI CQ
parent b7aef033fa
commit 760eee9e92
5 changed files with 112 additions and 21 deletions

View File

@ -43,6 +43,7 @@
#include "src/tint/sem/external_texture.h"
#include "src/tint/sem/multisampled_texture.h"
#include "src/tint/sem/sampled_texture.h"
#include "src/tint/utils/string.h"
namespace tint::reader::wgsl {
namespace {
@ -1194,6 +1195,66 @@ Maybe<const ast::Type*> ParserImpl::type_specifier() {
return type_specifier_without_ident();
}
template <typename ENUM, size_t N>
Expect<ENUM> ParserImpl::expect_enum(std::string_view name,
ENUM (*parse)(std::string_view str),
const char* const (&strings)[N],
std::string_view use) {
auto& t = peek();
if (t.IsIdentifier()) {
auto val = parse(t.to_str());
if (val != ENUM::kInvalid) {
synchronized_ = true;
next();
return {val, t.source()};
}
}
// Was the token itself an error?
if (handle_error(t)) {
return Failure::kErrored;
}
/// Create a sensible error message
std::stringstream err;
err << "expected " << name;
if (!use.empty()) {
err << " for " << use;
}
// 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 << "'";
}
synchronized_ = false;
return add_error(t.source(), err.str());
}
Expect<const ast::Type*> ParserImpl::expect_type(std::string_view use) {
auto type = type_specifier();
if (type.errored) {
@ -1663,17 +1724,7 @@ Expect<ast::InterpolationType> ParserImpl::expect_interpolation_type_name() {
// | vertex_index
// | workgroup_id
Expect<ast::BuiltinValue> ParserImpl::expect_builtin() {
auto ident = expect_ident("builtin");
if (ident.errored) {
return Failure::kErrored;
}
ast::BuiltinValue builtin = ast::ParseBuiltinValue(ident.value);
if (builtin == ast::BuiltinValue::kInvalid) {
return add_error(ident.source, "invalid value for builtin attribute");
}
return {builtin, ident.source};
return expect_enum("builtin", ast::ParseBuiltinValue, ast::kBuiltinValueStrings);
}
// compound_statement

View File

@ -868,6 +868,18 @@ class ParserImpl {
Expect<const ast::Type*> expect_type_specifier_matrix(const Source& s,
const MatrixDimensions& dims);
/// Parses the given enum, providing sensible error messages if the next token does not match
/// any of the enum values.
/// @param name the name of the enumerator
/// @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>
Expect<ENUM> expect_enum(std::string_view name,
ENUM (*parse)(std::string_view str),
const char* const (&strings)[N],
std::string_view use = "");
Expect<const ast::Type*> expect_type(std::string_view use);
Maybe<const ast::Statement*> non_block_statement();

View File

@ -765,7 +765,8 @@ type T = i32;
}
TEST_F(ParserImplErrorTest, GlobalDeclStaticAssertMissingLParen) {
EXPECT("static_assert true);", R"(test.wgsl:1:19 error: expected ';' for static assertion declaration
EXPECT("static_assert true);",
R"(test.wgsl:1:19 error: expected ';' for static assertion declaration
static_assert true);
^
)");
@ -989,17 +990,19 @@ TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinMissingRParen) {
TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinInvalidIdentifer) {
EXPECT("@builtin(1) var i : i32;",
R"(test.wgsl:1:10 error: expected identifier for builtin
R"(test.wgsl:1:10 error: expected builtin
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(1) var i : i32;
^
)");
}
TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBuiltinInvalidValue) {
EXPECT("@builtin(x) var i : i32;",
R"(test.wgsl:1:10 error: invalid value for builtin attribute
@builtin(x) var i : i32;
^
EXPECT("@builtin(frag_d3pth) var i : i32;",
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

@ -58,8 +58,19 @@ TEST_F(ParserImplTest, AttributeList_InvalidValue) {
EXPECT_TRUE(attrs.errored);
EXPECT_FALSE(attrs.matched);
EXPECT_TRUE(attrs.value.IsEmpty());
EXPECT_EQ(p->error(), "1:10: invalid value for builtin attribute");
EXPECT_EQ(p->error(), R"(1:10: expected builtin
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')");
}
TEST_F(ParserImplTest, AttributeList_InvalidValueSuggest) {
auto p = parser("@builtin(instanceindex)");
auto attrs = p->attribute_list();
EXPECT_TRUE(p->has_error());
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'?
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
} // namespace tint::reader::wgsl

View File

@ -177,7 +177,8 @@ TEST_F(ParserImplTest, Attribute_Builtin_MissingValue) {
EXPECT_TRUE(attr.errored);
EXPECT_EQ(attr.value, nullptr);
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
EXPECT_EQ(p->error(), R"(1:9: expected builtin
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')");
}
TEST_F(ParserImplTest, Attribute_Builtin_InvalidValue) {
@ -187,7 +188,19 @@ TEST_F(ParserImplTest, Attribute_Builtin_InvalidValue) {
EXPECT_TRUE(attr.errored);
EXPECT_EQ(attr.value, nullptr);
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:9: invalid value for builtin attribute");
EXPECT_EQ(p->error(), R"(1:9: expected builtin
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')");
}
TEST_F(ParserImplTest, Attribute_Builtin_InvalidValueSuggest) {
auto p = parser("builtin(front_face)");
auto attr = p->attribute();
EXPECT_FALSE(attr.matched);
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'?
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')");
}
TEST_F(ParserImplTest, Attribute_Builtin_MissingInvalid) {
@ -197,7 +210,8 @@ TEST_F(ParserImplTest, Attribute_Builtin_MissingInvalid) {
EXPECT_TRUE(attr.errored);
EXPECT_EQ(attr.value, nullptr);
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
EXPECT_EQ(p->error(), R"(1:9: expected builtin
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')");
}
TEST_F(ParserImplTest, Attribute_Interpolate_Flat) {