Merge pull request #83516 from egorzhdan/egorzhdan/extern-within-namespace

[cxx-interop] Import decls in extern blocks within namespaces
This commit is contained in:
Egor Zhdan
2025-08-06 12:27:45 +01:00
committed by GitHub
8 changed files with 173 additions and 60 deletions

View File

@@ -2738,72 +2738,87 @@ static void addNamespaceMembers(Decl *decl,
if (declOwner && declOwner != redeclOwner->getTopLevelModule())
continue;
}
for (auto member : redecl->decls()) {
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())
specWorklist.insert(spec);
for (auto spec : specWorklist) {
if (auto import =
ctx.getClangModuleLoader()->importDeclDirectly(spec))
if (addedMembers.insert(import).second)
members.push_back(import);
}
}
auto lookupAndAddMembers = [&](DeclName name) {
auto allResults = evaluateOrDefault(
ctx.evaluator, ClangDirectLookupRequest({decl, redecl, name}), {});
std::function<void(clang::DeclContext *)> addDeclsFromContext =
[&](clang::DeclContext *declContext) {
for (auto member : declContext->decls()) {
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())
specWorklist.insert(spec);
for (auto spec : specWorklist) {
if (auto import =
ctx.getClangModuleLoader()->importDeclDirectly(spec))
if (addedMembers.insert(import).second)
members.push_back(import);
}
}
for (auto found : allResults) {
auto clangMember = cast<clang::NamedDecl *>(found);
if (auto importedDecl =
ctx.getClangModuleLoader()->importDeclDirectly(clangMember)) {
if (addedMembers.insert(importedDecl).second) {
members.push_back(importedDecl);
auto lookupAndAddMembers = [&](clang::NamedDecl *namedDecl) {
auto name = ctx.getClangModuleLoader()->importName(namedDecl);
if (!name)
return;
// Handle macro-expanded declarations.
importedDecl->visitAuxiliaryDecls([&](Decl *decl) {
auto valueDecl = dyn_cast<ValueDecl>(decl);
if (!valueDecl)
return;
auto allResults = evaluateOrDefault(
ctx.evaluator, ClangDirectLookupRequest({decl, redecl, name}),
{});
// Bail out if the auxiliary decl was not produced by a macro.
auto module = decl->getDeclContext()->getParentModule();
auto *sf = module->getSourceFileContainingLocation(decl->getLoc());
if (!sf || sf->Kind != SourceFileKind::MacroExpansion)
return;
for (auto found : allResults) {
auto clangMember = cast<clang::NamedDecl *>(found);
if (auto importedDecl =
ctx.getClangModuleLoader()->importDeclDirectly(
clangMember)) {
if (addedMembers.insert(importedDecl).second) {
members.push_back(importedDecl);
members.push_back(valueDecl);
});
// Handle macro-expanded declarations.
importedDecl->visitAuxiliaryDecls([&](Decl *decl) {
auto valueDecl = dyn_cast<ValueDecl>(decl);
if (!valueDecl)
return;
// Bail out if the auxiliary decl was not produced by a
// macro.
auto module = decl->getDeclContext()->getParentModule();
auto *sf = module->getSourceFileContainingLocation(
decl->getLoc());
if (!sf || sf->Kind != SourceFileKind::MacroExpansion)
return;
members.push_back(valueDecl);
});
}
}
}
};
// Look through `extern` blocks.
if (auto linkageSpecDecl = dyn_cast<clang::LinkageSpecDecl>(member))
addDeclsFromContext(linkageSpecDecl);
auto namedDecl = dyn_cast<clang::NamedDecl>(member);
if (!namedDecl)
continue;
lookupAndAddMembers(namedDecl);
// Unscoped enums could have their enumerators present
// in the parent namespace.
if (auto *ed = dyn_cast<clang::EnumDecl>(member)) {
if (!ed->isScoped()) {
for (auto *ecd : ed->enumerators()) {
lookupAndAddMembers(ecd);
}
}
}
}
}
};
};
auto namedDecl = dyn_cast<clang::NamedDecl>(member);
if (!namedDecl)
continue;
auto name = ctx.getClangModuleLoader()->importName(namedDecl);
if (!name)
continue;
lookupAndAddMembers(name);
// Unscoped enums could have their enumerators present
// in the parent namespace.
if (auto *ed = dyn_cast<clang::EnumDecl>(member)) {
if (!ed->isScoped()) {
for (const auto *ecd : ed->enumerators()) {
auto name = ctx.getClangModuleLoader()->importName(ecd);
if (!name)
continue;
lookupAndAddMembers(name);
}
}
}
}
addDeclsFromContext(redecl);
}
}

View File

@@ -5064,6 +5064,11 @@ static bool isDirectLookupMemberContext(const clang::Decl *foundClangDecl,
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;
}

View File

@@ -1155,11 +1155,14 @@ namespace {
return nullptr;
// 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.
if (!decl->getParent()->isNamespace())
auto clangDC = decl->getDeclContext();
while (isa<clang::LinkageSpecDecl>(clangDC))
clangDC = clangDC->getParent();
if (!clangDC->isNamespace())
dc = Impl.ImportedHeaderUnit;
else {
// 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 =
Impl.importDecl(parentNS, getVersion(), /*UseCanonicalDecl*/ false);
// 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;
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)) {

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
}
module ExternWithinNamespace {
header "extern-within-namespace.h"
export *
requires cplusplus
}
module FreeFunctions {
header "free-functions.h"
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()