Convert binding and group attributes to expressions.

This CL updates the @group and @binding attributes to use
expressions instead of integers.

Bug: tint:1633
Change-Id: I91068874c104d5b84390f1617cb96265dda6e1e0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/105801
Auto-Submit: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 841649d..7c06020 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -3441,15 +3441,16 @@
     if (t == "binding") {
         const char* use = "binding attribute";
         return expect_paren_block(use, [&]() -> Result {
-            auto val = expect_positive_sint(use);
-            if (val.errored) {
+            auto expr = expression();
+            if (expr.errored) {
                 return Failure::kErrored;
             }
+            if (!expr.matched) {
+                return add_error(peek(), "expected binding expression");
+            }
             match(Token::Type::kComma);
 
-            return create<ast::BindingAttribute>(
-                t.source(), create<ast::IntLiteralExpression>(
-                                val.value, ast::IntLiteralExpression::Suffix::kNone));
+            return create<ast::BindingAttribute>(t.source(), expr.value);
         });
     }
 
@@ -3478,15 +3479,16 @@
     if (t == "group") {
         const char* use = "group attribute";
         return expect_paren_block(use, [&]() -> Result {
-            auto val = expect_positive_sint(use);
-            if (val.errored) {
+            auto expr = expression();
+            if (expr.errored) {
                 return Failure::kErrored;
             }
+            if (!expr.matched) {
+                return add_error(peek(), "expected group expression");
+            }
             match(Token::Type::kComma);
 
-            return create<ast::GroupAttribute>(
-                t.source(), create<ast::IntLiteralExpression>(
-                                val.value, ast::IntLiteralExpression::Suffix::kNone));
+            return create<ast::GroupAttribute>(t.source(), expr.value);
         });
     }
 
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 19434e6..0fc27dd 100644
--- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
@@ -1017,10 +1017,10 @@
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBindingInvalidValue) {
-    EXPECT("@binding(x) var i : i32;",
-           R"(test.wgsl:1:10 error: expected signed integer literal for binding attribute
-@binding(x) var i : i32;
-         ^
+    EXPECT("@binding(if) var i : i32;",
+           R"(test.wgsl:1:10 error: expected binding expression
+@binding(if) var i : i32;
+         ^^
 )");
 }
 
@@ -1041,10 +1041,10 @@
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarAttrBindingGroupValue) {
-    EXPECT("@group(x) var i : i32;",
-           R"(test.wgsl:1:8 error: expected signed integer literal for group attribute
-@group(x) var i : i32;
-       ^
+    EXPECT("@group(if) var i : i32;",
+           R"(test.wgsl:1:8 error: expected group expression
+@group(if) var i : i32;
+       ^^
 )");
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
index 388ad67..5f051db 100644
--- a/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_global_variable_decl_test.cc
@@ -139,7 +139,7 @@
     EXPECT_NE(e.value, nullptr);
 
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:10: expected signed integer literal for binding attribute");
+    EXPECT_EQ(p->error(), "1:10: expected binding expression");
 }
 
 TEST_F(ParserImplTest, GlobalVariableDecl_InvalidConstExpr) {
diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
index 4c68f9b..86e5487 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
@@ -392,6 +392,31 @@
     EXPECT_EQ(expr->suffix, ast::IntLiteralExpression::Suffix::kNone);
 }
 
+TEST_F(ParserImplTest, Attribute_Binding_Expression) {
+    auto p = parser("binding(4 + 5)");
+    auto attr = p->attribute();
+    EXPECT_TRUE(attr.matched);
+    EXPECT_FALSE(attr.errored);
+    ASSERT_NE(attr.value, nullptr);
+    auto* var_attr = attr.value->As<ast::Attribute>();
+    ASSERT_NE(var_attr, nullptr);
+    ASSERT_FALSE(p->has_error());
+    ASSERT_TRUE(var_attr->Is<ast::BindingAttribute>());
+
+    auto* binding = var_attr->As<ast::BindingAttribute>();
+    ASSERT_TRUE(binding->expr->Is<ast::BinaryExpression>());
+    auto* expr = binding->expr->As<ast::BinaryExpression>();
+
+    EXPECT_EQ(ast::BinaryOp::kAdd, expr->op);
+    auto* v = expr->lhs->As<ast::IntLiteralExpression>();
+    ASSERT_NE(nullptr, v);
+    EXPECT_EQ(v->value, 4u);
+
+    v = expr->rhs->As<ast::IntLiteralExpression>();
+    ASSERT_NE(nullptr, v);
+    EXPECT_EQ(v->value, 5u);
+}
+
 TEST_F(ParserImplTest, Attribute_Binding_TrailingComma) {
     auto p = parser("binding(4,)");
     auto attr = p->attribute();
@@ -437,17 +462,17 @@
     EXPECT_TRUE(attr.errored);
     EXPECT_EQ(attr.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:9: expected signed integer literal for binding attribute");
+    EXPECT_EQ(p->error(), "1:9: expected binding expression");
 }
 
 TEST_F(ParserImplTest, Attribute_Binding_MissingInvalid) {
-    auto p = parser("binding(nan)");
+    auto p = parser("binding(if)");
     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(), "1:9: expected signed integer literal for binding attribute");
+    EXPECT_EQ(p->error(), "1:9: expected binding expression");
 }
 
 TEST_F(ParserImplTest, Attribute_group) {
@@ -468,6 +493,31 @@
     EXPECT_EQ(expr->suffix, ast::IntLiteralExpression::Suffix::kNone);
 }
 
+TEST_F(ParserImplTest, Attribute_group_expression) {
+    auto p = parser("group(4 + 5)");
+    auto attr = p->attribute();
+    EXPECT_TRUE(attr.matched);
+    EXPECT_FALSE(attr.errored);
+    ASSERT_NE(attr.value, nullptr);
+    auto* var_attr = attr.value->As<ast::Attribute>();
+    ASSERT_FALSE(p->has_error());
+    ASSERT_NE(var_attr, nullptr);
+    ASSERT_TRUE(var_attr->Is<ast::GroupAttribute>());
+
+    auto* group = var_attr->As<ast::GroupAttribute>();
+    ASSERT_TRUE(group->expr->Is<ast::BinaryExpression>());
+    auto* expr = group->expr->As<ast::BinaryExpression>();
+
+    EXPECT_EQ(ast::BinaryOp::kAdd, expr->op);
+    auto* v = expr->lhs->As<ast::IntLiteralExpression>();
+    ASSERT_NE(nullptr, v);
+    EXPECT_EQ(v->value, 4u);
+
+    v = expr->rhs->As<ast::IntLiteralExpression>();
+    ASSERT_NE(nullptr, v);
+    EXPECT_EQ(v->value, 5u);
+}
+
 TEST_F(ParserImplTest, Attribute_group_TrailingComma) {
     auto p = parser("group(4,)");
     auto attr = p->attribute();
@@ -513,17 +563,17 @@
     EXPECT_TRUE(attr.errored);
     EXPECT_EQ(attr.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:7: expected signed integer literal for group attribute");
+    EXPECT_EQ(p->error(), "1:7: expected group expression");
 }
 
 TEST_F(ParserImplTest, Attribute_Group_MissingInvalid) {
-    auto p = parser("group(nan)");
+    auto p = parser("group(if)");
     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(), "1:7: expected signed integer literal for group attribute");
+    EXPECT_EQ(p->error(), "1:7: expected group expression");
 }
 
 }  // namespace
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 317fab0..ce6baba 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -1562,6 +1562,83 @@
               R"(12:34 error: interpolate attribute must only be used with @location)");
 }
 
