Skip to content

Commit f751131

Browse files
authored
Merge pull request #72060 from DougGregor/import-extension-visibility
Implement proper visibility rules for imported extensions
2 parents 7e4d170 + 61b19c8 commit f751131

File tree

12 files changed

+158
-14
lines changed

12 files changed

+158
-14
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ EXPERIMENTAL_FEATURE(ClosureIsolation, true)
365365
// Enable isolated(any) attribute on function types.
366366
CONDITIONALLY_SUPPRESSIBLE_EXPERIMENTAL_FEATURE(IsolatedAny, true)
367367

368+
369+
// Whether members of extensions respect the enclosing file's imports.
370+
EXPERIMENTAL_FEATURE(ExtensionImportVisibility, true)
371+
368372
// Alias for IsolatedAny
369373
EXPERIMENTAL_FEATURE(IsolatedAny2, true)
370374

@@ -377,6 +381,7 @@ EXPERIMENTAL_FEATURE(ObjCImplementation, true)
377381
// Enable @implementation on @_cdecl functions.
378382
EXPERIMENTAL_FEATURE(CImplementation, true)
379383

384+
380385
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
381386
#undef EXPERIMENTAL_FEATURE
382387
#undef UPCOMING_FEATURE

lib/AST/FeatureSet.cpp

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

686+
UNINTERESTING_FEATURE(ExtensionImportVisibility)
686687
UNINTERESTING_FEATURE(IsolatedAny2)
687688

688689
static bool usesFeatureGlobalActorIsolatedTypesUsability(Decl *decl) {

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

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+
}

test/SPI/client_reuse_spi.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// RUN: %target-swift-frontend -typecheck -verify -verify-ignore-unknown -DLIB_C %s -I %t
77

88
#if LIB_A
9-
9+
// expected-note@-1{{add import of module 'A'}}{{1-1=@_spi(A) import A\n}}
1010
@_spi(A) public struct SecretStruct {
1111
@_spi(A) public func bar() {}
1212
}
@@ -22,7 +22,7 @@
2222
@_spi(B) import B
2323

2424
var a = foo() // OK
25-
a.bar() // expected-error{{'bar' is inaccessible due to '@_spi' protection level}}
25+
a.bar() // expected-error{{instance method 'bar()' is not available due to missing import of defining module 'A'}}
2626

2727
var b = SecretStruct() // expected-error{{cannot find 'SecretStruct' in scope}}
2828

0 commit comments

Comments
 (0)