Skip to content

Commit b11bb1c

Browse files
committed
SE-0444: Fix interactions with Cxx interop.
With the upcoming `MemberImportVisibility` feature enabled, code built with Cxx interop also enabled could be rejected by the compiler with cryptic errors about the `__ObjC` module not being imported. This is the result of a surprising implementation detail of Cxx interop. When importing C++ namespaces and their members, the Clang importer puts these declarations in the Clang header import module (a.k.a. the bridging header module, `__ObjC`). C++ namespaces don't have a logical modular home in the Swift AST because they can span multiple modules, so it's understandable why this implementation was chosen. However, the concrete members of namespaces also get placed in the `__ObjC` module too, and this really confuses things. To work around this idiosyncrasy of Cxx interop, I've introduced `Decl::getModuleContextForNameLookup()` which returns the module that a declaration would ideally belong to if Cxx interop didn't have this behavior. This alternative to `Decl::getModuleContext()` is now used everywhere that `MemberImportVisibility` rules are enforced to provide consistency. Additionally, I found that I also had to further special-case the header import module for Cxx interop because it turns out that there are some additional declarations, beyond imported namespaces, that also live there and need to be implicitly visible in every source file. The `__ObjC` module is not implicitly imported in source files when Cxx interop is enabled, so these declarations are not deemed visible under normal name lookup rules. When I tried to add an implicit import of `__ObjC` when Cxx interop is enabled, it broke a bunch tests. So for now, when a decl really belongs to the `__ObjC` module in Cxx interop mode, we just always allow it to be referenced. This Cxx interop behavior really needs a re-think in my opinion, but that will require larger discussions. Resolves rdar://136600598.
1 parent 07b84fc commit b11bb1c

File tree

8 files changed

+134
-5
lines changed

8 files changed

+134
-5
lines changed

include/swift/AST/Decl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,12 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
979979
LLVM_READONLY
980980
ModuleDecl *getModuleContext() const;
981981

