From e48ef8ef9070c319c0a3beb372076948c2131437 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Sun, 26 Jun 2022 10:52:50 +0000 Subject: [PATCH] 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 Commit-Queue: Ben Clayton Kokoro: Kokoro --- src/tint/ast/const.cc | 4 + src/tint/ast/const.h | 3 + src/tint/ast/let.cc | 4 + src/tint/ast/let.h | 3 + src/tint/ast/override.cc | 4 + src/tint/ast/override.h | 3 + src/tint/ast/parameter.cc | 4 + src/tint/ast/parameter.h | 3 + src/tint/ast/var.cc | 4 + src/tint/ast/var.h | 3 + src/tint/ast/variable.h | 4 + src/tint/reader/wgsl/lexer_test.cc | 6 +- src/tint/reader/wgsl/parser_impl.cc | 55 +++++++- src/tint/reader/wgsl/parser_impl.h | 5 + .../reader/wgsl/parser_impl_error_msg_test.cc | 119 ++++++++++++++++-- .../parser_impl_global_constant_decl_test.cc | 91 ++++++++++++-- .../wgsl/parser_impl_global_decl_test.cc | 41 +++++- .../wgsl/parser_impl_reserved_keyword_test.cc | 7 ++ src/tint/resolver/dependency_graph.cc | 13 +- src/tint/resolver/validator.cc | 14 +-- src/tint/test_main.cc | 10 ++ 21 files changed, 356 insertions(+), 44 deletions(-) diff --git a/src/tint/ast/const.cc b/src/tint/ast/const.cc index 6ae8e8cfa2..e13cc25b30 100644 --- a/src/tint/ast/const.cc +++ b/src/tint/ast/const.cc @@ -34,6 +34,10 @@ Const::Const(Const&&) = default; Const::~Const() = default; +const char* Const::Kind() const { + return "const"; +} + const Const* Const::Clone(CloneContext* ctx) const { auto src = ctx->Clone(source); auto sym = ctx->Clone(symbol); diff --git a/src/tint/ast/const.h b/src/tint/ast/const.h index 39ac6c3ca2..32e3ddd835 100644 --- a/src/tint/ast/const.h +++ b/src/tint/ast/const.h @@ -52,6 +52,9 @@ class Const final : public Castable { /// Destructor ~Const() override; + /// @returns "const" + const char* Kind() const override; + /// Clones this node and all transitive child nodes using the `CloneContext` /// `ctx`. /// @param ctx the clone context diff --git a/src/tint/ast/let.cc b/src/tint/ast/let.cc index e771c4d2ba..8db8ec1a9b 100644 --- a/src/tint/ast/let.cc +++ b/src/tint/ast/let.cc @@ -34,6 +34,10 @@ Let::Let(Let&&) = default; Let::~Let() = default; +const char* Let::Kind() const { + return "let"; +} + const Let* Let::Clone(CloneContext* ctx) const { auto src = ctx->Clone(source); auto sym = ctx->Clone(symbol); diff --git a/src/tint/ast/let.h b/src/tint/ast/let.h index 2b0a6aaf87..2c1ad7c591 100644 --- a/src/tint/ast/let.h +++ b/src/tint/ast/let.h @@ -49,6 +49,9 @@ class Let final : public Castable { /// Destructor ~Let() override; + /// @returns "let" + const char* Kind() const override; + /// Clones this node and all transitive child nodes using the `CloneContext` /// `ctx`. /// @param ctx the clone context diff --git a/src/tint/ast/override.cc b/src/tint/ast/override.cc index f494bc83b7..efd048d02e 100644 --- a/src/tint/ast/override.cc +++ b/src/tint/ast/override.cc @@ -32,6 +32,10 @@ Override::Override(Override&&) = default; Override::~Override() = default; +const char* Override::Kind() const { + return "override"; +} + const Override* Override::Clone(CloneContext* ctx) const { auto src = ctx->Clone(source); auto sym = ctx->Clone(symbol); diff --git a/src/tint/ast/override.h b/src/tint/ast/override.h index 168b9b213c..98319e5bf6 100644 --- a/src/tint/ast/override.h +++ b/src/tint/ast/override.h @@ -50,6 +50,9 @@ class Override final : public Castable { /// Destructor ~Override() override; + /// @returns "override" + const char* Kind() const override; + /// Clones this node and all transitive child nodes using the `CloneContext` /// `ctx`. /// @param ctx the clone context diff --git a/src/tint/ast/parameter.cc b/src/tint/ast/parameter.cc index b7ea3b1152..ea3c51fef1 100644 --- a/src/tint/ast/parameter.cc +++ b/src/tint/ast/parameter.cc @@ -31,6 +31,10 @@ Parameter::Parameter(Parameter&&) = default; Parameter::~Parameter() = default; +const char* Parameter::Kind() const { + return "parameter"; +} + const Parameter* Parameter::Clone(CloneContext* ctx) const { auto src = ctx->Clone(source); auto sym = ctx->Clone(symbol); diff --git a/src/tint/ast/parameter.h b/src/tint/ast/parameter.h index d3ecf8dfa1..eb4b688333 100644 --- a/src/tint/ast/parameter.h +++ b/src/tint/ast/parameter.h @@ -51,6 +51,9 @@ class Parameter final : public Castable { /// Destructor ~Parameter() override; + /// @returns "parameter" + const char* Kind() const override; + /// Clones this node and all transitive child nodes using the `CloneContext` /// `ctx`. /// @param ctx the clone context diff --git a/src/tint/ast/var.cc b/src/tint/ast/var.cc index 622aa03022..854503e73f 100644 --- a/src/tint/ast/var.cc +++ b/src/tint/ast/var.cc @@ -36,6 +36,10 @@ Var::Var(Var&&) = default; Var::~Var() = default; +const char* Var::Kind() const { + return "var"; +} + const Var* Var::Clone(CloneContext* ctx) const { auto src = ctx->Clone(source); auto sym = ctx->Clone(symbol); diff --git a/src/tint/ast/var.h b/src/tint/ast/var.h index fd0358097e..565ebbb6fd 100644 --- a/src/tint/ast/var.h +++ b/src/tint/ast/var.h @@ -65,6 +65,9 @@ class Var final : public Castable { /// Destructor ~Var() override; + /// @returns "var" + const char* Kind() const override; + /// Clones this node and all transitive child nodes using the `CloneContext` /// `ctx`. /// @param ctx the clone context diff --git a/src/tint/ast/variable.h b/src/tint/ast/variable.h index bc25753171..631fbe5785 100644 --- a/src/tint/ast/variable.h +++ b/src/tint/ast/variable.h @@ -77,6 +77,10 @@ class Variable : public Castable { /// @note binding points should only be applied to Var and Parameter types. 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 const Symbol symbol; diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc index 712474f0d4..bf32a52121 100644 --- a/src/tint/reader/wgsl/lexer_test.cc +++ b/src/tint/reader/wgsl/lexer_test.cc @@ -144,18 +144,18 @@ TEST_P(LineCommentTerminatorTest, Terminators) { // Test that line comments are ended by blankspace characters other than // space, horizontal tab, left-to-right mark, and right-to-left mark. auto* c = GetParam(); - std::string src = "let// This is a comment"; + std::string src = "const// This is a comment"; src += c; src += "ident"; Source::File file("", src); Lexer l(&file); 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.column, 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) { return v == kSpace || v == kHTab || v == kL2R || v == kR2L; diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc index 3f1fad4371..f6ab166bab 100644 --- a/src/tint/reader/wgsl/parser_impl.cc +++ b/src/tint/reader/wgsl/parser_impl.cc @@ -47,6 +47,9 @@ namespace tint::reader::wgsl { namespace { +// TODO(crbug.com/tint/1580): Remove when 'const' is fully implemented. +bool const_enabled = false; + template using Expect = ParserImpl::Expect; @@ -245,6 +248,11 @@ ParserImpl::ParserImpl(Source::File const* file) : lexer_(std::make_unique ParserImpl::global_decl() { } if (gc.matched) { - if (!expect("'let' declaration", Token::Type::kSemicolon)) { - return Failure::kErrored; + // 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; + } } builder_.AST().AddGlobalVariable(gc.value); @@ -561,10 +573,14 @@ Maybe ParserImpl::global_variable_decl(ast::AttributeList& // global_const_initializer // : EQUAL const_expr Maybe ParserImpl::global_constant_decl(ast::AttributeList& attrs) { + bool is_const = false; bool is_overridable = false; const char* use = nullptr; if (match(Token::Type::kLet)) { use = "'let' declaration"; + } else if (const_enabled && match(Token::Type::kConst)) { + use = "'const' declaration"; + is_const = true; } else if (match(Token::Type::kOverride)) { use = "'override' declaration"; is_overridable = true; @@ -596,6 +612,13 @@ Maybe ParserImpl::global_constant_decl(ast::AttributeList& initializer = std::move(init.value); } + if (is_const) { + return create(decl->source, // source + builder_.Symbols().Register(decl->name), // symbol + decl->type, // type + initializer, // constructor + std::move(attrs)); // attributes + } if (is_overridable) { return create(decl->source, // source builder_.Symbols().Register(decl->name), // symbol @@ -1769,6 +1792,34 @@ Maybe ParserImpl::return_stmt() { // | variable_decl EQUAL logical_or_expression // | CONST variable_ident_decl EQUAL logical_or_expression Maybe 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(decl->source, // source + builder_.Symbols().Register(decl->name), // symbol + decl->type, // type + constructor.value, // constructor + ast::AttributeList{}); // attributes + + return create(decl->source, const_); + } + if (match(Token::Type::kLet)) { auto decl = expect_variable_ident_decl("'let' declaration", /*allow_inferred = */ true); diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h index c6e696bc91..89d5163d60 100644 --- a/src/tint/reader/wgsl/parser_impl.h +++ b/src/tint/reader/wgsl/parser_impl.h @@ -72,6 +72,11 @@ class ParserImpl { }; 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 /// 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 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 fc141c0c7a..5e99d34948 100644 --- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc +++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc @@ -471,6 +471,107 @@ test.wgsl:3:3 error: statement found outside of function body } 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 = vec2;", + R"(test.wgsl:1:32 error: expected '(' for type constructor +const i : vec2 = vec2; + ^ +)"); +} + +TEST_F(ParserImplErrorTest, GlobalDeclConstMissingRParen) { + EXPECT("const i : vec2 = vec2(1., 2.;", + R"(test.wgsl:1:39 error: expected ')' for type constructor +const i : vec2 = vec2(1., 2.; + ^ +)"); +} + +TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteral) { + EXPECT("const i : vec2 = vec2(!);", + R"(test.wgsl:1:33 error: unable to parse const_expr +const i : vec2 = vec2(!); + ^ +)"); +} + +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 = vec2 1, 2);", + R"(test.wgsl:1:33 error: expected '(' for type constructor +const i : vec2 = vec2 1, 2); + ^ +)"); +} + +TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingRParen) { + EXPECT("const i : vec2 = vec2(1, 2;", + R"(test.wgsl:1:37 error: expected ')' for type constructor +const i : vec2 = vec2(1, 2; + ^ +)"); +} + +TEST_F(ParserImplErrorTest, GlobalDeclLetInvalidIdentifier) { EXPECT("let ^ : i32 = 1;", R"(test.wgsl:1:5 error: expected identifier for 'let' declaration let ^ : i32 = 1; @@ -478,7 +579,7 @@ let ^ : i32 = 1; )"); } -TEST_F(ParserImplErrorTest, GlobalDeclConstMissingSemicolon) { +TEST_F(ParserImplErrorTest, GlobalDeclLetMissingSemicolon) { EXPECT("let i : i32 = 1", R"(test.wgsl:1:16 error: expected ';' for 'let' declaration let i : i32 = 1 @@ -486,7 +587,7 @@ let i : i32 = 1 )"); } -TEST_F(ParserImplErrorTest, GlobalDeclConstMissingLParen) { +TEST_F(ParserImplErrorTest, GlobalDeclLetMissingLParen) { EXPECT("let i : vec2 = vec2;", R"(test.wgsl:1:30 error: expected '(' for type constructor let i : vec2 = vec2; @@ -494,7 +595,7 @@ let i : vec2 = vec2; )"); } -TEST_F(ParserImplErrorTest, GlobalDeclConstMissingRParen) { +TEST_F(ParserImplErrorTest, GlobalDeclLetMissingRParen) { EXPECT("let i : vec2 = vec2(1., 2.;", R"(test.wgsl:1:37 error: expected ')' for type constructor let i : vec2 = vec2(1., 2.; @@ -502,7 +603,7 @@ let i : vec2 = vec2(1., 2.; )"); } -TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteral) { +TEST_F(ParserImplErrorTest, GlobalDeclLetBadConstLiteral) { EXPECT("let i : vec2 = vec2(!);", R"(test.wgsl:1:31 error: unable to parse const_expr let i : vec2 = vec2(!); @@ -510,7 +611,7 @@ let i : vec2 = vec2(!); )"); } -TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteralSpaceLessThan) { +TEST_F(ParserImplErrorTest, GlobalDeclLetBadConstLiteralSpaceLessThan) { EXPECT("let i = 1 < 2;", R"(test.wgsl:1:11 error: expected ';' for 'let' declaration let i = 1 < 2; @@ -518,7 +619,7 @@ let i = 1 < 2; )"); } -TEST_F(ParserImplErrorTest, GlobalDeclConstNotConstExpr) { +TEST_F(ParserImplErrorTest, GlobalDeclLetNotConstExpr) { EXPECT( "let a = 1;\n" "let b = a;", @@ -528,7 +629,7 @@ let b = a; )"); } -TEST_F(ParserImplErrorTest, GlobalDeclConstExprMaxDepth) { +TEST_F(ParserImplErrorTest, GlobalDeclLetExprMaxDepth) { uint32_t kMaxDepth = 128; std::stringstream src; @@ -555,7 +656,7 @@ TEST_F(ParserImplErrorTest, GlobalDeclConstExprMaxDepth) { EXPECT(src.str().c_str(), err.str().c_str()); } -TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingLParen) { +TEST_F(ParserImplErrorTest, GlobalDeclLetExprMissingLParen) { EXPECT("let i : vec2 = vec2 1, 2);", R"(test.wgsl:1:31 error: expected '(' for type constructor let i : vec2 = vec2 1, 2); @@ -563,7 +664,7 @@ let i : vec2 = vec2 1, 2); )"); } -TEST_F(ParserImplErrorTest, GlobalDeclConstExprMissingRParen) { +TEST_F(ParserImplErrorTest, GlobalDeclLetExprMissingRParen) { EXPECT("let i : vec2 = vec2(1, 2;", R"(test.wgsl:1:35 error: expected ')' for type constructor let i : vec2 = vec2(1, 2; diff --git a/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc index f5428fc13a..70caa3f8b3 100644 --- a/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc +++ b/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc @@ -18,7 +18,7 @@ namespace tint::reader::wgsl { namespace { -TEST_F(ParserImplTest, GlobalConstantDecl) { +TEST_F(ParserImplTest, GlobalLetDecl) { auto p = parser("let a : f32 = 1."); auto attrs = p->attribute_list(); EXPECT_FALSE(attrs.errored); @@ -43,7 +43,7 @@ TEST_F(ParserImplTest, GlobalConstantDecl) { EXPECT_TRUE(let->constructor->Is()); } -TEST_F(ParserImplTest, GlobalConstantDecl_Inferred) { +TEST_F(ParserImplTest, GlobalLetDecl_Inferred) { auto p = parser("let a = 1."); auto attrs = p->attribute_list(); EXPECT_FALSE(attrs.errored); @@ -67,7 +67,7 @@ TEST_F(ParserImplTest, GlobalConstantDecl_Inferred) { EXPECT_TRUE(let->constructor->Is()); } -TEST_F(ParserImplTest, GlobalConstantDecl_InvalidExpression) { +TEST_F(ParserImplTest, GlobalLetDecl_InvalidExpression) { auto p = parser("let a : f32 = if (a) {}"); auto attrs = p->attribute_list(); EXPECT_FALSE(attrs.errored); @@ -80,7 +80,7 @@ TEST_F(ParserImplTest, GlobalConstantDecl_InvalidExpression) { 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 attrs = p->attribute_list(); EXPECT_FALSE(attrs.errored); @@ -93,7 +93,82 @@ TEST_F(ParserImplTest, GlobalConstantDecl_MissingExpression) { 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(); + ASSERT_NE(c, nullptr); + + EXPECT_EQ(c->symbol, p->builder().Symbols().Get("a")); + ASSERT_NE(c->type, nullptr); + EXPECT_TRUE(c->type->Is()); + + 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()); +} + +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(); + 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()); +} + +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 attrs = p->attribute_list(); EXPECT_FALSE(attrs.errored); @@ -123,7 +198,7 @@ TEST_F(ParserImplTest, GlobalConstantDec_Override_WithId) { 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 attrs = p->attribute_list(); EXPECT_FALSE(attrs.errored); @@ -152,7 +227,7 @@ TEST_F(ParserImplTest, GlobalConstantDec_Override_WithoutId) { 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 attrs = p->attribute_list(); 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"); } -TEST_F(ParserImplTest, GlobalConstantDec_Override_InvalidId) { +TEST_F(ParserImplTest, GlobalOverrideDecl_InvalidId) { auto p = parser("@id(-7) override a : f32 = 1."); auto attrs = p->attribute_list(); EXPECT_TRUE(attrs.errored); diff --git a/src/tint/reader/wgsl/parser_impl_global_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_decl_test.cc index 59012a3aaa..a09486d79a 100644 --- a/src/tint/reader/wgsl/parser_impl_global_decl_test.cc +++ b/src/tint/reader/wgsl/parser_impl_global_decl_test.cc @@ -49,7 +49,7 @@ TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_MissingSemicolon) { 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;"); p->global_decl(); ASSERT_FALSE(p->has_error()) << p->error(); @@ -61,27 +61,60 @@ TEST_F(ParserImplTest, GlobalDecl_GlobalConstant) { 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;"); p->global_decl(); ASSERT_TRUE(p->has_error()); 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 1.0;"); p->global_decl(); ASSERT_TRUE(p->has_error()); 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 = vec2(1, 2)"); p->global_decl(); ASSERT_TRUE(p->has_error()); 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;"); + 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 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 = vec2(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) { auto p = parser("type A = i32;"); p->global_decl(); diff --git a/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc index 8f2d470090..65fae3c434 100644 --- a/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc +++ b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc @@ -32,6 +32,13 @@ TEST_P(ParserImplReservedKeywordTest, ModuleLet) { EXPECT_TRUE(p->has_error()); 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) { auto name = GetParam(); auto p = parser("var " + name + " : i32 = 1;"); diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc index ec450ec056..be4511b06a 100644 --- a/src/tint/resolver/dependency_graph.cc +++ b/src/tint/resolver/dependency_graph.cc @@ -487,14 +487,11 @@ struct DependencyAnalysis { /// declaration std::string KindOf(const ast::Node* node) { return Switch( - node, // - [&](const ast::Struct*) { return "struct"; }, // - [&](const ast::Alias*) { return "alias"; }, // - [&](const ast::Function*) { return "function"; }, // - [&](const ast::Var*) { return "var"; }, // - [&](const ast::Let*) { return "let"; }, // - [&](const ast::Override*) { return "override"; }, // - [&](const ast::Const*) { return "const"; }, // + node, // + [&](const ast::Struct*) { return "struct"; }, // + [&](const ast::Alias*) { return "alias"; }, // + [&](const ast::Function*) { return "function"; }, // + [&](const ast::Variable* v) { return v->Kind(); }, // [&](Default) { UnhandledNode(diagnostics_, node); return ""; diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc index 5676401799..6ffbd62725 100644 --- a/src/tint/resolver/validator.cc +++ b/src/tint/resolver/validator.cc @@ -327,16 +327,10 @@ bool Validator::VariableConstructorOrCast(const ast::Variable* v, // Value type has to match storage type if (storage_ty != value_type) { - std::string decl = Switch( - v, // - [&](const ast::Var*) { return "var"; }, // - [&](const ast::Let*) { return "let"; }, // - [&](const ast::Const*) { return "const"; }, // - [&](Default) { return ""; }); - - AddError("cannot initialize " + decl + " of type '" + sem_.TypeNameOf(storage_ty) + - "' with value of type '" + sem_.TypeNameOf(rhs_ty) + "'", - v->source); + std::stringstream s; + s << "cannot initialize " << v->Kind() << " of type '" << sem_.TypeNameOf(storage_ty) + << "' with value of type '" << sem_.TypeNameOf(rhs_ty) << "'"; + AddError(s.str(), v->source); return false; } diff --git a/src/tint/test_main.cc b/src/tint/test_main.cc index ca68271587..0114d4b5f5 100644 --- a/src/tint/test_main.cc +++ b/src/tint/test_main.cc @@ -15,6 +15,11 @@ #include "gmock/gmock.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 #include "src/tint/reader/spirv/parser_impl_test_helper.h" #endif @@ -54,6 +59,11 @@ struct Flags { int main(int argc, char** 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 tint::Program::printer = [](const tint::Program* program) { auto result = tint::writer::wgsl::Generate(program, {});