Skip to content

Commit bd90fe1

Browse files
zygoloidchandlerc
andauthored
Interop: map C++ std::string_view into Carbon str when importing. (#5985)
We assume these types have the same representation. For now, that will only be the case for libc++ on 64-bit targets, because libc++ puts the size field first, and `Core.String` always uses a 64-bit size field even on 32-bit targets. --------- Co-authored-by: Chandler Carruth <[email protected]>
1 parent 82ba1a4 commit bd90fe1

File tree

8 files changed

+356
-26
lines changed

8 files changed

+356
-26
lines changed

toolchain/check/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ cc_library(
2121
"context.cpp",
2222
"control_flow.cpp",
2323
"convert.cpp",
24+
"cpp_custom_type_mapping.cpp",
2425
"cpp_thunk.cpp",
2526
"decl_name_stack.cpp",
2627
"deduce.cpp",
@@ -66,6 +67,7 @@ cc_library(
6667
"context.h",
6768
"control_flow.h",
6869
"convert.h",
70+
"cpp_custom_type_mapping.h",
6971
"cpp_thunk.h",
7072
"decl_introducer_state.h",
7173
"decl_name_stack.h",

toolchain/check/convert.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,17 @@ auto ConvertCallArgs(Context& context, SemIR::LocId call_loc_id,
124124

125125
// A type that has been converted for use as a type expression.
126126
struct TypeExpr {
127+
static const TypeExpr None;
128+
127129
// The converted expression of type `type`, or `ErrorInst::InstId`.
128130
SemIR::TypeInstId inst_id;
129131
// The corresponding type, or `ErrorInst::TypeId`.
130132
SemIR::TypeId type_id;
131133
};
132134

135+
constexpr inline TypeExpr TypeExpr::None = {.inst_id = SemIR::TypeInstId::None,
136+
.type_id = SemIR::TypeId::None};
137+
133138
// Converts an expression for use as a type.
134139
//
135140
// If `diagnose` is true, errors are diagnosed to the user. Set it to false when
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
5+
#include "toolchain/check/cpp_custom_type_mapping.h"
6+
7+
#include "clang/AST/DeclCXX.h"
8+
#include "clang/AST/DeclTemplate.h"
9+
#include "llvm/ADT/STLExtras.h"
10+
#include "llvm/ADT/StringRef.h"
11+
12+
namespace Carbon::Check {
13+
14+
// A small, lightweight library of AST matchers. Unlike clang's ASTMatchers,
15+
// this avoids heap allocations and is suitable for one-off matching rather than
16+
// matching against a whole AST.
17+
namespace Matchers {
18+
// A matcher for a type T is just a function that takes a T and returns whether
19+
// it matched. Matchers should be invoked immediately, and are not expected to
20+
// outlive the arguments of the call that created them.
21+
// TODO: We could avoid the indirect calls by making the below functions be
22+
// templated on the inner matcher.
23+
template <typename T>
24+
using Matcher = llvm::function_ref<auto(T)->bool>;
25+
26+
// Returns a matcher for class declarations that determines whether the given
27+
// class is a class template specialization in namespace std with the specified
28+
// name and template arguments matching the given predicate.
29+
static auto StdClassTemplate(
30+
llvm::StringLiteral name,
31+
Matcher<const clang::TemplateArgumentList&> args_matcher
32+
[[clang::lifetimebound]]) -> auto {
33+
return [=](const clang::CXXRecordDecl* class_decl) -> bool {
34+
const auto* specialization =
35+
dyn_cast<clang::ClassTemplateSpecializationDecl>(class_decl);
36+
const auto* identifier = class_decl->getIdentifier();
37+
return specialization && identifier && identifier->isStr(name) &&
38+
specialization->isInStdNamespace() &&
39+
args_matcher(specialization->getTemplateArgs());
40+
};
41+
}
42+
43+
// Returns a matcher that matches types if they are class types whose class
44+
// matches the given matcher.
45+
static auto Class(Matcher<const clang::CXXRecordDecl*> class_matcher
46+
[[clang::lifetimebound]]) -> auto {
47+
return [=](clang::QualType type) -> bool {
48+
const auto* class_decl = type->getAsCXXRecordDecl();
49+
return !type.hasQualifiers() && class_decl && class_matcher(class_decl);
50+
};
51+
}
52+
53+
// Returns a matcher that determines whether the given template argument is a
54+
// type matching the given predicate.
55+
static auto TypeTemplateArgument(Matcher<clang::QualType> type_matcher
56+
[[clang::lifetimebound]]) -> auto {
57+
return [=](clang::TemplateArgument arg) -> bool {
58+
return arg.getKind() == clang::TemplateArgument::Type &&
59+
type_matcher(arg.getAsType());
60+
};
61+
}
62+
63+
// A matcher that determines whether the given type is `char`.
64+
static auto Char(clang::QualType type) -> bool {
65+
return !type.hasQualifiers() && type->isCharType();
66+
}
67+
68+
// Returns a matcher that determines whether the given template argument list
69+
// matches the given sequence of template argument matchers.
70+
static auto TemplateArgumentsAre(
71+
std::initializer_list<Matcher<clang::TemplateArgument>> arg_matchers
72+
[[clang::lifetimebound]]) -> auto {
73+
return [=](const clang::TemplateArgumentList& args) -> bool {
74+
if (args.size() != arg_matchers.size()) {
75+
return false;
76+
}
77+
for (auto [arg, matcher] : llvm::zip_equal(args.asArray(), arg_matchers)) {
78+
if (!matcher(arg)) {
79+
return false;
80+
}
81+
}
82+
return true;
83+
};
84+
}
85+
86+
// A matcher for `std::char_traits<char>`.
87+
static auto StdCharTraitsChar(clang::QualType type) -> bool {
88+
return Class(StdClassTemplate(
89+
"char_traits", TemplateArgumentsAre({TypeTemplateArgument(Char)})))(type);
90+
}
91+
92+
// A matcher for `std::string_view`.
93+
static auto StdStringView(const clang::CXXRecordDecl* record_decl) -> bool {
94+
return StdClassTemplate(
95+
"basic_string_view",
96+
TemplateArgumentsAre({TypeTemplateArgument(Char),
97+
TypeTemplateArgument(StdCharTraitsChar)}))(
98+
record_decl);
99+
}
100+
} // end namespace Matchers
101+
102+
auto GetCustomCppTypeMapping(const clang::CXXRecordDecl* record_decl)
103+
-> CustomCppTypeMapping {
104+
if (Matchers::StdStringView(record_decl)) {
105+
return CustomCppTypeMapping::Str;
106+
}
107+
108+
return CustomCppTypeMapping::None;
109+
}
110+
111+
} // namespace Carbon::Check
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
5+
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_CUSTOM_TYPE_MAPPING_H_
6+
#define CARBON_TOOLCHAIN_CHECK_CPP_CUSTOM_TYPE_MAPPING_H_
7+
8+
#include "clang/AST/DeclCXX.h"
9+
10+
namespace Carbon::Check {
11+
12+
// Carbon types that have a custom mapping from C++.
13+
enum class CustomCppTypeMapping : uint8_t {
14+
// None.
15+
None,
16+
17+
// The Carbon `Str` type, which maps to `std::string_view`.
18+
Str,
19+
};
20+
21+
// Determines whether record_decl is a C++ class that has a custom mapping into
22+
// Carbon, and if so, returns the corresponding Carbon type. Otherwise returns
23+
// None.
24+
auto GetCustomCppTypeMapping(const clang::CXXRecordDecl* record_decl)
25+
-> CustomCppTypeMapping;
26+
27+
} // namespace Carbon::Check
28+
29+
#endif // CARBON_TOOLCHAIN_CHECK_CPP_CUSTOM_TYPE_MAPPING_H_

toolchain/check/import_cpp.cpp

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "toolchain/check/class.h"
3030
#include "toolchain/check/context.h"
3131
#include "toolchain/check/convert.h"
32+
#include "toolchain/check/cpp_custom_type_mapping.h"
3233
#include "toolchain/check/cpp_thunk.h"
3334
#include "toolchain/check/diagnostic_helpers.h"
3435
#include "toolchain/check/eval.h"
@@ -1200,7 +1201,24 @@ static auto MapBuiltinType(Context& context, SemIR::LocId loc_id,
12001201
// TODO: Handle floating-point types that map to named aliases.
12011202
}
12021203

1203-
return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
1204+
return TypeExpr::None;
1205+
}
1206+
1207+
// Determines whether record_decl is a C++ class that has a custom mapping into
1208+
// Carbon, and if so, returns the corresponding Carbon type. Otherwise returns
1209+
// None.
1210+
static auto LookupCustomRecordType(Context& context,
1211+
const clang::CXXRecordDecl* record_decl)
1212+
-> TypeExpr {
1213+
switch (GetCustomCppTypeMapping(record_decl)) {
1214+
case CustomCppTypeMapping::None:
1215+
return TypeExpr::None;
1216+
1217+
case CustomCppTypeMapping::Str:
1218+
return MakeStringType(
1219+
context,
1220+
AddImportIRInst(context.sem_ir(), record_decl->getLocation()));
1221+
}
12041222
}
12051223

12061224
// Maps a C++ tag type (class, struct, union, enum) to a Carbon type.
@@ -1210,12 +1228,21 @@ static auto MapTagType(Context& context, const clang::TagType& type)
12101228
CARBON_CHECK(tag_decl);
12111229

12121230
// Check if the declaration is already mapped.
1213-
SemIR::InstId record_inst_id = LookupClangDeclInstId(context, tag_decl);
1214-
if (!record_inst_id.has_value()) {
1215-
record_inst_id = ImportTagDecl(context, tag_decl);
1231+
SemIR::InstId tag_inst_id = LookupClangDeclInstId(context, tag_decl);
1232+
if (!tag_inst_id.has_value()) {
1233+
if (auto* record_decl = dyn_cast<clang::CXXRecordDecl>(tag_decl)) {
1234+
auto custom_type = LookupCustomRecordType(context, record_decl);
1235+
if (custom_type.inst_id.has_value()) {
1236+
context.sem_ir().clang_decls().Add(
1237+
{.decl = record_decl, .inst_id = custom_type.inst_id});
1238+
return custom_type;
1239+
}
1240+
}
1241+
1242+
tag_inst_id = ImportTagDecl(context, tag_decl);
12161243
}
12171244
SemIR::TypeInstId record_type_inst_id =
1218-
context.types().GetAsTypeInstId(record_inst_id);
1245+
context.types().GetAsTypeInstId(tag_inst_id);
12191246
return {
12201247
.inst_id = record_type_inst_id,
12211248
.type_id = context.types().GetTypeIdForTypeInstId(record_type_inst_id)};
@@ -1237,7 +1264,7 @@ static auto MapNonWrapperType(Context& context, SemIR::LocId loc_id,
12371264
CARBON_CHECK(!type.hasQualifiers() && !type->isPointerType(),
12381265
"Should not see wrapper types here");
12391266

1240-
return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
1267+
return TypeExpr::None;
12411268
}
12421269

12431270
// Maps a qualified C++ type to a Carbon type.
@@ -1254,7 +1281,7 @@ static auto MapQualifiedType(Context& context, clang::QualType type,
12541281

12551282
// TODO: Support other qualifiers.
12561283
if (!quals.empty()) {
1257-
return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
1284+
return TypeExpr::None;
12581285
}
12591286

12601287
return type_expr;
@@ -1269,7 +1296,7 @@ static auto MapPointerType(Context& context, clang::QualType type,
12691296
!nullability.has_value() ||
12701297
*nullability != clang::NullabilityKind::NonNull) {
12711298
// TODO: Support nullable pointers.
1272-
return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
1299+
return TypeExpr::None;
12731300
}
12741301

12751302
SemIR::TypeId pointer_type_id =
@@ -1467,7 +1494,7 @@ static auto GetReturnTypeExpr(Context& context, SemIR::LocId loc_id,
14671494

14681495
if (!isa<clang::CXXConstructorDecl>(clang_decl)) {
14691496
// void.
1470-
return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
1497+
return TypeExpr::None;
14711498
}
14721499

14731500
// TODO: Make this a `PartialType`.

toolchain/check/literal.cpp

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "toolchain/check/call.h"
88
#include "toolchain/check/context.h"
99
#include "toolchain/check/convert.h"
10+
#include "toolchain/check/diagnostic_helpers.h"
1011
#include "toolchain/check/name_lookup.h"
1112
#include "toolchain/check/type.h"
1213
#include "toolchain/check/type_completion.h"
@@ -106,23 +107,23 @@ static auto GetStringLiteralRepr(Context& context, SemIR::LocId loc_id,
106107

107108
auto MakeStringLiteral(Context& context, Parse::StringLiteralId node_id,
108109
StringLiteralValueId value_id) -> SemIR::InstId {
109-
auto str_type_id = MakeStringType(context, node_id);
110-
if (!RequireCompleteType(context, str_type_id, node_id, [&] {
110+
auto str_type = MakeStringType(context, node_id);
111+
if (!RequireCompleteType(context, str_type.type_id, node_id, [&] {
111112
CARBON_DIAGNOSTIC(StringLiteralTypeIncomplete, Error,
112-
"type {0} is incomplete", SemIR::TypeId);
113+
"type {0} is incomplete", InstIdAsType);
113114
return context.emitter().Build(node_id, StringLiteralTypeIncomplete,
114-
str_type_id);
115+
str_type.inst_id);
115116
})) {
116117
return SemIR::ErrorInst::InstId;
117118
}
118119

119-
auto repr = GetStringLiteralRepr(context, node_id, str_type_id);
120+
auto repr = GetStringLiteralRepr(context, node_id, str_type.type_id);
120121
if (!repr) {
121-
if (str_type_id != SemIR::ErrorInst::TypeId) {
122+
if (str_type.type_id != SemIR::ErrorInst::TypeId) {
122123
CARBON_DIAGNOSTIC(StringLiteralTypeUnexpected, Error,
123-
"unexpected representation for type {0}",
124-
SemIR::TypeId);
125-
context.emitter().Emit(node_id, StringLiteralTypeUnexpected, str_type_id);
124+
"unexpected representation for type {0}", InstIdAsType);
125+
context.emitter().Emit(node_id, StringLiteralTypeUnexpected,
126+
str_type.inst_id);
126127
}
127128
return SemIR::ErrorInst::InstId;
128129
}
@@ -157,17 +158,18 @@ auto MakeStringLiteral(Context& context, Parse::StringLiteralId node_id,
157158
// Build the representation struct.
158159
auto elements_id = context.inst_blocks().Add({ptr_value_id, size_value_id});
159160
return AddInst<SemIR::StructValue>(
160-
context, node_id, {.type_id = str_type_id, .elements_id = elements_id});
161+
context, node_id,
162+
{.type_id = str_type.type_id, .elements_id = elements_id});
161163
}
162164

163-
auto MakeStringTypeLiteral(Context& context, Parse::NodeId node_id)
165+
auto MakeStringTypeLiteral(Context& context, SemIR::LocId loc_id)
164166
-> SemIR::InstId {
165-
return LookupNameInCore(context, node_id, "String");
167+
return LookupNameInCore(context, loc_id, "String");
166168
}
167169

168-
auto MakeStringType(Context& context, Parse::NodeId node_id) -> SemIR::TypeId {
169-
auto type_inst_id = MakeStringTypeLiteral(context, node_id);
170-
return ExprAsType(context, node_id, type_inst_id).type_id;
170+
auto MakeStringType(Context& context, SemIR::LocId loc_id) -> TypeExpr {
171+
auto type_inst_id = MakeStringTypeLiteral(context, loc_id);
172+
return ExprAsType(context, loc_id, type_inst_id);
171173
}
172174

173175
} // namespace Carbon::Check

toolchain/check/literal.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "toolchain/base/value_ids.h"
99
#include "toolchain/check/context.h"
10+
#include "toolchain/check/convert.h"
1011
#include "toolchain/lex/token_info.h"
1112
#include "toolchain/sem_ir/ids.h"
1213

@@ -35,11 +36,11 @@ auto MakeStringLiteral(Context& context, Parse::StringLiteralId node_id,
3536
StringLiteralValueId value_id) -> SemIR::InstId;
3637

3738
// Forms a string literal type expression for a `str` literal.
38-
auto MakeStringTypeLiteral(Context& context, Parse::NodeId node_id)
39+
auto MakeStringTypeLiteral(Context& context, SemIR::LocId loc_id)
3940
-> SemIR::InstId;
4041

4142
// Forms a string type.
42-
auto MakeStringType(Context& context, Parse::NodeId node_id) -> SemIR::TypeId;
43+
auto MakeStringType(Context& context, SemIR::LocId) -> TypeExpr;
4344

4445
} // namespace Carbon::Check
4546

0 commit comments

Comments
 (0)