+using GroupAndBindingTest = ResolverTest;
+
+TEST_F(GroupAndBindingTest, Const_I32) {
+    GlobalConst("b", Expr(4_i));
+    GlobalConst("g", Expr(2_i));
+    GlobalVar("val", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Binding("b"),
+              Group("g"));
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(GroupAndBindingTest, Const_U32) {
+    GlobalConst("b", Expr(4_u));
+    GlobalConst("g", Expr(2_u));
+    GlobalVar("val", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Binding("b"),
+              Group("g"));
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(GroupAndBindingTest, Const_AInt) {
+    GlobalConst("b", Expr(4_a));
+    GlobalConst("g", Expr(2_a));
+    GlobalVar("val", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Binding("b"),
+              Group("g"));
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(GroupAndBindingTest, Binding_Negative) {
+    GlobalVar("val", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+              Binding(Source{{12, 34}}, -2_i), Group(1_i));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: 'binding' value must be non-negative)");
+}
+
+TEST_F(GroupAndBindingTest, Binding_F32) {
+    GlobalVar("val", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+              Binding(Source{{12, 34}}, 2.0_f), Group(1_u));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: 'binding' must be an i32 or u32 value)");
+}
+
+TEST_F(GroupAndBindingTest, Binding_AFloat) {
+    GlobalVar("val", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+              Binding(Source{{12, 34}}, 2.0_a), Group(1_u));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: 'binding' must be an i32 or u32 value)");
+}
+
+TEST_F(GroupAndBindingTest, Group_Negative) {
+    GlobalVar("val", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Binding(2_u),
+              Group(Source{{12, 34}}, -1_i));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: 'group' value must be non-negative)");
+}
+
+TEST_F(GroupAndBindingTest, Group_F32) {
+    GlobalVar("val", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Binding(2_u),
+              Group(Source{{12, 34}}, 1.0_f));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: 'group' must be an i32 or u32 value)");
+}
+
+TEST_F(GroupAndBindingTest, Group_AFloat) {
+    GlobalVar("val", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), Binding(2_u),
+              Group(Source{{12, 34}}, 1.0_a));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: 'group' must be an i32 or u32 value)");
+}
+
 }  // namespace
 }  // namespace InterpolateTests
 
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index 168734f..514e0cb 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -198,6 +198,7 @@
             [&](const ast::Variable* var) {
                 Declare(var->symbol, var);
                 TraverseType(var->type);
+                TraverseAttributes(var->attributes);
                 if (var->constructor) {
                     TraverseExpression(var->constructor);
                 }
@@ -416,25 +417,38 @@
     /// Traverses the attribute, performing symbol resolution and determining
     /// global dependencies.
     void TraverseAttribute(const ast::Attribute* attr) {
-        if (auto* wg = attr->As<ast::WorkgroupAttribute>()) {
-            TraverseExpression(wg->x);
-            TraverseExpression(wg->y);
-            TraverseExpression(wg->z);
-            return;
-        }
-        if (auto* align = attr->As<ast::StructMemberAlignAttribute>()) {
-            TraverseExpression(align->expr);
-            return;
-        }
-        if (auto* size = attr->As<ast::StructMemberSizeAttribute>()) {
-            TraverseExpression(size->expr);
+        bool handled = Switch(
+            attr,
+            [&](const ast::BindingAttribute* binding) {
+                TraverseExpression(binding->expr);
+                return true;
+            },
+            [&](const ast::GroupAttribute* group) {
+                TraverseExpression(group->expr);
+                return true;
+            },
+            [&](const ast::StructMemberAlignAttribute* align) {
+                TraverseExpression(align->expr);
+                return true;
+            },
+            [&](const ast::StructMemberSizeAttribute* size) {
+                TraverseExpression(size->expr);
+                return true;
+            },
+            [&](const ast::WorkgroupAttribute* wg) {
+                TraverseExpression(wg->x);
+                TraverseExpression(wg->y);
+                TraverseExpression(wg->z);
+                return true;
+            });
+        if (handled) {
             return;
         }
 
-        if (attr->IsAnyOf<ast::BindingAttribute, ast::BuiltinAttribute, ast::GroupAttribute,
-                          ast::IdAttribute, ast::InternalAttribute, ast::InterpolateAttribute,
-                          ast::InvariantAttribute, ast::LocationAttribute, ast::StageAttribute,
-                          ast::StrideAttribute, ast::StructMemberOffsetAttribute>()) {
+        if (attr->IsAnyOf<ast::BuiltinAttribute, ast::IdAttribute, ast::InternalAttribute,
+                          ast::InterpolateAttribute, ast::InvariantAttribute,
+                          ast::LocationAttribute, ast::StageAttribute, ast::StrideAttribute,
+                          ast::StructMemberOffsetAttribute>()) {
             return;
         }
 
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
index 53bf3ee..150d65a 100644
--- a/src/tint/resolver/dependency_graph_test.cc
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -1289,6 +1289,9 @@
     GlobalVar(Sym(), ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float,
                                         ast::Access::kRead));  //
     GlobalVar(Sym(), ty.sampler(ast::SamplerKind::kSampler));
+
+    GlobalVar(Sym(), ty.i32(), utils::Vector{Binding(V), Group(V)});
+
     Func(Sym(), utils::Empty, ty.void_(), utils::Empty);
 #undef V
 #undef T
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 0ae9cd9..eebd5ea 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -619,34 +619,50 @@
         if (var->HasBindingPoint()) {
             uint32_t binding = 0;
             {
+                ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@binding"};
+                TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
+
                 auto* attr = ast::GetAttribute<ast::BindingAttribute>(var->attributes);
-                auto* materialize = Materialize(Expression(attr->expr));
-                if (!materialize) {
+                auto* materialized = Materialize(Expression(attr->expr));
+                if (!materialized) {
                     return nullptr;
                 }
-                auto* c = materialize->ConstantValue();
-                if (!c) {
-                    // TODO(crbug.com/tint/1633): Add error message about invalid materialization
-                    // when binding can be an expression.
+                if (!materialized->Type()->IsAnyOf<sem::I32, sem::U32>()) {
+                    AddError("'binding' must be an i32 or u32 value", attr->source);
                     return nullptr;
                 }
-                binding = c->As<uint32_t>();
+
+                auto const_value = materialized->ConstantValue();
+                auto value = const_value->As<AInt>();
+                if (value < 0) {
+                    AddError("'binding' value must be non-negative", attr->source);
+                    return nullptr;
+                }
+                binding = u32(value);
             }
 
             uint32_t group = 0;
             {
+                ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@group"};
+                TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
+
                 auto* attr = ast::GetAttribute<ast::GroupAttribute>(var->attributes);
-                auto* materialize = Materialize(Expression(attr->expr));
-                if (!materialize) {
+                auto* materialized = Materialize(Expression(attr->expr));
+                if (!materialized) {
                     return nullptr;
                 }
-                auto* c = materialize->ConstantValue();
-                if (!c) {
-                    // TODO(crbug.com/tint/1633): Add error message about invalid materialization
-                    // when binding can be an expression.
+                if (!materialized->Type()->IsAnyOf<sem::I32, sem::U32>()) {
+                    AddError("'group' must be an i32 or u32 value", attr->source);
                     return nullptr;
                 }
-                group = c->As<uint32_t>();
+
+                auto const_value = materialized->ConstantValue();
+                auto value = const_value->As<AInt>();
+                if (value < 0) {
+                    AddError("'group' value must be non-negative", attr->source);
+                    return nullptr;
+                }
+                group = u32(value);
             }
             binding_point = {group, binding};
         }