Skip to content

Commit ec8c999

Browse files
authored
Support passing Carbon Optional(T*) to C++ T* parameter. (#6422)
We already did this translation in the other direction, but we had no mapping from `Optional(T)` to anything, so round-tripping a nullable pointer from C++ through Carbon and back to C++ was previously rejected.
1 parent 6b775b3 commit ec8c999

File tree

8 files changed

+472
-120
lines changed

8 files changed

+472
-120
lines changed

examples/interop/cpp/hello_world.carbon

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,17 @@ fn HelloPutchar() {
2727

2828
// Demonstrate passing a null-terminated string to a C++ function.
2929
fn HelloStdio() {
30-
// TODO: Requires mapping from Optional(const char*) into C++.
3130
// TODO: There should be a better way to interact with functions that expect a
3231
// null-terminated string.
33-
// Cpp.puts(Cpp.std.data("Hello world!\n\0"));
32+
Cpp.puts(Cpp.std.data("Hello world!\0"));
3433

3534
// TODO: Requires variadic function support.
3635
// Cpp.printf("Hello world!\n\0");
3736
}
3837

3938
// Demonstrate passing a string as a void pointer to a C++ function.
4039
fn HelloWrite() {
41-
// TODO: Requires mapping from Optional(const char*) into a C++ const void*.
40+
// TODO: Requires conversion from `const char*` to `const void*`.
4241
// let s: str = "Hello world!\n";
4342
// Cpp.write(1, Cpp.std.data(s), Cpp.std.size(s));
4443
}

toolchain/check/cpp/type_mapping.cpp

Lines changed: 111 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@
2828

2929
namespace Carbon::Check {
3030

31+
// A function that wraps a C++ type to form another C++ type. Note that this is
32+
// a raw function pointer; we don't currently use any lambda captures here. This
33+
// can be replaced by a `std::function` if captures are found to be needed.
34+
using WrapFn = auto (*)(Context& context, clang::QualType inner_type)
35+
-> clang::QualType;
36+
37+
// Represents a type that requires a subtype to be mapped into a Clang type
38+
// before it can be mapped.
39+
struct WrappedType {
40+
// The type contained in this wrapped type.
41+
SemIR::TypeId inner_type_id;
42+
// A function to construct the wrapped type from the mapped unwrapped type.
43+
WrapFn wrap_fn;
44+
};
45+
46+
// Possible results from attempting to map a type. A null QualType indicates
47+
// that the type couldn't be mapped.
48+
using TryMapTypeResult = std::variant<clang::QualType, WrappedType>;
49+
3150
// Find the bit width of an integer literal. Following the C++ standard rules
3251
// for assigning a type to a decimal integer literal, the first signed integer
3352
// in which the value could fit among bit widths of 32, 64 and 128 is selected.
@@ -44,9 +63,12 @@ static auto FindIntLiteralBitWidth(Context& context, SemIR::InstId arg_id)
4463
// TODO: Add tests for these cases.
4564
return IntId::None;
4665
}
47-
auto arg = context.insts().GetAs<SemIR::IntValue>(
66+
auto arg = context.insts().TryGetAs<SemIR::IntValue>(
4867
context.constant_values().GetInstId(arg_const_id));
49-
llvm::APInt arg_val = context.ints().Get(arg.int_id);
68+
if (!arg) {
69+
return IntId::None;
70+
}
71+
llvm::APInt arg_val = context.ints().Get(arg->int_id);
5072
int arg_non_sign_bits = arg_val.getSignificantBits() - 1;
5173

5274
if (arg_non_sign_bits >= 128) {
@@ -55,7 +77,7 @@ static auto FindIntLiteralBitWidth(Context& context, SemIR::InstId arg_id)
5577
"integer type; requires {1} bits, but max is 128",
5678
TypedInt, int);
5779
context.emitter().Emit(arg_id, IntTooLargeForCppType,
58-
{.type = arg.type_id, .value = arg_val},
80+
{.type = arg->type_id, .value = arg_val},
5981
arg_non_sign_bits + 1);
6082
return IntId::None;
6183
}
@@ -103,7 +125,7 @@ static auto LookupCppType(
103125
// Maps a Carbon class type to a C++ type. Returns a null `QualType` if the
104126
// type is not supported.
105127
static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
106-
-> clang::QualType {
128+
-> TryMapTypeResult {
107129
clang::ASTContext& ast_context = context.ast_context();
108130

109131
// If the class was imported from C++, return the original C++ type.
@@ -126,10 +148,10 @@ static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
126148
break;
127149
}
128150
case SemIR::RecognizedTypeInfo::Numeric: {
129-
// Carbon supports large bit width beyond C++ builtins; we don't need to
130-
// translate those.
151+
// Carbon supports large bit width beyond C++ builtins; we don't translate
152+
// those into integer types.
131153
if (!type_info.numeric.bit_width_id.is_embedded_value()) {
132-
return clang::QualType();
154+
break;
133155
}
134156
int bit_width = type_info.numeric.bit_width_id.AsValue();
135157

@@ -164,6 +186,25 @@ static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
164186
case SemIR::RecognizedTypeInfo::CppVoidBase: {
165187
return ast_context.VoidTy;
166188
}
189+
case SemIR::RecognizedTypeInfo::Optional: {
190+
auto args = context.inst_blocks().GetOrEmpty(type_info.args_id);
191+
if (args.size() == 1) {
192+
auto arg_id = args[0];
193+
if (auto facet = context.insts().TryGetAs<SemIR::FacetValue>(arg_id)) {
194+
arg_id = facet->type_inst_id;
195+
}
196+
if (auto pointer_type =
197+
context.insts().TryGetAs<SemIR::PointerType>(arg_id)) {
198+
return WrappedType{
199+
.inner_type_id = context.types().GetTypeIdForTypeInstId(
200+
pointer_type->pointee_id),
201+
.wrap_fn = [](Context& context, clang::QualType inner_type) {
202+
return context.ast_context().getPointerType(inner_type);
203+
}};
204+
}
205+
}
206+
break;
207+
}
167208
case SemIR::RecognizedTypeInfo::Str: {
168209
return LookupCppType(context, {"std", "string_view"});
169210
}
@@ -175,12 +216,13 @@ static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
175216
return clang::QualType();
176217
}
177218

178-
// Maps a non-wrapper (no const or pointer) Carbon type to a C++ type. Returns a
179-
// null QualType if the type is not supported.
219+
// Maps a Carbon type to a C++ type. Either returns the mapped type, a null type
220+
// as a placeholder indicating the type can't be mapped, or a `WrappedType`
221+
// representing a type that needs more work before it can be mapped.
180222
// TODO: Have both Carbon -> C++ and C++ -> Carbon mappings in a single place
181223
// to keep them in sync.
182-
static auto MapNonWrapperType(Context& context, SemIR::InstId inst_id,
183-
SemIR::TypeId type_id) -> clang::QualType {
224+
static auto TryMapType(Context& context, SemIR::TypeId type_id)
225+
-> TryMapTypeResult {
184226
auto type_inst = context.types().GetAsInst(type_id);
185227

186228
CARBON_KIND_SWITCH(type_inst) {
@@ -193,67 +235,62 @@ static auto MapNonWrapperType(Context& context, SemIR::InstId inst_id,
193235
case CARBON_KIND(SemIR::ClassType class_type): {
194236
return TryMapClassType(context, class_type);
195237
}
196-
case SemIR::IntLiteralType::Kind: {
197-
IntId bit_width_id = FindIntLiteralBitWidth(context, inst_id);
198-
if (bit_width_id == IntId::None) {
199-
return clang::QualType();
200-
}
201-
return context.ast_context().getIntTypeForBitwidth(bit_width_id.AsValue(),
202-
true);
238+
case CARBON_KIND(SemIR::ConstType const_type): {
239+
return WrappedType{
240+
.inner_type_id =
241+
context.types().GetTypeIdForTypeInstId(const_type.inner_id),
242+
.wrap_fn = [](Context& /*context*/, clang::QualType inner_type) {
243+
return inner_type.withConst();
244+
}};
203245
}
204246
case SemIR::FloatLiteralType::Kind: {
205247
return context.ast_context().DoubleTy;
206248
}
249+
case CARBON_KIND(SemIR::PointerType pointer_type): {
250+
return WrappedType{
251+
.inner_type_id =
252+
context.types().GetTypeIdForTypeInstId(pointer_type.pointee_id),
253+
.wrap_fn = [](Context& context, clang::QualType inner_type) {
254+
auto pointer_type =
255+
context.ast_context().getPointerType(inner_type);
256+
return context.ast_context().getAttributedType(
257+
clang::attr::TypeNonNull, pointer_type, pointer_type);
258+
}};
259+
}
260+
207261
default: {
208262
return clang::QualType();
209263
}
210264
}
265+
266+
return clang::QualType();
211267
}
212268

213-
// Maps a Carbon type to a C++ type. Accepts an InstId, representing a value
214-
// whose type is mapped to a C++ type. Returns `clang::QualType` if the mapping
269+
// Maps a Carbon type to a C++ type. Returns `clang::QualType` if the mapping
215270
// succeeds, or `clang::QualType::isNull()` if the type is not supported.
216271
// TODO: unify this with the C++ to Carbon type mapping function.
217-
static auto MapToCppType(Context& context, SemIR::InstId inst_id)
272+
static auto MapToCppType(Context& context, SemIR::TypeId type_id)
218273
-> clang::QualType {
219-
auto type_id = context.insts().Get(inst_id).type_id();
220-
llvm::SmallVector<SemIR::TypeId> wrapper_types;
274+
llvm::SmallVector<WrapFn> wrap_fns;
221275
while (true) {
222-
SemIR::TypeId orig_type_id = type_id;
223-
if (auto const_type = context.types().TryGetAs<SemIR::ConstType>(type_id);
224-
const_type) {
225-
type_id = context.types().GetTypeIdForTypeInstId(const_type->inner_id);
226-
} else if (auto pointer_type =
227-
context.types().TryGetAs<SemIR::PointerType>(type_id);
228-
pointer_type) {
229-
type_id =
230-
context.types().GetTypeIdForTypeInstId(pointer_type->pointee_id);
231-
} else {
232-
break;
233-
}
234-
wrapper_types.push_back(orig_type_id);
235-
}
236-
237-
clang::QualType mapped_type = MapNonWrapperType(context, inst_id, type_id);
238-
if (mapped_type.isNull()) {
239-
return mapped_type;
240-
}
276+
CARBON_KIND_SWITCH(TryMapType(context, type_id)) {
277+
case CARBON_KIND(clang::QualType type): {
278+
for (auto wrap_fn : llvm::reverse(wrap_fns)) {
279+
if (type.isNull()) {
280+
break;
281+
}
282+
type = wrap_fn(context, type);
283+
}
284+
return type;
285+
}
241286

242-
for (auto wrapper_type_id : llvm::reverse(wrapper_types)) {
243-
if (auto const_type =
244-
context.types().TryGetAs<SemIR::ConstType>(wrapper_type_id);
245-
const_type) {
246-
mapped_type.addConst();
247-
} else if (context.types().TryGetAs<SemIR::PointerType>(wrapper_type_id)) {
248-
auto pointer_type = context.ast_context().getPointerType(mapped_type);
249-
mapped_type = context.ast_context().getAttributedType(
250-
clang::attr::TypeNonNull, pointer_type, pointer_type);
251-
} else {
252-
return clang::QualType();
287+
case CARBON_KIND(WrappedType wrapped): {
288+
wrap_fns.push_back(wrapped.wrap_fn);
289+
type_id = wrapped.inner_type_id;
290+
break;
291+
}
253292
}
254293
}
255-
256-
return mapped_type;
257294
}
258295

259296
auto InventClangArg(Context& context, SemIR::InstId arg_id) -> clang::Expr* {
@@ -296,7 +333,25 @@ auto InventClangArg(Context& context, SemIR::InstId arg_id) -> clang::Expr* {
296333
return nullptr;
297334
}
298335

299-
clang::QualType arg_cpp_type = MapToCppType(context, arg_id);
336+
clang::QualType arg_cpp_type;
337+
338+
// Special case: if the argument is an integer literal, look at its value.
339+
// TODO: Consider producing a `clang::IntegerLiteral` in this case instead, so
340+
// that C++ overloads that behave differently for zero-valued int literals can
341+
// recognize it.
342+
auto type_id = context.insts().Get(arg_id).type_id();
343+
if (context.types().Is<SemIR::IntLiteralType>(type_id)) {
344+
IntId bit_width_id = FindIntLiteralBitWidth(context, arg_id);
345+
if (bit_width_id != IntId::None) {
346+
arg_cpp_type = context.ast_context().getIntTypeForBitwidth(
347+
bit_width_id.AsValue(), true);
348+
}
349+
}
350+
351+
if (arg_cpp_type.isNull()) {
352+
arg_cpp_type = MapToCppType(context, type_id);
353+
}
354+
300355
if (arg_cpp_type.isNull()) {
301356
CARBON_DIAGNOSTIC(CppCallArgTypeNotSupported, Error,
302357
"call argument of type {0} is not supported",

0 commit comments

Comments
 (0)