[wgsl-reader] Add support for constant_id decoration

Bug: tint:155
Change-Id: I207fd87d0708c66ea0fe7de81b156db98eea8e60
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/39560
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: James Price <jrprice@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 8fc397b..1e4a834 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -27,6 +27,7 @@
 #include "src/ast/builtin_decoration.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/case_statement.h"
+#include "src/ast/constant_id_decoration.h"
 #include "src/ast/continue_statement.h"
 #include "src/ast/discard_statement.h"
 #include "src/ast/else_statement.h"
@@ -134,6 +135,7 @@
 const char kBindingDecoration[] = "binding";
 const char kBlockDecoration[] = "block";
 const char kBuiltinDecoration[] = "builtin";
+const char kConstantIdDecoration[] = "constant_id";
 const char kGroupDecoration[] = "group";
 const char kLocationDecoration[] = "location";
 const char kOffsetDecoration[] = "offset";
@@ -149,10 +151,10 @@
   auto s = t.to_str();
   return s == kAccessDecoration || s == kBindingDecoration ||
          s == kBlockDecoration || s == kBuiltinDecoration ||
-         s == kLocationDecoration || s == kOffsetDecoration ||
-         s == kSetDecoration || s == kGroupDecoration ||
-         s == kStageDecoration || s == kStrideDecoration ||
-         s == kWorkgroupSizeDecoration;
+         s == kConstantIdDecoration || s == kLocationDecoration ||
+         s == kOffsetDecoration || s == kSetDecoration ||
+         s == kGroupDecoration || s == kStageDecoration ||
+         s == kStrideDecoration || s == kWorkgroupSizeDecoration;
 }
 
 /// Enter-exit counters for block token types.
@@ -323,7 +325,7 @@
       return true;
     }
 
-    auto gc = global_constant_decl();
+    auto gc = global_constant_decl(decos.value);
     if (gc.errored)
       return Failure::kErrored;
 
@@ -441,8 +443,9 @@
 }
 
 // global_constant_decl
-//  : CONST variable_ident_decl EQUAL const_expr
-Maybe<ast::Variable*> ParserImpl::global_constant_decl() {
+//  : variable_decoration_list* CONST variable_ident_decl EQUAL const_expr
+Maybe<ast::Variable*> ParserImpl::global_constant_decl(
+    ast::DecorationList& decos) {
   if (!match(Token::Type::kConst))
     return Failure::kNoMatch;
 
@@ -459,6 +462,10 @@
   if (init.errored)
     return Failure::kErrored;
 
+  auto var_decos = cast_decorations<ast::VariableDecoration>(decos);
+  if (var_decos.errored)
+    return Failure::kErrored;
+
   return create<ast::Variable>(
       decl->source,                             // source
       builder_.Symbols().Register(decl->name),  // symbol
@@ -466,7 +473,7 @@
       decl->type,                               // type
       true,                                     // is_const
       init.value,                               // constructor
-      ast::VariableDecorationList{});           // decorations
+      std::move(var_decos.value));              // decorations
 }
 
 // variable_decl
@@ -2939,6 +2946,17 @@
     });
   }
 
+  if (s == kConstantIdDecoration) {
+    const char* use = "constant_id decoration";
+    return expect_paren_block(use, [&]() -> Result {
+      auto val = expect_positive_sint(use);
+      if (val.errored)
+        return Failure::kErrored;
+
+      return create<ast::ConstantIdDecoration>(t.source(), val.value);
+    });
+  }
+
   return Failure::kNoMatch;
 }
 
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 56229b7..4105416 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -365,9 +365,11 @@
   /// @returns the variable parsed or nullptr
   /// @param decos the list of decorations for the variable declaration.
   Maybe<ast::Variable*> global_variable_decl(ast::DecorationList& decos);
-  /// Parses a `global_constant_decl` grammar element
+  /// Parses a `global_constant_decl` grammar element with the initial
+  /// `variable_decoration_list*` provided as `decos`
   /// @returns the const object or nullptr
-  Maybe<ast::Variable*> global_constant_decl();
+  /// @param decos the list of decorations for the constant declaration.
+  Maybe<ast::Variable*> global_constant_decl(ast::DecorationList& decos);
   /// Parses a `variable_decl` grammar element
   /// @returns the parsed variable declaration info
   Maybe<VarDeclInfo> variable_decl();
