[tint] Always evaluate template arguments

When short-circuiting an expression, do not skip evaluation of any
template arguments inside it. This means we always evaluate and
validate array element counts within short-circuited expressions.

Change-Id: Ic38cd7cc3816b700c1f4914f1e64159a70a37521
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/181961
Auto-Submit: James Price <jrprice@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/constant/eval_binary_op_test.cc b/src/tint/lang/core/constant/eval_binary_op_test.cc
index ef9f163..8a1bb14 100644
--- a/src/tint/lang/core/constant/eval_binary_op_test.cc
+++ b/src/tint/lang/core/constant/eval_binary_op_test.cc
@@ -2541,6 +2541,66 @@
 }
 
 ////////////////////////////////////////////////
+// Short-Circuit templated identifier arguments
+////////////////////////////////////////////////
+
+TEST_F(ConstEvalTest, ShortCircuit_And_ArrayElementCountTooSmall) {
+    // const one = 1;
+    // const result = (one == 0) && array<bool, 3-4>()[0];
+    GlobalConst("one", Expr(1_a));
+    auto* lhs = Equal("one", 0_a);
+    auto* count = Sub(3_a, 4_a);
+    auto* rhs = IndexAccessor(Call(ty.array(ty.bool_(), count)), 0_a);
+    auto* binary = LogicalAnd(lhs, rhs);
+    GlobalConst("result", binary);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "error: array count (-1) must be greater than 0");
+}
+
+TEST_F(ConstEvalTest, ShortCircuit_Or_ArrayElementCountTooSmall) {
+    // const one = 1;
+    // const result = (one == 1) || array<bool, 3-4>()[0];
+    GlobalConst("one", Expr(1_a));
+    auto* lhs = Equal("one", 1_a);
+    auto* count = Sub(3_a, 4_a);
+    auto* rhs = IndexAccessor(Call(ty.array(ty.bool_(), count)), 0_a);
+    auto* binary = LogicalOr(lhs, rhs);
+    GlobalConst("result", binary);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "error: array count (-1) must be greater than 0");
+}
+
+TEST_F(ConstEvalTest, ShortCircuit_And_InvalidArrayElementCount) {
+    // const one = 1;
+    // const result = (one == 0) && array<bool, u32(sqrt(-1))>()[0];
+    GlobalConst("one", Expr(1_a));
+    auto* lhs = Equal("one", 0_a);
+    auto* count = Call("u32", Call("sqrt", -1_a));
+    auto* rhs = IndexAccessor(Call(ty.array(ty.bool_(), count)), 0_a);
+    auto* binary = LogicalAnd(lhs, rhs);
+    GlobalConst("result", binary);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "error: sqrt must be called with a value >= 0");
+}
+
+TEST_F(ConstEvalTest, ShortCircuit_Or_InvalidArrayElementCount) {
+    // const one = 1;
+    // const result = (one == 1) || array<bool, u32(sqrt(-1))>()[0];
+    GlobalConst("one", Expr(1_a));
+    auto* lhs = Equal("one", 1_a);
+    auto* count = Call("u32", Call("sqrt", -1_a));
+    auto* rhs = IndexAccessor(Call(ty.array(ty.bool_(), count)), 0_a);
+    auto* binary = LogicalOr(lhs, rhs);
+    GlobalConst("result", binary);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "error: sqrt must be called with a value >= 0");
+}
+
+////////////////////////////////////////////////
 // Short-Circuit Nested
 ////////////////////////////////////////////////
 
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index 8e4b9b3..91258cb 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -1592,6 +1592,13 @@
                     auto r = ast::TraverseExpressions(  //
                         (*binary)->rhs, [&](const ast::Expression* e) {
                             not_evaluated_.Add(e);
+                            if (e->Is<ast::IdentifierExpression>()) {
+                                // Template arguments are still evaluated when the outer identifier
+                                // expression is skipped. This happens in expressions like:
+                                //    false && array<T, N>()[i]
+                                // where we still need to evaluate and validate `N`.
+                                return ast::TraverseAction::Skip;
+                            }
                             return ast::TraverseAction::Descend;
                         });
                     if (!r) {
@@ -4171,10 +4178,7 @@
 
     switch (count_sem->Stage()) {
         case core::EvaluationStage::kNotEvaluated:
-            // Happens in expressions like:
-            //    false && array<T, N>()[i]
-            // The end result will not be used, so just make N=1.
-            return b.create<core::type::ConstantArrayCount>(static_cast<uint32_t>(1));
+            ICE(count_expr->source) << "array element count was not evaluated";
 
         case core::EvaluationStage::kOverride: {
             // array count is an override expression.