982+
/// Retrieve the module in which this declaration would be found by name
983+
/// lookup queries. The result can differ from that of `getModuleContext()`
984+
/// when the decl was imported via Cxx interop.
985+
LLVM_READONLY
986+
ModuleDecl *getModuleContextForNameLookup() const;
987+
982988
/// getASTContext - Return the ASTContext that this decl lives in.
983989
LLVM_READONLY
984990
ASTContext &getASTContext() const {

lib/AST/Decl.cpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,42 @@ ModuleDecl *Decl::getModuleContext() const {
770770
return getDeclContext()->getParentModule();
771771
}
772772

773+
/// If `decl` is an imported Cxx decl, returns the actual module that the decl
774+
/// was imported from. This is necessary to compensate for the way that
775+
/// Cxx `namespace` declarations are imported. Since namespaces can span
776+
/// multiple Clang modules, the corresponding decls and all of their members get
777+
/// associated with the Clang imported header module (named `__ObjC`). This
778+
/// utility reaches through the Clang AST to find the actual module that members
779+
/// of namespaces originated from.
780+
static ModuleDecl *getModuleContextForNameLookupForCxxDecl(const Decl *decl) {
781+
auto &ctx = decl->getASTContext();
782+
if (!ctx.LangOpts.EnableCXXInterop)
783+
return nullptr;
784+
785+
if (!decl->hasClangNode())
786+
return nullptr;
787+
788+
auto parentModule = decl->getModuleContext();
789+
790+
// We only need to look for the real parent module when the existing parent
791+
// is the imported header module.
792+
if (!parentModule->isClangHeaderImportModule())
793+
return nullptr;
794+
795+
auto clangModule = decl->getClangDecl()->getOwningModule();
796+
if (!clangModule)
797+
return nullptr;
798+
799+
return ctx.getClangModuleLoader()->getWrapperForModule(clangModule);
800+
}
801+
802+
ModuleDecl *Decl::getModuleContextForNameLookup() const {
803+
if (auto parentModule = getModuleContextForNameLookupForCxxDecl(this))
804+
return parentModule;
805+
806+
return getModuleContext();
807+
}
808+
773809
/// Retrieve the diagnostic engine for diagnostics emission.
774810
DiagnosticEngine &Decl::getDiags() const {
775811
return getASTContext().Diags;
@@ -3941,7 +3977,7 @@ ValueDecl::getSatisfiedProtocolRequirements(bool Sorted) const {
39413977
std::optional<AttributedImport<ImportedModule>>
39423978
ValueDecl::findImport(const DeclContext *fromDC) const {
39433979
// If the type is from the current module, there's no import.
3944-
auto module = getModuleContext();
3980+
auto module = getModuleContextForNameLookup();
39453981
if (module == fromDC->getParentModule())
39463982
return std::nullopt;
39473983

lib/AST/NameLookup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2504,7 +2504,7 @@ bool DeclContext::lookupQualified(Type type,
25042504
}
25052505

25062506
bool DeclContext::isDeclImported(const Decl *decl) const {
2507-
auto declModule = decl->getDeclContext()->getParentModule();
2507+
auto declModule = decl->getModuleContextForNameLookup();
25082508
return getASTContext().getImportCache().isImportedBy(declModule, this);
25092509
}
25102510

lib/Sema/TypeCheckNameLookup.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ class MissingImportFixItCache {
809809
static void diagnoseMissingImportForMember(const ValueDecl *decl,
810810
SourceFile *sf, SourceLoc loc) {
811811
auto &ctx = sf->getASTContext();
812-
auto definingModule = decl->getModuleContext();
812+
auto definingModule = decl->getModuleContextForNameLookup();
813813
ctx.Diags.diagnose(loc, diag::candidate_from_missing_import,
814814
decl->getDescriptiveKind(), decl->getName(),
815815
definingModule);
@@ -823,7 +823,7 @@ diagnoseAndFixMissingImportForMember(const ValueDecl *decl, SourceFile *sf,
823823
diagnoseMissingImportForMember(decl, sf, loc);
824824

825825
auto &ctx = sf->getASTContext();
826-
auto definingModule = decl->getModuleContext();
826+
auto definingModule = decl->getModuleContextForNameLookup();
827827
SourceLoc bestLoc = ctx.Diags.getBestAddImportFixItLoc(decl, sf);
828828
if (!bestLoc.isValid())
829829
return;
@@ -874,9 +874,18 @@ diagnoseAndFixMissingImportForMember(const ValueDecl *decl, SourceFile *sf,
874874
bool swift::maybeDiagnoseMissingImportForMember(const ValueDecl *decl,
875875
const DeclContext *dc,
876876
SourceLoc loc) {
877-
if (decl->findImport(dc))
877+
if (dc->isDeclImported(decl))
878878
return false;
879879

880+
if (dc->getASTContext().LangOpts.EnableCXXInterop) {
881+
// With Cxx interop enabled, there are some declarations that always belong
882+
// to the Clang header import module which should always be implicitly
883+
// visible. However, that module is not implicitly imported in source files
884+
// so we need to special case it here and avoid diagnosing.
885+
if (decl->getModuleContextForNameLookup()->isClangHeaderImportModule())
886+
return false;
887+
}
888+
880889
auto sf = dc->getParentSourceFile();
881890
if (!sf)
882891
return false;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#ifndef TEST_INTEROP_CXX_CLASS_INPUTS_MEMBERS_DIRECT_H
2+
#define TEST_INTEROP_CXX_CLASS_INPUTS_MEMBERS_DIRECT_H
3+
4+
#include <members-transitive.h>
5+
6+
namespace DirectNamespace { }
7+
8+
namespace CommonNamespace {
9+
10+
struct DirectStruct {
11+
int memberVar;
12+
};
13+
14+
} // namespace CommonNamespace
15+
16+
CommonNamespace::TransitiveStruct returnsTransitiveStruct() {
17+
return CommonNamespace::TransitiveStruct{};
18+
}
19+
20+
TopLevelTransitiveStruct returnsTopLevelTransitiveStruct() {
21+
return TopLevelTransitiveStruct{};
22+
}
23+
24+
#endif // TEST_INTEROP_CXX_CLASS_INPUTS_MEMBERS_DIRECT_H
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#ifndef TEST_INTEROP_CXX_CLASS_INPUTS_MEMBERS_TRANSITIVE_H
2+
#define TEST_INTEROP_CXX_CLASS_INPUTS_MEMBERS_TRANSITIVE_H
3+
4+
namespace TransitiveNamespace { }
5+
6+
namespace CommonNamespace {
7+
8+
struct TransitiveStruct {
9+
bool memberVar;
10+
};
11+
12+
} // namespace CommonNamespace
13+
14+
struct TopLevelTransitiveStruct {
15+
bool memberVar;
16+
};
17+
18+
#endif // TEST_INTEROP_CXX_CLASS_INPUTS_MEMBERS_TRANSITIVE_H

test/Interop/Cxx/namespace/Inputs/module.modulemap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,13 @@ module Enums {
5959
header "enums.h"
6060
requires cplusplus
6161
}
62+
63+
module MembersDirect {
64+
header "members-direct.h"
65+
requires cplusplus
66+
}
67+
68+
module MembersTransitive {
69+
header "members-transitive.h"
70+
requires cplusplus
71+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %target-typecheck-verify-swift -I %S/Inputs -enable-experimental-cxx-interop
2+
// RUN: %target-typecheck-verify-swift -I %S/Inputs -enable-experimental-cxx-interop -enable-upcoming-feature MemberImportVisibility -verify-additional-prefix member-visibility-
3+
4+
import MembersDirect
5+
6+
// expected-member-visibility-note@-1 4 {{add import of module 'MembersTransitive'}}
7+
8+
extension DirectNamespace { }
9+
extension TransitiveNamespace { } // expected-error {{cannot find type 'TransitiveNamespace' in scope}}
10+
11+
extension CommonNamespace.DirectStruct {
12+
func extensionMethod() -> Int32 {
13+
return memberVar
14+
}
15+
}
16+
17+
extension CommonNamespace.TransitiveStruct { // expected-member-visibility-error {{struct 'TransitiveStruct' is not available due to missing import of defining module 'MembersTransitive'}}
18+
func extensionMethod() -> Bool {
19+
return memberVar // expected-member-visibility-error {{property 'memberVar' is not available due to missing import of defining module 'MembersTransitive'}}
20+
}
21+
}
22+
23+
func test() {
24+
let _: Bool = returnsTransitiveStruct().memberVar // expected-member-visibility-error {{property 'memberVar' is not available due to missing import of defining module 'MembersTransitive'}}
25+
let _: Bool = returnsTopLevelTransitiveStruct().memberVar // expected-member-visibility-error {{property 'memberVar' is not available due to missing import of defining module 'MembersTransitive'}}
26+
}

0 commit comments

Comments
 (0)