Interop: support all C++ integer types that map to intN_t or uintN_t. (#5836)

Expand support for `int` and `short` to cover all the other `intN_t` and
`uintN_t` types too. We achieve this by asking Clang what the `intN_t` /
`uintN_t` type that it would use for the given bitwidth is, and checking
if that's the type we're trying to map.
This commit is contained in:
Richard Smith
2025-07-22 21:29:09 +00:00
committed by GitHub
parent b01767a5e4
commit c90c6728fd
5 changed files with 174 additions and 38 deletions
+18 -21
View File
@@ -559,32 +559,29 @@ static auto ImportCXXRecordDecl(Context& context, SemIR::LocId loc_id,
}
// Creates an integer type of the given size.
static auto MakeIntType(Context& context, IntId size_id) -> TypeExpr {
// TODO: Fill in a location for the type once available.
auto type_inst_id = MakeIntTypeLiteral(context, Parse::NodeId::None,
SemIR::IntKind::Signed, size_id);
static auto MakeIntType(Context& context, IntId size_id, bool is_signed)
-> TypeExpr {
auto type_inst_id = MakeIntTypeLiteral(
context, Parse::NodeId::None,
is_signed ? SemIR::IntKind::Signed : SemIR::IntKind::Unsigned, size_id);
return ExprAsType(context, Parse::NodeId::None, type_inst_id);
}
// Maps a C++ builtin type to a Carbon type.
// TODO: Support more builtin types.
static auto MapBuiltinType(Context& context, const clang::BuiltinType& type)
-> TypeExpr {
// TODO: Refactor to avoid duplication.
switch (type.getKind()) {
case clang::BuiltinType::Short:
if (context.ast_context().getTypeSize(&type) == 16) {
return MakeIntType(context, context.ints().Add(16));
}
break;
case clang::BuiltinType::Int:
if (context.ast_context().getTypeSize(&type) == 32) {
return MakeIntType(context, context.ints().Add(32));
}
break;
default:
break;
static auto MapBuiltinType(Context& context, clang::QualType qual_type,
const clang::BuiltinType& type) -> TypeExpr {
if (type.isInteger()) {
auto width = context.ast_context().getIntWidth(qual_type);
bool is_signed = type.isSignedInteger();
auto int_n_type =
context.ast_context().getIntTypeForBitwidth(width, is_signed);
if (context.ast_context().hasSameType(qual_type, int_n_type)) {
return MakeIntType(context, context.ints().Add(width), is_signed);
}
// TODO: Handle integer types that map to named aliases.
}
return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
}
@@ -615,7 +612,7 @@ static auto MapRecordType(Context& context, SemIR::LocId loc_id,
static auto MapNonWrapperType(Context& context, SemIR::LocId loc_id,
clang::QualType type) -> TypeExpr {
if (const auto* builtin_type = type->getAs<clang::BuiltinType>()) {
return MapBuiltinType(context, *builtin_type);
return MapBuiltinType(context, type, *builtin_type);
}
if (const auto* record_type = type->getAs<clang::RecordType>()) {
@@ -0,0 +1,39 @@
// 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/uint.carbon
// EXTRA-ARGS: --target=x86_64-linux-gnu
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/fail_todo_arithmetic_types_unmapped.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/fail_todo_arithmetic_types_unmapped.carbon
// Ensure we get a diagnostic when using a type that doesn't yet have a mapping
// from C++ to Carbon.
// --- u64_types.h
unsigned long GetUL();
unsigned long long GetULL();
// --- fail_todo_use_u64_types.carbon
import Cpp library "u64_types.h";
fn CallGetUL() -> u64 { return Cpp.GetUL(); }
// TODO: Eventually, `unsigned long long` should map to
// `Cpp.unsigned_long_long`, which should implicitly convert to `u64`.
// We should switch to testing a different type when that case works.
// CHECK:STDERR: fail_todo_use_u64_types.carbon:[[@LINE+7]]:33: error: semantics TODO: `Unsupported: return type: unsigned long long` [SemanticsTodo]
// CHECK:STDERR: fn CallGetULL() -> u64 { return Cpp.GetULL(); }
// CHECK:STDERR: ^~~~~~~~~~
// CHECK:STDERR: fail_todo_use_u64_types.carbon:[[@LINE+4]]:33: note: in `Cpp` name lookup for `GetULL` [InCppNameLookup]
// CHECK:STDERR: fn CallGetULL() -> u64 { return Cpp.GetULL(); }
// CHECK:STDERR: ^~~~~~~~~~
// CHECK:STDERR:
fn CallGetULL() -> u64 { return Cpp.GetULL(); }
@@ -10,6 +10,11 @@
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/arithmetic_types_bridged.carbon
// Tests for the case where we need a thunk when calling a C++ function from
// Carbon, because the parameter and return types might have a non-trivial
// calling convention.
// TODO: Create thunks for these cases.
// ============================================================================
// short param
// ============================================================================
@@ -2,13 +2,17 @@
// 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/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/arithmetic_types_mapped.carbon
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/function/arithmetic_types_direct.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/arithmetic_types_mapped.carbon
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/arithmetic_types_direct.carbon
// Tests for the case where we can directly call a C++ function from Carbon,
// because the parameter and return types are known to have a sufficiently
// trivial calling convention.
// ============================================================================
// int param
@@ -102,6 +106,52 @@ fn F() {
//@dump-sem-ir-end
}
// ============================================================================
// unsigned int param
// ============================================================================
// --- unsigned_int_param.h
auto foo(unsigned int a) -> void;
// --- import_unsigned_int_param.carbon
library "[[@TEST_NAME]]";
import Cpp library "unsigned_int_param.h";
fn F() {
//@dump-sem-ir-begin
Cpp.foo(1);
//@dump-sem-ir-end
}
// --- fail_import_unsigned_int_param_overflow.carbon
library "[[@TEST_NAME]]";
import Cpp library "unsigned_int_param.h";
fn F() {
Cpp.foo(0);
// CHECK:STDERR: fail_import_unsigned_int_param_overflow.carbon:[[@LINE+5]]:11: error: negative integer value -1 converted to unsigned type `u32` [NegativeIntInUnsignedType]
// CHECK:STDERR: Cpp.foo(-1);
// CHECK:STDERR: ^~
// CHECK:STDERR: fail_import_unsigned_int_param_overflow.carbon: note: initializing function parameter [InCallToFunctionParam]
// CHECK:STDERR:
Cpp.foo(-1);
Cpp.foo(0x0_FFFF_FFFF);
// CHECK:STDERR: fail_import_unsigned_int_param_overflow.carbon:[[@LINE+5]]:11: error: integer value 4294967296 too large for type `u32` [IntTooLargeForType]
// CHECK:STDERR: Cpp.foo(0x1_0000_0000);
// CHECK:STDERR: ^~~~~~~~~~~~~
// CHECK:STDERR: fail_import_unsigned_int_param_overflow.carbon: note: initializing function parameter [InCallToFunctionParam]
// CHECK:STDERR:
Cpp.foo(0x1_0000_0000);
}
// ============================================================================
// signed param
// ============================================================================
@@ -585,6 +635,61 @@ fn F() {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- import_unsigned_int_param.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete]
// CHECK:STDOUT: %u32: type = class_type @UInt, @UInt(%int_32) [concrete]
// CHECK:STDOUT: %foo.type: type = fn_type @foo [concrete]
// CHECK:STDOUT: %foo: %foo.type = struct_value () [concrete]
// CHECK:STDOUT: %int_1.5b8: Core.IntLiteral = int_value 1 [concrete]
// CHECK:STDOUT: %ImplicitAs.type.9b4: type = facet_type <@ImplicitAs, @ImplicitAs(%u32)> [concrete]
// CHECK:STDOUT: %ImplicitAs.Convert.type.92a: type = fn_type @ImplicitAs.Convert, @ImplicitAs(%u32) [concrete]
// CHECK:STDOUT: %To: Core.IntLiteral = bind_symbolic_name To, 0 [symbolic]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.30e: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.d1a: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.30e = struct_value () [symbolic]
// CHECK:STDOUT: %ImplicitAs.impl_witness.ac5: <witness> = impl_witness imports.%ImplicitAs.impl_witness_table.bb8, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.feb: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.319: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.feb = struct_value () [concrete]
// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.9b4 = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.ac5) [concrete]
// CHECK:STDOUT: %.bd1: type = fn_type_with_self_type %ImplicitAs.Convert.type.92a, %ImplicitAs.facet [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.319 [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.319, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete]
// CHECK:STDOUT: %bound_method: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
// CHECK:STDOUT: %int_1.c1d: %u32 = int_value 1 [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
// CHECK:STDOUT: .foo = %foo.decl
// CHECK:STDOUT: import Cpp//...
// CHECK:STDOUT: }
// CHECK:STDOUT: %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: } {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import_ref.c3d: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.30e) = import_ref Core//prelude/parts/uint, loc16_40, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.d1a)]
// CHECK:STDOUT: %ImplicitAs.impl_witness_table.bb8 = impl_witness_table (%Core.import_ref.c3d), @Core.IntLiteral.as.ImplicitAs.impl [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [concrete = constants.%int_1.5b8]
// CHECK:STDOUT: %impl.elem0: %.bd1 = impl_witness_access constants.%ImplicitAs.impl_witness.ac5, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.319]
// CHECK:STDOUT: %bound_method.loc8_11.1: <bound method> = bound_method %int_1, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
// CHECK:STDOUT: %specific_fn: <specific function> = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
// CHECK:STDOUT: %bound_method.loc8_11.2: <bound method> = bound_method %int_1, %specific_fn [concrete = constants.%bound_method]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %u32 = call %bound_method.loc8_11.2(%int_1) [concrete = constants.%int_1.c1d]
// CHECK:STDOUT: %.loc8_11.1: %u32 = value_of_initializer %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_1.c1d]
// CHECK:STDOUT: %.loc8_11.2: %u32 = converted %int_1, %.loc8_11.1 [concrete = constants.%int_1.c1d]
// CHECK:STDOUT: %foo.call: init %empty_tuple.type = call %foo.ref(%.loc8_11.2)
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- import_signed_param.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
+4 -14
View File
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/uint.carbon
// EXTRA-ARGS: --target=x86_64-linux-gnu
//
// AUTOUPDATE
// TIP: To test this file alone, run:
@@ -18,7 +19,6 @@
#include <stddef.h>
ptrdiff_t GetSize();
inline int GetSizeAsInt() { return GetSize(); }
// --- import_stddef_indirectly.carbon
@@ -26,24 +26,14 @@ library "[[@TEST_NAME]]";
import Cpp library "include_stddef.h";
// TODO: `fn CallGetSize() -> Cpp.ptrdiff_t {`
fn CallGetSize() -> i32 {
// TODO: Call `Cpp.GetSize` directly once we can import the types `long` and `long long`.
return Cpp.GetSizeAsInt() as i32;
fn CallGetSize() -> Cpp.ptrdiff_t {
return Cpp.GetSize();
}
// --- fail_todo_import_stddef_directly.carbon
// --- import_stddef_directly.carbon
library "[[@TEST_NAME]]";
import Cpp library "stddef.h";
// TODO: Once we can import `unsigned long` / `unsigned long long`, this should work.
// CHECK:STDERR: fail_todo_import_stddef_directly.carbon:[[@LINE+7]]:8: error: semantics TODO: `Unsupported: Type declaration: size_t` [SemanticsTodo]
// CHECK:STDERR: var n: Cpp.size_t = 42;
// CHECK:STDERR: ^~~~~~~~~~
// CHECK:STDERR: fail_todo_import_stddef_directly.carbon:[[@LINE+4]]:8: note: in `Cpp` name lookup for `size_t` [InCppNameLookup]
// CHECK:STDERR: var n: Cpp.size_t = 42;
// CHECK:STDERR: ^~~~~~~~~~
// CHECK:STDERR:
var n: Cpp.size_t = 42;