From 6df9d5ba327e1880ca6915e5cf50d0c08df35f03 Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Tue, 17 Feb 2026 21:34:06 +0000 Subject: [PATCH] Add initial support for non-type template parameters (#6740) This adds basic support for using templates with integer parameters. https://github.com/carbon-language/carbon-lang/issues/6717 --- toolchain/check/cpp/call.cpp | 49 ++++++++++- .../cpp/template/non_type_param.carbon | 84 +++++++++++++++---- 2 files changed, 112 insertions(+), 21 deletions(-) diff --git a/toolchain/check/cpp/call.cpp b/toolchain/check/cpp/call.cpp index df3eeff9c23a..2ad80c912553 100644 --- a/toolchain/check/cpp/call.cpp +++ b/toolchain/check/cpp/call.cpp @@ -114,10 +114,51 @@ static auto ConvertArgToTemplateArg(Context& context, return MakePlaceholderTemplateArg(context, arg_id); } - if (isa(param_decl)) { - // TODO: Check the argument has a concrete constant value, and convert it to - // a Clang constant value. - context.TODO(arg_id, "argument for non-type template parameter"); + if (const auto* non_type = + dyn_cast(param_decl)) { + auto param_type = non_type->getType(); + + // Handle integer parameters. + if (param_type->isIntegerType()) { + // Get the Carbon type corresponding to the parameter's Clang type. + const auto type_expr = + ImportCppType(context, SemIR::LocId(arg_id), param_type); + + // Try to convert the argument to the parameter type. + const auto converted_inst_id = + Convert(context, SemIR::LocId(arg_id), arg_id, + { + .kind = ConversionTarget::Value, + .type_id = type_expr.type_id, + }); + + if (converted_inst_id == SemIR::ErrorInst::InstId) { + return std::nullopt; + } + + auto const_inst_id = + context.constant_values().GetConstantInstId(converted_inst_id); + if (const_inst_id.has_value()) { + if (auto int_value = + context.insts().TryGetAs(const_inst_id)) { + const auto& ap_int = context.ints().Get(int_value->int_id); + const bool is_unsigned = + !param_type->isSignedIntegerOrEnumerationType(); + auto aps_int = + llvm::APSInt(ap_int, is_unsigned) + .extOrTrunc(context.ast_context().getIntWidth(param_type)); + auto template_arg = clang::TemplateArgument(context.ast_context(), + aps_int, param_type); + // TODO: provide a better location. + auto loc = clang::TemplateArgumentLocInfo(); + return clang::TemplateArgumentLoc(template_arg, loc); + } + } + } + + // TODO: Support other types. + context.TODO(arg_id, + "unsupported argument type for non-type template parameter"); return std::nullopt; } 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 174793edc68f..41a7bf028f30 100644 --- a/toolchain/check/testdata/interop/cpp/template/non_type_param.carbon +++ b/toolchain/check/testdata/interop/cpp/template/non_type_param.carbon @@ -2,7 +2,7 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon // // AUTOUPDATE // TIP: To test this file alone, run: @@ -14,20 +14,62 @@ struct A {}; +template struct Simple {}; +Simple<123> MakeSimple123() { return Simple<123>(); } + template struct TwoNonType {}; template struct DependentNonType {}; -// --- fail_todo_valid.carbon +// --- valid_int_param.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "templates.h"; + +var s: Cpp.Simple(123) = Cpp.MakeSimple123(); + +// --- fail_int_param_mismatch.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "templates.h"; + +// CHECK:STDERR: fail_int_param_mismatch.carbon:[[@LINE+7]]:1: error: cannot implicitly convert expression of type `Cpp.Simple` to `Cpp.Simple` [ConversionFailure] +// CHECK:STDERR: var s: Cpp.Simple(456) = Cpp.MakeSimple123(); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_int_param_mismatch.carbon:[[@LINE+4]]:1: note: type `Cpp.Simple` does not implement interface `Core.ImplicitAs(Cpp.Simple)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: var s: Cpp.Simple(456) = Cpp.MakeSimple123(); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +var s: Cpp.Simple(456) = Cpp.MakeSimple123(); + +// --- nonliteral.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "templates.h"; + +var s: Cpp.Simple(100 + 20 + 3) = Cpp.MakeSimple123(); + +// --- conversion.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "templates.h"; + +class C { adapt {}; } +impl C as Core.ImplicitAs(i32) { + eval fn Convert[self: Self]() -> i32 { return 123; } +} +var s: Cpp.Simple({} as C) = Cpp.MakeSimple123(); + +// --- valid.carbon library "[[@TEST_NAME]]"; import Cpp library "templates.h"; //@dump-sem-ir-begin -// CHECK:STDERR: fail_todo_valid.carbon:[[@LINE+4]]:23: error: semantics TODO: `argument for non-type template parameter` [SemanticsTodo] -// CHECK:STDERR: var x: Cpp.TwoNonType(1, 2); -// CHECK:STDERR: ^ -// CHECK:STDERR: var x: Cpp.TwoNonType(1, 2); //@dump-sem-ir-end @@ -40,13 +82,13 @@ import Cpp library "templates.h"; var n: i32; //@dump-sem-ir-begin -// CHECK:STDERR: fail_todo_dependent.carbon:[[@LINE+4]]:34: error: semantics TODO: `argument for non-type template parameter` [SemanticsTodo] +// 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); -// CHECK:STDERR: fail_todo_dependent.carbon:[[@LINE+4]]:35: error: semantics TODO: `argument for non-type template parameter` [SemanticsTodo] +// 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); // CHECK:STDERR: ^~ // CHECK:STDERR: @@ -59,9 +101,12 @@ library "[[@TEST_NAME]]"; import Cpp library "templates.h"; -// CHECK:STDERR: fail_type_argument.carbon:[[@LINE+4]]:23: error: semantics TODO: `argument for non-type template parameter` [SemanticsTodo] +// CHECK:STDERR: fail_type_argument.carbon:[[@LINE+7]]:26: error: cannot implicitly convert expression of type `type` to `i32` [ConversionFailure] // CHECK:STDERR: var x: Cpp.TwoNonType(1, Cpp.A); -// CHECK:STDERR: ^ +// CHECK:STDERR: ^~~~~ +// CHECK:STDERR: fail_type_argument.carbon:[[@LINE+4]]:26: note: type `type` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: var x: Cpp.TwoNonType(1, Cpp.A); +// CHECK:STDERR: ^~~~~ // CHECK:STDERR: var x: Cpp.TwoNonType(1, Cpp.A); @@ -73,15 +118,20 @@ import Cpp library "templates.h"; var n: i32; -// CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+4]]:23: error: semantics TODO: `argument for non-type template parameter` [SemanticsTodo] +// CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+7]]:26: error: cannot implicitly convert expression of type `i32*` to `i32` [ConversionFailure] // CHECK:STDERR: var x: Cpp.TwoNonType(1, &n); -// CHECK:STDERR: ^ +// CHECK:STDERR: ^~ +// CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+4]]:26: note: type `i32*` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: var x: Cpp.TwoNonType(1, &n); +// CHECK:STDERR: ^~ // CHECK:STDERR: var x: Cpp.TwoNonType(1, &n); -// CHECK:STDOUT: --- fail_todo_valid.carbon +// CHECK:STDOUT: --- valid.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { +// CHECK:STDOUT: %TwoNonType: type = class_type @TwoNonType [concrete] +// CHECK:STDOUT: %pattern_type.8a6: type = pattern_type %TwoNonType [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -89,11 +139,11 @@ var x: Cpp.TwoNonType(1, &n); // 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.8a6 = ref_binding_pattern x [concrete] +// CHECK:STDOUT: %x.var_patt: %pattern_type.8a6 = 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 %TwoNonType = var %x.var_patt [concrete] +// CHECK:STDOUT: %x: ref %TwoNonType = ref_binding x, %x.var [concrete = %x.var] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: --- fail_todo_dependent.carbon