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.
This commit is contained in:
Boaz Brickner
2025-08-06 12:02:38 +00:00
committed by GitHub
parent 3c9d267388
commit 1d314c7c4d
9 changed files with 1074 additions and 43 deletions
+178 -40
View File
@@ -45,6 +45,7 @@
#include "toolchain/diagnostics/format_providers.h"
#include "toolchain/parse/node_ids.h"
#include "toolchain/sem_ir/clang_decl.h"
#include "toolchain/sem_ir/class.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst.h"
@@ -455,10 +456,10 @@ auto ImportCppFiles(Context& context,
return std::move(generated_ast);
}
// Look ups the given name in the Clang AST in a specific scope. Returns the
// Looks up the given name in the Clang AST in a specific scope. Returns the
// lookup result if lookup was successful.
static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
SemIR::NameId name_id)
static auto ClangLookupName(Context& context, SemIR::NameScopeId scope_id,
SemIR::NameId name_id)
-> std::optional<clang::LookupResult> {
std::optional<llvm::StringRef> name =
context.names().GetAsStringIfIdentifier(name_id);
@@ -497,6 +498,112 @@ static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
return lookup;
}
// Looks up for constructors in the class scope and returns the lookup result.
static auto ClangConstructorLookup(const Context& context,
SemIR::NameScopeId scope_id)
-> clang::DeclContextLookupResult {
const SemIR::NameScope& scope = context.sem_ir().name_scopes().Get(scope_id);
clang::Sema& sema = context.sem_ir().cpp_ast()->getSema();
clang::Decl* decl =
context.sem_ir().clang_decls().Get(scope.clang_decl_context_id()).decl;
return sema.LookupConstructors(cast<clang::CXXRecordDecl>(decl));
}
// Returns true if the given Clang declaration is the implicit injected class
// name within the class.
static auto IsDeclInjectedClassName(const Context& context,
SemIR::NameScopeId scope_id,
SemIR::NameId name_id,
const clang::NamedDecl* named_decl)
-> bool {
if (!named_decl->isImplicit()) {
return false;
}
const auto* record_decl = dyn_cast<clang::CXXRecordDecl>(named_decl);
if (!record_decl) {
return false;
}
const SemIR::ClangDecl& clang_decl = context.sem_ir().clang_decls().Get(
context.sem_ir().name_scopes().Get(scope_id).clang_decl_context_id());
const auto* scope_record_decl = cast<clang::CXXRecordDecl>(clang_decl.decl);
const clang::ASTContext& ast_context =
context.sem_ir().cpp_ast()->getASTContext();
CARBON_CHECK(
ast_context.getCanonicalType(
ast_context.getRecordType(scope_record_decl)) ==
ast_context.getCanonicalType(ast_context.getRecordType(record_decl)));
auto class_decl =
context.sem_ir().insts().GetAs<SemIR::ClassDecl>(clang_decl.inst_id);
CARBON_CHECK(name_id ==
context.sem_ir().classes().Get(class_decl.class_id).name_id);
return true;
}
// Looks up the given name in the Clang AST in a specific scope, and returns the
// found declaration and its access. If the found declaration is the injected
// class name, looks up constructors instead. If not found, returns `nullopt`.
// If there's not a single result, returns `nullptr` and default access.
// Otherwise, returns the single declaration and its access.
static auto ClangLookup(Context& context, SemIR::LocId loc_id,
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
-> std::optional<std::tuple<clang::NamedDecl*, clang::AccessSpecifier>> {
auto lookup = ClangLookupName(context, scope_id, name_id);
if (!lookup) {
return std::nullopt;
}
std::tuple<clang::NamedDecl*, clang::AccessSpecifier> result{
nullptr, clang::AccessSpecifier::AS_none};
// Access checks are performed separately by the Carbon name lookup logic.
lookup->suppressAccessDiagnostics();
if (!lookup->isSingleResult()) {
// Clang will diagnose ambiguous lookup results for us.
if (!lookup->isAmbiguous()) {
context.TODO(loc_id,
llvm::formatv("Unsupported: Lookup succeeded but couldn't "
"find a single result; LookupResultKind: {0}",
static_cast<int>(lookup->getResultKind())));
}
return result;
}
if (!IsDeclInjectedClassName(context, scope_id, name_id,
lookup->getFoundDecl())) {
result = {lookup->getFoundDecl(), lookup->begin().getAccess()};
return result;
}
clang::DeclContextLookupResult constructors_lookup =
ClangConstructorLookup(context, scope_id);
llvm::SmallVector<clang::CXXConstructorDecl*> constructors;
for (clang::Decl* decl : constructors_lookup) {
auto* constructor = cast<clang::CXXConstructorDecl>(decl);
if (constructor->isDeleted() || constructor->isCopyOrMoveConstructor()) {
continue;
}
constructors.push_back(constructor);
}
if (constructors.size() != 1) {
context.TODO(
loc_id,
llvm::formatv("Unsupported: Constructors lookup succeeded but couldn't "
"find a single result; Found {0} constructors",
constructors.size()));
return result;
}
result = {constructors[0], constructors[0]->getAccess()};
return result;
}
// Returns whether `decl` already mapped to an instruction.
static auto IsClangDeclImported(const Context& context, clang::Decl* decl)
-> bool {
@@ -1109,7 +1216,8 @@ static auto MakeImplicitParamPatternsBlockId(
Context& context, SemIR::LocId loc_id,
const clang::FunctionDecl& clang_decl) -> SemIR::InstBlockId {
const auto* method_decl = dyn_cast<clang::CXXMethodDecl>(&clang_decl);
if (!method_decl || method_decl->isStatic()) {
if (!method_decl || method_decl->isStatic() ||
isa<clang::CXXConstructorDecl>(clang_decl)) {
return SemIR::InstBlockId::Empty;
}
@@ -1229,22 +1337,54 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
return context.inst_blocks().Add(params);
}
// Returns the return type of the given function declaration. In case of an
// unsupported return type, it produces a diagnostic and returns
// `SemIR::ErrorInst::InstId`.
// Returns the return `TypeExpr` of the given function declaration. In case of
// an unsupported return type, returns `SemIR::ErrorInst::InstId`. Constructors
// are treated as returning a class instance.
// TODO: Support more return types.
static auto GetReturnType(Context& context, SemIR::LocId loc_id,
const clang::FunctionDecl* clang_decl)
-> SemIR::InstId {
static auto GetReturnTypeExpr(Context& context, SemIR::LocId loc_id,
clang::FunctionDecl* clang_decl) -> TypeExpr {
clang::QualType ret_type = clang_decl->getReturnType();
if (ret_type->isVoidType()) {
return SemIR::InstId::None;
if (!ret_type->isVoidType()) {
TypeExpr mapped_type = MapType(context, loc_id, ret_type);
if (!mapped_type.inst_id.has_value()) {
return {.inst_id = SemIR::ErrorInst::TypeInstId,
.type_id = SemIR::ErrorInst::TypeId};
}
return mapped_type;
}
auto [type_inst_id, type_id] = MapType(context, loc_id, ret_type);
if (!isa<clang::CXXConstructorDecl>(clang_decl)) {
// void.
return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
}
// TODO: Make this a `PartialType`.
SemIR::TypeInstId record_type_inst_id = context.types().GetAsTypeInstId(
context.sem_ir()
.clang_decls()
.Get(context.sem_ir().clang_decls().Lookup(
cast<clang::Decl>(clang_decl->getParent())))
.inst_id);
return {
.inst_id = record_type_inst_id,
.type_id = context.types().GetTypeIdForTypeInstId(record_type_inst_id)};
}
// Returns the return pattern of the given function declaration. In case of an
// unsupported return type, it produces a diagnostic and returns
// `SemIR::ErrorInst::InstId`. Constructors are treated as returning a class
// instance.
static auto GetReturnPattern(Context& context, SemIR::LocId loc_id,
clang::FunctionDecl* clang_decl) -> SemIR::InstId {
auto [type_inst_id, type_id] = GetReturnTypeExpr(context, loc_id, clang_decl);
if (!type_inst_id.has_value()) {
context.TODO(loc_id, llvm::formatv("Unsupported: return type: {0}",
ret_type.getAsString()));
// void.
return SemIR::InstId::None;
}
if (type_inst_id == SemIR::ErrorInst::TypeInstId) {
context.TODO(loc_id,
llvm::formatv("Unsupported: return type: {0}",
clang_decl->getReturnType().getAsString()));
return SemIR::ErrorInst::InstId;
}
auto pattern_type_id = GetPatternType(context, type_id);
@@ -1283,10 +1423,10 @@ struct FunctionParamsInsts {
// Produces a diagnostic and returns `std::nullopt` if the function declaration
// has an unsupported parameter type.
static auto CreateFunctionParamsInsts(Context& context, SemIR::LocId loc_id,
const clang::FunctionDecl* clang_decl)
clang::FunctionDecl* clang_decl)
-> std::optional<FunctionParamsInsts> {
if (isa<clang::CXXConstructorDecl, clang::CXXDestructorDecl>(clang_decl)) {
context.TODO(loc_id, "Unsupported: Constructor/Destructor");
if (isa<clang::CXXDestructorDecl>(clang_decl)) {
context.TODO(loc_id, "Unsupported: Destructor");
return std::nullopt;
}
@@ -1300,7 +1440,7 @@ static auto CreateFunctionParamsInsts(Context& context, SemIR::LocId loc_id,
if (!param_patterns_id.has_value()) {
return std::nullopt;
}
auto return_slot_pattern_id = GetReturnType(context, loc_id, clang_decl);
auto return_slot_pattern_id = GetReturnPattern(context, loc_id, clang_decl);
if (SemIR::ErrorInst::InstId == return_slot_pattern_id) {
return std::nullopt;
}
@@ -1343,8 +1483,19 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id,
AddPlaceholderInstInNoBlock(context, Parse::NodeId::None, function_decl);
context.imports().push_back(decl_id);
SemIR::NameId function_name_id =
isa<clang::CXXConstructorDecl>(clang_decl)
? context.classes()
.Get(context.insts()
.GetAs<SemIR::ClassDecl>(LookupClangDeclInstId(
context,
cast<clang::Decl>(clang_decl->getParent())))
.class_id)
.name_id
: AddIdentifierName(context, clang_decl->getName());
auto function_info = SemIR::Function{
{.name_id = AddIdentifierName(context, clang_decl->getName()),
{.name_id = function_name_id,
.parent_scope_id = GetParentNameScopeId(context, clang_decl),
.generic_id = SemIR::GenericId::None,
.first_param_node_id = Parse::NodeId::None,
@@ -1526,8 +1677,7 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
}
context.TODO(loc_id, llvm::formatv("Unsupported: Declaration type {0}",
clang_decl->getDeclKindName())
.str());
clang_decl->getDeclKindName()));
return SemIR::InstId::None;
}
@@ -1600,31 +1750,19 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
builder.Note(loc_id, InCppNameLookup, name_id);
});
auto lookup = ClangLookup(context, scope_id, name_id);
if (!lookup) {
auto decl_and_access = ClangLookup(context, loc_id, scope_id, name_id);
if (!decl_and_access) {
return SemIR::ScopeLookupResult::MakeNotFound();
}
// Access checks are performed separately by the Carbon name lookup logic.
lookup->suppressAccessDiagnostics();
if (!lookup->isSingleResult()) {
// Clang will diagnose ambiguous lookup results for us.
if (!lookup->isAmbiguous()) {
context.TODO(loc_id,
llvm::formatv("Unsupported: Lookup succeeded but couldn't "
"find a single result; LookupResultKind: {0}",
static_cast<int>(lookup->getResultKind()))
.str());
}
auto [decl, access] = *decl_and_access;
if (!decl) {
context.name_scopes().AddRequiredName(scope_id, name_id,
SemIR::ErrorInst::InstId);
return SemIR::ScopeLookupResult::MakeError();
}
return ImportNameDeclIntoScope(context, loc_id, scope_id, name_id,
lookup->getFoundDecl(),
lookup->begin().getAccess());
return ImportNameDeclIntoScope(context, loc_id, scope_id, name_id, decl,
access);
}
} // namespace Carbon::Check