Skip to content

Commit b047396

Browse files
committed
AST: Use availability to control decl visibility in public swiftinterfaces.
Declarations that are unavailable at runtime because of an `@available` attribute referencing a custom domain that was imported `@_spiOnly` should be hidden from public swiftinterface files in `-library-level=api` modules. For remaining declarations that do get printed in the public swiftinterface, skip printing any `@available` attribute that refers to the domains from those `@_spiOnly` dependencies. This allows API developers to control declaration visibility using availability defined by another module. Resolves rdar://156512028.
1 parent ddca4b7 commit b047396

File tree

7 files changed

+185
-4
lines changed

7 files changed

+185
-4
lines changed

lib/AST/ASTPrinter.cpp

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include "swift/AST/ASTMangler.h"
2121
#include "swift/AST/ASTVisitor.h"
2222
#include "swift/AST/Attr.h"
23+
#include "swift/AST/AvailabilityConstraint.h"
24+
#include "swift/AST/AvailabilityContext.h"
2325
#include "swift/AST/Builtins.h"
2426
#include "swift/AST/ClangModuleLoader.h"
2527
#include "swift/AST/Comment.h"
@@ -189,6 +191,38 @@ static bool shouldPrintAllSemanticDetails(const PrintOptions &options) {
189191
return false;
190192
}
191193

