Only export each class or namespace to C++ once. (#7042)

Instead of exporting a class or namespace each time a new C++ name
lookup discovers it, track that we have exported the entity on its name
scope, and if a new name lookup finds the same entity, produce the same
clang declaration.
This commit is contained in:
Richard Smith
2026-04-13 19:43:44 +00:00
committed by GitHub
parent 3cdb159067
commit f31e1685fd
11 changed files with 325 additions and 161 deletions
+163 -4
View File
@@ -14,6 +14,159 @@
namespace Carbon::Check {
// If the given name scope was produced by importing a C++ declaration or has
// already been exported to C++, return the corresponding Clang decl context.
static auto GetClangDeclContextForScope(Context& context,
SemIR::NameScopeId scope_id)
-> clang::DeclContext* {
if (!scope_id.has_value()) {
return nullptr;
}
auto& scope = context.name_scopes().Get(scope_id);
auto clang_decl_context_id = scope.clang_decl_context_id();
if (!clang_decl_context_id.has_value()) {
return nullptr;
}
auto* decl = context.clang_decls().Get(clang_decl_context_id).key.decl;
return cast<clang::DeclContext>(decl);
}
auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id,
SemIR::NameScopeId name_scope_id)
-> clang::DeclContext* {
llvm::SmallVector<SemIR::NameScopeId> name_scope_ids_to_create;
// Walk through the parent scopes, looking for one that's already mapped into
// C++. We already mapped the package scope to ::Carbon, so we must find one.
clang::DeclContext* decl_context = nullptr;
while (true) {
// If this name scope was produced by importing a C++ declaration or has
// already been exported to C++, return the corresponding Clang declaration.
if (auto* existing_decl_context =
GetClangDeclContextForScope(context, name_scope_id)) {
decl_context = existing_decl_context;
break;
}
// Otherwise, continue to the parent and create a scope for it first.
name_scope_ids_to_create.push_back(name_scope_id);
name_scope_id = context.name_scopes().Get(name_scope_id).parent_scope_id();
// TODO: What should happen if there's an intervening function scope?
CARBON_CHECK(
name_scope_id.has_value(),
"Reached the top level without finding a scope mapped into C++");
}
// Create the name scopes in order, starting from the outermost one.
while (!name_scope_ids_to_create.empty()) {
name_scope_id = name_scope_ids_to_create.pop_back_val();
auto& name_scope = context.name_scopes().Get(name_scope_id);
auto* identifier_info =
GetClangIdentifierInfo(context, name_scope.name_id());
if (!identifier_info) {
// TODO: Handle keyword package names like `Cpp` and `Core`. These can
// be named from C++ via an alias.
context.TODO(loc_id, "interop with non-identifier package name");
return nullptr;
}
auto inst = context.insts().Get(name_scope.inst_id());
if (inst.Is<SemIR::Namespace>()) {
// TODO: Provide a source location.
auto* namespace_decl = clang::NamespaceDecl::Create(
context.ast_context(), decl_context, false, clang::SourceLocation(),
clang::SourceLocation(), identifier_info, nullptr, false);
decl_context = namespace_decl;
} else if (inst.Is<SemIR::ClassDecl>()) {
// TODO: Provide a source location.
auto* record_decl = clang::CXXRecordDecl::Create(
context.ast_context(), clang::TagTypeKind::Class, decl_context,
clang::SourceLocation(), clang::SourceLocation(), identifier_info);
// If this is a member class, set its access.
if (isa<clang::CXXRecordDecl>(decl_context)) {
// TODO: Map Carbon access to C++ access.
record_decl->setAccess(clang::AS_public);
}
decl_context = record_decl;
decl_context->setHasExternalLexicalStorage();
} else {
context.TODO(loc_id, "non-class non-namespace name scope");
return nullptr;
}
decl_context->setHasExternalVisibleStorage();
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(
cast<clang::Decl>(decl_context));
auto clang_decl_id = context.clang_decls().Add(
{.key = key, .inst_id = name_scope.inst_id()});
name_scope.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
}
return decl_context;
}
auto ExportClassToCpp(Context& context, SemIR::LocId loc_id,
SemIR::InstId class_inst_id, SemIR::ClassType class_type)
-> clang::TagDecl* {
// TODO: A lot of logic in this function is shared with ExportNameScopeToCpp.
// This should be refactored.
if (class_type.specific_id.has_value()) {
context.TODO(loc_id, "interop with specific class");
return nullptr;
}
const auto& class_info = context.classes().Get(class_type.class_id);
// If this class was produced by importing a C++ declaration or has
// already been exported to C++, return the corresponding Clang declaration.
// That could either be a CXXRecordDecl or an EnumDecl.
if (auto* decl_context =
GetClangDeclContextForScope(context, class_info.scope_id)) {
return cast<clang::TagDecl>(decl_context);
}
auto* identifier_info = GetClangIdentifierInfo(context, class_info.name_id);
if (!identifier_info) {
// TODO: Handle keyword package names like `Cpp` and `Core`. These can
// be named from C++ via an alias.
context.TODO(loc_id, "interop with non-identifier package name");
return nullptr;
}
auto* decl_context =
ExportNameScopeToCpp(context, loc_id, class_info.parent_scope_id);
// TODO: Provide a source location.
auto* record_decl = clang::CXXRecordDecl::Create(
context.ast_context(), clang::TagTypeKind::Class, decl_context,
clang::SourceLocation(), clang::SourceLocation(), identifier_info);
// If this is a member class, set its access.
if (isa<clang::CXXRecordDecl>(decl_context)) {
// TODO: Map Carbon access to C++ access.
record_decl->setAccess(clang::AS_public);
}
record_decl->setHasExternalLexicalStorage();
record_decl->setHasExternalVisibleStorage();
auto key =
SemIR::ClangDeclKey::ForNonFunctionDecl(cast<clang::Decl>(record_decl));
auto clang_decl_id =
context.clang_decls().Add({.key = key, .inst_id = class_inst_id});
if (class_info.scope_id.has_value()) {
// TODO: Record the Carbon class -> clang declaration mapping for incomplete
// classes too.
context.name_scopes()
.Get(class_info.scope_id)
.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
}
return record_decl;
}
// Create a `clang::FunctionDecl` for the given Carbon function. This
// can be used to call the Carbon function from C++. The Carbon
// function's ABI must be compatible with C++.
@@ -233,9 +386,8 @@ static auto BuildCarbonToCarbonThunk(
return carbon_thunk_function_id;
}
auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
clang::DeclContext& decl_context,
SemIR::FunctionId callee_function_id)
auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
SemIR::FunctionId callee_function_id)
-> clang::FunctionDecl* {
const SemIR::Function& callee = context.functions().Get(callee_function_id);
@@ -260,6 +412,13 @@ auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
return nullptr;
}
// Map the parent scope into the C++ AST.
auto* decl_context =
ExportNameScopeToCpp(context, loc_id, callee.parent_scope_id);
if (!decl_context) {
return nullptr;
}
// Get the parameter types of the Carbon function being called.
auto callee_function_params =
context.inst_blocks().Get(callee.call_param_patterns_id);
@@ -283,7 +442,7 @@ auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
}
// Create a C++ thunk that calls the Carbon thunk.
return BuildCppToCarbonThunk(context, loc_id, &decl_context,
return BuildCppToCarbonThunk(context, loc_id, decl_context,
context.names().GetFormatted(callee.name_id),
carbon_function_decl, callee_param_type_ids);
}
+22 -4
View File
@@ -11,11 +11,29 @@
namespace Carbon::Check {
// Exports a Carbon name scope into C++ as a namespace or class, or returns the
// C++ namespace or class declaration that it was imported from.
//
// If the name scope has already been exported, returns the existing context.
// Otherwise, creates a new C++ declaration context and returns it. Returns
// nullptr if the name scope could not be exported and an error was diagnosed.
auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id,
SemIR::NameScopeId name_scope_id)
-> clang::DeclContext*;
// Exports a Carbon class into C++ as a class, or returns the C++ tag type that
// the class was imported from.
//
// If the class has already been exported, returns the existing C++ class.
// Otherwise, creates a new C++ class and returns it. Returns nullptr if the
// class could not be exported and an error was diagnosed.
auto ExportClassToCpp(Context& context, SemIR::LocId loc_id,
SemIR::InstId class_inst_id, SemIR::ClassType class_type)
-> clang::TagDecl*;
// Get a `clang::FunctionDecl` that can be used to call a Carbon function.
auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
clang::DeclContext& decl_context,
SemIR::FunctionId function_id)
-> clang::FunctionDecl*;
auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
SemIR::FunctionId function_id) -> clang::FunctionDecl*;
} // namespace Carbon::Check
+56 -124
View File
@@ -321,9 +321,7 @@ class ShallowCopyCompilerInvocation : public clang::CompilerInvocation {
// Provides clang AST nodes representing Carbon SemIR entities.
class CarbonExternalASTSource : public clang::ExternalASTSource {
public:
explicit CarbonExternalASTSource(Context* context,
clang::ASTContext* ast_context)
: context_(context), ast_context_(ast_context) {}
explicit CarbonExternalASTSource(Context* context) : context_(context) {}
auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override;
@@ -336,10 +334,13 @@ class CarbonExternalASTSource : public clang::ExternalASTSource {
auto CompleteType(clang::TagDecl* tag_decl) -> void override;
private:
// Builds the top-level C++ namespace `Carbon` and adds it to the translation
// unit.
auto BuildCarbonNamespace() -> void;
// Map a Carbon entity to a Clang NamedDecl. Returns null if the entity cannot
// currently be represented in C++.
auto MapInstIdToClangDecl(clang::DeclContext& decl_context,
LookupResult lookup) -> clang::NamedDecl*;
auto MapInstIdToClangDecl(LookupResult lookup) -> clang::NamedDecl*;
// Get a current best-effort location for the current position within C++
// processing.
@@ -364,41 +365,16 @@ class CarbonExternalASTSource : public clang::ExternalASTSource {
}
Check::Context* context_;
clang::ASTContext* ast_context_;
// Has the "Carbon" C++ namespace been created yet
// (this could be replaced with `!scope_map_.empty()` if Carbon::Map supported
// `empty()`)
bool root_scope_initialized_ = false;
};
} // namespace
void CarbonExternalASTSource::StartTranslationUnit(
clang::ASTConsumer* /*Consumer*/) {
auto& translation_unit = *ast_context_->getTranslationUnitDecl();
// Mark the translation unit as having external storage so we get a query for
// the `Carbon` namespace in the top level/translation unit scope.
translation_unit.setHasExternalVisibleStorage();
BuildCarbonNamespace();
}
// If the given name scope was produced by importing a C++ declaration, return
// the corresponding Clang declaration.
static auto GetClangDeclForScope(Context& context, SemIR::NameScopeId scope_id)
-> clang::NamedDecl* {
if (!scope_id.has_value()) {
return nullptr;
}
auto& scope = context.name_scopes().Get(scope_id);
if (!scope.is_cpp_scope()) {
return nullptr;
}
return dyn_cast<clang::NamedDecl>(
context.clang_decls().Get(scope.clang_decl_context_id()).key.decl);
}
auto CarbonExternalASTSource::MapInstIdToClangDecl(
clang::DeclContext& decl_context, LookupResult lookup)
auto CarbonExternalASTSource::MapInstIdToClangDecl(LookupResult lookup)
-> clang::NamedDecl* {
auto target_inst_id = lookup.scope_result.target_inst_id();
auto target_constant =
@@ -406,57 +382,22 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl(
auto target_inst = context_->insts().Get(target_constant);
CARBON_KIND_SWITCH(target_inst) {
case CARBON_KIND(SemIR::Namespace namespace_info): {
if (auto* decl =
GetClangDeclForScope(*context_, namespace_info.name_scope_id)) {
return decl;
}
auto& name_scope =
context_->name_scopes().Get(namespace_info.name_scope_id);
auto* identifier_info =
GetClangIdentifierInfo(*context_, name_scope.name_id());
if (!identifier_info) {
// TODO: Handle keyword package names like `Cpp` and `Core`. These can
// be named from C++ via an alias.
context_->TODO(SemIR::LocId(target_inst_id),
"interop with non-identifier package name");
auto* decl_context =
ExportNameScopeToCpp(*context_, SemIR::LocId(target_inst_id),
namespace_info.name_scope_id);
if (!decl_context) {
return nullptr;
}
// TODO: Don't immediately use the decl_context - build any intermediate
// namespaces iteratively.
// Eventually add a mapping and use that/populate it/keep it up to date.
// decl_context could be prepopulated in that mapping and not passed
// explicitly to MapInstIdToClangDecl.
auto* namespace_decl = clang::NamespaceDecl::Create(
*ast_context_, &decl_context, false, clang::SourceLocation(),
clang::SourceLocation(), identifier_info, nullptr, false);
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(namespace_decl);
context_->clang_decls().Add({.key = key, .inst_id = target_inst_id});
namespace_decl->setHasExternalVisibleStorage();
return namespace_decl;
if (isa<clang::TranslationUnitDecl>(decl_context)) {
context_->TODO(GetCurrentCppLocId(),
"interop with translation unit decl");
return nullptr;
}
return cast<clang::NamedDecl>(decl_context);
}
case CARBON_KIND(SemIR::ClassType class_type): {
const auto& class_info = context_->classes().Get(class_type.class_id);
if (auto* decl = GetClangDeclForScope(*context_, class_info.scope_id)) {
return decl;
}
auto* identifier_info =
GetClangIdentifierInfo(*context_, class_info.name_id);
CARBON_CHECK(identifier_info, "class with non-identifier name {0}",
class_info.name_id);
// TODO: Check whether we've already mapped this class and if so, return
// the prior mapping.
auto* record_decl = clang::CXXRecordDecl::Create(
*ast_context_, clang::TagTypeKind::Class, &decl_context,
clang::SourceLocation(), clang::SourceLocation(), identifier_info);
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(record_decl);
context_->clang_decls().Add({.key = key, .inst_id = target_inst_id});
record_decl->setHasExternalLexicalStorage();
record_decl->setHasExternalVisibleStorage();
if (isa<clang::CXXRecordDecl>(decl_context)) {
// TODO: Map Carbon access to C++ access.
record_decl->setAccess(clang::AS_public);
}
return record_decl;
return ExportClassToCpp(*context_, SemIR::LocId(target_inst_id),
target_inst_id, class_type);
}
case SemIR::StructValue::Kind: {
auto callee = GetCallee(context_->sem_ir(), target_constant);
@@ -472,50 +413,44 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl(
context_->clang_decls().Get(function.clang_decl_id).key.decl);
}
return GetReverseInteropFunctionDecl(
*context_, SemIR::LocId(target_inst_id), decl_context,
callee_function->function_id);
return ExportFunctionToCpp(*context_, SemIR::LocId(target_inst_id),
callee_function->function_id);
}
default:
return nullptr;
}
}
auto CarbonExternalASTSource::BuildCarbonNamespace() -> void {
static const llvm::StringLiteral carbon_namespace_name = "Carbon";
auto& ast_context = context_->ast_context();
auto* identifier = &ast_context.Idents.get(carbon_namespace_name);
// Create the namespace and add it to the translation unit scope.
auto* decl_context = ast_context.getTranslationUnitDecl();
auto* carbon_cpp_namespace = clang::NamespaceDecl::Create(
ast_context, decl_context, /*Inline=*/false, clang::SourceLocation(),
clang::SourceLocation(), identifier, /*PrevDecl=*/nullptr,
/*Nested=*/false);
decl_context->addDecl(carbon_cpp_namespace);
// We provide custom lookup results within this namespace.
carbon_cpp_namespace->setHasExternalVisibleStorage();
// Register this file's package scope as corresponding to the `Carbon`
// namespace in C++.
// TODO: For mangling purposes, include the package as a sub-namespace.
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(carbon_cpp_namespace);
auto clang_decl_id = context_->clang_decls().Add(
{.key = key, .inst_id = SemIR::Namespace::PackageInstId});
context_->name_scopes()
.Get(SemIR::NameScopeId::Package)
.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
}
auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
const clang::DeclContext* decl_context, clang::DeclarationName decl_name,
const clang::DeclContext* /*OriginalDC*/) -> bool {
if (decl_context->getDeclKind() == clang::Decl::Kind::TranslationUnit) {
// If the context doesn't already have a mapping between C++ and Carbon,
// check if this is the root mapping (for the "Carbon" namespace in the
// translation unit scope) and if so, create that mapping.
if (root_scope_initialized_) {
return false;
}
static const llvm::StringLiteral carbon_namespace_name = "Carbon";
if (auto* identifier = decl_name.getAsIdentifierInfo();
!identifier || !identifier->isStr(carbon_namespace_name)) {
return false;
}
// Build the top level 'Carbon' namespace
auto& ast_context = decl_context->getParentASTContext();
auto& mutable_tu_decl_context = *ast_context.getTranslationUnitDecl();
auto* carbon_cpp_namespace = clang::NamespaceDecl::Create(
ast_context, &mutable_tu_decl_context, false, clang::SourceLocation(),
clang::SourceLocation(), &ast_context.Idents.get(carbon_namespace_name),
nullptr, false);
carbon_cpp_namespace->setHasExternalVisibleStorage();
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(carbon_cpp_namespace);
context_->clang_decls().Add(
{.key = key, .inst_id = SemIR::Namespace::PackageInstId});
SetExternalVisibleDeclsForName(decl_context, decl_name,
{carbon_cpp_namespace});
root_scope_initialized_ = true;
return true;
}
// Find the Carbon declaration corresponding to this Clang declaration.
auto* decl = cast<clang::Decl>(
const_cast<clang::DeclContext*>(decl_context->getPrimaryContext()));
@@ -555,11 +490,7 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
}
// Map the found Carbon entity to a Clang NamedDecl.
// TODO: Stop passing in the `DeclContext` here; the context in which we
// performed the lookup that first found the Carbon declaration should not
// affect the Clang declaration we produce.
auto* clang_decl = MapInstIdToClangDecl(
*const_cast<clang::DeclContext*>(decl_context), result);
auto* clang_decl = MapInstIdToClangDecl(result);
if (!clang_decl) {
return false;
}
@@ -681,16 +612,17 @@ class GenerateASTAction : public clang::ASTFrontendAction {
auto& parser = *parser_ptr;
clang_instance.getPreprocessor().EnterMainSourceFile();
parser.Initialize();
context_->set_cpp_context(
std::make_unique<CppContext>(clang_instance, std::move(parser_ptr)));
if (auto* source = clang_instance.getASTContext().getExternalSource()) {
source->StartTranslationUnit(&clang_instance.getASTConsumer());
}
parser.Initialize();
clang_instance.getSema().ActOnStartOfTranslationUnit();
context_->set_cpp_context(
std::make_unique<CppContext>(clang_instance, std::move(parser_ptr)));
ParseTopLevelDecls(parser, clang_instance.getASTConsumer());
}
@@ -765,7 +697,7 @@ auto GenerateAst(Context& context,
// support. Implement multiplexing support (possibly in Clang) to restore
// modules functionality.
ast.setExternalSource(
llvm::makeIntrusiveRefCnt<CarbonExternalASTSource>(&context, &ast));
llvm::makeIntrusiveRefCnt<CarbonExternalASTSource>(&context));
if (llvm::Error error = action.Execute()) {
// `Execute` currently never fails, but its contract allows it to.
+9 -6
View File
@@ -137,10 +137,12 @@ auto ImportCpp(Context& context,
name_scope.set_is_closed_import(true);
if (GenerateAst(context, imports, fs, llvm_context, std::move(invocation))) {
name_scope.set_clang_decl_context_id(context.clang_decls().Add(
{.key = SemIR::ClangDeclKey(
context.ast_context().getTranslationUnitDecl()),
.inst_id = name_scope.inst_id()}));
name_scope.set_clang_decl_context_id(
context.clang_decls().Add(
{.key = SemIR::ClangDeclKey(
context.ast_context().getTranslationUnitDecl()),
.inst_id = name_scope.inst_id()}),
/*is_cpp_scope=*/true);
} else {
name_scope.set_has_error();
}
@@ -302,7 +304,8 @@ static auto ImportNamespaceDecl(Context& context,
context.name_scopes()
.Get(result.name_scope_id)
.set_clang_decl_context_id(
context.clang_decls().Add({.key = key, .inst_id = result.inst_id}));
context.clang_decls().Add({.key = key, .inst_id = result.inst_id}),
/*is_cpp_scope=*/true);
return result.inst_id;
}
@@ -373,7 +376,7 @@ static auto ImportTagDecl(Context& context, clang::TagDecl* clang_decl)
class_inst_id, SemIR::NameId::None, class_info.parent_scope_id);
context.name_scopes()
.Get(class_info.scope_id)
.set_clang_decl_context_id(clang_decl_id);
.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/true);
return class_inst_id;
}