Skip to content

Commit cff9184

Browse files
committed
Implement proper visibility rules for imported extensions
If an extension isn't imported either directly or via a transitive (`@_exported`) import, its members should not be visible to name lookup. Implement this behavior behind the experimental flag ExtensionImportVisibility.
1 parent e98186b commit cff9184

File tree

11 files changed

+142
-11
lines changed

11 files changed

+142
-11
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
@@ -625,6 +625,15 @@ SelfBounds getSelfBoundsFromWhereClause(
625625
/// given protocol or protocol extension.
626626
SelfBounds getSelfBoundsFromGenericSignature(const ExtensionDecl *extDecl);
627627

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

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

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,9 @@ EXPERIMENTAL_FEATURE(BorrowingSwitch, true)
337337
// Enable isolated(any) attribute on function types.
338338
CONDITIONALLY_SUPPRESSIBLE_EXPERIMENTAL_FEATURE(IsolatedAny, true)
339339

340+
// Whether members of extensions respect the enclosing file's imports.
341+
EXPERIMENTAL_FEATURE(ExtensionImportVisibility, true)
342+
340343
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
341344
#undef EXPERIMENTAL_FEATURE
342345
#undef UPCOMING_FEATURE

lib/AST/FeatureSet.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,8 @@ static bool usesFeatureIsolatedAny(Decl *decl) {
649649
});
650650
}
651651

652+
UNINTERESTING_FEATURE(ExtensionImportVisibility)
653+
652654
// ----------------------------------------------------------------------------
653655
// MARK: - FeatureSet
654656
// ----------------------------------------------------------------------------

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
@@ -2271,8 +2271,17 @@ static bool isAcceptableLookupResult(const DeclContext *dc,
22712271
if (!(options & NL_IgnoreAccessControl) &&
22722272
!dc->getASTContext().isAccessControlDisabled()) {
22732273
bool allowUsableFromInline = options & NL_IncludeUsableFromInline;
2274-
return decl->isAccessibleFrom(dc, /*forConformance*/ false,
2275-
allowUsableFromInline);
2274+
if (!decl->isAccessibleFrom(dc, /*forConformance*/ false,
2275+
allowUsableFromInline))
2276+
return false;
2277+
2278+
// Check that there is some import in the originating context that
2279+
// makes this decl visible.
2280+
if (decl->getDeclContext()->getParentModule() != dc->getParentModule() &&
2281+
dc->getASTContext().LangOpts.hasFeature(
2282+
Feature::ExtensionImportVisibility) &&
2283+
!decl->findImport(dc))
2284+
return false;
22762285
}
22772286

22782287
return true;

lib/Sema/CSDiagnostics.cpp

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6163,7 +6163,54 @@ bool InaccessibleMemberFailure::diagnoseAsError() {
61636163

61646164
auto loc = nameLoc.isValid() ? nameLoc.getStartLoc() : ::getLoc(anchor);
61656165
auto accessLevel = Member->getFormalAccessScope().accessLevelForDiagnostics();
6166-
if (auto *CD = dyn_cast<ConstructorDecl>(Member)) {
6166+
bool suppressDeclHereNote = false;
6167+
if (accessLevel == AccessLevel::Public) {
6168+
auto definingModule = Member->getDeclContext()->getParentModule();
6169+
emitDiagnosticAt(loc, diag::candidate_from_missing_import,
6170+
Member->getDescriptiveKind(), Member->getName(),
6171+
definingModule->getName());
6172+
6173+
if (auto enclosingSF = getDC()->getParentSourceFile()) {
6174+
SourceLoc bestLoc;
6175+
SourceManager &srcMgr = Member->getASTContext().SourceMgr;
6176+
for (auto item : enclosingSF->getTopLevelItems()) {
6177+
// If we found an import declaration, we want to insert after it.
6178+
if (auto importDecl =
6179+
dyn_cast_or_null<ImportDecl>(item.dyn_cast<Decl *>())) {
6180+
SourceLoc loc = importDecl->getEndLoc();
6181+
if (loc.isValid()) {
6182+
bestLoc = Lexer::getLocForEndOfLine(srcMgr, loc);
6183+
}
6184+
6185+
// Keep looking for more import declarations.
6186+
continue;
6187+
}
6188+
6189+
// If we got a location based on import declarations, we're done.
6190+
if (bestLoc.isValid())
6191+
break;
6192+
6193+
// For any other item, we want to insert before it.
6194+
SourceLoc loc = item.getStartLoc();
6195+
if (loc.isValid()) {
6196+
bestLoc = Lexer::getLocForStartOfLine(srcMgr, loc);
6197+
break;
6198+
}
6199+
}
6200+
6201+
if (bestLoc.isValid()) {
6202+
llvm::SmallString<64> importText;
6203+
importText += "import ";
6204+
importText += definingModule->getName().str();
6205+
importText += "\n";
6206+
emitDiagnosticAt(bestLoc, diag::candidate_add_import,
6207+
definingModule->getName())
6208+
.fixItInsert(bestLoc, importText);
6209+
}
6210+
}
6211+
6212+
suppressDeclHereNote = true;
6213+
} else if (auto *CD = dyn_cast<ConstructorDecl>(Member)) {
61676214
emitDiagnosticAt(loc, diag::init_candidate_inaccessible,
61686215
CD->getResultInterfaceType(), accessLevel)
61696216
.highlight(nameLoc.getSourceRange());
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
public struct X { }
2+
3+
public protocol P { }
4+
5+
public struct Y<T> { }
6+
7+
extension Y: P where T: P { }
8+
9+
public struct Z: P { }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import extensions_A
2+
3+
public extension X {
4+
public func XinB() { }
5+
}
6+
7+
public extension Y {
8+
public func YinB() { }
9+
}
10+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import extensions_A
2+
import extensions_B
3+
4+
public extension X {
5+
public func XinC() { }
6+
}
7+
8+
public extension Y {
9+
public func YinC() { }
10+
}
11+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/extensions_A.swift
3+
// RUN: %target-swift-frontend -emit-module -I %t -o %t %S/Inputs/extensions_B.swift
4+
// RUN: %target-swift-frontend -emit-module -I %t -o %t %S/Inputs/extensions_C.swift
5+
// RUN: %target-swift-frontend -typecheck %s -I %t -verify -enable-experimental-feature ExtensionImportVisibility
6+
7+
import extensions_A
8+
import extensions_C
9+
// expected-note 2{{add import of module 'extensions_B'}}{{1-1=import extensions_B\n}}
10+
func test(x: X, y: Y<Z>) {
11+
x.XinB() // expected-error{{instance method 'XinB()' is not available due to missing import of defining module 'extensions_B'}}
12+
y.YinB() // expected-error{{instance method 'YinB()' is not available due to missing import of defining module 'extensions_B'}}
13+
14+
x.XinC()
15+
y.YinC()
16+
}

0 commit comments

Comments
 (0)