From 2e65d28a16b68f8ab7cb794607e138b8b34fe568 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 16 Dec 2025 11:00:49 -0800 Subject: [PATCH] Support for passing C++ templates as arguments to template template parameters. (#6475) Allow passing a C++ template as a template template argument to another C++ template. Does not allow passing a Carbon generic as an argument. Depends on #6474. --- toolchain/check/cpp/call.cpp | 52 ++++++++++--- .../template/template_template_param.carbon | 73 ++++++++++++++++--- 2 files changed, 104 insertions(+), 21 deletions(-) diff --git a/toolchain/check/cpp/call.cpp b/toolchain/check/cpp/call.cpp index 33ac0c9b1414..23bbc5a94c1e 100644 --- a/toolchain/check/cpp/call.cpp +++ b/toolchain/check/cpp/call.cpp @@ -48,6 +48,29 @@ auto PerformCallToCppFunction(Context& context, SemIR::LocId loc_id, } } +// Synthesize a placeholder `void{}` template argument, that will never be a +// valid argument for any template parameter. This is used in order to get Clang +// to diagnose invalid template argument errors for us. The location of the +// Carbon expression is used as the location of the C++ expression, so +// Clang's diagnostics will point into the Carbon code. +// +// TODO: If Clang ever tries to print the type of the expression or to +// pretty-print the expression itself, it would print the wrong thing. Currently +// this doesn't appear to happen, but in principle it could. Ideally we'd add an +// extension point to Clang to represent a "foreign expression" and use it here +// instead of creating a bogus placeholder expression. +static auto MakePlaceholderTemplateArg(Context& context, SemIR::InstId arg_id) + -> clang::TemplateArgumentLoc { + auto arg_loc = GetCppLocation(context, SemIR::LocId(arg_id)); + auto void_type = context.ast_context().VoidTy; + auto* arg = new (context.ast_context()) clang::CXXScalarValueInitExpr( + void_type, + context.ast_context().getTrivialTypeSourceInfo(void_type, arg_loc), + arg_loc); + return clang::TemplateArgumentLoc( + clang::TemplateArgument(arg, /*IsCanonical=*/false), arg); +} + // Converts an argument in a call to a C++ template name into a corresponding // clang template argument, given the template parameter it will be matched // against. @@ -72,10 +95,22 @@ static auto ConvertArgToTemplateArg(Context& context, } if (isa(param_decl)) { - // TODO: Check the type of the argument `CppTemplateNameType` and - // convert it to a `clang::TemplateName`. - context.TODO(arg_id, "argument for template template parameter"); - return std::nullopt; + auto inst = context.sem_ir().insts().Get(arg_id); + if (auto template_name_type = + context.types().TryGetAs( + inst.type_id())) { + clang::TemplateName name(cast( + context.clang_decls().Get(template_name_type->decl_id).key.decl)); + return clang::TemplateArgumentLoc( + context.ast_context(), clang::TemplateArgument(name), + /*TemplateKWLoc=*/clang::SourceLocation(), + clang::NestedNameSpecifierLoc(), + GetCppLocation(context, SemIR::LocId(arg_id))); + } + + // TODO: Eventually we should also support passing Carbon generics as + // template template arguments. + return MakePlaceholderTemplateArg(context, arg_id); } if (isa(param_decl)) { @@ -120,14 +155,7 @@ static auto ConvertArgsToTemplateArgs(Context& context, // placeholder template arguments so that Clang will diagnose it for us. for (auto arg_id : arg_ids) { // Synthesize a placeholder `void{}` template argument. - auto arg_loc = GetCppLocation(context, SemIR::LocId(arg_id)); - auto void_type = context.ast_context().VoidTy; - auto* arg = new (context.ast_context()) clang::CXXScalarValueInitExpr( - void_type, - context.ast_context().getTrivialTypeSourceInfo(void_type, arg_loc), - arg_loc); - arg_list.addArgument(clang::TemplateArgumentLoc( - clang::TemplateArgument(arg, /*IsCanonical=*/false), arg)); + arg_list.addArgument(MakePlaceholderTemplateArg(context, arg_id)); } return true; diff --git a/toolchain/check/testdata/interop/cpp/template/template_template_param.carbon b/toolchain/check/testdata/interop/cpp/template/template_template_param.carbon index 6fd106edd1d6..a73e3d0c1086 100644 --- a/toolchain/check/testdata/interop/cpp/template/template_template_param.carbon +++ b/toolchain/check/testdata/interop/cpp/template/template_template_param.carbon @@ -14,37 +14,92 @@ template struct A {}; template struct B {}; +template struct C {}; template typename, template typename> struct TwoTemplates {}; -// --- fail_todo_valid.carbon +// --- valid.carbon library "[[@TEST_NAME]]"; import Cpp library "templates.h"; //@dump-sem-ir-begin -// CHECK:STDERR: fail_todo_valid.carbon:[[@LINE+4]]:25: error: semantics TODO: `argument for template template parameter` [SemanticsTodo] -// CHECK:STDERR: var x: Cpp.TwoTemplates(Cpp.A, Cpp.B); -// CHECK:STDERR: ^~~~~ -// CHECK:STDERR: var x: Cpp.TwoTemplates(Cpp.A, Cpp.B); //@dump-sem-ir-end +// --- fail_template_mismatch.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "templates.h"; + +//@dump-sem-ir-begin +// CHECK:STDERR: fail_template_mismatch.carbon:[[@LINE+15]]:35: error: too few template arguments for class template 'C' [CppInteropParseError] +// CHECK:STDERR: 22 | var x: Cpp.TwoTemplates(Cpp.A, Cpp.C); +// CHECK:STDERR: | ^ +// CHECK:STDERR: fail_template_mismatch.carbon:[[@LINE+12]]:35: note: template template argument has different template parameters than its corresponding template template parameter [CppInteropParseNote] +// CHECK:STDERR: 22 | var x: Cpp.TwoTemplates(Cpp.A, Cpp.C); +// CHECK:STDERR: | ^ +// CHECK:STDERR: fail_template_mismatch.carbon:[[@LINE-9]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./templates.h:6:66: note: previous template template parameter is here [CppInteropParseNote] +// CHECK:STDERR: 6 | template typename, template typename> +// CHECK:STDERR: | ~~~~~~~~~~~~~~~~~~ ^ +// CHECK:STDERR: fail_template_mismatch.carbon:[[@LINE-13]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./templates.h:4:37: note: template is declared here [CppInteropParseNote] +// CHECK:STDERR: 4 | template struct C {}; +// CHECK:STDERR: | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ +// CHECK:STDERR: +var x: Cpp.TwoTemplates(Cpp.A, Cpp.C); +//@dump-sem-ir-end + +// --- fail_type_argument.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "templates.h"; + +class X {} + +// CHECK:STDERR: fail_type_argument.carbon:[[@LINE+4]]:32: error: template argument for template template parameter must be a class template or type alias template [CppInteropParseError] +// CHECK:STDERR: 12 | var x: Cpp.TwoTemplates(Cpp.A, X); +// CHECK:STDERR: | ^ +// CHECK:STDERR: +var x: Cpp.TwoTemplates(Cpp.A, X); + // --- fail_non_type_argument.carbon library "[[@TEST_NAME]]"; import Cpp library "templates.h"; -// CHECK:STDERR: fail_non_type_argument.carbon:[[@LINE+4]]:25: error: semantics TODO: `argument for template template parameter` [SemanticsTodo] -// CHECK:STDERR: var x: Cpp.TwoTemplates(Cpp.A, true); -// CHECK:STDERR: ^~~~~ +// CHECK:STDERR: fail_non_type_argument.carbon:[[@LINE+4]]:32: error: template argument for template template parameter must be a class template or type alias template [CppInteropParseError] +// CHECK:STDERR: 10 | var x: Cpp.TwoTemplates(Cpp.A, true); +// CHECK:STDERR: | ^ // CHECK:STDERR: var x: Cpp.TwoTemplates(Cpp.A, true); -// CHECK:STDOUT: --- fail_todo_valid.carbon +// CHECK:STDOUT: --- valid.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %TwoTemplates: type = class_type @TwoTemplates [concrete] +// CHECK:STDOUT: %pattern_type: type = pattern_type %TwoTemplates [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %x.patt: %pattern_type = ref_binding_pattern x [concrete] +// CHECK:STDOUT: %x.var_patt: %pattern_type = var_pattern %x.patt [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: %x.var: ref %TwoTemplates = var %x.var_patt [concrete] +// CHECK:STDOUT: %x: ref %TwoTemplates = ref_binding x, %x.var [concrete = %x.var] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_template_mismatch.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: }