tint/reader: Enable 'const' parsing for unit-tests

This enables the WGSL parsing of 'const' for only tint_unittests,
allowing code to be split up into smaller chunks for review.

Once all the logic is submitted to handle 'const', the test-only
flag will be removed, and 'const' will be enabled for production.

Bug: tint:1580
Change-Id: I3189b6bd15445ecc3fa1cd6f568885e7ba3c91c0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/94680
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Ben Clayton 2022-06-26 10:52:50 +00:00 committed by Dawn LUCI CQ
parent 511529f082
commit e48ef8ef90
21 changed files with 356 additions and 44 deletions

View File

@ -34,6 +34,10 @@ Const::Const(Const&&) = default;
Const::~Const() = default; Const::~Const() = default;
const char* Const::Kind() const {
return "const";
}
const Const* Const::Clone(CloneContext* ctx) const { const Const* Const::Clone(CloneContext* ctx) const {
auto src = ctx->Clone(source); auto src = ctx->Clone(source);
auto sym = ctx->Clone(symbol); auto sym = ctx->Clone(symbol);

View File

@ -52,6 +52,9 @@ class Const final : public Castable<Const, Variable> {
/// Destructor /// Destructor
~Const() override; ~Const() override;
/// @returns "const"
const char* Kind() const override;
/// Clones this node and all transitive child nodes using the `CloneContext` /// Clones this node and all transitive child nodes using the `CloneContext`
/// `ctx`. /// `ctx`.
/// @param ctx the clone context /// @param ctx the clone context

View File

@ -34,6 +34,10 @@ Let::Let(Let&&) = default;
Let::~Let() = default; Let::~Let() = default;
const char* Let::Kind() const {
return "let";
}
const Let* Let::Clone(CloneContext* ctx) const { const Let* Let::Clone(CloneContext* ctx) const {
auto src = ctx->Clone(source); auto src = ctx->Clone(source);
auto sym = ctx->Clone(symbol); auto sym = ctx->Clone(symbol);

View File

@ -49,6 +49,9 @@ class Let final : public Castable<Let, Variable> {
/// Destructor /// Destructor
~Let() override; ~Let() override;
/// @returns "let"
const char* Kind() const override;
/// Clones this node and all transitive child nodes using the `CloneContext` /// Clones this node and all transitive child nodes using the `CloneContext`
/// `ctx`. /// `ctx`.
/// @param ctx the clone context /// @param ctx the clone context

View File

@ -32,6 +32,10 @@ Override::Override(Override&&) = default;
Override::~Override() = default; Override::~Override() = default;
const char* Override::Kind() const {
return "override";
}
const Override* Override::Clone(CloneContext* ctx) const { const Override* Override::Clone(CloneContext* ctx) const {
auto src = ctx->Clone(source); auto src = ctx->Clone(source);
auto sym = ctx->Clone(symbol); auto sym = ctx->Clone(symbol);

View File

@ -50,6 +50,9 @@ class Override final : public Castable<Override, Variable> {
/// Destructor /// Destructor
~Override() override; ~Override() override;
/// @returns "override"
const char* Kind() const override;
/// Clones this node and all transitive child nodes using the `CloneContext` /// Clones this node and all transitive child nodes using the `CloneContext`
/// `ctx`. /// `ctx`.
/// @param ctx the clone context /// @param ctx the clone context

View File

@ -31,6 +31,10 @@ Parameter::Parameter(Parameter&&) = default;
Parameter::~Parameter() = default; Parameter::~Parameter() = default;
const char* Parameter::Kind() const {
return "parameter";
}
const Parameter* Parameter::Clone(CloneContext* ctx) const { const Parameter* Parameter::Clone(CloneContext* ctx) const {
auto src = ctx->Clone(source); auto src = ctx->Clone(source);
auto sym = ctx->Clone(symbol); auto sym = ctx->Clone(symbol);

View File

@ -51,6 +51,9 @@ class Parameter final : public Castable<Parameter, Variable> {
/// Destructor /// Destructor
~Parameter() override; ~Parameter() override;
/// @returns "parameter"
const char* Kind() const override;
/// Clones this node and all transitive child nodes using the `CloneContext` /// Clones this node and all transitive child nodes using the `CloneContext`
/// `ctx`. /// `ctx`.
/// @param ctx the clone context /// @param ctx the clone context

View File

@ -36,6 +36,10 @@ Var::Var(Var&&) = default;
Var::~Var() = default; Var::~Var() = default;
const char* Var::Kind() const {
return "var";
}
const Var* Var::Clone(CloneContext* ctx) const { const Var* Var::Clone(CloneContext* ctx) const {
auto src = ctx->Clone(source); auto src = ctx->Clone(source);
auto sym = ctx->Clone(symbol); auto sym = ctx->Clone(symbol);

View File

@ -65,6 +65,9 @@ class Var final : public Castable<Var, Variable> {
/// Destructor /// Destructor
~Var() override; ~Var() override;
/// @returns "var"
const char* Kind() const override;
/// Clones this node and all transitive child nodes using the `CloneContext` /// Clones this node and all transitive child nodes using the `CloneContext`
/// `ctx`. /// `ctx`.
/// @param ctx the clone context /// @param ctx the clone context

View File

@ -77,6 +77,10 @@ class Variable : public Castable<Variable, Node> {
/// @note binding points should only be applied to Var and Parameter types. /// @note binding points should only be applied to Var and Parameter types.
VariableBindingPoint BindingPoint() const; VariableBindingPoint BindingPoint() const;
/// @returns the kind of the variable, which can be used in diagnostics
/// e.g. "var", "let", "const", etc
virtual const char* Kind() const = 0;
/// The variable symbol /// The variable symbol
const Symbol symbol; const Symbol symbol;

View File

@ -144,18 +144,18 @@ TEST_P(LineCommentTerminatorTest, Terminators) {
// Test that line comments are ended by blankspace characters other than // Test that line comments are ended by blankspace characters other than
// space, horizontal tab, left-to-right mark, and right-to-left mark. // space, horizontal tab, left-to-right mark, and right-to-left mark.
auto* c = GetParam(); auto* c = GetParam();
std::string src = "let// This is a comment"; std::string src = "const// This is a comment";
src += c; src += c;
src += "ident"; src += "ident";
Source::File file("", src); Source::File file("", src);
Lexer l(&file); Lexer l(&file);
auto t = l.next(); auto t = l.next();
EXPECT_TRUE(t.Is(Token::Type::kLet)); EXPECT_TRUE(t.Is(Token::Type::kConst));
EXPECT_EQ(t.source().range.begin.line, 1u); EXPECT_EQ(t.source().range.begin.line, 1u);
EXPECT_EQ(t.source().range.begin.column, 1u); EXPECT_EQ(t.source().range.begin.column, 1u);
EXPECT_EQ(t.source().range.end.line, 1u); EXPECT_EQ(t.source().range.end.line, 1u);
EXPECT_EQ(t.source().range.end.column, 4u); EXPECT_EQ(t.source().range.end.column, 6u);
auto is_same_line = [](std::string_view v) { auto is_same_line = [](std::string_view v) {
return v == kSpace || v == kHTab || v == kL2R || v == kR2L; return v == kSpace || v == kHTab || v == kL2R || v == kR2L;

View File

@ -47,6 +47,9 @@
namespace tint::reader::wgsl { namespace tint::reader::wgsl {
namespace { namespace {
// TODO(crbug.com/tint/1580): Remove when 'const' is fully implemented.
bool const_enabled = false;
template <typename T> template <typename T>
using Expect = ParserImpl::Expect<T>; using Expect = ParserImpl::Expect<T>;
@ -245,6 +248,11 @@ ParserImpl::ParserImpl(Source::File const* file) : lexer_(std::make_unique<Lexer
ParserImpl::~ParserImpl() = default; ParserImpl::~ParserImpl() = default;
// TODO(crbug.com/tint/1580): Remove when 'const' is fully implemented.
void ParserImpl::EnableConst() {
const_enabled = true;
}
ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source, ParserImpl::Failure::Errored ParserImpl::add_error(const Source& source,
std::string_view err, std::string_view err,
std::string_view use) { std::string_view use) {
@ -437,9 +445,13 @@ Maybe<bool> ParserImpl::global_decl() {
} }
if (gc.matched) { if (gc.matched) {
if (!expect("'let' declaration", Token::Type::kSemicolon)) { // Avoid the cost of the string allocation for the common no-error case
if (!peek().Is(Token::Type::kSemicolon)) {
std::string kind = gc->Kind();
if (!expect("'" + kind + "' declaration", Token::Type::kSemicolon)) {
return Failure::kErrored; return Failure::kErrored;
} }
}
builder_.AST().AddGlobalVariable(gc.value); builder_.AST().AddGlobalVariable(gc.value);
return true; return true;
@ -561,10 +573,14 @@ Maybe<const ast::Variable*> ParserImpl::global_variable_decl(ast::AttributeList&
// global_const_initializer // global_const_initializer
// : EQUAL const_expr // : EQUAL const_expr
Maybe<const ast::Variable*> ParserImpl::global_constant_decl(ast::AttributeList& attrs) { Maybe<const ast::Variable*> ParserImpl::global_constant_decl(ast::AttributeList& attrs) {
bool is_const = false;
bool is_overridable = false; bool is_overridable = false;
const char* use = nullptr; const char* use = nullptr;
if (match(Token::Type::kLet)) { if (match(Token::Type::kLet)) {
use = "'let' declaration"; use = "'let' declaration";
} else if (const_enabled && match(Token::Type::kConst)) {
use = "'const' declaration";
is_const = true;
} else if (match(Token::Type::kOverride)) { } else if (match(Token::Type::kOverride)) {
use = "'override' declaration"; use = "'override' declaration";
is_overridable = true; is_overridable = true;
@ -596,6 +612,13 @@ Maybe<const ast::Variable*> ParserImpl::global_constant_decl(ast::AttributeList&
initializer = std::move(init.value); initializer = std::move(init.value);
} }
if (is_const) {
return create<ast::Const>(decl->source, // source
builder_.Symbols().Register(decl->name), // symbol
decl->type, // type
initializer, // constructor
std::move(attrs)); // attributes
}
if (is_overridable) { if (is_overridable) {
return create<ast::Override>(decl->source, // source return create<ast::Override>(decl->source, // source
builder_.Symbols().Register(decl->name), // symbol builder_.Symbols().Register(decl->name), // symbol
@ -1769,6 +1792,34 @@ Maybe<const ast::ReturnStatement*> ParserImpl::return_stmt() {
// | variable_decl EQUAL logical_or_expression // | variable_decl EQUAL logical_or_expression
// | CONST variable_ident_decl EQUAL logical_or_expression // | CONST variable_ident_decl EQUAL logical_or_expression
Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_stmt() { Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_stmt() {
if (const_enabled && match(Token::Type::kConst)) {
auto decl = expect_variable_ident_decl("'const' declaration",
/*allow_inferred = */ true);
if (decl.errored) {
return Failure::kErrored;
}
if (!expect("'const' declaration", Token::Type::kEqual)) {
return Failure::kErrored;
}
auto constructor = logical_or_expression();
if (constructor.errored) {
return Failure::kErrored;
}
if (!constructor.matched) {
return add_error(peek(), "missing constructor for 'const' declaration");
}
auto* const_ = create<ast::Const>(decl->source, // source
builder_.Symbols().Register(decl->name), // symbol
decl->type, // type
constructor.value, // constructor
ast::AttributeList{}); // attributes
return create<ast::VariableDeclStatement>(decl->source, const_);
}
if (match(Token::Type::kLet)) { if (match(Token::Type::kLet)) {
auto decl = expect_variable_ident_decl("'let' declaration", auto decl = expect_variable_ident_decl("'let' declaration",
/*allow_inferred = */ true); /*allow_inferred = */ true);

View File

@ -72,6 +72,11 @@ class ParserImpl {
}; };
public: public:
/// A temporary bodge to enable unit-testing of 'const' variable types while still under active
/// development.
// TODO(crbug.com/tint/1580): Remove when 'const' is fully implemented.
static void EnableConst();
/// Expect is the return type of the parser methods that are expected to /// Expect is the return type of the parser methods that are expected to
/// return a parsed value of type T, unless there was an parse error. /// return a parsed value of type T, unless there was an parse error.
/// In the case of a parse error the called method will have called /// In the case of a parse error the called method will have called

View File

@ -471,6 +471,107 @@ test.wgsl:3:3 error: statement found outside of function body
} }
TEST_F(ParserImplErrorTest, GlobalDeclConstInvalidIdentifier) { TEST_F(ParserImplErrorTest, GlobalDeclConstInvalidIdentifier) {
EXPECT("const ^ : i32 = 1;",
R"(test.wgsl:1:7 error: expected identifier for 'const' declaration
const ^ : i32 = 1;
^
)");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstMissingSemicolon) {
EXPECT("const i : i32 = 1",
R"(test.wgsl:1:18 error: expected ';' for 'const' declaration
const i : i32 = 1
^
)");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstMissingLParen) {
EXPECT("const i : vec2<i32> = vec2<i32>;",
R"(test.wgsl:1:32 error: expected '(' for type constructor
const i : vec2<i32> = vec2<i32>;
^
)");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstMissingRParen) {
EXPECT("const i : vec2<i32> = vec2<i32>(1., 2.;",
R"(test.wgsl:1:39 error: expected ')' for type constructor
const i : vec2<i32> = vec2<i32>(1., 2.;
^
)");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteral) {
EXPECT("const i : vec2<i32> = vec2<i32>(!);",
R"(test.wgsl:1:33 error: unable to parse const_expr
const i : vec2<i32> = vec2<i32>(!);
^
)");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteralSpaceLessThan) {
EXPECT("const i = 1 < 2;",
R"(test.wgsl:1:13 error: expected ';' for 'const' declaration
const i = 1 < 2;
^
)");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstNotConstExpr) {
EXPECT(
"const a = 1;\n"
"const b = a;",
R"(test.wgsl:2:11 error: unable to parse const_expr
const b = a;
^
)");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstExprMaxDepth) {
uint32_t kMaxDepth = 128;
std::stringstream src;
std::stringstream mkr;
src << "const i : i32 = ";
mkr << " ";
for (size_t i = 0; i < kMaxDepth + 8; i++) {
src << "f32(";
if (i < kMaxDepth) {
mkr << " ";
} else if (i == kMaxDepth) {
mkr << "^^^";
}
}
src << "1.0";
for (size_t i = 0; i < 200; i++) {
src << ")";
}
src << ";";
std::stringstream err;
err << "test.wgsl:1:529 error: maximum parser recursive depth reached\n"
<< src.str() << "\n"
<< mkr.str() << "\n";
EXPECT(src.str().c_str(), err.str().c_str());
}
TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingLParen) {
EXPECT("const i : vec2<i32> = vec2<i32> 1, 2);",
R"(test.wgsl:1:33 error: expected '(' for type constructor
const i : vec2<i32> = vec2<i32> 1, 2);
^
)");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingRParen) {
EXPECT("const i : vec2<i32> = vec2<i32>(1, 2;",
R"(test.wgsl:1:37 error: expected ')' for type constructor
const i : vec2<i32> = vec2<i32>(1, 2;
^
)");
}
TEST_F(ParserImplErrorTest, GlobalDeclLetInvalidIdentifier) {
EXPECT("let ^ : i32 = 1;", EXPECT("let ^ : i32 = 1;",
R"(test.wgsl:1:5 error: expected identifier for 'let' declaration R"(test.wgsl:1:5 error: expected identifier for 'let' declaration
let ^ : i32 = 1; let ^ : i32 = 1;
@ -478,7 +579,7 @@ let ^ : i32 = 1;
)"); )");
} }
TEST_F(ParserImplErrorTest, GlobalDeclConstMissingSemicolon) { TEST_F(ParserImplErrorTest, GlobalDeclLetMissingSemicolon) {
EXPECT("let i : i32 = 1", EXPECT("let i : i32 = 1",
R"(test.wgsl:1:16 error: expected ';' for 'let' declaration R"(test.wgsl:1:16 error: expected ';' for 'let' declaration
let i : i32 = 1 let i : i32 = 1
@ -486,7 +587,7 @@ let i : i32 = 1
)"); )");
} }
TEST_F(ParserImplErrorTest, GlobalDeclConstMissingLParen) { TEST_F(ParserImplErrorTest, GlobalDeclLetMissingLParen) {
EXPECT("let i : vec2<i32> = vec2<i32>;", EXPECT("let i : vec2<i32> = vec2<i32>;",
R"(test.wgsl:1:30 error: expected '(' for type constructor R"(test.wgsl:1:30 error: expected '(' for type constructor
let i : vec2<i32> = vec2<i32>; let i : vec2<i32> = vec2<i32>;
@ -494,7 +595,7 @@ let i : vec2<i32> = vec2<i32>;
)"); )");
} }
TEST_F(ParserImplErrorTest, GlobalDeclConstMissingRParen) { TEST_F(ParserImplErrorTest, GlobalDeclLetMissingRParen) {
EXPECT("let i : vec2<i32> = vec2<i32>(1., 2.;", EXPECT("let i : vec2<i32> = vec2<i32>(1., 2.;",
R"(test.wgsl:1:37 error: expected ')' for type constructor R"(test.wgsl:1:37 error: expected ')' for type constructor
let i : vec2<i32> = vec2<i32>(1., 2.; let i : vec2<i32> = vec2<i32>(1., 2.;
@ -502,7 +603,7 @@ let i : vec2<i32> = vec2<i32>(1., 2.;
)"); )");
} }
TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteral) { TEST_F(ParserImplErrorTest, GlobalDeclLetBadConstLiteral) {
EXPECT("let i : vec2<i32> = vec2<i32>(!);", EXPECT("let i : vec2<i32> = vec2<i32>(!);",
R"(test.wgsl:1:31 error: unable to parse const_expr R"(test.wgsl:1:31 error: unable to parse const_expr
let i : vec2<i32> = vec2<i32>(!); let i : vec2<i32> = vec2<i32>(!);
@ -510,7 +611,7 @@ let i : vec2<i32> = vec2<i32>(!);
)"); )");
} }
TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteralSpaceLessThan) { TEST_F(ParserImplErrorTest, GlobalDeclLetBadConstLiteralSpaceLessThan) {
EXPECT("let i = 1 < 2;", EXPECT("let i = 1 < 2;",
R"(test.wgsl:1:11 error: expected ';' for 'let' declaration R"(test.wgsl:1:11 error: expected ';' for 'let' declaration
let i = 1 < 2; let i = 1 < 2;
@ -518,7 +619,7 @@ let i = 1 < 2;
)"); )");
} }
TEST_F(ParserImplErrorTest, GlobalDeclConstNotConstExpr) { TEST_F(ParserImplErrorTest, GlobalDeclLetNotConstExpr) {
EXPECT( EXPECT(
"let a = 1;\n" "let a = 1;\n"
"let b = a;", "let b = a;",
@ -528,7 +629,7 @@ let b = a;
)"); )");
} }
TEST_F(ParserImplErrorTest, GlobalDeclConstExprMaxDepth) { TEST_F(ParserImplErrorTest, GlobalDeclLetExprMaxDepth) {
uint32_t kMaxDepth = 128; uint32_t kMaxDepth = 128;
std::stringstream src; std::stringstream src;
@ -555,7 +656,7 @@ TEST_F(ParserImplErrorTest, GlobalDeclConstExprMaxDepth) {
EXPECT(src.str().c_str(), err.str().c_str()); EXPECT(src.str().c_str(), err.str().c_str());
} }
TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingLParen) { TEST_F(ParserImplErrorTest, GlobalDeclLetExprMissingLParen) {
EXPECT("let i : vec2<i32> = vec2<i32> 1, 2);", EXPECT("let i : vec2<i32> = vec2<i32> 1, 2);",
R"(test.wgsl:1:31 error: expected '(' for type constructor R"(test.wgsl:1:31 error: expected '(' for type constructor
let i : vec2<i32> = vec2<i32> 1, 2); let i : vec2<i32> = vec2<i32> 1, 2);
@ -563,7 +664,7 @@ let i : vec2<i32> = vec2<i32> 1, 2);
)"); )");
} }
TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingRParen) { TEST_F(ParserImplErrorTest, GlobalDeclLetExprMissingRParen) {
EXPECT("let i : vec2<i32> = vec2<i32>(1, 2;", EXPECT("let i : vec2<i32> = vec2<i32>(1, 2;",
R"(test.wgsl:1:35 error: expected ')' for type constructor R"(test.wgsl:1:35 error: expected ')' for type constructor
let i : vec2<i32> = vec2<i32>(1, 2; let i : vec2<i32> = vec2<i32>(1, 2;

View File

@ -18,7 +18,7 @@
namespace tint::reader::wgsl { namespace tint::reader::wgsl {
namespace { namespace {
TEST_F(ParserImplTest, GlobalConstantDecl) { TEST_F(ParserImplTest, GlobalLetDecl) {
auto p = parser("let a : f32 = 1."); auto p = parser("let a : f32 = 1.");
auto attrs = p->attribute_list(); auto attrs = p->attribute_list();
EXPECT_FALSE(attrs.errored); EXPECT_FALSE(attrs.errored);
@ -43,7 +43,7 @@ TEST_F(ParserImplTest, GlobalConstantDecl) {
EXPECT_TRUE(let->constructor->Is<ast::LiteralExpression>()); EXPECT_TRUE(let->constructor->Is<ast::LiteralExpression>());
} }
TEST_F(ParserImplTest, GlobalConstantDecl_Inferred) { TEST_F(ParserImplTest, GlobalLetDecl_Inferred) {
auto p = parser("let a = 1."); auto p = parser("let a = 1.");
auto attrs = p->attribute_list(); auto attrs = p->attribute_list();
EXPECT_FALSE(attrs.errored); EXPECT_FALSE(attrs.errored);
@ -67,7 +67,7 @@ TEST_F(ParserImplTest, GlobalConstantDecl_Inferred) {
EXPECT_TRUE(let->constructor->Is<ast::LiteralExpression>()); EXPECT_TRUE(let->constructor->Is<ast::LiteralExpression>());
} }
TEST_F(ParserImplTest, GlobalConstantDecl_InvalidExpression) { TEST_F(ParserImplTest, GlobalLetDecl_InvalidExpression) {
auto p = parser("let a : f32 = if (a) {}"); auto p = parser("let a : f32 = if (a) {}");
auto attrs = p->attribute_list(); auto attrs = p->attribute_list();
EXPECT_FALSE(attrs.errored); EXPECT_FALSE(attrs.errored);
@ -80,7 +80,7 @@ TEST_F(ParserImplTest, GlobalConstantDecl_InvalidExpression) {
EXPECT_EQ(p->error(), "1:15: invalid type for const_expr"); EXPECT_EQ(p->error(), "1:15: invalid type for const_expr");
} }
TEST_F(ParserImplTest, GlobalConstantDecl_MissingExpression) { TEST_F(ParserImplTest, GlobalLetDecl_MissingExpression) {
auto p = parser("let a : f32 ="); auto p = parser("let a : f32 =");
auto attrs = p->attribute_list(); auto attrs = p->attribute_list();
EXPECT_FALSE(attrs.errored); EXPECT_FALSE(attrs.errored);
@ -93,7 +93,82 @@ TEST_F(ParserImplTest, GlobalConstantDecl_MissingExpression) {
EXPECT_EQ(p->error(), "1:14: unable to parse const_expr"); EXPECT_EQ(p->error(), "1:14: unable to parse const_expr");
} }
TEST_F(ParserImplTest, GlobalConstantDec_Override_WithId) { TEST_F(ParserImplTest, GlobalConstDecl) {
auto p = parser("const a : f32 = 1.");
auto attrs = p->attribute_list();
EXPECT_FALSE(attrs.errored);
EXPECT_FALSE(attrs.matched);
auto e = p->global_constant_decl(attrs.value);
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_TRUE(e.matched);
EXPECT_FALSE(e.errored);
auto* c = e.value->As<ast::Const>();
ASSERT_NE(c, nullptr);
EXPECT_EQ(c->symbol, p->builder().Symbols().Get("a"));
ASSERT_NE(c->type, nullptr);
EXPECT_TRUE(c->type->Is<ast::F32>());
EXPECT_EQ(c->source.range.begin.line, 1u);
EXPECT_EQ(c->source.range.begin.column, 7u);
EXPECT_EQ(c->source.range.end.line, 1u);
EXPECT_EQ(c->source.range.end.column, 8u);
ASSERT_NE(c->constructor, nullptr);
EXPECT_TRUE(c->constructor->Is<ast::LiteralExpression>());
}
TEST_F(ParserImplTest, GlobalConstDecl_Inferred) {
auto p = parser("const a = 1.");
auto attrs = p->attribute_list();
EXPECT_FALSE(attrs.errored);
EXPECT_FALSE(attrs.matched);
auto e = p->global_constant_decl(attrs.value);
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_TRUE(e.matched);
EXPECT_FALSE(e.errored);
auto* c = e.value->As<ast::Const>();
ASSERT_NE(c, nullptr);
EXPECT_EQ(c->symbol, p->builder().Symbols().Get("a"));
EXPECT_EQ(c->type, nullptr);
EXPECT_EQ(c->source.range.begin.line, 1u);
EXPECT_EQ(c->source.range.begin.column, 7u);
EXPECT_EQ(c->source.range.end.line, 1u);
EXPECT_EQ(c->source.range.end.column, 8u);
ASSERT_NE(c->constructor, nullptr);
EXPECT_TRUE(c->constructor->Is<ast::LiteralExpression>());
}
TEST_F(ParserImplTest, GlobalConstDecl_InvalidExpression) {
auto p = parser("const a : f32 = if (a) {}");
auto attrs = p->attribute_list();
EXPECT_FALSE(attrs.errored);
EXPECT_FALSE(attrs.matched);
auto e = p->global_constant_decl(attrs.value);
EXPECT_TRUE(p->has_error());
EXPECT_TRUE(e.errored);
EXPECT_FALSE(e.matched);
EXPECT_EQ(e.value, nullptr);
EXPECT_EQ(p->error(), "1:17: invalid type for const_expr");
}
TEST_F(ParserImplTest, GlobalConstDecl_MissingExpression) {
auto p = parser("const a : f32 =");
auto attrs = p->attribute_list();
EXPECT_FALSE(attrs.errored);
EXPECT_FALSE(attrs.matched);
auto e = p->global_constant_decl(attrs.value);
EXPECT_TRUE(p->has_error());
EXPECT_TRUE(e.errored);
EXPECT_FALSE(e.matched);
EXPECT_EQ(e.value, nullptr);
EXPECT_EQ(p->error(), "1:16: unable to parse const_expr");
}
TEST_F(ParserImplTest, GlobalOverrideDecl_WithId) {
auto p = parser("@id(7) override a : f32 = 1."); auto p = parser("@id(7) override a : f32 = 1.");
auto attrs = p->attribute_list(); auto attrs = p->attribute_list();
EXPECT_FALSE(attrs.errored); EXPECT_FALSE(attrs.errored);
@ -123,7 +198,7 @@ TEST_F(ParserImplTest, GlobalConstantDec_Override_WithId) {
EXPECT_EQ(override_attr->value, 7u); EXPECT_EQ(override_attr->value, 7u);
} }
TEST_F(ParserImplTest, GlobalConstantDec_Override_WithoutId) { TEST_F(ParserImplTest, GlobalOverrideDecl_WithoutId) {
auto p = parser("override a : f32 = 1."); auto p = parser("override a : f32 = 1.");
auto attrs = p->attribute_list(); auto attrs = p->attribute_list();
EXPECT_FALSE(attrs.errored); EXPECT_FALSE(attrs.errored);
@ -152,7 +227,7 @@ TEST_F(ParserImplTest, GlobalConstantDec_Override_WithoutId) {
ASSERT_EQ(id_attr, nullptr); ASSERT_EQ(id_attr, nullptr);
} }
TEST_F(ParserImplTest, GlobalConstantDec_Override_MissingId) { TEST_F(ParserImplTest, GlobalOverrideDecl_MissingId) {
auto p = parser("@id() override a : f32 = 1."); auto p = parser("@id() override a : f32 = 1.");
auto attrs = p->attribute_list(); auto attrs = p->attribute_list();
EXPECT_TRUE(attrs.errored); EXPECT_TRUE(attrs.errored);
@ -168,7 +243,7 @@ TEST_F(ParserImplTest, GlobalConstantDec_Override_MissingId) {
EXPECT_EQ(p->error(), "1:5: expected signed integer literal for id attribute"); EXPECT_EQ(p->error(), "1:5: expected signed integer literal for id attribute");
} }
TEST_F(ParserImplTest, GlobalConstantDec_Override_InvalidId) { TEST_F(ParserImplTest, GlobalOverrideDecl_InvalidId) {
auto p = parser("@id(-7) override a : f32 = 1."); auto p = parser("@id(-7) override a : f32 = 1.");
auto attrs = p->attribute_list(); auto attrs = p->attribute_list();
EXPECT_TRUE(attrs.errored); EXPECT_TRUE(attrs.errored);

View File

@ -49,7 +49,7 @@ TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_MissingSemicolon) {
EXPECT_EQ(p->error(), "1:27: expected ';' for variable declaration"); EXPECT_EQ(p->error(), "1:27: expected ';' for variable declaration");
} }
TEST_F(ParserImplTest, GlobalDecl_GlobalConstant) { TEST_F(ParserImplTest, GlobalDecl_GlobalLet) {
auto p = parser("let a : i32 = 2;"); auto p = parser("let a : i32 = 2;");
p->global_decl(); p->global_decl();
ASSERT_FALSE(p->has_error()) << p->error(); ASSERT_FALSE(p->has_error()) << p->error();
@ -61,27 +61,60 @@ TEST_F(ParserImplTest, GlobalDecl_GlobalConstant) {
EXPECT_EQ(v->symbol, program.Symbols().Get("a")); EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
} }
TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_MissingInitializer) { TEST_F(ParserImplTest, GlobalDecl_GlobalLet_MissingInitializer) {
auto p = parser("let a : vec2<i32>;"); auto p = parser("let a : vec2<i32>;");
p->global_decl(); p->global_decl();
ASSERT_TRUE(p->has_error()); ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:18: expected '=' for 'let' declaration"); EXPECT_EQ(p->error(), "1:18: expected '=' for 'let' declaration");
} }
TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_Invalid) { TEST_F(ParserImplTest, GlobalDecl_GlobalLet_Invalid) {
auto p = parser("let a : vec2<i32> 1.0;"); auto p = parser("let a : vec2<i32> 1.0;");
p->global_decl(); p->global_decl();
ASSERT_TRUE(p->has_error()); ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:19: expected '=' for 'let' declaration"); EXPECT_EQ(p->error(), "1:19: expected '=' for 'let' declaration");
} }
TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_MissingSemicolon) { TEST_F(ParserImplTest, GlobalDecl_GlobalLet_MissingSemicolon) {
auto p = parser("let a : vec2<i32> = vec2<i32>(1, 2)"); auto p = parser("let a : vec2<i32> = vec2<i32>(1, 2)");
p->global_decl(); p->global_decl();
ASSERT_TRUE(p->has_error()); ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:36: expected ';' for 'let' declaration"); EXPECT_EQ(p->error(), "1:36: expected ';' for 'let' declaration");
} }
TEST_F(ParserImplTest, GlobalDecl_GlobalConst) {
auto p = parser("const a : i32 = 2;");
p->global_decl();
ASSERT_FALSE(p->has_error()) << p->error();
auto program = p->program();
ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
auto* v = program.AST().GlobalVariables()[0];
EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
}
TEST_F(ParserImplTest, GlobalDecl_GlobalConst_MissingInitializer) {
auto p = parser("const a : vec2<i32>;");
p->global_decl();
ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:20: expected '=' for 'const' declaration");
}
TEST_F(ParserImplTest, GlobalDecl_GlobalConst_Invalid) {
auto p = parser("const a : vec2<i32> 1.0;");
p->global_decl();
ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:21: expected '=' for 'const' declaration");
}
TEST_F(ParserImplTest, GlobalDecl_GlobalConst_MissingSemicolon) {
auto p = parser("const a : vec2<i32> = vec2<i32>(1, 2)");
p->global_decl();
ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:38: expected ';' for 'const' declaration");
}
TEST_F(ParserImplTest, GlobalDecl_TypeAlias) { TEST_F(ParserImplTest, GlobalDecl_TypeAlias) {
auto p = parser("type A = i32;"); auto p = parser("type A = i32;");
p->global_decl(); p->global_decl();

View File

@ -32,6 +32,13 @@ TEST_P(ParserImplReservedKeywordTest, ModuleLet) {
EXPECT_TRUE(p->has_error()); EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:5: '" + name + "' is a reserved keyword"); EXPECT_EQ(p->error(), "1:5: '" + name + "' is a reserved keyword");
} }
TEST_P(ParserImplReservedKeywordTest, ModuleConst) {
auto name = GetParam();
auto p = parser("const " + name + " : i32 = 1;");
EXPECT_FALSE(p->Parse());
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:7: '" + name + "' is a reserved keyword");
}
TEST_P(ParserImplReservedKeywordTest, ModuleVar) { TEST_P(ParserImplReservedKeywordTest, ModuleVar) {
auto name = GetParam(); auto name = GetParam();
auto p = parser("var " + name + " : i32 = 1;"); auto p = parser("var " + name + " : i32 = 1;");

View File

@ -491,10 +491,7 @@ struct DependencyAnalysis {
[&](const ast::Struct*) { return "struct"; }, // [&](const ast::Struct*) { return "struct"; }, //
[&](const ast::Alias*) { return "alias"; }, // [&](const ast::Alias*) { return "alias"; }, //
[&](const ast::Function*) { return "function"; }, // [&](const ast::Function*) { return "function"; }, //
[&](const ast::Var*) { return "var"; }, // [&](const ast::Variable* v) { return v->Kind(); }, //
[&](const ast::Let*) { return "let"; }, //
[&](const ast::Override*) { return "override"; }, //
[&](const ast::Const*) { return "const"; }, //
[&](Default) { [&](Default) {
UnhandledNode(diagnostics_, node); UnhandledNode(diagnostics_, node);
return "<error>"; return "<error>";

View File

@ -327,16 +327,10 @@ bool Validator::VariableConstructorOrCast(const ast::Variable* v,
// Value type has to match storage type // Value type has to match storage type
if (storage_ty != value_type) { if (storage_ty != value_type) {
std::string decl = Switch( std::stringstream s;
v, // s << "cannot initialize " << v->Kind() << " of type '" << sem_.TypeNameOf(storage_ty)
[&](const ast::Var*) { return "var"; }, // << "' with value of type '" << sem_.TypeNameOf(rhs_ty) << "'";
[&](const ast::Let*) { return "let"; }, // AddError(s.str(), v->source);
[&](const ast::Const*) { return "const"; }, //
[&](Default) { return "<unknown>"; });
AddError("cannot initialize " + decl + " of type '" + sem_.TypeNameOf(storage_ty) +
"' with value of type '" + sem_.TypeNameOf(rhs_ty) + "'",
v->source);
return false; return false;
} }

View File

@ -15,6 +15,11 @@
#include "gmock/gmock.h" #include "gmock/gmock.h"
#include "src/tint/program.h" #include "src/tint/program.h"
// TODO(crbug.com/tint/1580): Remove when 'const' is fully implemented.
#if TINT_BUILD_WGSL_READER
#include "src/tint/reader/wgsl/parser_impl.h"
#endif
#if TINT_BUILD_SPV_READER #if TINT_BUILD_SPV_READER
#include "src/tint/reader/spirv/parser_impl_test_helper.h" #include "src/tint/reader/spirv/parser_impl_test_helper.h"
#endif #endif
@ -54,6 +59,11 @@ struct Flags {
int main(int argc, char** argv) { int main(int argc, char** argv) {
testing::InitGoogleMock(&argc, argv); testing::InitGoogleMock(&argc, argv);
// TODO(crbug.com/tint/1580): Remove when 'const' is fully implemented.
#if TINT_BUILD_WGSL_READER
tint::reader::wgsl::ParserImpl::EnableConst();
#endif
#if TINT_BUILD_WGSL_WRITER #if TINT_BUILD_WGSL_WRITER
tint::Program::printer = [](const tint::Program* program) { tint::Program::printer = [](const tint::Program* program) {
auto result = tint::writer::wgsl::Generate(program, {}); auto result = tint::writer::wgsl::Generate(program, {});