SemIR::Namespace->clang::NamespaceDecl interop (#6935)

Rough-in with TODO for caching and scoping/nesting, this only handles
top level namespaces and doesn't nest them appropriately.
This commit is contained in:
David Blaikie
2026-03-18 23:33:49 +00:00
committed by GitHub
parent 7b3f120f97
commit 14b72f16da
4 changed files with 103 additions and 20 deletions
+88 -19
View File
@@ -25,6 +25,8 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
#include "toolchain/check/context.h"
#include "toolchain/check/cpp/import.h"
#include "toolchain/check/name_lookup.h"
#include "toolchain/diagnostics/diagnostic.h"
#include "toolchain/diagnostics/emitter.h"
#include "toolchain/diagnostics/format_providers.h"
@@ -334,8 +336,9 @@ class ShallowCopyCompilerInvocation : public clang::CompilerInvocation {
class CarbonExternalASTSource : public clang::ExternalASTSource {
public:
explicit CarbonExternalASTSource(clang::ASTContext* ast_context)
: ast_context_(*ast_context) {}
explicit CarbonExternalASTSource(Context* context,
clang::ASTContext* ast_context)
: context_(context), ast_context_(ast_context) {}
auto FindExternalVisibleDeclsByName(
const clang::DeclContext* decl_context, clang::DeclarationName decl_name,
@@ -344,41 +347,107 @@ class CarbonExternalASTSource : public clang::ExternalASTSource {
auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override;
private:
clang::ASTContext& ast_context_;
Check::Context* context_;
clang::ASTContext* ast_context_;
clang::NamespaceDecl* carbon_cpp_namespace_ = nullptr;
};
void CarbonExternalASTSource::StartTranslationUnit(
clang::ASTConsumer* /*Consumer*/) {
auto& translation_unit = *ast_context_.getTranslationUnitDecl();
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();
}
// Map a Carbon entity to a Clang NamedDecl.
static auto MapInstIdToClangDecl(Context& context,
clang::ASTContext& ast_context,
clang::DeclContext& decl_context,
LookupResult lookup) -> clang::NamedDecl* {
auto target_inst_id = lookup.scope_result.target_inst_id();
if (auto target_inst =
context.insts().TryGetAs<SemIR::Namespace>(target_inst_id)) {
auto& name_scope = context.name_scopes().Get(target_inst->name_scope_id);
auto* identifier_info =
GetClangIdentifierInfo(context, name_scope.name_id());
// 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.
return clang::NamespaceDecl::Create(
ast_context, &decl_context, false, clang::SourceLocation(),
clang::SourceLocation(), identifier_info, nullptr, false);
}
return nullptr;
}
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 (decl_context != carbon_cpp_namespace_) {
if (decl_context->getDeclKind() != clang::Decl::Kind::TranslationUnit) {
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();
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();
SetExternalVisibleDeclsForName(decl_context, decl_name,
{carbon_cpp_namespace_});
return true;
}
// Lookup the name in Carbon package scope
llvm::SmallVector<Check::LookupScope> lookup_scopes;
// LocId::None seems fine here because we shouldn't produce any diagnostics
// here - completeness should've been checked by clang before this point.
if (!AppendLookupScopesForConstant(
*context_, SemIR::LocId::None,
context_->constant_values().Get(SemIR::Namespace::PackageInstId),
SemIR::ConstantId::None, &lookup_scopes)) {
return false;
}
static const llvm::StringLiteral carbon_namespace_name = "Carbon";
if (auto* identifier = decl_name.getAsIdentifierInfo();
!identifier || !identifier->isStr(carbon_namespace_name)) {
auto* identifier = decl_name.getAsIdentifierInfo();
if (!identifier) {
// Only supporting identifiers for now.
return false;
}
auto& ast_context = decl_context->getParentASTContext();
auto& mutable_tu_decl_context = *ast_context.getTranslationUnitDecl();
SetExternalVisibleDeclsForName(
decl_context, decl_name,
{
clang::NamespaceDecl::Create(
ast_context, &mutable_tu_decl_context, false,
clang::SourceLocation(), clang::SourceLocation(),
&ast_context.Idents.get(carbon_namespace_name), nullptr, false),
});
auto name_id = AddIdentifierName(*context_, identifier->getName());
// `required=false` so Carbon doesn't diagnose a failure, let Clang diagnose
// it or even SFINAE.
LookupResult result =
LookupQualifiedName(*context_, SemIR::LocId::None, name_id, lookup_scopes,
/*required=*/false);
if (!result.scope_result.is_found()) {
return false;
}
// Map the found Carbon entity to a Clang NamedDecl.
auto* clang_decl = MapInstIdToClangDecl(*context_, *ast_context_,
*carbon_cpp_namespace_, result);
if (!clang_decl) {
return false;
}
SetExternalVisibleDeclsForName(decl_context, decl_name, {clang_decl});
return true;
}
@@ -526,7 +595,7 @@ auto GenerateAst(Context& context,
// support. Implement multiplexing support (possibly in Clang) to restore
// modules functionality.
ast.setExternalSource(
llvm::makeIntrusiveRefCnt<CarbonExternalASTSource>(&ast));
llvm::makeIntrusiveRefCnt<CarbonExternalASTSource>(&context, &ast));
if (llvm::Error error = action.Execute()) {
// `Execute` currently never fails, but its contract allows it to.