From 157de6e370255213f8401cfdd277835d5d2a62e8 Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Wed, 25 Feb 2026 15:50:04 -0500 Subject: [PATCH] Support dependent non-type template parameters (#6791) https://github.com/carbon-language/carbon-lang/issues/6717 --- toolchain/check/cpp/call.cpp | 51 +++++++++++++++-- .../cpp/template/non_type_param.carbon | 57 ++++++++++++++++--- 2 files changed, 93 insertions(+), 15 deletions(-) diff --git a/toolchain/check/cpp/call.cpp b/toolchain/check/cpp/call.cpp index 2ad80c912553..26284b3f2185 100644 --- a/toolchain/check/cpp/call.cpp +++ b/toolchain/check/cpp/call.cpp @@ -5,6 +5,7 @@ #include "toolchain/check/cpp/call.h" #include "clang/Sema/Sema.h" +#include "clang/Sema/Template.h" #include "toolchain/base/kind_switch.h" #include "toolchain/check/call.h" #include "toolchain/check/cpp/import.h" @@ -75,9 +76,10 @@ static auto MakePlaceholderTemplateArg(Context& context, SemIR::InstId arg_id) // 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. -static auto ConvertArgToTemplateArg(Context& context, - const clang::NamedDecl* param_decl, - SemIR::InstId arg_id) +static auto ConvertArgToTemplateArg( + Context& context, clang::TemplateDecl* template_decl, + clang::NamedDecl* param_decl, SemIR::InstId arg_id, + clang::SmallVector* template_args) -> std::optional { if (isa(param_decl)) { auto type = ExprAsType(context, SemIR::LocId(arg_id), arg_id); @@ -114,10 +116,44 @@ static auto ConvertArgToTemplateArg(Context& context, return MakePlaceholderTemplateArg(context, arg_id); } - if (const auto* non_type = - dyn_cast(param_decl)) { + if (auto* non_type = dyn_cast(param_decl)) { auto param_type = non_type->getType(); + // Handle non-type parameters with a dependent type. For example: + // + // C++: template struct S{}; + // Carbon: Cpp.S(i32, 42) + // + // When evaluating the second template argument, the generic type of + // `T` should be substituted with `i32`. + if (param_type->isInstantiationDependentType()) { + clang::Sema::InstantiatingTemplate inst( + context.clang_sema(), clang::SourceLocation(), param_decl, non_type, + *template_args, clang::SourceRange()); + if (inst.isInvalid()) { + return std::nullopt; + } + clang::MultiLevelTemplateArgumentList mltal(template_decl, *template_args, + /*Final=*/true); + + mltal.addOuterRetainedLevels(non_type->getDepth()); + // TODO: handle pack expansion by passing in the pack index from + // `ConvertArgsToTemplateArgs`. + if (!param_type->getAs()) { + param_type = context.clang_sema().SubstType(param_type, mltal, + non_type->getLocation(), + non_type->getDeclName()); + } + + if (!param_type.isNull()) { + param_type = context.clang_sema().CheckNonTypeTemplateParameterType( + param_type, non_type->getLocation()); + } + if (param_type.isNull()) { + return std::nullopt; + } + } + // Handle integer parameters. if (param_type->isIntegerType()) { // Get the Carbon type corresponding to the parameter's Clang type. @@ -172,6 +208,7 @@ static auto ConvertArgsToTemplateArgs(Context& context, llvm::ArrayRef arg_ids, clang::TemplateArgumentListInfo& arg_list) -> bool { + clang::SmallVector template_args; for (auto* param_decl : template_decl->getTemplateParameters()->asArray()) { if (arg_ids.empty()) { return true; @@ -185,8 +222,10 @@ 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, param_decl, arg_id)) { + if (auto arg = ConvertArgToTemplateArg(context, template_decl, param_decl, + arg_id, &template_args)) { arg_list.addArgument(*arg); + template_args.push_back(arg->getArgument()); } else { return false; } diff --git a/toolchain/check/testdata/interop/cpp/template/non_type_param.carbon b/toolchain/check/testdata/interop/cpp/template/non_type_param.carbon index 29c1487222aa..194d59e5ea3f 100644 --- a/toolchain/check/testdata/interop/cpp/template/non_type_param.carbon +++ b/toolchain/check/testdata/interop/cpp/template/non_type_param.carbon @@ -20,6 +20,9 @@ constexpr int Constant123 = 123; template struct TwoNonType {}; template struct DependentNonType {}; +DependentNonType MakeDependentNonType123() { + return DependentNonType(); +} // --- valid_int_param.carbon @@ -91,11 +94,7 @@ import Cpp library "templates.h"; var n: i32; //@dump-sem-ir-begin -// CHECK:STDERR: fail_todo_dependent.carbon:[[@LINE+4]]:34: error: semantics TODO: `unsupported argument type for non-type template parameter` [SemanticsTodo] -// CHECK:STDERR: var x: Cpp.DependentNonType(i32, 1); -// CHECK:STDERR: ^ -// CHECK:STDERR: -var x: Cpp.DependentNonType(i32, 1); +var x: Cpp.DependentNonType(i32, 123) = Cpp.MakeDependentNonType123(); // CHECK:STDERR: fail_todo_dependent.carbon:[[@LINE+4]]:35: error: semantics TODO: `unsupported argument type for non-type template parameter` [SemanticsTodo] // CHECK:STDERR: var y: Cpp.DependentNonType(i32*, &n); @@ -154,13 +153,41 @@ var x: Cpp.TwoNonType(1, &n); // CHECK:STDOUT: // CHECK:STDOUT: --- fail_todo_dependent.carbon // CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %DependentNonType.type: type = cpp_type_template_type DependentNonType [concrete] +// CHECK:STDOUT: %DependentNonType.template: %DependentNonType.type = struct_value () [concrete] +// CHECK:STDOUT: %DependentNonType: type = class_type @DependentNonType [concrete] +// CHECK:STDOUT: %pattern_type.b95: type = pattern_type %DependentNonType [concrete] +// CHECK:STDOUT: %MakeDependentNonType123.cpp_overload_set.type: type = cpp_overload_set_type @MakeDependentNonType123.cpp_overload_set [concrete] +// CHECK:STDOUT: %MakeDependentNonType123.cpp_overload_set.value: %MakeDependentNonType123.cpp_overload_set.type = cpp_overload_set_value @MakeDependentNonType123.cpp_overload_set [concrete] +// CHECK:STDOUT: %ptr.5b4: type = ptr_type %DependentNonType [concrete] +// CHECK:STDOUT: %MakeDependentNonType123__carbon_thunk.type: type = fn_type @MakeDependentNonType123__carbon_thunk [concrete] +// CHECK:STDOUT: %MakeDependentNonType123__carbon_thunk: %MakeDependentNonType123__carbon_thunk.type = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Cpp: = namespace file.%Cpp.import_cpp, [concrete] { +// CHECK:STDOUT: .DependentNonType = %DependentNonType.template +// CHECK:STDOUT: .MakeDependentNonType123 = %MakeDependentNonType123.cpp_overload_set.value +// CHECK:STDOUT: import Cpp//... +// CHECK:STDOUT: } +// CHECK:STDOUT: %DependentNonType.template: %DependentNonType.type = struct_value () [concrete = constants.%DependentNonType.template] +// CHECK:STDOUT: %MakeDependentNonType123.cpp_overload_set.value: %MakeDependentNonType123.cpp_overload_set.type = cpp_overload_set_value @MakeDependentNonType123.cpp_overload_set [concrete = constants.%MakeDependentNonType123.cpp_overload_set.value] +// CHECK:STDOUT: %MakeDependentNonType123__carbon_thunk.decl: %MakeDependentNonType123__carbon_thunk.type = fn_decl @MakeDependentNonType123__carbon_thunk [concrete = constants.%MakeDependentNonType123__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: file { // CHECK:STDOUT: name_binding_decl { -// CHECK:STDOUT: %x.patt: = ref_binding_pattern x [concrete] -// CHECK:STDOUT: %x.var_patt: = var_pattern %x.patt [concrete] +// CHECK:STDOUT: %x.patt: %pattern_type.b95 = ref_binding_pattern x [concrete] +// CHECK:STDOUT: %x.var_patt: %pattern_type.b95 = var_pattern %x.patt [concrete] // CHECK:STDOUT: } -// CHECK:STDOUT: %x.var: ref = var %x.var_patt [concrete = ] -// CHECK:STDOUT: %x: ref = ref_binding x, [concrete = ] +// CHECK:STDOUT: %x.var: ref %DependentNonType = var %x.var_patt [concrete] +// CHECK:STDOUT: %x: ref %DependentNonType = ref_binding x, %x.var [concrete = %x.var] // CHECK:STDOUT: name_binding_decl { // CHECK:STDOUT: %y.patt: = ref_binding_pattern y [concrete] // CHECK:STDOUT: %y.var_patt: = var_pattern %y.patt [concrete] @@ -169,3 +196,15 @@ var x: Cpp.TwoNonType(1, &n); // CHECK:STDOUT: %y: ref = ref_binding y, [concrete = ] // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Cpp.ref: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] +// CHECK:STDOUT: %MakeDependentNonType123.ref: %MakeDependentNonType123.cpp_overload_set.type = name_ref MakeDependentNonType123, imports.%MakeDependentNonType123.cpp_overload_set.value [concrete = constants.%MakeDependentNonType123.cpp_overload_set.value] +// CHECK:STDOUT: %.loc9_1: ref %DependentNonType = splice_block file.%x.var [concrete = file.%x.var] {} +// CHECK:STDOUT: %addr: %ptr.5b4 = addr_of %.loc9_1 +// CHECK:STDOUT: %MakeDependentNonType123__carbon_thunk.call: init %empty_tuple.type = call imports.%MakeDependentNonType123__carbon_thunk.decl(%addr) +// CHECK:STDOUT: %.loc9_69: init %DependentNonType to %.loc9_1 = mark_in_place_init %MakeDependentNonType123__carbon_thunk.call +// CHECK:STDOUT: assign file.%x.var, %.loc9_69 +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: