Skip to content

Commit 1d314c7

Browse files
authored
Import C++ constructors of class Type fn Type(...) -> Type (#5879)
Only supports classes with a single (non copy non move) constructor (without default values), until overloading is supported. Based on #5878. C++ Interop Demo: ```c++ // hello_world.h #include <cstdio> class C { public: C(int x, int y) : x_(x), y_(y) {} int x() const { return x_;} int y() const { return y_;} private: int x_; int y_; }; void hello_world(C* _Nonnull c); ``` ```c++ // hello_world.cpp #include "hello_world.h" #include <cstdio> void hello_world(C* _Nonnull c) { printf("C.x = %d. C.y = %d\n", c->x(), c->y()); } ``` ```carbon // main.carbon library "Main"; import Cpp library "hello_world.h"; fn Run() -> i32 { var c : Cpp.C = Cpp.C.C(1, 2); Cpp.hello_world(&c); return 0; } ``` ```shell $ clang -c hello_world.cpp $ bazel-bin/toolchain/carbon compile main.carbon $ bazel-bin/toolchain/carbon link hello_world.o main.o --output=demo $ ./demo C.x = 1. C.y = 2 ``` Part of #5880.
1 parent 3c9d267 commit 1d314c7

File tree

9 files changed

+1074
-43
lines changed

9 files changed

+1074
-43
lines changed

toolchain/check/import_cpp.cpp

Lines changed: 178 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "toolchain/diagnostics/format_providers.h"
4646
#include "toolchain/parse/node_ids.h"
4747
#include "toolchain/sem_ir/clang_decl.h"
48+
#include "toolchain/sem_ir/class.h"
4849
#include "toolchain/sem_ir/function.h"
4950
#include "toolchain/sem_ir/ids.h"
5051
#include "toolchain/sem_ir/inst.h"
@@ -455,10 +456,10 @@ auto ImportCppFiles(Context& context,
455456
return std::move(generated_ast);
456457
}
457458

458-
// Look ups the given name in the Clang AST in a specific scope. Returns the
459+
// Looks up the given name in the Clang AST in a specific scope. Returns the
459460
// lookup result if lookup was successful.
460-
static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
461-
SemIR::NameId name_id)
461+
static auto ClangLookupName(Context& context, SemIR::NameScopeId scope_id,
462+
SemIR::NameId name_id)
462463
-> std::optional<clang::LookupResult> {
463464
std::optional<llvm::StringRef> name =
464465
context.names().GetAsStringIfIdentifier(name_id);
@@ -497,6 +498,112 @@ static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
497498
return lookup;
498499
}
499500

