Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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 = {
Expand Down
11 changes: 11 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 1 addition & 8 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,14 +354,7 @@ static bool usesFeatureCoroutineAccessors(Decl *decl) {
}

UNINTERESTING_FEATURE(GeneralizedIsSameMetaTypeBuiltin)

static bool usesFeatureCustomAvailability(Decl *decl) {
for (auto attr : decl->getSemanticAvailableAttrs()) {
if (attr.getDomain().isCustom())
return true;
}
return false;
}
UNINTERESTING_FEATURE(CustomAvailability)

static bool usesFeatureAsyncExecutionBehaviorAttributes(Decl *decl) {
// Explicit `@concurrent` attribute on the declaration.
Expand Down
1 change: 1 addition & 0 deletions lib/AST/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
105 changes: 72 additions & 33 deletions lib/Sema/ResilienceDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,42 @@ static bool diagnoseTypeAliasDeclRefExportability(SourceLoc loc,
return true;
}

/// Returns true if access to \p D should be diagnosed during exportability
/// checking. These diagnostics would typically be handled by the access
/// checker, and therefore should be suppressed to avoid duplicate diagnostics.
/// However, extensions are special because they do not have an intrinsic access
/// level and therefore the access checker does not currently handle them.
/// Instead, diagnostics for decls referenced in extension signatures are
/// deferred to exportability checking. An exportable extension is effectively a
/// public extension.
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:
case ExportabilityReason::ExtensionWithConditionalConformances:
return true;
case ExportabilityReason::Inheritance:
return isa<ProtocolDecl>(D);
case ExportabilityReason::AvailableAttribute:
// If the context is an extension and that extension has an explicit
// access level then availability domains access has already been
// diagnosed.
if (auto *ED = dyn_cast_or_null<ExtensionDecl>(DC->getAsDecl()))
return !ED->getAttrs().getAttribute<AccessControlAttr>();
return false;

case ExportabilityReason::General:
case ExportabilityReason::ResultBuilder:
case ExportabilityReason::PropertyWrapper:
return false;
}
}

static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D,
const ExportContext &where) {
assert(where.mustOnlyReferenceExportedDecls());
Expand Down Expand Up @@ -247,41 +283,44 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D,
}
});

// Access levels from imports are reported with the others access levels.
// Except for extensions and protocol conformances, we report them here.
if (originKind == DisallowedOriginKind::NonPublicImport) {
bool reportHere = [&] {
switch (*reason) {
case ExportabilityReason::ExtensionWithPublicMembers:
case ExportabilityReason::ExtensionWithConditionalConformances:
return true;
case ExportabilityReason::Inheritance:
return isa<ProtocolDecl>(D);
case ExportabilityReason::AvailableAttribute:
// If the context is an extension and that extension has an explicit
// access level, then access has already been diagnosed for the
// @available attribute.
if (auto *ED = dyn_cast_or_null<ExtensionDecl>(DC->getAsDecl()))
return !ED->getAttrs().getAttribute<AccessControlAttr>();
return false;
default:
return false;
}
}();
if (!reportHere)
return false;
}

if (originKind == DisallowedOriginKind::None)
switch (originKind) {
case DisallowedOriginKind::None:
// The decl does not come from a source that needs to be checked for
// exportability.
return false;

// Some diagnostics emitted with the `MemberImportVisibility` feature enabled
// subsume these diagnostics.
if (originKind == DisallowedOriginKind::MissingImport &&
ctx.LangOpts.hasFeature(Feature::MemberImportVisibility,
/*allowMigration=*/true) &&
SF)
return false;
case DisallowedOriginKind::NonPublicImport:
// With a few exceptions, access levels from imports are diagnosed during
// access checking and should be skipped here.
if (!shouldDiagnoseDeclAccess(D, where))
return false;
break;

case DisallowedOriginKind::MissingImport:
// Some diagnostics emitted with the `MemberImportVisibility` feature
// enabled subsume these diagnostics.
if (ctx.LangOpts.hasFeature(Feature::MemberImportVisibility,
/*allowMigration=*/true) &&
SF)
return false;
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:
case DisallowedOriginKind::FragileCxxAPI:
break;
}

if (auto accessor = dyn_cast<AccessorDecl>(D)) {
// Only diagnose accessors if their disallowed origin kind differs from
Expand Down
4 changes: 4 additions & 0 deletions test/Inputs/custom-modules/availability-domains/Lakes.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 3 additions & 0 deletions test/Inputs/custom-modules/availability-domains/Seas.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 4 additions & 6 deletions test/ModuleInterface/availability_custom_domains.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@

import Oceans // re-exports Rivers

// CHECK: #if compiler(>=5.3) && $CustomAvailability
// CHECK-NEXT: @available(Colorado)
// CHECK-NOT: $CustomAvailability

// CHECK: @available(Colorado)
// CHECK-NEXT: public func availableInColorado()
// CHECK-NEXT: #endif
@available(Colorado)
public func availableInColorado() { }

// CHECK: #if compiler(>=5.3) && $CustomAvailability
// CHECK-NEXT: @available(Arctic, unavailable)
// CHECK: @available(Arctic, unavailable)
// CHECK-NEXT: @available(Pacific)
// CHECK-NEXT: public func unavailableInArcticButAvailableInPacific()
// CHECK-NEXT: #endif
@available(Arctic, unavailable)
@available(Pacific)
public func unavailableInArcticButAvailableInPacific() { }
Loading