C++ Interop: Basic C++ record (class/struct/union) import support (#5156)

Focus: Calling static C++ function defined in C++ classes.

Limitations:
* Ignores visibility (public / protected / private).
* No support for: dynamic classes, member methods, data members,
declarations without definitions, importing inheritance.

Based on #5142.

C++ Interop Demo with a class:

```c++
// hello_world.h

namespace some_namespace {

class MyClass {
 public:
  static void hello_world();
};

}  // namespace some_namespace
```

```c++
// hello_world.cpp

#include "hello_world.h"

#include <cstdio>

namespace some_namespace {

void MyClass::hello_world() { printf("Hello World!\n"); }

}  // namespace some_namespace
```

```carbon
// main.carbon

library "Main";

import Cpp library "hello_world.h";

fn Run() -> i32 {
  Cpp.some_namespace.MyClass.hello_world();
  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
Hello World!
```

Part of #5150.
This commit is contained in:
Boaz Brickner
2025-04-10 08:15:12 +00:00
committed by GitHub
parent a91752de60
commit 68111a994c
9 changed files with 3441 additions and 244 deletions
+116 -9
View File
@@ -15,9 +15,11 @@
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
#include "toolchain/check/class.h"
#include "toolchain/check/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/diagnostic_helpers.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/import.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/literal.h"
@@ -218,8 +220,8 @@ auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
// Look ups 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::LocId loc_id,
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
SemIR::NameId name_id)
-> std::optional<clang::LookupResult> {
std::optional<llvm::StringRef> name =
context.names().GetAsStringIfIdentifier(name_id);
@@ -239,16 +241,14 @@ static auto ClangLookup(Context& context, SemIR::LocId loc_id,
sema.getPreprocessor().getIdentifierInfo(*name)),
clang::SourceLocation()),
clang::Sema::LookupNameKind::LookupOrdinaryName);
// TODO: Diagnose on access and return the `AccessKind` for storage. We'll
// probably need a dedicated `DiagnosticConsumer` because
// `TextDiagnosticPrinter` assumes we're processing a C++ source file.
lookup.suppressDiagnostics();
bool found = sema.LookupQualifiedName(
lookup, context.name_scopes().Get(scope_id).cpp_decl_context());
if (lookup.isClassLookup()) {
// TODO: To support class lookup, also return the AccessKind for storage.
context.TODO(loc_id, "Unsupported: Lookup in Class");
return std::nullopt;
}
if (!found) {
return std::nullopt;
}
@@ -433,6 +433,108 @@ static auto ImportNamespaceDecl(Context& context,
return result.inst_id;
}
// Creates a class declaration for the given class name in the given scope.
// Returns the `InstId` for the declaration.
static auto BuildClassDecl(Context& context, SemIR::NameScopeId parent_scope_id,
SemIR::NameId name_id)
-> std::tuple<SemIR::ClassId, SemIR::InstId> {
// Add the class declaration.
auto class_decl =
SemIR::ClassDecl{.type_id = SemIR::TypeType::SingletonTypeId,
.class_id = SemIR::ClassId::None,
.decl_block_id = SemIR::InstBlockId::None};
// TODO: Consider setting a proper location.
auto class_decl_id =
AddPlaceholderInst(context, SemIR::LocIdAndInst::NoLoc(class_decl));
SemIR::Class class_info = {
{.name_id = name_id,
.parent_scope_id = parent_scope_id,
.generic_id = SemIR::GenericId::None,
.first_param_node_id = Parse::NodeId::None,
.last_param_node_id = Parse::NodeId::None,
.pattern_block_id = SemIR::InstBlockId::None,
.implicit_param_patterns_id = SemIR::InstBlockId::None,
.param_patterns_id = SemIR::InstBlockId::None,
.is_extern = false,
.extern_library_id = SemIR::LibraryNameId::None,
.non_owning_decl_id = SemIR::InstId::None,
.first_owning_decl_id = class_decl_id},
{// `.self_type_id` depends on the ClassType, so is set below.
.self_type_id = SemIR::TypeId::None,
// TODO: Support Dynamic classes.
// TODO: Support Final classes.
.inheritance_kind = SemIR::Class::Base}};
class_decl.class_id = context.classes().Add(class_info);
// Write the class ID into the ClassDecl.
ReplaceInstBeforeConstantUse(context, class_decl_id, class_decl);
SetClassSelfType(context, class_decl.class_id);
return {class_decl.class_id, class_decl_id};
}
// Creates a class definition for the given class name in the given scope based
// on the information in the given Clang declaration. Returns the `InstId` for
// the declaration, which is assumed to be for a class definition. Returns the
// new class id and instruction id.
static auto BuildClassDefinition(Context& context,
SemIR::NameScopeId parent_scope_id,
SemIR::NameId name_id,
clang::CXXRecordDecl* clang_decl)
-> std::tuple<SemIR::ClassId, SemIR::InstId> {
auto [class_id, class_decl_id] =
BuildClassDecl(context, parent_scope_id, name_id);
auto& class_info = context.classes().Get(class_id);
StartClassDefinition(context, class_info, class_decl_id);
context.name_scopes()
.Get(class_info.scope_id)
.set_cpp_decl_context(clang_decl);
return {class_id, class_decl_id};
}
// Imports a record declaration from Clang to Carbon. If successful, returns
// the new Carbon class declaration `InstId`.
// TODO: Change `clang_decl` to `const &` when lookup is using `clang::DeclID`
// and we don't need to store the decl for lookup context.
static auto ImportCXXRecordDecl(Context& context, SemIR::LocId loc_id,
SemIR::NameScopeId parent_scope_id,
SemIR::NameId name_id,
clang::CXXRecordDecl* clang_decl)
-> SemIR::InstId {
clang::CXXRecordDecl* clang_def = clang_decl->getDefinition();
if (!clang_def) {
context.TODO(loc_id,
"Unsupported: Record declarations without a definition");
return SemIR::ErrorInst::SingletonInstId;
}
if (clang_def->isDynamicClass()) {
context.TODO(loc_id, "Unsupported: Dynamic Class");
return SemIR::ErrorInst::SingletonInstId;
}
auto [class_id, class_def_id] =
BuildClassDefinition(context, parent_scope_id, name_id, clang_def);
// The class type is now fully defined. Compute its object representation.
ComputeClassObjectRepr(context,
// TODO: Consider having a proper location here.
Parse::NodeId::None, class_id,
// TODO: Set fields.
/*field_decls=*/{},
// TODO: Set vtable.
/*vtable_contents=*/{},
// TODO: Set block.
/*body=*/{});
return class_def_id;
}
// Imports a declaration from Clang to Carbon. If successful, returns the
// instruction for the new Carbon declaration.
static auto ImportNameDecl(Context& context, SemIR::LocId loc_id,
@@ -448,6 +550,11 @@ static auto ImportNameDecl(Context& context, SemIR::LocId loc_id,
return ImportNamespaceDecl(context, scope_id, name_id,
clang_namespace_decl);
}
if (auto* clang_record_decl =
clang::dyn_cast<clang::CXXRecordDecl>(clang_decl)) {
return ImportCXXRecordDecl(context, loc_id, scope_id, name_id,
clang_record_decl);
}
context.TODO(loc_id, llvm::formatv("Unsupported: Declaration type {0}",
clang_decl->getDeclKindName())
@@ -465,7 +572,7 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
builder.Note(loc_id, InCppNameLookup, name_id);
});
auto lookup = ClangLookup(context, loc_id, scope_id, name_id);
auto lookup = ClangLookup(context, scope_id, name_id);
if (!lookup) {
return SemIR::InstId::None;
}