194+
static bool shouldSkipDeclInPublicInterface(const Decl *D) {
195+
// @_spi should be skipped in the public interface.
196+
if (D->isSPI())
197+
return true;
198+
199+
// Decls that are unavailable at runtime in an availability domain that has
200+
// been @_spiOnly imported should be hidden from the public interface of
201+
// a -library-level=api module.
202+
auto &ctx = D->getDeclContext()->getASTContext();
203+
if (ctx.LangOpts.LibraryLevel != LibraryLevel::API)
204+
return false;
205+
206+
auto *SF = D->getDeclContext()->getParentSourceFile();
207+
if (!SF)
208+
return false;
209+
210+
auto constraints = getAvailabilityConstraintsForDecl(
211+
D, AvailabilityContext::forDeploymentTarget(ctx));
212+
llvm::SmallVector<AvailabilityDomain, 4> unavailableDomains;
213+
getRuntimeUnavailableDomains(constraints, unavailableDomains, ctx);
214+
215+
for (auto domain : unavailableDomains) {
216+
if (auto *domainDecl = domain.getDecl()) {
217+
if (SF->getRestrictedImportKind(domainDecl->getModuleContext()) ==
218+
RestrictedImportKind::SPIOnly)
219+
return true;
220+
}
221+
}
222+
223+
return false;
224+
}
225+
192226
/// Get the non-recursive printing options that should be applied when
193227
/// printing the type of a value decl.
194228
static NonRecursivePrintOptions getNonRecursiveOptions(const ValueDecl *D) {
@@ -293,8 +327,7 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
293327
if (D->getAttrs().hasAttribute<ImplementationOnlyAttr>())
294328
return false;
295329

296-
// Skip SPI decls if `PrintSPIs`.
297-
if (options.printPublicInterface() && D->isSPI())
330+
if (options.printPublicInterface() && shouldSkipDeclInPublicInterface(D))
298331
return false;
299332

300333
if (auto *VD = dyn_cast<ValueDecl>(D)) {
@@ -410,8 +443,6 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
410443
result.CurrentPrintabilityChecker =
411444
std::make_shared<ShouldPrintForModuleInterface>();
412445

413-
// FIXME: We don't really need 'public' on everything; we could just change
414-
// the default to 'public' and mark the 'internal' things.
415446
result.PrintAccess = true;
416447

417448
result.ExcludeAttrList = {

lib/AST/Attr.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,7 @@ void DeclAttributes::print(ASTPrinter &Printer, const PrintOptions &Options,
805805
AttributeVector modifiers;
806806
bool libraryLevelAPI =
807807
D && D->getASTContext().LangOpts.LibraryLevel == LibraryLevel::API;
808+
auto *SF = D ? D->getDeclContext()->getParentSourceFile() : nullptr;
808809

809810
for (auto DA : llvm::reverse(FlattenedAttrs)) {
810811
// Don't skip implicit custom attributes. Custom attributes like global
@@ -849,6 +850,16 @@ void DeclAttributes::print(ASTPrinter &Printer, const PrintOptions &Options,
849850
if (!semanticAttr)
850851
continue;
851852

853+
// In the public interfaces of -library-level=api modules, skip @available
854+
// attributes that refer to domains imported from @_spiOnly modules.
855+
if (Options.printPublicInterface() && libraryLevelAPI) {
856+
if (auto *domainDecl = semanticAttr->getDomain().getDecl()) {
857+
if (SF->getRestrictedImportKind(domainDecl->getModuleContext()) ==
858+
RestrictedImportKind::SPIOnly)
859+
continue;
860+
}
861+
}
862+
852863
if (isShortAvailable(*semanticAttr)) {
853864
if (semanticAttr->isSwiftLanguageModeSpecific())
854865
swiftVersionAvailableAttribute.emplace(*semanticAttr);

lib/AST/Module.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2879,6 +2879,7 @@ bool SourceFile::hasTestableOrPrivateImport(
28792879
});
28802880
}
28812881

2882+
// FIXME: This should probably be requestified.
28822883
RestrictedImportKind SourceFile::getRestrictedImportKind(const ModuleDecl *module) const {
28832884
auto &imports = getASTContext().getImportCache();
28842885
RestrictedImportKind importKind = RestrictedImportKind::MissingImport;

lib/Sema/ResilienceDiagnostics.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ static bool shouldDiagnoseDeclAccess(const ValueDecl *D,
231231
const ExportContext &where) {
232232
auto reason = where.getExportabilityReason();
233233
auto DC = where.getDeclContext();
234+
if (!reason)
235+
return false;
234236

235237
switch (*reason) {
236238
case ExportabilityReason::ExtensionWithPublicMembers:
@@ -304,6 +306,15 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D,
304306
break;
305307

306308
case DisallowedOriginKind::SPIOnly:
309+
// Availability attributes referring to availability domains from modules
310+
// that are imported @_spiOnly in a -library-level=api will not be printed
311+
// in the public swiftinterface of the module and should therefore not be
312+
// diagnosed for exportability.
313+
if (reason && reason == ExportabilityReason::AvailableAttribute &&
314+
ctx.LangOpts.LibraryLevel == LibraryLevel::API)
315+
return false;
316+
break;
317+
307318
case DisallowedOriginKind::ImplementationOnly:
308319
case DisallowedOriginKind::SPIImported:
309320
case DisallowedOriginKind::SPILocal:

test/Inputs/custom-modules/availability-domains/Lakes.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#include <availability_domain.h>
22

3+
int huron_pred(void);
4+
35
CLANG_ENABLED_AVAILABILITY_DOMAIN(Salt);
6+
CLANG_DISABLED_AVAILABILITY_DOMAIN(Erie);
7+
CLANG_DYNAMIC_AVAILABILITY_DOMAIN(Huron, huron_pred);
48

59
#define AVAIL 0
610
#define UNAVAIL 1

test/Inputs/custom-modules/availability-domains/Seas.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#include <availability_domain.h>
22

3+
int aegean_pred(void);
4+
35
CLANG_ENABLED_AVAILABILITY_DOMAIN(Baltic);
46
CLANG_DISABLED_AVAILABILITY_DOMAIN(Mediterranean);
7+
CLANG_DYNAMIC_AVAILABILITY_DOMAIN(Aegean, aegean_pred);
58

69
#define AVAIL 0
710
#define UNAVAIL 1
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-swift-frontend -emit-module %s \
4+
// RUN: -I %S/../Inputs/custom-modules/availability-domains \
5+
// RUN: -enable-experimental-feature CustomAvailability \
6+
// RUN: -experimental-spi-only-imports \
7+
// RUN: -enable-library-evolution -swift-version 5 -library-level api \
8+
// RUN: -package-name TestPackage -module-name Test \
9+
// RUN: -emit-module-interface-path %t/Test.swiftinterface \
10+
// RUN: -emit-private-module-interface-path %t/Test.private.swiftinterface \
11+
// RUN: -emit-package-module-interface-path %t/Test.package.swiftinterface
12+
13+
// RUN: %target-swift-typecheck-module-from-interface(%t/Test.swiftinterface) \
14+
// RUN: -I %S/../Inputs/custom-modules/availability-domains \
15+
// RUN: -module-name Test
16+
17+
// RUN: %target-swift-typecheck-module-from-interface(%t/Test.private.swiftinterface) \
18+
// RUN: -I %S/../Inputs/custom-modules/availability-domains \
19+
// RUN: -module-name Test
20+
21+
// RUN: %target-swift-typecheck-module-from-interface(%t/Test.package.swiftinterface) \
22+
// RUN: -I %S/../Inputs/custom-modules/availability-domains \
23+
// RUN: -module-name Test
24+
25+
// RUN: %FileCheck %s --check-prefixes=CHECK --input-file %t/Test.swiftinterface
26+
// RUN: %FileCheck %s --check-prefixes=CHECK-PUBLIC --input-file %t/Test.swiftinterface
27+
// RUN: %FileCheck %s --check-prefixes=CHECK,CHECK-NONPUBLIC --input-file %t/Test.private.swiftinterface
28+
// RUN: %FileCheck %s --check-prefixes=CHECK,CHECK-NONPUBLIC --input-file %t/Test.package.swiftinterface
29+
30+
// REQUIRES: swift_feature_CustomAvailability
31+
32+
import Lakes
33+
@_spiOnly import Seas
34+
35+
// CHECK: @available(Salt)
36+
// CHECK: public func availableInPublicEnabledDomain()
37+
@available(Salt)
38+
public func availableInPublicEnabledDomain() { }
39+
40+
// CHECK: @available(Erie)
41+
// CHECK: public func availableInPublicDisabledDomain()
42+
@available(Erie)
43+
public func availableInPublicDisabledDomain() { }
44+
45+
// CHECK: @available(Huron)
46+
// CHECK: public func availableInPublicDynamicDomain()
47+
@available(Huron)
48+
public func availableInPublicDynamicDomain() { }
49+
50+
// CHECK: @available(Salt, unavailable)
51+
// CHECK: public func unavailableInPublicEnabledDomain()
52+
@available(Salt, unavailable)
53+
public func unavailableInPublicEnabledDomain() { }
54+
55+
// CHECK: @available(Erie, unavailable)
56+
// CHECK: public func unavailableInPublicDisabledDomain()
57+
@available(Erie, unavailable)
58+
public func unavailableInPublicDisabledDomain() { }
59+
60+
// CHECK: @available(Huron, unavailable)
61+
// CHECK: public func unavailableInPublicDynamicDomain()
62+
@available(Huron, unavailable)
63+
public func unavailableInPublicDynamicDomain() { }
64+
65+
// CHECK-NONPUBLIC: @available(Baltic)
66+
// CHECK-PUBLIC-NOT: Baltic
67+
// CHECK-LABEL: public func availableInSPIOnlyEnabledDomain()
68+
@available(Baltic)
69+
public func availableInSPIOnlyEnabledDomain() { }
70+
71+
// CHECK-NONPUBLIC: @available(Mediterranean)
72+
// CHECK-NONPUBLIC-LABEL: public func availableInSPIOnlyDisabledDomain_Secret()
73+
// CHECK-PUBLIC-NOT: Mediterranean
74+
// CHECK-PUBLIC-NOT: availableInSPIOnlyDisabledDomain_Secret
75+
@available(Mediterranean)
76+
public func availableInSPIOnlyDisabledDomain_Secret() { }
77+
78+
// CHECK-NONPUBLIC: @available(Aegean)
79+
// CHECK-PUBLIC-NOT: Aegean
80+
// CHECK-LABEL: public func availableInSPIOnlyDynamicDomain()
81+
@available(Aegean)
82+
public func availableInSPIOnlyDynamicDomain() { }
83+
84+
// CHECK-NONPUBLIC: @available(Baltic, unavailable)
85+
// CHECK-NONPUBLIC-LABEL: public func unavailableInSPIOnlyEnabledDomain_Secret()
86+
// CHECK-PUBLIC-NOT: Baltic
87+
// CHECK-PUBLIC-NOT: unavailableInSPIOnlyEnabledDomain_Secret
88+
@available(Baltic, unavailable)
89+
public func unavailableInSPIOnlyEnabledDomain_Secret() { }
90+
91+
// CHECK-NONPUBLIC: @available(Mediterranean, unavailable)
92+
// CHECK-PUBLIC-NOT: Mediterranean
93+
// CHECK-LABEL: public func unavailableInSPIOnlyDisabledDomain()
94+
@available(Mediterranean, unavailable)
95+
public func unavailableInSPIOnlyDisabledDomain() { }
96+
97+
// CHECK: @available(*, unavailable)
98+
// CHECK-NONPUBLIC: @available(Baltic)
99+
// CHECK-PUBLIC-NOT: Baltic
100+
// CHECK-LABEL: public func availableInSPIOnlyEnabledDomainAndUnavailableUniversally()
101+
@available(*, unavailable)
102+
@available(Baltic)
103+
public func availableInSPIOnlyEnabledDomainAndUnavailableUniversally() { }
104+
105+
// CHECK-NONPUBLIC: @available(*, unavailable)
106+
// CHECK-NONPUBLIC: @available(Mediterranean)
107+
// CHECK-NONPUBLIC-LABEL: public func availableInSPIOnlyDisabledDomainAndUnavailableUniversally_Secret()
108+
// CHECK-PUBLIC-NOT: Mediterranean
109+
// CHECK-PUBLIC-NOT: availableInSPIOnlyDisabledDomainAndUnavailableUniversally_Secret
110+
@available(*, unavailable)
111+
@available(Mediterranean)
112+
public func availableInSPIOnlyDisabledDomainAndUnavailableUniversally_Secret() { }
113+
114+
// CHECK: @available(*, unavailable)
115+
// CHECK-NONPUBLIC: @available(Aegean)
116+
// CHECK-PUBLIC-NOT: Aegean
117+
// CHECK-LABEL: public func availableInSPIOnlyDynamicDomainAndUnavailableUniversally()
118+
@available(*, unavailable)
119+
@available(Aegean)
120+
public func availableInSPIOnlyDynamicDomainAndUnavailableUniversally() { }

0 commit comments

Comments
 (0)