tint: Validate no template args with functions / builtin calls

This was already handled when resolving regular identifiers, but calls special case this resolving, and failed to check for template arguments.

Bug: chromium:1424273
Change-Id: Id756c7fbca93afcd9fd3792466471aa43d3dff04
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/123980
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/tint/resolver/call_validation_test.cc b/src/tint/resolver/call_validation_test.cc
index b749df9..b00db68 100644
--- a/src/tint/resolver/call_validation_test.cc
+++ b/src/tint/resolver/call_validation_test.cc
@@ -478,5 +478,34 @@
     EXPECT_EQ(r()->error(), "12:34 error: ignoring return value of builtin 'max'");
 }
 
+TEST_F(ResolverCallValidationTest, UnexpectedFunctionTemplateArgs) {
+    // fn a() {}
+    // fn b() {
+    //   a<i32>();
+    // }
+    Func(Source{{56, 78}}, "a", utils::Empty, ty.void_(), utils::Empty);
+    Func("b", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call(Ident(Source{{12, 34}}, "a", "i32"))),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: function 'a' does not take template arguments
+56:78 note: function 'a' declared here)");
+}
+
+TEST_F(ResolverCallValidationTest, UnexpectedBuiltinTemplateArgs) {
+    // fn f() {
+    //   min<i32>(1, 2);
+    // }
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Var("v", Call(Ident(Source{{12, 34}}, "min", "i32"), 1_a, 2_a))),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: builtin 'min' does not take template arguments)");
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 74b32bb..f6f1876 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -2156,7 +2156,12 @@
             return Switch(
                 sem_.Get(ast_node),  //
                 [&](type::Type* t) { return ty_init_or_conv(t); },
-                [&](sem::Function* f) { return FunctionCall(expr, f, args, arg_behaviors); },
+                [&](sem::Function* f) -> sem::Call* {
+                    if (!TINT_LIKELY(CheckNotTemplated("function", ident))) {
+                        return nullptr;
+                    }
+                    return FunctionCall(expr, f, args, arg_behaviors);
+                },
                 [&](sem::Expression* e) {
                     sem_.ErrorUnexpectedExprKind(e, "call target");
                     return nullptr;
@@ -2168,6 +2173,9 @@
         }
 
         if (auto f = resolved->BuiltinFunction(); f != builtin::Function::kNone) {
+            if (!TINT_LIKELY(CheckNotTemplated("builtin", ident))) {
+                return nullptr;
+            }
             return BuiltinCall(expr, f, args);
         }
 
@@ -2331,13 +2339,7 @@
     auto& b = *builder_;
 
     auto check_no_tmpl_args = [&](type::Type* ty) -> type::Type* {
-        if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
-            AddError("type '" + b.Symbols().NameFor(ident->symbol) +
-                         "' does not take template arguments",
-                     ident->source);
-            return nullptr;
-        }
-        return ty;
+        return TINT_LIKELY(CheckNotTemplated("type", ident)) ? ty : nullptr;
     };
     auto f32 = [&] { return b.create<type::F32>(); };
     auto i32 = [&] { return b.create<type::I32>(); };
@@ -3019,25 +3021,15 @@
                 return user;
             },
             [&](const type::Type* ty) -> sem::TypeExpression* {
-                if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
-                    AddError("type '" + builder_->Symbols().NameFor(ident->symbol) +
-                                 "' does not take template arguments",
-                             ident->source);
-                    sem_.NoteDeclarationSource(ast_node);
+                if (!TINT_LIKELY(CheckNotTemplated("type", ident))) {
                     return nullptr;
                 }
-
                 return builder_->create<sem::TypeExpression>(expr, current_statement_, ty);
             },
             [&](const sem::Function* fn) -> sem::FunctionExpression* {
-                if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
-                    AddError("function '" + builder_->Symbols().NameFor(ident->symbol) +
-                                 "' does not take template arguments",
-                             ident->source);
-                    sem_.NoteDeclarationSource(ast_node);
+                if (!TINT_LIKELY(CheckNotTemplated("function", ident))) {
                     return nullptr;
                 }
-
                 return builder_->create<sem::FunctionExpression>(expr, current_statement_, fn);
             });
     }
@@ -4329,6 +4321,21 @@
     }
 }
 
+bool Resolver::CheckNotTemplated(const char* use, const ast::Identifier* ident) {
+    if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
+        AddError(std::string(use) + " '" + builder_->Symbols().NameFor(ident->symbol) +
+                     "' does not take template arguments",
+                 ident->source);
+        if (auto resolved = dependencies_.resolved_identifiers.Get(ident)) {
+            if (auto* ast_node = resolved->Node()) {
+                sem_.NoteDeclarationSource(ast_node);
+            }
+        }
+        return false;
+    }
+    return true;
+}
+
 void Resolver::ErrorMismatchedResolvedIdentifier(const Source& source,
                                                  const ResolvedIdentifier& resolved,
                                                  std::string_view wanted) {
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index cab7b37..266f3a2 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -480,6 +480,11 @@
     template <typename NODE>
     void ApplyDiagnosticSeverities(NODE* node);
 
+    /// Checks @p ident is not an ast::TemplatedIdentifier.
+    /// If @p ident is a ast::TemplatedIdentifier, then an error diagnostic is raised.
+    /// @returns true if @p ident is not a ast::TemplatedIdentifier.
+    bool CheckNotTemplated(const char* use, const ast::Identifier* ident);
+
     /// Raises an error diagnostic that the resolved identifier @p resolved was not of the expected
     /// kind.
     /// @param source the source of the error diagnostic