Skip to content

Commit 4ed6ffe

Browse files
authored
Merge pull request #72974 from tshortli/extension-import-visibility-6.0
[6.0] Implement proper visibility rules for imported extensions
2 parents 0b87ee6 + 8124ee6 commit 4ed6ffe

29 files changed

+361
-33
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ ERROR(init_candidate_inaccessible,none,
165165
"'%select{private|fileprivate|internal|package|@_spi|@_spi}1' protection level",
166166
(Type, AccessLevel))
167167

168+
ERROR(candidate_from_missing_import,none,
169+
"%0 %1 is not available due to missing import of defining module %2",
170+
(DescriptiveDeclKind, DeclName, DeclName))
171+
NOTE(candidate_add_import,none,
172+
"add import of module %0", (DeclName))
173+
168174
ERROR(cannot_pass_rvalue_mutating_subelement,none,
169175
"cannot use mutating member on immutable value: %0",
170176
(StringRef))

include/swift/AST/NameLookup.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,15 @@ SelfBounds getSelfBoundsFromWhereClause(
628628
/// given protocol or protocol extension.
629629
SelfBounds getSelfBoundsFromGenericSignature(const ExtensionDecl *extDecl);
630630

631+
/// Determine whether the given declaration is visible to name lookup when
632+
/// found from the given module scope context.
633+
///
634+
/// Note that this routine does not check ASTContext::isAccessControlDisabled();
635+
/// that's left for the caller.
636+
bool declIsVisibleToNameLookup(
637+
const ValueDecl *decl, const DeclContext *moduleScopeContext,
638+
NLOptions options);
639+
631640
namespace namelookup {
632641

633642
/// The bridge between the legacy UnqualifiedLookupFactory and the new ASTScope

include/swift/AST/SourceFile.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,11 @@ class SourceFile final : public FileUnit {
418418
ImportedUnderlyingModule = module;
419419
}
420420

421+
/// Finds the import declaration that effectively imports a given module in
422+
/// this source file.
423+
std::optional<AttributedImport<ImportedModule>>
424+
findImport(const ModuleDecl *mod) const;
425+
421426
/// Whether the given import has used @preconcurrency.
422427
bool hasImportUsedPreconcurrency(
423428
AttributedImport<ImportedModule> import) const;

include/swift/AST/TypeCheckRequests.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4845,6 +4845,27 @@ class ObjCRequirementMapRequest
48454845
bool isCached() const { return true; }
48464846
};
48474847

4848+
/// Finds the import declaration that effectively imports a given module in a
4849+
/// source file.
4850+
class ImportDeclRequest
4851+
: public SimpleRequest<ImportDeclRequest,
4852+
std::optional<AttributedImport<ImportedModule>>(
4853+
const SourceFile *sf, const ModuleDecl *mod),
4854+
RequestFlags::Cached> {
4855+
public:
4856+
using SimpleRequest::SimpleRequest;
4857+
4858+
private:
4859+
friend SimpleRequest;
4860+
4861+
std::optional<AttributedImport<ImportedModule>>
4862+
evaluate(Evaluator &evaluator, const SourceFile *sf,
4863+
const ModuleDecl *mod) const;
4864+
4865+
public:
4866+
bool isCached() const { return true; }
4867+
};
4868+
48484869
#define SWIFT_TYPEID_ZONE TypeChecker
48494870
#define SWIFT_TYPEID_HEADER "swift/AST/TypeCheckerTypeIDZone.def"
48504871
#include "swift/Basic/DefineTypeIDZone.h"

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,4 +562,7 @@ SWIFT_REQUEST(TypeChecker, LocalTypeDeclsRequest,
562562
SWIFT_REQUEST(TypeChecker, ObjCRequirementMapRequest,
563563
ObjCRequirementMap(const ProtocolDecl *proto),
564564
Cached, NoLocationInfo)
565-
565+
SWIFT_REQUEST(TypeChecker, ImportDeclRequest,
566+
std::optional<AttributedImport<ImportedModule>>(
567+
const SourceFile *sf, const ModuleDecl *mod),
568+
Cached, NoLocationInfo)

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,9 @@ EXPERIMENTAL_FEATURE(ClosureIsolation, true)
364364
// Enable isolated(any) attribute on function types.
365365
CONDITIONALLY_SUPPRESSIBLE_EXPERIMENTAL_FEATURE(IsolatedAny, true)
366366

367+
// Whether members of extensions respect the enclosing file's imports.
368+
EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE(ExtensionImportVisibility, true)
369+
367370
// Alias for IsolatedAny
368371
EXPERIMENTAL_FEATURE(IsolatedAny2, true)
369372

lib/AST/Decl.cpp

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3853,24 +3853,7 @@ ValueDecl::findImport(const DeclContext *fromDC) {
38533853
if (!fromSourceFile)
38543854
return std::nullopt;
38553855

3856-
// Look to see if the owning module was directly imported.
3857-
for (const auto &import : fromSourceFile->getImports()) {
3858-
if (import.module.importedModule == module)
3859-
return import;
3860-
}
3861-
3862-
// Now look for transitive imports.
3863-
auto &importCache = getASTContext().getImportCache();
3864-
for (const auto &import : fromSourceFile->getImports()) {
3865-
auto &importSet = importCache.getImportSet(import.module.importedModule);
3866-
for (const auto &transitive : importSet.getTransitiveImports()) {
3867-
if (transitive.importedModule == module) {
3868-
return import;
3869-
}
3870-
}
3871-
}
3872-
3873-
return std::nullopt;
3856+
return fromSourceFile->findImport(module);
38743857
}
38753858

38763859
bool ValueDecl::isProtocolRequirement() const {

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,7 @@ static bool usesFeatureIsolatedAny(Decl *decl) {
682682
});
683683
}
684684

685+
UNINTERESTING_FEATURE(ExtensionImportVisibility)
685686
UNINTERESTING_FEATURE(IsolatedAny2)
686687

687688
UNINTERESTING_FEATURE(ObjCImplementation)

lib/AST/Module.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2580,6 +2580,38 @@ SourceFile::setImports(ArrayRef<AttributedImport<ImportedModule>> imports) {
25802580
Imports = getASTContext().AllocateCopy(imports);
25812581
}
25822582

2583+
std::optional<AttributedImport<ImportedModule>>
2584+
SourceFile::findImport(const ModuleDecl *module) const {
2585+
return evaluateOrDefault(getASTContext().evaluator,
2586+
ImportDeclRequest{this, module}, std::nullopt);
2587+
}
2588+
2589+
std::optional<AttributedImport<ImportedModule>>
2590+
ImportDeclRequest::evaluate(Evaluator &evaluator, const SourceFile *sf,
2591+
const ModuleDecl *module) const {
2592+
auto &ctx = sf->getASTContext();
2593+
auto imports = sf->getImports();
2594+
2595+
// Look to see if the owning module was directly imported.
2596+
for (const auto &import : imports) {
2597+
if (import.module.importedModule == module)
2598+
return import;
2599+
}
2600+
2601+
// Now look for transitive imports.
2602+
auto &importCache = ctx.getImportCache();
2603+
for (const auto &import : imports) {
2604+
auto &importSet = importCache.getImportSet(import.module.importedModule);
2605+
for (const auto &transitive : importSet.getTransitiveImports()) {
2606+
if (transitive.importedModule == module) {
2607+
return import;
2608+
}
2609+
}
2610+
}
2611+
2612+
return std::nullopt;
2613+
}
2614+
25832615
bool SourceFile::hasImportUsedPreconcurrency(
25842616
AttributedImport<ImportedModule> import) const {
25852617
return PreconcurrencyImportsUsed.count(import) != 0;

lib/AST/ModuleNameLookup.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,22 @@ class LookupVisibleDecls : public ModuleNameLookup<LookupVisibleDecls> {
126126

127127
} // end anonymous namespace
128128

129+
bool swift::declIsVisibleToNameLookup(
130+
const ValueDecl *decl, const DeclContext *moduleScopeContext,
131+
NLOptions options) {
132+
// NL_IgnoreAccessControl only applies to the current module. If
133+
// it applies here, the declaration is visible.
134+
if ((options & NL_IgnoreAccessControl) &&
135+
moduleScopeContext &&
136+
moduleScopeContext->getParentModule() ==
137+
decl->getDeclContext()->getParentModule())
138+
return true;
139+
140+
bool includeUsableFromInline = options & NL_IncludeUsableFromInline;
141+
return decl->isAccessibleFrom(moduleScopeContext, false,
142+
includeUsableFromInline);
143+
}
144+
129145
template <typename LookupStrategy>
130146
void ModuleNameLookup<LookupStrategy>::lookupInModule(
131147
SmallVectorImpl<ValueDecl *> &decls,
@@ -151,7 +167,6 @@ void ModuleNameLookup<LookupStrategy>::lookupInModule(
151167

152168
const size_t initialCount = decls.size();
153169
size_t currentCount = decls.size();
154-
bool includeUsableFromInline = options & NL_IncludeUsableFromInline;
155170

156171
auto updateNewDecls = [&](const DeclContext *moduleScopeContext) {
157172
if (decls.size() == currentCount)
@@ -165,13 +180,7 @@ void ModuleNameLookup<LookupStrategy>::lookupInModule(
165180
if (resolutionKind == ResolutionKind::MacrosOnly && !isa<MacroDecl>(VD))
166181
return true;
167182
if (respectAccessControl &&
168-
// NL_IgnoreAccessControl applies only to the current module.
169-
!((options & NL_IgnoreAccessControl) &&
170-
moduleScopeContext &&
171-
moduleScopeContext->getParentModule() ==
172-
VD->getDeclContext()->getParentModule()) &&
173-
!VD->isAccessibleFrom(moduleScopeContext, false,
174-
includeUsableFromInline))
183+
!declIsVisibleToNameLookup(VD, moduleScopeContext, options))
175184
return true;
176185
return false;
177186
});

lib/AST/NameLookup.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2285,8 +2285,17 @@ static bool isAcceptableLookupResult(const DeclContext *dc,
22852285
if (!(options & NL_IgnoreAccessControl) &&
22862286
!dc->getASTContext().isAccessControlDisabled()) {
22872287
bool allowUsableFromInline = options & NL_IncludeUsableFromInline;
2288-
return decl->isAccessibleFrom(dc, /*forConformance*/ false,
2289-
allowUsableFromInline);
2288+
if (!decl->isAccessibleFrom(dc, /*forConformance*/ false,
2289+
allowUsableFromInline))
2290+
return false;
2291+
2292+
// Check that there is some import in the originating context that
2293+
// makes this decl visible.
2294+
if (decl->getDeclContext()->getParentModule() != dc->getParentModule() &&
2295+
dc->getASTContext().LangOpts.hasFeature(
2296+
Feature::ExtensionImportVisibility) &&
2297+
!decl->findImport(dc))
2298+
return false;
22902299
}
22912300

22922301
return true;

lib/Sema/CSDiagnostics.cpp

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6187,7 +6187,65 @@ bool InaccessibleMemberFailure::diagnoseAsError() {
61876187

61886188
auto loc = nameLoc.isValid() ? nameLoc.getStartLoc() : ::getLoc(anchor);
61896189
auto accessLevel = Member->getFormalAccessScope().accessLevelForDiagnostics();
6190-
if (auto *CD = dyn_cast<ConstructorDecl>(Member)) {
6190+
bool suppressDeclHereNote = false;
6191+
if (accessLevel == AccessLevel::Public &&
6192+
!Member->findImport(getDC())) {
6193+
auto definingModule = Member->getDeclContext()->getParentModule();
6194+
emitDiagnosticAt(loc, diag::candidate_from_missing_import,
6195+
Member->getDescriptiveKind(), Member->getName(),
6196+
definingModule->getName());
6197+
6198+
auto enclosingSF = getDC()->getParentSourceFile();
6199+
SourceLoc bestLoc;
6200+
SourceManager &srcMgr = Member->getASTContext().SourceMgr;
6201+
for (auto item : enclosingSF->getTopLevelItems()) {
6202+
// If we found an import declaration, we want to insert after it.
6203+
if (auto importDecl =
6204+
dyn_cast_or_null<ImportDecl>(item.dyn_cast<Decl *>())) {
6205+
SourceLoc loc = importDecl->getEndLoc();
6206+
if (loc.isValid()) {
6207+
bestLoc = Lexer::getLocForEndOfLine(srcMgr, loc);
6208+
}
6209+
6210+
// Keep looking for more import declarations.
6211+
continue;
6212+
}
6213+
6214+
// If we got a location based on import declarations, we're done.
6215+
if (bestLoc.isValid())
6216+
break;
6217+
6218+
// For any other item, we want to insert before it.
6219+
SourceLoc loc = item.getStartLoc();
6220+
if (loc.isValid()) {
6221+
bestLoc = Lexer::getLocForStartOfLine(srcMgr, loc);
6222+
break;
6223+
}
6224+
}
6225+
6226+
if (bestLoc.isValid()) {
6227+
llvm::SmallString<64> importText;
6228+
6229+
// @_spi imports.
6230+
if (Member->isSPI()) {
6231+
auto spiGroups = Member->getSPIGroups();
6232+
if (!spiGroups.empty()) {
6233+
importText += "@_spi(";
6234+
importText += spiGroups[0].str();
6235+
importText += ") ";
6236+
}
6237+
}
6238+
6239+
importText += "import ";
6240+
importText += definingModule->getName().str();
6241+
importText += "\n";
6242+
emitDiagnosticAt(bestLoc, diag::candidate_add_import,
6243+
definingModule->getName())
6244+
.fixItInsert(bestLoc, importText);
6245+
}
6246+
6247+
suppressDeclHereNote = true;
6248+
} else if (auto *CD = dyn_cast<ConstructorDecl>(Member)) {
61916249
emitDiagnosticAt(loc, diag::init_candidate_inaccessible,
61926250
CD->getResultInterfaceType(), accessLevel)
61936251
.highlight(nameLoc.getSourceRange());
@@ -6197,7 +6255,8 @@ bool InaccessibleMemberFailure::diagnoseAsError() {
61976255
.highlight(nameLoc.getSourceRange());
61986256
}
61996257

6200-
emitDiagnosticAt(Member, diag::decl_declared_here, Member);
6258+
if (!suppressDeclHereNote)
6259+
emitDiagnosticAt(Member, diag::decl_declared_here, Member);
62016260
return true;
62026261
}
62036262

stdlib/cmake/modules/SwiftSource.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,8 @@ function(_compile_swift_files
628628
list(APPEND swift_flags "-enable-experimental-feature" "NonescapableTypes")
629629
endif()
630630

631+
list(APPEND swift_flags "-enable-experimental-feature" "ExtensionImportVisiblity")
632+
631633
if (SWIFT_STDLIB_ENABLE_STRICT_CONCURRENCY_COMPLETE)
632634
list(APPEND swift_flags "-strict-concurrency=complete")
633635
endif()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@import Foundation;
2+
3+
@interface X
4+
@end
5+
6+
@interface X (A)
7+
- (void)fromA;
8+
@end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@_exported import Categories_A
2+
3+
extension X {
4+
public func fromOverlayForA() {}
5+
@objc public func fromOverlayForAObjC() {}
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@import Categories_A;
2+
3+
@interface X (B)
4+
- (void)fromB;
5+
@end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@_exported import Categories_B
2+
3+
extension X {
4+
public func fromOverlayForB() {}
5+
@objc public func fromOverlayForBObjC() {}
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@import Categories_A;
2+
3+
@interface X (C)
4+
- (void)fromC;
5+
@end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@_exported import Categories_C
2+
3+
extension X {
4+
public func fromOverlayForC() {}
5+
@objc public func fromOverlayForCObjC() {}
6+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Intentionally empty
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Categories_C
2+
import Categories_D.Submodule
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@import Categories_A;
2+
3+
@interface X (SubmoduleOfD)
4+
- (void)fromSubmoduleOfD;
5+
@end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module Categories_A {
2+
header "Categories_A.h"
3+
export *
4+
}
5+
6+
module Categories_B {
7+
header "Categories_B.h"
8+
export *
9+
}
10+
11+
module Categories_C {
12+
header "Categories_C.h"
13+
export *
14+
}
15+
16+
module Categories_D {
17+
header "Categories_D.h"
18+
export *
19+
20+
explicit module Submodule {
21+
header "Submodule.h"
22+
export *
23+
}
24+
}
25+

0 commit comments

Comments
 (0)