diff --git a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
index ca2b7f4..2025e3b 100644
--- a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
@@ -26,7 +26,10 @@
 
 TEST_F(ParserImplTest, GlobalConstantDecl) {
   auto p = parser("const a : f32 = 1.");
-  auto e = p->global_constant_decl();
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto e = p->global_constant_decl(decos.value);
   EXPECT_FALSE(p->has_error()) << p->error();
   EXPECT_TRUE(e.matched);
   EXPECT_FALSE(e.errored);
@@ -44,11 +47,16 @@
 
   ASSERT_NE(e->constructor(), nullptr);
   EXPECT_TRUE(e->constructor()->Is<ast::ConstructorExpression>());
+
+  EXPECT_FALSE(e.value->HasConstantIdDecoration());
 }
 
 TEST_F(ParserImplTest, GlobalConstantDecl_MissingEqual) {
   auto p = parser("const a: f32 1.");
-  auto e = p->global_constant_decl();
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto e = p->global_constant_decl(decos.value);
   EXPECT_TRUE(p->has_error());
   EXPECT_TRUE(e.errored);
   EXPECT_FALSE(e.matched);
@@ -58,7 +66,10 @@
 
 TEST_F(ParserImplTest, GlobalConstantDecl_InvalidVariable) {
   auto p = parser("const a: invalid = 1.");
-  auto e = p->global_constant_decl();
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto e = p->global_constant_decl(decos.value);
   EXPECT_TRUE(p->has_error());
   EXPECT_TRUE(e.errored);
   EXPECT_FALSE(e.matched);
@@ -68,7 +79,10 @@
 
 TEST_F(ParserImplTest, GlobalConstantDecl_InvalidExpression) {
   auto p = parser("const a: f32 = if (a) {}");
-  auto e = p->global_constant_decl();
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto e = p->global_constant_decl(decos.value);
   EXPECT_TRUE(p->has_error());
   EXPECT_TRUE(e.errored);
   EXPECT_FALSE(e.matched);
@@ -78,7 +92,10 @@
 
 TEST_F(ParserImplTest, GlobalConstantDecl_MissingExpression) {
   auto p = parser("const a: f32 =");
-  auto e = p->global_constant_decl();
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto e = p->global_constant_decl(decos.value);
   EXPECT_TRUE(p->has_error());
   EXPECT_TRUE(e.errored);
   EXPECT_FALSE(e.matched);
@@ -86,6 +103,66 @@
   EXPECT_EQ(p->error(), "1:15: unable to parse const literal");
 }
 
+TEST_F(ParserImplTest, GlobalConstantDec_ConstantId) {
+  auto p = parser("[[constant_id(7)]] const a : f32 = 1.");
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+
+  auto e = p->global_constant_decl(decos.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(e->is_const());
+  EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
+  ASSERT_NE(e->type(), nullptr);
+  EXPECT_TRUE(e->type()->Is<type::F32>());
+
+  EXPECT_EQ(e->source().range.begin.line, 1u);
+  EXPECT_EQ(e->source().range.begin.column, 26u);
+  EXPECT_EQ(e->source().range.end.line, 1u);
+  EXPECT_EQ(e->source().range.end.column, 27u);
+
+  ASSERT_NE(e->constructor(), nullptr);
+  EXPECT_TRUE(e->constructor()->Is<ast::ConstructorExpression>());
+
+  EXPECT_TRUE(e.value->HasConstantIdDecoration());
+  EXPECT_EQ(e.value->constant_id(), 7u);
+}
+
+TEST_F(ParserImplTest, GlobalConstantDec_ConstantId_Missing) {
+  auto p = parser("[[constant_id()]] const a : f32 = 1.");
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+
+  auto e = p->global_constant_decl(decos.value);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(),
+            "1:15: expected signed integer literal for constant_id decoration");
+}
+
+TEST_F(ParserImplTest, GlobalConstantDec_ConstantId_Invalid) {
+  auto p = parser("[[constant_id(-7)]] const a : f32 = 1.");
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+
+  auto e = p->global_constant_decl(decos.value);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:15: constant_id decoration must be positive");
+}
+
 }  // namespace
 }  // namespace wgsl
 }  // namespace reader