[cxx-interop] Import decls in extern blocks within namespaces

This teaches ClangImporter to import C++ decls that are declared within `extern "C" { ... }`/`extern "C++" { ... }` blocks which are nested in namespaces.

rdar://139067788
This commit is contained in:
Egor Zhdan
2025-08-04 16:15:43 +01:00
parent 8335ce577f
commit 844787fddb
8 changed files with 173 additions and 60 deletions

View File

@@ -2738,11 +2738,17 @@ static void addNamespaceMembers(Decl *decl,
if (declOwner && declOwner != redeclOwner->getTopLevelModule()) if (declOwner && declOwner != redeclOwner->getTopLevelModule())
continue; continue;
} }
for (auto member : redecl->decls()) {
if (auto classTemplate = dyn_cast<clang::ClassTemplateDecl>(member)) { std::function<void(clang::DeclContext *)> addDeclsFromContext =
// Add all specializations to a worklist so we don't accidentally mutate [&](clang::DeclContext *declContext) {
// the list of decls we're iterating over. for (auto member : declContext->decls()) {
llvm::SmallPtrSet<const clang::ClassTemplateSpecializationDecl *, 16> specWorklist; if (auto classTemplate =
dyn_cast<clang::ClassTemplateDecl>(member)) {
// Add all specializations to a worklist so we don't accidentally
// mutate the list of decls we're iterating over.
llvm::SmallPtrSet<const clang::ClassTemplateSpecializationDecl *,
16>
specWorklist;
for (auto spec : classTemplate->specializations()) for (auto spec : classTemplate->specializations())
specWorklist.insert(spec); specWorklist.insert(spec);
for (auto spec : specWorklist) { for (auto spec : specWorklist) {
@@ -2753,14 +2759,20 @@ static void addNamespaceMembers(Decl *decl,
} }
} }
auto lookupAndAddMembers = [&](DeclName name) { auto lookupAndAddMembers = [&](clang::NamedDecl *namedDecl) {
auto name = ctx.getClangModuleLoader()->importName(namedDecl);
if (!name)
return;
auto allResults = evaluateOrDefault( auto allResults = evaluateOrDefault(
ctx.evaluator, ClangDirectLookupRequest({decl, redecl, name}), {}); ctx.evaluator, ClangDirectLookupRequest({decl, redecl, name}),
{});
for (auto found : allResults) { for (auto found : allResults) {
auto clangMember = cast<clang::NamedDecl *>(found); auto clangMember = cast<clang::NamedDecl *>(found);
if (auto importedDecl = if (auto importedDecl =
ctx.getClangModuleLoader()->importDeclDirectly(clangMember)) { ctx.getClangModuleLoader()->importDeclDirectly(
clangMember)) {
if (addedMembers.insert(importedDecl).second) { if (addedMembers.insert(importedDecl).second) {
members.push_back(importedDecl); members.push_back(importedDecl);
@@ -2770,9 +2782,11 @@ static void addNamespaceMembers(Decl *decl,
if (!valueDecl) if (!valueDecl)
return; return;
// Bail out if the auxiliary decl was not produced by a macro. // Bail out if the auxiliary decl was not produced by a
// macro.
auto module = decl->getDeclContext()->getParentModule(); auto module = decl->getDeclContext()->getParentModule();
auto *sf = module->getSourceFileContainingLocation(decl->getLoc()); auto *sf = module->getSourceFileContainingLocation(
decl->getLoc());
if (!sf || sf->Kind != SourceFileKind::MacroExpansion) if (!sf || sf->Kind != SourceFileKind::MacroExpansion)
return; return;
@@ -2783,27 +2797,28 @@ static void addNamespaceMembers(Decl *decl,
} }
}; };
// Look through `extern` blocks.
if (auto linkageSpecDecl = dyn_cast<clang::LinkageSpecDecl>(member))
addDeclsFromContext(linkageSpecDecl);
auto namedDecl = dyn_cast<clang::NamedDecl>(member); auto namedDecl = dyn_cast<clang::NamedDecl>(member);
if (!namedDecl) if (!namedDecl)
continue; continue;
auto name = ctx.getClangModuleLoader()->importName(namedDecl); lookupAndAddMembers(namedDecl);
if (!name)
continue;
lookupAndAddMembers(name);
// Unscoped enums could have their enumerators present // Unscoped enums could have their enumerators present
// in the parent namespace. // in the parent namespace.
if (auto *ed = dyn_cast<clang::EnumDecl>(member)) { if (auto *ed = dyn_cast<clang::EnumDecl>(member)) {
if (!ed->isScoped()) { if (!ed->isScoped()) {
for (const auto *ecd : ed->enumerators()) { for (auto *ecd : ed->enumerators()) {
auto name = ctx.getClangModuleLoader()->importName(ecd); lookupAndAddMembers(ecd);
if (!name)
continue;
lookupAndAddMembers(name);
} }
} }
} }
} }
};
addDeclsFromContext(redecl);
} }
} }

View File

@@ -5060,6 +5060,11 @@ static bool isDirectLookupMemberContext(const clang::Decl *foundClangDecl,
return firstDecl->getCanonicalDecl() == parent->getCanonicalDecl(); return firstDecl->getCanonicalDecl() == parent->getCanonicalDecl();
} }
} }
// Look through `extern` blocks.
if (auto linkageSpecDecl = dyn_cast<clang::LinkageSpecDecl>(memberContext)) {
if (auto parentDecl = dyn_cast<clang::Decl>(linkageSpecDecl->getParent()))
return isDirectLookupMemberContext(foundClangDecl, parentDecl, parent);
}
return false; return false;
} }

