mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
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.
This commit is contained in:
@@ -20,6 +20,8 @@
|
||||
#include "swift/AST/ASTMangler.h"
|
||||
#include "swift/AST/ASTVisitor.h"
|
||||
#include "swift/AST/Attr.h"
|
||||
#include "swift/AST/AvailabilityConstraint.h"
|
||||
#include "swift/AST/AvailabilityContext.h"
|
||||
#include "swift/AST/Builtins.h"
|
||||
#include "swift/AST/ClangModuleLoader.h"
|
||||
#include "swift/AST/Comment.h"
|
||||
@@ -189,6 +191,38 @@ static bool shouldPrintAllSemanticDetails(const PrintOptions &options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool shouldSkipDeclInPublicInterface(const Decl *D) {
|
||||
// @_spi should be skipped in the public interface.
|
||||
if (D->isSPI())
|
||||
return true;
|
||||
|
||||
// Decls that are unavailable at runtime in an availability domain that has
|
||||
// been @_spiOnly imported should be hidden from the public interface of
|
||||
// a -library-level=api module.
|
||||
auto &ctx = D->getDeclContext()->getASTContext();
|
||||
if (ctx.LangOpts.LibraryLevel != LibraryLevel::API)
|
||||
return false;
|
||||
|
||||
auto *SF = D->getDeclContext()->getParentSourceFile();
|
||||
if (!SF)
|
||||
return false;
|
||||
|
||||
auto constraints = getAvailabilityConstraintsForDecl(
|
||||
D, AvailabilityContext::forDeploymentTarget(ctx));
|
||||
llvm::SmallVector<AvailabilityDomain, 4> unavailableDomains;
|
||||
getRuntimeUnavailableDomains(constraints, unavailableDomains, ctx);
|
||||
|
||||
for (auto domain : unavailableDomains) {
|
||||
if (auto *domainDecl = domain.getDecl()) {
|
||||
if (SF->getRestrictedImportKind(domainDecl->getModuleContext()) ==
|
||||
RestrictedImportKind::SPIOnly)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Get the non-recursive printing options that should be applied when
|
||||
/// printing the type of a value decl.
|
||||
static NonRecursivePrintOptions getNonRecursiveOptions(const ValueDecl *D) {
|
||||
@@ -293,8 +327,7 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
|
||||
if (D->getAttrs().hasAttribute<ImplementationOnlyAttr>())
|
||||
return false;
|
||||
|
||||
// Skip SPI decls if `PrintSPIs`.
|
||||
if (options.printPublicInterface() && D->isSPI())
|
||||
if (options.printPublicInterface() && shouldSkipDeclInPublicInterface(D))
|
||||
return false;
|
||||
|
||||
if (auto *VD = dyn_cast<ValueDecl>(D)) {
|
||||
@@ -410,8 +443,6 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
|
||||
result.CurrentPrintabilityChecker =
|
||||
std::make_shared<ShouldPrintForModuleInterface>();
|
||||
|
||||
// FIXME: We don't really need 'public' on everything; we could just change
|
||||
// the default to 'public' and mark the 'internal' things.
|
||||
result.PrintAccess = true;
|
||||
|
||||
result.ExcludeAttrList = {
|
||||
|
||||
@@ -805,6 +805,7 @@ void DeclAttributes::print(ASTPrinter &Printer, const PrintOptions &Options,
|
||||
AttributeVector modifiers;
|
||||
bool libraryLevelAPI =
|
||||
D && D->getASTContext().LangOpts.LibraryLevel == LibraryLevel::API;
|
||||
auto *SF = D ? D->getDeclContext()->getParentSourceFile() : nullptr;
|
||||
|
||||
for (auto DA : llvm::reverse(FlattenedAttrs)) {
|
||||
// Don't skip implicit custom attributes. Custom attributes like global
|
||||
@@ -849,6 +850,16 @@ void DeclAttributes::print(ASTPrinter &Printer, const PrintOptions &Options,
|
||||
if (!semanticAttr)
|
||||
continue;
|
||||
|
||||
// In the public interfaces of -library-level=api modules, skip @available
|
||||
// attributes that refer to domains imported from @_spiOnly modules.
|
||||
if (Options.printPublicInterface() && libraryLevelAPI) {
|
||||
if (auto *domainDecl = semanticAttr->getDomain().getDecl()) {
|
||||
if (SF->getRestrictedImportKind(domainDecl->getModuleContext()) ==
|
||||
RestrictedImportKind::SPIOnly)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (isShortAvailable(*semanticAttr)) {
|
||||
if (semanticAttr->isSwiftLanguageModeSpecific())
|
||||
swiftVersionAvailableAttribute.emplace(*semanticAttr);
|
||||
|
||||
@@ -2879,6 +2879,7 @@ bool SourceFile::hasTestableOrPrivateImport(
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME: This should probably be requestified.
|
||||
RestrictedImportKind SourceFile::getRestrictedImportKind(const ModuleDecl *module) const {
|
||||
auto &imports = getASTContext().getImportCache();
|
||||
RestrictedImportKind importKind = RestrictedImportKind::MissingImport;
|
||||
|
||||
@@ -231,6 +231,8 @@ static bool shouldDiagnoseDeclAccess(const ValueDecl *D,
|
||||
const ExportContext &where) {
|
||||
auto reason = where.getExportabilityReason();
|
||||
auto DC = where.getDeclContext();
|
||||
if (!reason)
|
||||
return false;
|
||||
|
||||
switch (*reason) {
|
||||
case ExportabilityReason::ExtensionWithPublicMembers:
|
||||
@@ -304,6 +306,15 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D,
|
||||
break;
|
||||
|
||||
case DisallowedOriginKind::SPIOnly:
|
||||
// Availability attributes referring to availability domains from modules
|
||||
// that are imported @_spiOnly in a -library-level=api will not be printed
|
||||
// in the public swiftinterface of the module and should therefore not be
|
||||
// diagnosed for exportability.
|
||||
if (reason && reason == ExportabilityReason::AvailableAttribute &&
|
||||
ctx.LangOpts.LibraryLevel == LibraryLevel::API)
|
||||
return false;
|
||||
break;
|
||||
|
||||
case DisallowedOriginKind::ImplementationOnly:
|
||||
case DisallowedOriginKind::SPIImported:
|
||||
case DisallowedOriginKind::SPILocal:
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#include <availability_domain.h>
|
||||
|
||||
int huron_pred(void);
|
||||
|
||||
CLANG_ENABLED_AVAILABILITY_DOMAIN(Salt);
|
||||
CLANG_DISABLED_AVAILABILITY_DOMAIN(Erie);
|
||||
CLANG_DYNAMIC_AVAILABILITY_DOMAIN(Huron, huron_pred);
|
||||
|
||||
#define AVAIL 0
|
||||
#define UNAVAIL 1
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include <availability_domain.h>
|
||||
|
||||
int aegean_pred(void);
|
||||
|
||||
CLANG_ENABLED_AVAILABILITY_DOMAIN(Baltic);
|
||||
CLANG_DISABLED_AVAILABILITY_DOMAIN(Mediterranean);
|
||||
CLANG_DYNAMIC_AVAILABILITY_DOMAIN(Aegean, aegean_pred);
|
||||
|
||||
#define AVAIL 0
|
||||
#define UNAVAIL 1
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
|
||||
// RUN: %target-swift-frontend -emit-module %s \
|
||||
// RUN: -I %S/../Inputs/custom-modules/availability-domains \
|
||||
// RUN: -enable-experimental-feature CustomAvailability \
|
||||
// RUN: -experimental-spi-only-imports \
|
||||
// RUN: -enable-library-evolution -swift-version 5 -library-level api \
|
||||
// RUN: -package-name TestPackage -module-name Test \
|
||||
// RUN: -emit-module-interface-path %t/Test.swiftinterface \
|
||||
// RUN: -emit-private-module-interface-path %t/Test.private.swiftinterface \
|
||||
// RUN: -emit-package-module-interface-path %t/Test.package.swiftinterface
|
||||
|
||||
// RUN: %target-swift-typecheck-module-from-interface(%t/Test.swiftinterface) \
|
||||
// RUN: -I %S/../Inputs/custom-modules/availability-domains \
|
||||
// RUN: -module-name Test
|
||||
|
||||
// RUN: %target-swift-typecheck-module-from-interface(%t/Test.private.swiftinterface) \
|
||||
// RUN: -I %S/../Inputs/custom-modules/availability-domains \
|
||||
// RUN: -module-name Test
|
||||
|
||||
// RUN: %target-swift-typecheck-module-from-interface(%t/Test.package.swiftinterface) \
|
||||
// RUN: -I %S/../Inputs/custom-modules/availability-domains \
|
||||
// RUN: -module-name Test
|
||||
|
||||
// RUN: %FileCheck %s --check-prefixes=CHECK --input-file %t/Test.swiftinterface
|
||||
// RUN: %FileCheck %s --check-prefixes=CHECK-PUBLIC --input-file %t/Test.swiftinterface
|
||||
// RUN: %FileCheck %s --check-prefixes=CHECK,CHECK-NONPUBLIC --input-file %t/Test.private.swiftinterface
|
||||
// RUN: %FileCheck %s --check-prefixes=CHECK,CHECK-NONPUBLIC --input-file %t/Test.package.swiftinterface
|
||||
|
||||
// REQUIRES: swift_feature_CustomAvailability
|
||||
|
||||
import Lakes
|
||||
@_spiOnly import Seas
|
||||
|
||||
// CHECK: @available(Salt)
|
||||
// CHECK: public func availableInPublicEnabledDomain()
|
||||
@available(Salt)
|
||||
public func availableInPublicEnabledDomain() { }
|
||||
|
||||
// CHECK: @available(Erie)
|
||||
// CHECK: public func availableInPublicDisabledDomain()
|
||||
@available(Erie)
|
||||
public func availableInPublicDisabledDomain() { }
|
||||
|
||||
// CHECK: @available(Huron)
|
||||
// CHECK: public func availableInPublicDynamicDomain()
|
||||
@available(Huron)
|
||||
public func availableInPublicDynamicDomain() { }
|
||||
|
||||
// CHECK: @available(Salt, unavailable)
|
||||
// CHECK: public func unavailableInPublicEnabledDomain()
|
||||
@available(Salt, unavailable)
|
||||
public func unavailableInPublicEnabledDomain() { }
|
||||
|
||||
// CHECK: @available(Erie, unavailable)
|
||||
// CHECK: public func unavailableInPublicDisabledDomain()
|
||||
@available(Erie, unavailable)
|
||||
public func unavailableInPublicDisabledDomain() { }
|
||||
|
||||
// CHECK: @available(Huron, unavailable)
|
||||
// CHECK: public func unavailableInPublicDynamicDomain()
|
||||
@available(Huron, unavailable)
|
||||
public func unavailableInPublicDynamicDomain() { }
|
||||
|
||||
// CHECK-NONPUBLIC: @available(Baltic)
|
||||
// CHECK-PUBLIC-NOT: Baltic
|
||||
// CHECK-LABEL: public func availableInSPIOnlyEnabledDomain()
|
||||
@available(Baltic)
|
||||
public func availableInSPIOnlyEnabledDomain() { }
|
||||
|
||||
// CHECK-NONPUBLIC: @available(Mediterranean)
|
||||
// CHECK-NONPUBLIC-LABEL: public func availableInSPIOnlyDisabledDomain_Secret()
|
||||
// CHECK-PUBLIC-NOT: Mediterranean
|
||||
// CHECK-PUBLIC-NOT: availableInSPIOnlyDisabledDomain_Secret
|
||||
@available(Mediterranean)
|
||||
public func availableInSPIOnlyDisabledDomain_Secret() { }
|
||||
|
||||
// CHECK-NONPUBLIC: @available(Aegean)
|
||||
// CHECK-PUBLIC-NOT: Aegean
|
||||
// CHECK-LABEL: public func availableInSPIOnlyDynamicDomain()
|
||||
@available(Aegean)
|
||||
public func availableInSPIOnlyDynamicDomain() { }
|
||||
|
||||
// CHECK-NONPUBLIC: @available(Baltic, unavailable)
|
||||
// CHECK-NONPUBLIC-LABEL: public func unavailableInSPIOnlyEnabledDomain_Secret()
|
||||
// CHECK-PUBLIC-NOT: Baltic
|
||||
// CHECK-PUBLIC-NOT: unavailableInSPIOnlyEnabledDomain_Secret
|
||||
@available(Baltic, unavailable)
|
||||
public func unavailableInSPIOnlyEnabledDomain_Secret() { }
|
||||
|
||||
// CHECK-NONPUBLIC: @available(Mediterranean, unavailable)
|
||||
// CHECK-PUBLIC-NOT: Mediterranean
|
||||
// CHECK-LABEL: public func unavailableInSPIOnlyDisabledDomain()
|
||||
@available(Mediterranean, unavailable)
|
||||
public func unavailableInSPIOnlyDisabledDomain() { }
|
||||
|
||||
// CHECK: @available(*, unavailable)
|
||||
// CHECK-NONPUBLIC: @available(Baltic)
|
||||
// CHECK-PUBLIC-NOT: Baltic
|
||||
// CHECK-LABEL: public func availableInSPIOnlyEnabledDomainAndUnavailableUniversally()
|
||||
@available(*, unavailable)
|
||||
@available(Baltic)
|
||||
public func availableInSPIOnlyEnabledDomainAndUnavailableUniversally() { }
|
||||
|
||||
// CHECK-NONPUBLIC: @available(*, unavailable)
|
||||
// CHECK-NONPUBLIC: @available(Mediterranean)
|
||||
// CHECK-NONPUBLIC-LABEL: public func availableInSPIOnlyDisabledDomainAndUnavailableUniversally_Secret()
|
||||
// CHECK-PUBLIC-NOT: Mediterranean
|
||||
// CHECK-PUBLIC-NOT: availableInSPIOnlyDisabledDomainAndUnavailableUniversally_Secret
|
||||
@available(*, unavailable)
|
||||
@available(Mediterranean)
|
||||
public func availableInSPIOnlyDisabledDomainAndUnavailableUniversally_Secret() { }
|
||||
|
||||
// CHECK: @available(*, unavailable)
|
||||
// CHECK-NONPUBLIC: @available(Aegean)
|
||||
// CHECK-PUBLIC-NOT: Aegean
|
||||
// CHECK-LABEL: public func availableInSPIOnlyDynamicDomainAndUnavailableUniversally()
|
||||
@available(*, unavailable)
|
||||
@available(Aegean)
|
||||
public func availableInSPIOnlyDynamicDomainAndUnavailableUniversally() { }
|
||||
Reference in New Issue
Block a user