Skip to content

Commit 237ddb6

Browse files
DougGregortshortli
authored andcommitted
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 34c2f92 commit 237ddb6

File tree

11 files changed

+141
-11
lines changed

11 files changed

+141
-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
@@ -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/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(ExtensionImportVisibility, true)
369+
367370
// Alias for IsolatedAny
368371
EXPERIMENTAL_FEATURE(IsolatedAny2, true)
369372

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/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: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6187,7 +6187,54 @@ 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+
auto definingModule = Member->getDeclContext()->getParentModule();
6193+
emitDiagnosticAt(loc, diag::candidate_from_missing_import,
6194+
Member->getDescriptiveKind(), Member->getName(),
6195+
definingModule->getName());
6196+
6197+
if (auto enclosingSF = getDC()->getParentSourceFile()) {
6198+
SourceLoc bestLoc;
6199+
SourceManager &srcMgr = Member->getASTContext().SourceMgr;
6200+
for (auto item : enclosingSF->getTopLevelItems()) {
6201+
// If we found an import declaration, we want to insert after it.
6202+
if (auto importDecl =
6203+
dyn_cast_or_null<ImportDecl>(item.dyn_cast<Decl *>())) {
6204+
SourceLoc loc = importDecl->getEndLoc();
6205+
if (loc.isValid()) {
6206+
bestLoc = Lexer::getLocForEndOfLine(srcMgr, loc);
6207+
}
6208+
6209+
// Keep looking for more import declarations.
6210+
continue;
6211+
}
6212+
6213+
// If we got a location based on import declarations, we're done.
6214+
if (bestLoc.isValid())
6215+
break;
6216+
6217+
// For any other item, we want to insert before it.
6218+
SourceLoc loc = item.getStartLoc();
6219+
if (loc.isValid()) {
6220+
bestLoc = Lexer::getLocForStartOfLine(srcMgr, loc);
6221+
break;
6222+
}
6223+
}
6224+
6225+
if (bestLoc.isValid()) {
6226+
llvm::SmallString<64> importText;
6227+
importText += "import ";
6228+
importText += definingModule->getName().str();
6229+
importText += "\n";
6230+
emitDiagnosticAt(bestLoc, diag::candidate_add_import,
6231+
definingModule->getName())
6232+
.fixItInsert(bestLoc, importText);
6233+
}
6234+
}
6235+
6236+
suppressDeclHereNote = true;
6237+
} else if (auto *CD = dyn_cast<ConstructorDecl>(Member)) {
61916238
emitDiagnosticAt(loc, diag::init_candidate_inaccessible,
61926239
CD->getResultInterfaceType(), accessLevel)
61936240
.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+

0 commit comments

Comments
 (0)