View File

@@ -1154,11 +1154,14 @@ namespace {
return nullptr; return nullptr;
// If this is a top-level namespace, don't put it in the module we're // If this is a top-level namespace, don't put it in the module we're
// importing, put it in the "__ObjC" module that is implicitly imported. // importing, put it in the "__ObjC" module that is implicitly imported.
if (!decl->getParent()->isNamespace()) auto clangDC = decl->getDeclContext();
while (isa<clang::LinkageSpecDecl>(clangDC))
clangDC = clangDC->getParent();
if (!clangDC->isNamespace())
dc = Impl.ImportedHeaderUnit; dc = Impl.ImportedHeaderUnit;
else { else {
// This is a nested namespace, so just lookup it's parent normally. // This is a nested namespace, so just lookup it's parent normally.
auto parentNS = cast<clang::NamespaceDecl>(decl->getParent()); auto parentNS = cast<clang::NamespaceDecl>(clangDC);
auto parent = auto parent =
Impl.importDecl(parentNS, getVersion(), /*UseCanonicalDecl*/ false); Impl.importDecl(parentNS, getVersion(), /*UseCanonicalDecl*/ false);
// The parent namespace might not be imported if it's `swift_private`. // The parent namespace might not be imported if it's `swift_private`.

View File

@@ -2034,6 +2034,20 @@ void importer::addEntryToLookupTable(SwiftLookupTable &table,
namedMember = def; namedMember = def;
addEntryToLookupTable(table, namedMember, nameImporter); addEntryToLookupTable(table, namedMember, nameImporter);
} }
if (auto linkageSpecDecl =
dyn_cast<clang::LinkageSpecDecl>(canonicalMember)) {
std::function<void(clang::DeclContext *)> addDeclsFromContext =
[&](clang::DeclContext *declContext) {
for (auto nestedDecl : declContext->decls()) {
if (auto namedMember = dyn_cast<clang::NamedDecl>(nestedDecl))
addEntryToLookupTable(table, namedMember, nameImporter);
else if (auto nestedLinkageSpecDecl =
dyn_cast<clang::LinkageSpecDecl>(nestedDecl))
addDeclsFromContext(nestedLinkageSpecDecl);
}
};
addDeclsFromContext(linkageSpecDecl);
}
} }
} }
if (auto usingDecl = dyn_cast<clang::UsingDecl>(named)) { if (auto usingDecl = dyn_cast<clang::UsingDecl>(named)) {

View File

@@ -0,0 +1,28 @@
namespace Outer {
namespace Inner {
extern "C" {
int foobar() { return 123; }
struct NestedType {
char c;
};
}
} // namespace Inner
inline namespace InnerInline {
extern "C" {
int baz() { return 321; }
}
} // namespace InnerInline
} // namespace Outer
namespace ExternWithinExtern {
extern "C" {
extern "C++" {
namespace Inner {
extern "C" {
int deep() { return 42; }
}
} // namespace Inner
}
}
} // namespace ExternWithinExtern

View File

@@ -12,6 +12,12 @@ module ClassesSecondHeader {
requires cplusplus requires cplusplus
} }
module ExternWithinNamespace {
header "extern-within-namespace.h"
export *
requires cplusplus
}
module FreeFunctions { module FreeFunctions {
header "free-functions.h" header "free-functions.h"
requires cplusplus requires cplusplus

View File

@@ -0,0 +1,17 @@
// RUN: %target-swift-ide-test -print-module -module-to-print=ExternWithinNamespace -I %S/Inputs -source-filename=x -cxx-interoperability-mode=upcoming-swift | %FileCheck %s
// CHECK: enum Outer {
// CHECK-NEXT: enum Inner {
// CHECK-NEXT: static func foobar()
// CHECK-NEXT: struct NestedType {
// CHECK: }
// CHECK-NEXT: }
// CHECK-NEXT: enum InnerInline {
// CHECK-NEXT: static func baz() -> Int32
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: enum ExternWithinExtern {
// CHECK-NEXT: enum Inner {
// CHECK-NEXT: static func deep() -> Int32
// CHECK-NEXT: }
// CHECK-NEXT: }

View File

@@ -0,0 +1,25 @@
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -cxx-interoperability-mode=upcoming-swift)
// REQUIRES: executable_test
import StdlibUnittest
import ExternWithinNamespace
var ExternTestSuite = TestSuite("Extern block within namespaces")
ExternTestSuite.test("Function within extern block within namespace") {
let r = Outer.Inner.foobar()
expectEqual(r, 123)
}
ExternTestSuite.test("Function within extern block within inline namespace") {
let r = Outer.InnerInline.baz()
expectEqual(r, 321)
}
ExternTestSuite.test("Function within extern block within extern block") {
let r = ExternWithinExtern.Inner.deep()
expectEqual(r, 42)
}
runAllTests()