501+
// Looks up for constructors in the class scope and returns the lookup result.
502+
static auto ClangConstructorLookup(const Context& context,
503+
SemIR::NameScopeId scope_id)
504+
-> clang::DeclContextLookupResult {
505+
const SemIR::NameScope& scope = context.sem_ir().name_scopes().Get(scope_id);
506+
507+
clang::Sema& sema = context.sem_ir().cpp_ast()->getSema();
508+
clang::Decl* decl =
509+
context.sem_ir().clang_decls().Get(scope.clang_decl_context_id()).decl;
510+
return sema.LookupConstructors(cast<clang::CXXRecordDecl>(decl));
511+
}
512+
513+
// Returns true if the given Clang declaration is the implicit injected class
514+
// name within the class.
515+
static auto IsDeclInjectedClassName(const Context& context,
516+
SemIR::NameScopeId scope_id,
517+
SemIR::NameId name_id,
518+
const clang::NamedDecl* named_decl)
519+
-> bool {
520+
if (!named_decl->isImplicit()) {
521+
return false;
522+
}
523+
524+
const auto* record_decl = dyn_cast<clang::CXXRecordDecl>(named_decl);
525+
if (!record_decl) {
526+
return false;
527+
}
528+
529+
const SemIR::ClangDecl& clang_decl = context.sem_ir().clang_decls().Get(
530+
context.sem_ir().name_scopes().Get(scope_id).clang_decl_context_id());
531+
const auto* scope_record_decl = cast<clang::CXXRecordDecl>(clang_decl.decl);
532+
533+
const clang::ASTContext& ast_context =
534+
context.sem_ir().cpp_ast()->getASTContext();
535+
CARBON_CHECK(
536+
ast_context.getCanonicalType(
537+
ast_context.getRecordType(scope_record_decl)) ==
538+
ast_context.getCanonicalType(ast_context.getRecordType(record_decl)));
539+
540+
auto class_decl =
541+
context.sem_ir().insts().GetAs<SemIR::ClassDecl>(clang_decl.inst_id);
542+
CARBON_CHECK(name_id ==
543+
context.sem_ir().classes().Get(class_decl.class_id).name_id);
544+
return true;
545+
}
546+
547+
// Looks up the given name in the Clang AST in a specific scope, and returns the
548+
// found declaration and its access. If the found declaration is the injected
549+
// class name, looks up constructors instead. If not found, returns `nullopt`.
550+
// If there's not a single result, returns `nullptr` and default access.
551+
// Otherwise, returns the single declaration and its access.
552+
static auto ClangLookup(Context& context, SemIR::LocId loc_id,
553+
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
554+
-> std::optional<std::tuple<clang::NamedDecl*, clang::AccessSpecifier>> {
555+
auto lookup = ClangLookupName(context, scope_id, name_id);
556+
if (!lookup) {
557+
return std::nullopt;
558+
}
559+
560+
std::tuple<clang::NamedDecl*, clang::AccessSpecifier> result{
561+
nullptr, clang::AccessSpecifier::AS_none};
562+
563+
// Access checks are performed separately by the Carbon name lookup logic.
564+
lookup->suppressAccessDiagnostics();
565+
566+
if (!lookup->isSingleResult()) {
567+
// Clang will diagnose ambiguous lookup results for us.
568+
if (!lookup->isAmbiguous()) {
569+
context.TODO(loc_id,
570+
llvm::formatv("Unsupported: Lookup succeeded but couldn't "
571+
"find a single result; LookupResultKind: {0}",
572+
static_cast<int>(lookup->getResultKind())));
573+
}
574+
575+
return result;
576+
}
577+
578+
if (!IsDeclInjectedClassName(context, scope_id, name_id,
579+
lookup->getFoundDecl())) {
580+
result = {lookup->getFoundDecl(), lookup->begin().getAccess()};
581+
return result;
582+
}
583+
584+
clang::DeclContextLookupResult constructors_lookup =
585+
ClangConstructorLookup(context, scope_id);
586+
587+
llvm::SmallVector<clang::CXXConstructorDecl*> constructors;
588+
for (clang::Decl* decl : constructors_lookup) {
589+
auto* constructor = cast<clang::CXXConstructorDecl>(decl);
590+
if (constructor->isDeleted() || constructor->isCopyOrMoveConstructor()) {
591+
continue;
592+
}
593+
constructors.push_back(constructor);
594+
}
595+
if (constructors.size() != 1) {
596+
context.TODO(
597+
loc_id,
598+
llvm::formatv("Unsupported: Constructors lookup succeeded but couldn't "
599+
"find a single result; Found {0} constructors",
600+
constructors.size()));
601+
return result;
602+
}
603+
result = {constructors[0], constructors[0]->getAccess()};
604+
return result;
605+
}
606+
500607
// Returns whether `decl` already mapped to an instruction.
501608
static auto IsClangDeclImported(const Context& context, clang::Decl* decl)
502609
-> bool {
@@ -1109,7 +1216,8 @@ static auto MakeImplicitParamPatternsBlockId(
11091216
Context& context, SemIR::LocId loc_id,
11101217
const clang::FunctionDecl& clang_decl) -> SemIR::InstBlockId {
11111218
const auto* method_decl = dyn_cast<clang::CXXMethodDecl>(&clang_decl);
1112-
if (!method_decl || method_decl->isStatic()) {
1219+
if (!method_decl || method_decl->isStatic() ||
1220+
isa<clang::CXXConstructorDecl>(clang_decl)) {
11131221
return SemIR::InstBlockId::Empty;
11141222
}
11151223

@@ -1229,22 +1337,54 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
12291337
return context.inst_blocks().Add(params);
12301338
}
12311339

1232-
// Returns the return type of the given function declaration. In case of an
1233-
// unsupported return type, it produces a diagnostic and returns
1234-
// `SemIR::ErrorInst::InstId`.
1340+
// Returns the return `TypeExpr` of the given function declaration. In case of
1341+
// an unsupported return type, returns `SemIR::ErrorInst::InstId`. Constructors
1342+
// are treated as returning a class instance.
12351343
// TODO: Support more return types.
1236-
static auto GetReturnType(Context& context, SemIR::LocId loc_id,
1237-
const clang::FunctionDecl* clang_decl)
1238-
-> SemIR::InstId {
1344+
static auto GetReturnTypeExpr(Context& context, SemIR::LocId loc_id,
1345+
clang::FunctionDecl* clang_decl) -> TypeExpr {
12391346
clang::QualType ret_type = clang_decl->getReturnType();
1240-
if (ret_type->isVoidType()) {
1241-
return SemIR::InstId::None;
1347+
if (!ret_type->isVoidType()) {
1348+
TypeExpr mapped_type = MapType(context, loc_id, ret_type);
1349+
if (!mapped_type.inst_id.has_value()) {
1350+
return {.inst_id = SemIR::ErrorInst::TypeInstId,
1351+
.type_id = SemIR::ErrorInst::TypeId};
1352+
}
1353+
return mapped_type;
1354+
}
1355+
1356+
if (!isa<clang::CXXConstructorDecl>(clang_decl)) {
1357+
// void.
1358+
return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
12421359
}
12431360

1244-
auto [type_inst_id, type_id] = MapType(context, loc_id, ret_type);
1361+
// TODO: Make this a `PartialType`.
1362+
SemIR::TypeInstId record_type_inst_id = context.types().GetAsTypeInstId(
1363+
context.sem_ir()
1364+
.clang_decls()
1365+
.Get(context.sem_ir().clang_decls().Lookup(
1366+
cast<clang::Decl>(clang_decl->getParent())))
1367+
.inst_id);
1368+
return {
1369+
.inst_id = record_type_inst_id,
1370+
.type_id = context.types().GetTypeIdForTypeInstId(record_type_inst_id)};
1371+
}
1372+
1373+
// Returns the return pattern of the given function declaration. In case of an
1374+
// unsupported return type, it produces a diagnostic and returns
1375+
// `SemIR::ErrorInst::InstId`. Constructors are treated as returning a class
1376+
// instance.
1377+
static auto GetReturnPattern(Context& context, SemIR::LocId loc_id,
1378+
clang::FunctionDecl* clang_decl) -> SemIR::InstId {
1379+
auto [type_inst_id, type_id] = GetReturnTypeExpr(context, loc_id, clang_decl);
12451380
if (!type_inst_id.has_value()) {
1246-
context.TODO(loc_id, llvm::formatv("Unsupported: return type: {0}",
1247-
ret_type.getAsString()));
1381+
// void.
1382+
return SemIR::InstId::None;
1383+
}
1384+
if (type_inst_id == SemIR::ErrorInst::TypeInstId) {
1385+
context.TODO(loc_id,
1386+
llvm::formatv("Unsupported: return type: {0}",
1387+
clang_decl->getReturnType().getAsString()));
12481388
return SemIR::ErrorInst::InstId;
12491389
}
12501390
auto pattern_type_id = GetPatternType(context, type_id);
@@ -1283,10 +1423,10 @@ struct FunctionParamsInsts {
12831423
// Produces a diagnostic and returns `std::nullopt` if the function declaration
12841424
// has an unsupported parameter type.
12851425
static auto CreateFunctionParamsInsts(Context& context, SemIR::LocId loc_id,
1286-
const clang::FunctionDecl* clang_decl)
1426+
clang::FunctionDecl* clang_decl)
12871427
-> std::optional<FunctionParamsInsts> {
1288-
if (isa<clang::CXXConstructorDecl, clang::CXXDestructorDecl>(clang_decl)) {
1289-
context.TODO(loc_id, "Unsupported: Constructor/Destructor");
1428+
if (isa<clang::CXXDestructorDecl>(clang_decl)) {
1429+
context.TODO(loc_id, "Unsupported: Destructor");
12901430
return std::nullopt;
12911431
}
12921432

@@ -1300,7 +1440,7 @@ static auto CreateFunctionParamsInsts(Context& context, SemIR::LocId loc_id,
13001440
if (!param_patterns_id.has_value()) {
13011441
return std::nullopt;
13021442
}
1303-
auto return_slot_pattern_id = GetReturnType(context, loc_id, clang_decl);
1443+
auto return_slot_pattern_id = GetReturnPattern(context, loc_id, clang_decl);
13041444
if (SemIR::ErrorInst::InstId == return_slot_pattern_id) {
13051445
return std::nullopt;
13061446
}
@@ -1343,8 +1483,19 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id,
13431483
AddPlaceholderInstInNoBlock(context, Parse::NodeId::None, function_decl);
13441484
context.imports().push_back(decl_id);
13451485

1486+
SemIR::NameId function_name_id =
1487+
isa<clang::CXXConstructorDecl>(clang_decl)
1488+
? context.classes()
1489+
.Get(context.insts()
1490+
.GetAs<SemIR::ClassDecl>(LookupClangDeclInstId(
1491+
context,
1492+
cast<clang::Decl>(clang_decl->getParent())))
1493+
.class_id)
1494+
.name_id
1495+
: AddIdentifierName(context, clang_decl->getName());
1496+
13461497
auto function_info = SemIR::Function{
1347-
{.name_id = AddIdentifierName(context, clang_decl->getName()),
1498+
{.name_id = function_name_id,
13481499
.parent_scope_id = GetParentNameScopeId(context, clang_decl),
13491500
.generic_id = SemIR::GenericId::None,
13501501
.first_param_node_id = Parse::NodeId::None,
@@ -1526,8 +1677,7 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
15261677
}
15271678

15281679
context.TODO(loc_id, llvm::formatv("Unsupported: Declaration type {0}",
1529-
clang_decl->getDeclKindName())
1530-
.str());
1680+
clang_decl->getDeclKindName()));
15311681
return SemIR::InstId::None;
15321682
}
15331683

@@ -1600,31 +1750,19 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
16001750
builder.Note(loc_id, InCppNameLookup, name_id);
16011751
});
16021752

1603-
auto lookup = ClangLookup(context, scope_id, name_id);
1604-
if (!lookup) {
1753+
auto decl_and_access = ClangLookup(context, loc_id, scope_id, name_id);
1754+
if (!decl_and_access) {
16051755
return SemIR::ScopeLookupResult::MakeNotFound();
16061756
}
1607-
1608-
// Access checks are performed separately by the Carbon name lookup logic.
1609-
lookup->suppressAccessDiagnostics();
1610-
1611-
if (!lookup->isSingleResult()) {
1612-
// Clang will diagnose ambiguous lookup results for us.
1613-
if (!lookup->isAmbiguous()) {
1614-
context.TODO(loc_id,
1615-
llvm::formatv("Unsupported: Lookup succeeded but couldn't "
1616-
"find a single result; LookupResultKind: {0}",
1617-
static_cast<int>(lookup->getResultKind()))
1618-
.str());
1619-
}
1757+
auto [decl, access] = *decl_and_access;
1758+
if (!decl) {
16201759
context.name_scopes().AddRequiredName(scope_id, name_id,
16211760
SemIR::ErrorInst::InstId);
16221761
return SemIR::ScopeLookupResult::MakeError();
16231762
}
16241763

1625-
return ImportNameDeclIntoScope(context, loc_id, scope_id, name_id,
1626-
lookup->getFoundDecl(),
1627-
lookup->begin().getAccess());
1764+
return ImportNameDeclIntoScope(context, loc_id, scope_id, name_id, decl,
1765+
access);
16281766
}
16291767

16301768
} // namespace Carbon::Check

toolchain/check/import_cpp.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ auto ImportCppFiles(Context& context,
2525
-> std::unique_ptr<clang::ASTUnit>;
2626

2727
// Looks up the given name in the Clang AST generated when importing C++ code
28-
// and returns a lookup result.
28+
// and returns a lookup result. If using the injected class name (`X.X()`),
29+
// imports the class constructor as a function named as the class.
2930
auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
3031
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
3132
-> SemIR::ScopeLookupResult;

0 commit comments

Comments
 (0)