Kaynağa Gözat

Support calling functions with explicit template arguments. (#6814)

Treat the initial sequence ofarguments in a call to a C++ function up to
and including the last argument that is a type or template as being the
explicit template arguments for the call, rather than rejecting them
because they can't be converted to the parameter types.

Implements the current direction on leads issue #6768, except that no
syntax for explicitly annotating an argument as being a template
argument is provided.

---------

Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
Richard Smith 2 ay önce
ebeveyn
işleme
15680ba101

+ 7 - 5
toolchain/check/convert.cpp

@@ -2112,10 +2112,12 @@ auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id)
 }
 
 auto ConvertToValueOfType(Context& context, SemIR::LocId loc_id,
-                          SemIR::InstId expr_id, SemIR::TypeId type_id)
-    -> SemIR::InstId {
+                          SemIR::InstId expr_id, SemIR::TypeId type_id,
+                          bool diagnose) -> SemIR::InstId {
   return Convert(context, loc_id, expr_id,
-                 {.kind = ConversionTarget::Value, .type_id = type_id});
+                 {.kind = ConversionTarget::Value,
+                  .type_id = type_id,
+                  .diagnose = diagnose});
 }
 
 auto ConvertToValueOrRefOfType(Context& context, SemIR::LocId loc_id,
@@ -2198,8 +2200,8 @@ static auto DiagnoseTypeExprEvaluationFailure(Context& context,
 
 auto ExprAsType(Context& context, SemIR::LocId loc_id, SemIR::InstId value_id,
                 bool diagnose) -> TypeExpr {
-  auto type_as_inst_id =
-      ConvertToValueOfType(context, loc_id, value_id, SemIR::TypeType::TypeId);
+  auto type_as_inst_id = ConvertToValueOfType(
+      context, loc_id, value_id, SemIR::TypeType::TypeId, diagnose);
   if (type_as_inst_id == SemIR::ErrorInst::InstId) {
     return {.inst_id = SemIR::ErrorInst::TypeInstId,
             .type_id = SemIR::ErrorInst::TypeId};

+ 13 - 5
toolchain/check/convert.h

@@ -70,8 +70,9 @@ struct ConversionTarget {
   // If this is not null or empty, its last element must be storage_id.
   PendingBlock* storage_access_block = nullptr;
   // Whether failure of conversion is an error and is diagnosed to the user.
-  // When looking for a possible conversion but with graceful fallback, diagnose
-  // should be false.
+  // When looking for a possible conversion but with graceful fallback,
+  // `diagnose` should be false. If `diagnose` is false, an `ErrorInst` may be
+  // returned, but it must be discarded.
   bool diagnose = true;
 
   // Are we converting this value into an initializer for an object?
@@ -117,9 +118,14 @@ auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id)
     -> SemIR::InstId;
 
 // Converts `expr_id` to a value expression of type `type_id`.
+//
+// If `diagnose` is true, errors are diagnosed to the user. Set it to false when
+// looking to see if a conversion is possible but with graceful fallback. If
+// `diagnose` is false, an `ErrorInst` may be returned, but it must be
+// discarded.
 auto ConvertToValueOfType(Context& context, SemIR::LocId loc_id,
-                          SemIR::InstId expr_id, SemIR::TypeId type_id)
-    -> SemIR::InstId;
+                          SemIR::InstId expr_id, SemIR::TypeId type_id,
+                          bool diagnose = true) -> SemIR::InstId;
 
 // Convert the given expression to a value or reference expression of the given
 // type.
@@ -176,7 +182,9 @@ inline constexpr TypeExpr TypeExpr::None = {.inst_id = SemIR::TypeInstId::None,
 // Converts an expression for use as a type.
 //
 // If `diagnose` is true, errors are diagnosed to the user. Set it to false when
-// looking to see if a conversion is possible but with graceful fallback.
+// looking to see if a conversion is possible but with graceful fallback. If
+// `diagnose` is false, an `ErrorInst` may be returned, but it must be
+// discarded.
 //
 // TODO: Most of the callers of this function discard the `inst_id` and lose
 // track of the conversion. In most cases we should be retaining that as the

+ 63 - 19
toolchain/check/cpp/call.cpp

@@ -20,13 +20,46 @@
 
 namespace Carbon::Check {
 
+// Returns true if the given instruction can only be a template argument, and
+// not a function argument. We classify arguments as definitely being template
+// arguments if they are types or the name of a template or generic.
+// TODO: We should also have a way to specify that an argument is a non-type
+// template argument.
+static auto IsTemplateArg(Context& context, SemIR::InstId arg_id) -> bool {
+  auto arg_type_id = context.insts().Get(arg_id).type_id();
+  auto arg_type = context.types().GetAsInst(arg_type_id);
+  return arg_type
+      .IsOneOf<SemIR::TypeType, SemIR::FacetType, SemIR::CppTemplateNameType,
+               SemIR::GenericClassType, SemIR::GenericInterfaceType,
+               SemIR::GenericNamedConstraintType>();
+}
+
+// Splits a call argument list into a list of template arguments followed by a
+// list of function arguments. We split the argument list as early as possible,
+// subject to the constraint that if an argument is a template argument, it goes
+// in the template argument list.
+static auto SplitCallArgumentList(Context& context,
+                                  llvm::ArrayRef<SemIR::InstId> arg_ids)
+    -> std::pair<llvm::ArrayRef<SemIR::InstId>, llvm::ArrayRef<SemIR::InstId>> {
+  for (auto [n, arg_id] : llvm::enumerate(llvm::reverse(arg_ids))) {
+    if (IsTemplateArg(context, arg_id)) {
+      return {arg_ids.drop_back(n), arg_ids.take_back(n)};
+    }
+  }
+  // No template arguments found.
+  return {{}, arg_ids};
+}
+
 auto PerformCallToCppFunction(Context& context, SemIR::LocId loc_id,
                               SemIR::CppOverloadSetId overload_set_id,
                               SemIR::InstId self_id,
                               llvm::ArrayRef<SemIR::InstId> arg_ids,
                               bool is_operator_syntax) -> SemIR::InstId {
-  SemIR::InstId callee_id = PerformCppOverloadResolution(
-      context, loc_id, overload_set_id, self_id, arg_ids);
+  auto [template_arg_ids, function_arg_ids] =
+      SplitCallArgumentList(context, arg_ids);
+  auto callee_id =
+      PerformCppOverloadResolution(context, loc_id, overload_set_id,
+                                   template_arg_ids, self_id, function_arg_ids);
   SemIR::Callee callee = GetCallee(context.sem_ir(), callee_id);
   CARBON_KIND_SWITCH(callee) {
     case CARBON_KIND(SemIR::CalleeError _): {
@@ -38,8 +71,8 @@ auto PerformCallToCppFunction(Context& context, SemIR::LocId loc_id,
         // Preserve the `self` argument from the original callee.
         fn.self_id = self_id;
       }
-      return PerformCallToFunction(context, loc_id, callee_id, fn, arg_ids,
-                                   is_operator_syntax);
+      return PerformCallToFunction(context, loc_id, callee_id, fn,
+                                   function_arg_ids, is_operator_syntax);
     }
     case CARBON_KIND(SemIR::CalleeCppOverloadSet _): {
       CARBON_FATAL("overloads can't be recursive");
@@ -79,16 +112,18 @@ static auto MakePlaceholderTemplateArg(Context& context, SemIR::InstId arg_id)
 static auto ConvertArgToTemplateArg(
     Context& context, clang::TemplateDecl* template_decl,
     clang::NamedDecl* param_decl, SemIR::InstId arg_id,
-    clang::SmallVector<clang::TemplateArgument>* template_args)
+    clang::SmallVector<clang::TemplateArgument>* template_args, bool diagnose)
     -> std::optional<clang::TemplateArgumentLoc> {
   if (isa<clang::TemplateTypeParmDecl>(param_decl)) {
-    auto type = ExprAsType(context, SemIR::LocId(arg_id), arg_id);
+    auto type = ExprAsType(context, SemIR::LocId(arg_id), arg_id, diagnose);
     if (type.type_id == SemIR::ErrorInst::TypeId) {
       return std::nullopt;
     }
     auto clang_type = MapToCppType(context, type.type_id);
     if (clang_type.isNull()) {
-      context.TODO(arg_id, "unsupported type used as template argument");
+      if (diagnose) {
+        context.TODO(arg_id, "unsupported type used as template argument");
+      }
       return std::nullopt;
     }
     return clang::TemplateArgumentLoc(
@@ -127,6 +162,13 @@ static auto ConvertArgToTemplateArg(
     // When evaluating the second template argument, the generic type of
     // `T` should be substituted with `i32`.
     if (param_type->isInstantiationDependentType()) {
+      // If we don't want to diagnose errors, create a SFINAE context so that
+      // Clang knows to suppress error messages.
+      std::optional<clang::Sema::SFINAETrap> sfinae;
+      if (!diagnose) {
+        sfinae.emplace(context.clang_sema());
+      }
+
       clang::Sema::InstantiatingTemplate inst(
           context.clang_sema(), clang::SourceLocation(), param_decl, non_type,
           *template_args, clang::SourceRange());
@@ -149,7 +191,7 @@ static auto ConvertArgToTemplateArg(
         param_type = context.clang_sema().CheckNonTypeTemplateParameterType(
             param_type, non_type->getLocation());
       }
-      if (param_type.isNull()) {
+      if (param_type.isNull() || (sfinae && sfinae->hasErrorOccurred())) {
         return std::nullopt;
       }
     }
@@ -164,6 +206,7 @@ static auto ConvertArgToTemplateArg(
                 {
                     .kind = ConversionTarget::Value,
                     .type_id = type_expr.type_id,
+                    .diagnose = diagnose,
                 });
 
     if (converted_inst_id == SemIR::ErrorInst::InstId) {
@@ -221,21 +264,21 @@ static auto ConvertArgToTemplateArg(
     }
 
     // TODO: Support other types.
-    context.TODO(arg_id,
-                 "unsupported argument type for non-type template parameter");
+    if (diagnose) {
+      context.TODO(arg_id,
+                   "unsupported argument type for non-type template parameter");
+    }
     return std::nullopt;
   }
 
   CARBON_FATAL("Unknown declaration kind for template parameter");
 }
 
-// Converts a call argument list into a Clang template argument list for a given
-// template. Returns true on success, or false if an error was diagnosed.
-static auto ConvertArgsToTemplateArgs(Context& context,
-                                      clang::TemplateDecl* template_decl,
-                                      llvm::ArrayRef<SemIR::InstId> arg_ids,
-                                      clang::TemplateArgumentListInfo& arg_list)
-    -> bool {
+auto ConvertArgsToTemplateArgs(Context& context,
+                               clang::TemplateDecl* template_decl,
+                               llvm::ArrayRef<SemIR::InstId> arg_ids,
+                               clang::TemplateArgumentListInfo& arg_list,
+                               bool diagnose) -> bool {
   clang::SmallVector<clang::TemplateArgument> template_args;
   for (auto* param_decl : template_decl->getTemplateParameters()->asArray()) {
     if (arg_ids.empty()) {
@@ -250,8 +293,9 @@ static auto ConvertArgsToTemplateArgs(Context& context,
         param_decl->isTemplateParameterPack() ? std::exchange(arg_ids, {})
                                               : arg_ids.consume_front();
     for (auto arg_id : args_for_param) {
-      if (auto arg = ConvertArgToTemplateArg(context, template_decl, param_decl,
-                                             arg_id, &template_args)) {
+      if (auto arg =
+              ConvertArgToTemplateArg(context, template_decl, param_decl,
+                                      arg_id, &template_args, diagnose)) {
         arg_list.addArgument(*arg);
         template_args.push_back(arg->getArgument());
       } else {

+ 10 - 0
toolchain/check/cpp/call.h

@@ -10,6 +10,16 @@
 
 namespace Carbon::Check {
 
+// Converts a call argument list into a Clang template argument list for a given
+// template. Returns true on success, or false if an error was diagnosed.
+//
+// If `diagnose` is false, errors will be suppressed.
+auto ConvertArgsToTemplateArgs(Context& context,
+                               clang::TemplateDecl* template_decl,
+                               llvm::ArrayRef<SemIR::InstId> arg_ids,
+                               clang::TemplateArgumentListInfo& arg_list,
+                               bool diagnose = true) -> bool;
+
 // Checks and builds SemIR for a call to a C++ function in the given overload
 // set with self `self_id` and arguments `arg_ids`. `is_operator_syntax`
 // indicates that this call was generated from an operator rather than from

+ 37 - 17
toolchain/check/cpp/overload_resolution.cpp

@@ -9,6 +9,7 @@
 #include "clang/Sema/Sema.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/cpp/access.h"
+#include "toolchain/check/cpp/call.h"
 #include "toolchain/check/cpp/import.h"
 #include "toolchain/check/cpp/location.h"
 #include "toolchain/check/cpp/operators.h"
@@ -34,18 +35,37 @@ static auto GetCppName(Context& context, SemIR::NameId name_id)
 }
 
 // Adds the given overload candidates to the candidate set.
-static auto AddOverloadCandidates(clang::Sema& sema,
-                                  clang::OverloadCandidateSet& candidate_set,
-                                  const clang::UnresolvedSet<4>& functions,
-                                  clang::Expr* self_arg,
-                                  llvm::ArrayRef<clang::Expr*> args) -> void {
+static auto AddOverloadCandidates(
+    Context& context, clang::OverloadCandidateSet& candidate_set,
+    const clang::UnresolvedSet<4>& functions,
+    llvm::ArrayRef<SemIR::InstId> template_arg_ids, clang::Expr* self_arg,
+    llvm::ArrayRef<clang::Expr*> args) -> void {
+  clang::Sema& sema = context.clang_sema();
+
   constexpr bool SuppressUserConversions = false;
   constexpr bool PartialOverloading = false;
-  constexpr clang::TemplateArgumentListInfo* ExplicitTemplateArgs = nullptr;
 
   for (auto found_decl : functions.pairs()) {
     auto* decl = found_decl->getUnderlyingDecl();
+
+    // Form an explicit template argument list if needed. Note that this is done
+    // per-candidate, as the conversions performed on the template arguments
+    // differ based on the corresponding template parameters.
     auto* template_decl = dyn_cast<clang::FunctionTemplateDecl>(decl);
+    clang::TemplateArgumentListInfo explicit_template_arg_storage;
+    clang::TemplateArgumentListInfo* explicit_template_args = nullptr;
+    if (!template_arg_ids.empty()) {
+      if (!template_decl) {
+        continue;
+      }
+      if (!ConvertArgsToTemplateArgs(context, template_decl, template_arg_ids,
+                                     explicit_template_arg_storage,
+                                     /*diagnose=*/false)) {
+        continue;
+      }
+      explicit_template_args = &explicit_template_arg_storage;
+    }
+
     auto* fn_decl = template_decl ? template_decl->getTemplatedDecl()
                                   : cast<clang::FunctionDecl>(decl);
     auto* method_decl = dyn_cast<clang::CXXMethodDecl>(fn_decl);
@@ -61,7 +81,7 @@ static auto AddOverloadCandidates(clang::Sema& sema,
         sema.AddMethodTemplateCandidate(
             template_decl, found_decl,
             cast<clang::CXXRecordDecl>(template_decl->getDeclContext()),
-            ExplicitTemplateArgs, self_type, self_classification, args,
+            explicit_template_args, self_type, self_classification, args,
             candidate_set, SuppressUserConversions, PartialOverloading);
       } else if (method_decl->isOverloadedOperator()) {
         sema.AddMemberOperatorCandidates(method_decl->getOverloadedOperator(),
@@ -75,8 +95,8 @@ static auto AddOverloadCandidates(clang::Sema& sema,
       }
     } else if (template_decl) {
       sema.AddTemplateOverloadCandidate(
-          template_decl, found_decl, ExplicitTemplateArgs, args, candidate_set,
-          SuppressUserConversions, PartialOverloading);
+          template_decl, found_decl, explicit_template_args, args,
+          candidate_set, SuppressUserConversions, PartialOverloading);
     } else {
       sema.AddOverloadCandidate(fn_decl, found_decl, args, candidate_set,
                                 SuppressUserConversions, PartialOverloading);
@@ -110,11 +130,11 @@ auto CheckCppOverloadAccess(
                .highest_allowed_access = allowed_access_kind});
 }
 
-auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
-                                  SemIR::CppOverloadSetId overload_set_id,
-                                  SemIR::InstId self_id,
-                                  llvm::ArrayRef<SemIR::InstId> arg_ids)
-    -> SemIR::InstId {
+auto PerformCppOverloadResolution(
+    Context& context, SemIR::LocId loc_id,
+    SemIR::CppOverloadSetId overload_set_id,
+    llvm::ArrayRef<SemIR::InstId> template_arg_ids, SemIR::InstId self_id,
+    llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::InstId {
   // Register an annotation scope to flush any Clang diagnostics when we return.
   // This is important to ensure that Clang diagnostics are properly interleaved
   // with Carbon diagnostics.
@@ -148,12 +168,12 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
           : clang::OverloadCandidateSet::CandidateSetKind::CSK_Normal,
       overload_set.operator_rewrite_info);
 
-  clang::Sema& sema = context.clang_sema();
-
-  AddOverloadCandidates(sema, candidate_set, overload_set.candidate_functions,
+  AddOverloadCandidates(context, candidate_set,
+                        overload_set.candidate_functions, template_arg_ids,
                         self_expr, arg_exprs);
 
   // Find best viable function among the candidates.
+  clang::Sema& sema = context.clang_sema();
   clang::OverloadCandidateSet::iterator best_viable_fn;
   clang::OverloadingResult overloading_result =
       candidate_set.BestViableFunction(sema, loc, best_viable_fn);

+ 9 - 8
toolchain/check/cpp/overload_resolution.h

@@ -20,19 +20,20 @@ auto CheckCppOverloadAccess(
     SemIR::KnownInstId<SemIR::FunctionDecl> overload_inst_id,
     SemIR::NameScopeId parent_scope_id = SemIR::NameScopeId::None) -> void;
 
-// Resolves which function to call using Clang overloading resolution, or
-// returns an error instruction if overload resolution failed.
+// Resolves which function to call using Clang overload resolution. Returns an
+// instruction referring to that function, or an error instruction if overload
+// resolution failed.
 //
 // A set with a single non-templated function goes through the same rules for
-// overloading resolution. This is to make sure that calls that have no viable
+// overload resolution. This is to make sure that calls that have no viable
 // implicit conversion sequence are rejected even when an implicit conversion is
 // possible. Keeping the same behavior here for consistency and supporting
 // migrations so that the migrated callers from C++ remain valid.
-auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
-                                  SemIR::CppOverloadSetId overload_set_id,
-                                  SemIR::InstId self_id,
-                                  llvm::ArrayRef<SemIR::InstId> arg_ids)
-    -> SemIR::InstId;
+auto PerformCppOverloadResolution(
+    Context& context, SemIR::LocId loc_id,
+    SemIR::CppOverloadSetId overload_set_id,
+    llvm::ArrayRef<SemIR::InstId> template_arg_ids, SemIR::InstId self_id,
+    llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::InstId;
 
 }  // namespace Carbon::Check
 

+ 149 - 0
toolchain/check/testdata/interop/cpp/function/template.carbon

@@ -0,0 +1,149 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/primitives.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/function/template.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/template.carbon
+
+// --- deduced_or_explicit.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''
+template<typename T> T min(T a, T b) { return a < b ? a : b; }
+''';
+
+let a: i32 = 1;
+let b: i32 = 2;
+let min_implicit: i32 = Cpp.min(a, b);
+let min_explicit: i32 = Cpp.min(i32, a, b);
+let min_explicit_literal: i32 = Cpp.min(i32, 1, 2);
+
+// --- template_template_arg.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''
+template<typename> struct X;
+template<typename> struct Y;
+template<template<typename> typename... TT, typename... T> void f(T ...a) {}
+''';
+
+fn Call() {
+  let a: i32 = 0;
+  let b: i32 = 0;
+
+  // `a` and `b` are not classified as template arguments, so are passed as the
+  // function parameter pack `a...`.
+  Cpp.f(a, b);
+
+  // `Cpp.X` and `Cpp.Y` are classified as template template arguments, so they
+  // are passed as the template parameter pack `TT...`.
+  Cpp.f(Cpp.X, Cpp.Y);
+
+  // Pass some template arguments followed by some function arguments.
+  Cpp.f(Cpp.X, Cpp.Y, a, b);
+  Cpp.f(Cpp.X, a, b, a);
+}
+
+// --- nontype_template_arg.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''
+template<int... I, typename... T> void f(T ...a) {}
+template<int I = 0, int J = 0, typename = void, typename... T> void g(T ...a) {}
+''';
+
+fn Call() {
+  let a: i32 = 0;
+  let b: i32 = 0;
+
+  Cpp.f(a, b);
+  // TODO: There should be a way to indicate that the arguments are template
+  // arguments, not function arguments, when calling `f`.
+  Cpp.f(1, 2);
+
+  // Two function arguments.
+  Cpp.g(1, 2);
+  // Three template arguments.
+  Cpp.g(1, 2, i32);
+  // Three template arguments and two function arguments.
+  Cpp.g(1, 2, i32, 3, 4);
+}
+
+// --- fail_function_arg_not_template_arg.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''
+template<int N> void f();
+''';
+
+fn Call() {
+  // We treat this as a function argument, even though that makes the call fail.
+  // CHECK:STDERR: fail_function_arg_not_template_arg.carbon:[[@LINE+7]]:10: error: no matching function for call to 'f' [CppInteropParseError]
+  // CHECK:STDERR:    17 |   Cpp.f(1);
+  // CHECK:STDERR:       |          ^
+  // CHECK:STDERR: fail_function_arg_not_template_arg.carbon:[[@LINE-8]]:22: note: candidate function template not viable: requires 0 arguments, but 1 was provided [CppInteropParseNote]
+  // CHECK:STDERR:     5 | template<int N> void f();
+  // CHECK:STDERR:       |                      ^
+  // CHECK:STDERR:
+  Cpp.f(1);
+}
+
+// --- function_arg_not_template_arg_overloaded.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''
+template<int N> void f();
+void f(int n);
+''';
+
+fn Call() {
+  Cpp.f(1);
+}
+
+// --- overloaded_with_different_template_params.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''
+template<typename T> struct X;
+struct Y;
+
+template<int N, typename T> void f();
+template<typename T> void f();
+template<template<typename> typename T> void f();
+''';
+
+fn Call() {
+  Cpp.f(1, i32);
+  Cpp.f(Cpp.X);
+  Cpp.f(Cpp.Y);
+}
+
+// --- only_call_templates_when_given_template_args.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''
+struct NonTemplate {};
+struct Template {};
+
+NonTemplate f(unsigned char, unsigned char);
+template<typename = void> Template f(unsigned char, int = 0);
+''';
+
+// Prefers the non-template because it has a better match for the second parameter.
+let x: Cpp.NonTemplate = Cpp.f(1 as u8, 2 as u8);
+
+// Calls the template; the non-template is not considered due to the template
+// argument `i32`.
+let y: Cpp.Template = Cpp.f(i32, 1 as u8);

+ 27 - 11
toolchain/lower/testdata/interop/cpp/template.carbon

@@ -26,6 +26,10 @@ fn PassI32(a: i32) -> i32 {
   return Cpp.identity(a);
 }
 
+fn PassI32Explicitly(a: i32) -> i32 {
+  return Cpp.identity(i32, a);
+}
+
 fn PassClass(a: Cpp.Class) -> Cpp.Class {
   return Cpp.identity(a);
 }
@@ -135,10 +139,17 @@ fn Call3() {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define void @_CPassClass.Main(ptr sret({}) %return, ptr %a) #2 !dbg !23 {
+// CHECK:STDOUT: define i32 @_CPassI32Explicitly.Main(i32 %a) #2 !dbg !23 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %identity.call = call i32 @_Z8identityIiET_S0_(i32 %a), !dbg !26
+// CHECK:STDOUT:   ret i32 %identity.call, !dbg !27
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CPassClass.Main(ptr sret({}) %return, ptr %a) #2 !dbg !28 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   call void @_Z8identityI5ClassET_S1_.carbon_thunk(ptr %a, ptr %return), !dbg !29
-// CHECK:STDOUT:   ret void, !dbg !30
+// CHECK:STDOUT:   call void @_Z8identityI5ClassET_S1_.carbon_thunk(ptr %a, ptr %return), !dbg !34
+// CHECK:STDOUT:   ret void, !dbg !35
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: attributes #0 = { alwaysinline mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
@@ -172,14 +183,19 @@ fn Call3() {
 // CHECK:STDOUT: !20 = !DILocalVariable(arg: 1, scope: !15, type: !18)
 // CHECK:STDOUT: !21 = !DILocation(line: 7, column: 10, scope: !15)
 // CHECK:STDOUT: !22 = !DILocation(line: 7, column: 3, scope: !15)
-// CHECK:STDOUT: !23 = distinct !DISubprogram(name: "PassClass", linkageName: "_CPassClass.Main", scope: null, file: !7, line: 10, type: !24, spFlags: DISPFlagDefinition, unit: !6, retainedNodes: !27)
-// CHECK:STDOUT: !24 = !DISubroutineType(types: !25)
-// CHECK:STDOUT: !25 = !{!26, !26}
-// CHECK:STDOUT: !26 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 8)
-// CHECK:STDOUT: !27 = !{!28}
-// CHECK:STDOUT: !28 = !DILocalVariable(arg: 1, scope: !23, type: !26)
-// CHECK:STDOUT: !29 = !DILocation(line: 11, column: 10, scope: !23)
-// CHECK:STDOUT: !30 = !DILocation(line: 11, column: 3, scope: !23)
+// CHECK:STDOUT: !23 = distinct !DISubprogram(name: "PassI32Explicitly", linkageName: "_CPassI32Explicitly.Main", scope: null, file: !7, line: 10, type: !16, spFlags: DISPFlagDefinition, unit: !6, retainedNodes: !24)
+// CHECK:STDOUT: !24 = !{!25}
+// CHECK:STDOUT: !25 = !DILocalVariable(arg: 1, scope: !23, type: !18)
+// CHECK:STDOUT: !26 = !DILocation(line: 11, column: 10, scope: !23)
+// CHECK:STDOUT: !27 = !DILocation(line: 11, column: 3, scope: !23)
+// CHECK:STDOUT: !28 = distinct !DISubprogram(name: "PassClass", linkageName: "_CPassClass.Main", scope: null, file: !7, line: 14, type: !29, spFlags: DISPFlagDefinition, unit: !6, retainedNodes: !32)
+// CHECK:STDOUT: !29 = !DISubroutineType(types: !30)
+// CHECK:STDOUT: !30 = !{!31, !31}
+// CHECK:STDOUT: !31 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 8)
+// CHECK:STDOUT: !32 = !{!33}
+// CHECK:STDOUT: !33 = !DILocalVariable(arg: 1, scope: !28, type: !31)
+// CHECK:STDOUT: !34 = !DILocation(line: 15, column: 10, scope: !28)
+// CHECK:STDOUT: !35 = !DILocation(line: 15, column: 3, scope: !28)
 // CHECK:STDOUT: ; ModuleID = 'variadic.carbon'
 // CHECK:STDOUT: source_filename = "variadic.carbon"
 // CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"