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
8 changes: 6 additions & 2 deletions include/swift/AST/AvailabilityDomain.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,12 @@ class AvailabilityDomain final {

/// Returns true if this domain is considered active in the current
/// compilation context.
bool isActive(const ASTContext &ctx) const;
bool isActive(const ASTContext &ctx, bool forTargetVariant = false) const;

/// Returns true if this domain is a platform domain and is considered active
/// in the current compilation context.
bool isActivePlatform(const ASTContext &ctx) const;
bool isActivePlatform(const ASTContext &ctx,
bool forTargetVariant = false) const;

/// Returns the domain's minimum available range for type checking. For
/// example, for the domain of the platform that compilation is targeting,
Expand Down Expand Up @@ -332,6 +333,9 @@ class CustomAvailabilityDomain : public llvm::FoldingSetNode {
enum class Kind {
/// A domain that is known to be enabled at compile time.
Enabled,
/// A domain that is known to be enabled at compile time and is also assumed
/// to be enabled for all deployments.
AlwaysEnabled,
/// A domain that is known to be disabled at compile time.
Disabled,
/// A domain with an enablement state that must be queried at runtime.
Expand Down
2 changes: 2 additions & 0 deletions include/swift/Frontend/FrontendOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,8 @@ class FrontendOptions {
struct CustomAvailabilityDomains {
/// Domains defined with `-define-enabled-availability-domain=`.
llvm::SmallVector<std::string> EnabledDomains;
/// Domains defined with `-define-always-enabled-availability-domain=`.
llvm::SmallVector<std::string> AlwaysEnabledDomains;
/// Domains defined with `-define-disabled-availability-domain=`.
llvm::SmallVector<std::string> DisabledDomains;
/// Domains defined with `-define-dynamic-availability-domain=`.
Expand Down
8 changes: 8 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,14 @@ def define_enabled_availability_domain : Separate<["-"], "define-enabled-availab
HelpText<"Defines a custom availability domain that is available at compile time">,
MetaVarName<"<domain>">;

def define_always_enabled_availability_domain
: Separate<["-"], "define-always-enabled-availability-domain">,
Flags<[HelpHidden, FrontendOption, NoInteractiveOption,
ModuleInterfaceOptionIgnorable]>,
HelpText<"Defines a custom availability domain that is available for all "
"deployments">,
MetaVarName<"<domain>">;

def define_disabled_availability_domain : Separate<["-"], "define-disabled-availability-domain">,
Flags<[HelpHidden, FrontendOption, NoInteractiveOption, ModuleInterfaceOptionIgnorable]>,
HelpText<"Defines a custom availability domain that is unavailable at compile time">,
Expand Down
86 changes: 72 additions & 14 deletions lib/AST/AvailabilityConstraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,15 @@ getAvailabilityConstraintForAttr(const Decl *decl,

auto &ctx = decl->getASTContext();
auto domain = attr.getDomain();
auto deploymentRange = domain.getDeploymentRange(ctx);
bool domainSupportsRefinement = domain.supportsContextRefinement();
std::optional<AvailabilityRange> availableRange =
domainSupportsRefinement ? context.getAvailabilityRange(domain, ctx)
: deploymentRange;

// Compute the available range in the given context. If there is no explicit
// range defined by the context, use the deployment range as fallback.
std::optional<AvailabilityRange> availableRange;
if (domainSupportsRefinement)
availableRange = context.getAvailabilityRange(domain, ctx);
if (!availableRange)
availableRange = domain.getDeploymentRange(ctx);

// Is the decl obsoleted in this context?
if (auto obsoletedRange = attr.getObsoletedRange(ctx)) {
Expand Down Expand Up @@ -323,24 +327,78 @@ swift::getAvailabilityConstraintForDeclInDomain(
return std::nullopt;
}

/// Returns true if unsatisfied `@available(..., unavailable)` constraints for
/// \p domain make code unreachable at runtime
static bool
domainCanBeUnconditionallyUnavailableAtRuntime(AvailabilityDomain domain,
const ASTContext &ctx) {
switch (domain.getKind()) {
case AvailabilityDomain::Kind::Universal:
return true;

case AvailabilityDomain::Kind::Platform:
if (ctx.LangOpts.TargetVariant &&
domain.isActive(ctx, /*forTargetVariant=*/true))
return true;
return domain.isActive(ctx);

case AvailabilityDomain::Kind::SwiftLanguage:
case AvailabilityDomain::Kind::PackageDescription:
return false;

case AvailabilityDomain::Kind::Embedded:
return ctx.LangOpts.hasFeature(Feature::Embedded);

case AvailabilityDomain::Kind::Custom:
switch (domain.getCustomDomain()->getKind()) {
case CustomAvailabilityDomain::Kind::Enabled:
case CustomAvailabilityDomain::Kind::AlwaysEnabled:
return true;
case CustomAvailabilityDomain::Kind::Disabled:
case CustomAvailabilityDomain::Kind::Dynamic:
return false;
}
}
}

/// Returns true if unsatisfied introduction constraints for \p domain make
/// code unreachable at runtime.
static bool
domainIsUnavailableAtRuntimeIfUnintroduced(AvailabilityDomain domain,
const ASTContext &ctx) {
switch (domain.getKind()) {
case AvailabilityDomain::Kind::Universal:
case AvailabilityDomain::Kind::Platform:
case AvailabilityDomain::Kind::SwiftLanguage:
case AvailabilityDomain::Kind::PackageDescription:
return false;

case AvailabilityDomain::Kind::Embedded:
return !ctx.LangOpts.hasFeature(Feature::Embedded);

case AvailabilityDomain::Kind::Custom:
switch (domain.getCustomDomain()->getKind()) {
case CustomAvailabilityDomain::Kind::Enabled:
case CustomAvailabilityDomain::Kind::AlwaysEnabled:
case CustomAvailabilityDomain::Kind::Dynamic:
return false;
case CustomAvailabilityDomain::Kind::Disabled:
return true;
}
}
}

static bool constraintIndicatesRuntimeUnavailability(
const AvailabilityConstraint &constraint, const ASTContext &ctx) {
std::optional<CustomAvailabilityDomain::Kind> customDomainKind;
if (auto customDomain = constraint.getDomain().getCustomDomain())
customDomainKind = customDomain->getKind();

auto domain = constraint.getDomain();
switch (constraint.getReason()) {
case AvailabilityConstraint::Reason::UnavailableUnconditionally:
if (customDomainKind)
return customDomainKind == CustomAvailabilityDomain::Kind::Enabled;
return true;
return domainCanBeUnconditionallyUnavailableAtRuntime(domain, ctx);
case AvailabilityConstraint::Reason::UnavailableObsolete:
case AvailabilityConstraint::Reason::UnavailableUnintroduced:
return false;
case AvailabilityConstraint::Reason::Unintroduced:
if (customDomainKind)
return customDomainKind == CustomAvailabilityDomain::Kind::Disabled;
return false;
return domainIsUnavailableAtRuntimeIfUnintroduced(domain, ctx);
}
}

Expand Down
32 changes: 26 additions & 6 deletions lib/AST/AvailabilityDomain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ getCustomDomainKind(clang::FeatureAvailKind featureAvailKind) {
return CustomAvailabilityDomain::Kind::Disabled;
case clang::FeatureAvailKind::Dynamic:
return CustomAvailabilityDomain::Kind::Dynamic;
case clang::FeatureAvailKind::AlwaysAvailable:
return CustomAvailabilityDomain::Kind::AlwaysEnabled;
default:
llvm::report_fatal_error("unexpected kind");
}
Expand All @@ -57,6 +59,7 @@ customDomainForClangDecl(ValueDecl *decl) {
case clang::FeatureAvailKind::Available:
case clang::FeatureAvailKind::Unavailable:
case clang::FeatureAvailKind::Dynamic:
case clang::FeatureAvailKind::AlwaysAvailable:
break;
default:
return nullptr;
Expand Down Expand Up @@ -181,27 +184,29 @@ bool AvailabilityDomain::supportsQueries() const {
}
}

bool AvailabilityDomain::isActive(const ASTContext &ctx) const {
bool AvailabilityDomain::isActive(const ASTContext &ctx,
bool forTargetVariant) const {
switch (getKind()) {
case Kind::Universal:
case Kind::SwiftLanguage:
case Kind::PackageDescription:
case Kind::Embedded:
return true;
case Kind::Platform:
return isPlatformActive(getPlatformKind(), ctx.LangOpts);
return isPlatformActive(getPlatformKind(), ctx.LangOpts, forTargetVariant);
case Kind::Custom:
// For now, custom domains are always active but it's conceivable that in
// the future someone might want to define a domain but leave it inactive.
return true;
}
}

bool AvailabilityDomain::isActivePlatform(const ASTContext &ctx) const {
bool AvailabilityDomain::isActivePlatform(const ASTContext &ctx,
bool forTargetVariant) const {
if (!isPlatform())
return false;

return isActive(ctx);
return isActive(ctx, forTargetVariant);
}

static std::optional<llvm::VersionTuple>
Expand All @@ -224,8 +229,23 @@ getDeploymentVersion(const AvailabilityDomain &domain, const ASTContext &ctx) {

std::optional<AvailabilityRange>
AvailabilityDomain::getDeploymentRange(const ASTContext &ctx) const {
if (auto version = getDeploymentVersion(*this, ctx))
return AvailabilityRange{*version};
if (isVersioned()) {
if (auto version = getDeploymentVersion(*this, ctx))
return AvailabilityRange{*version};

return std::nullopt;
}

if (auto customDomain = getCustomDomain()) {
switch (customDomain->getKind()) {
case CustomAvailabilityDomain::Kind::AlwaysEnabled:
return AvailabilityRange::alwaysAvailable();
case CustomAvailabilityDomain::Kind::Enabled:
case CustomAvailabilityDomain::Kind::Disabled:
case CustomAvailabilityDomain::Kind::Dynamic:
return std::nullopt;
}
}
return std::nullopt;
}

Expand Down
1 change: 1 addition & 0 deletions lib/AST/AvailabilityScopeBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,7 @@ class AvailabilityScopeBuilder : private ASTWalker {

switch (customDomain->getKind()) {
case CustomAvailabilityDomain::Kind::Enabled:
case CustomAvailabilityDomain::Kind::AlwaysEnabled:
return AvailabilityQuery::constant(domain, true);
case CustomAvailabilityDomain::Kind::Disabled:
return AvailabilityQuery::constant(domain, false);
Expand Down
3 changes: 3 additions & 0 deletions lib/Frontend/ArgsToFrontendOptionsConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ bool ArgsToFrontendOptionsConverter::computeAvailabilityDomains() {

for (const Arg *A :
Args.filtered_reverse(OPT_define_enabled_availability_domain,
OPT_define_always_enabled_availability_domain,
OPT_define_disabled_availability_domain,
OPT_define_dynamic_availability_domain)) {
std::string domain = A->getValue();
Expand All @@ -602,6 +603,8 @@ bool ArgsToFrontendOptionsConverter::computeAvailabilityDomains() {
auto &option = A->getOption();
if (option.matches(OPT_define_enabled_availability_domain))
Opts.AvailabilityDomains.EnabledDomains.emplace_back(domain);
if (option.matches(OPT_define_always_enabled_availability_domain))
Opts.AvailabilityDomains.AlwaysEnabledDomains.emplace_back(domain);
else if (option.matches(OPT_define_disabled_availability_domain))
Opts.AvailabilityDomains.DisabledDomains.emplace_back(domain);
else if (option.matches(OPT_define_dynamic_availability_domain))
Expand Down
3 changes: 3 additions & 0 deletions lib/Frontend/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,9 @@ static void configureAvailabilityDomains(const ASTContext &ctx,

for (auto enabled : opts.AvailabilityDomains.EnabledDomains)
createAndInsertDomain(enabled, CustomAvailabilityDomain::Kind::Enabled);
for (auto alwaysEnabled : opts.AvailabilityDomains.AlwaysEnabledDomains)
createAndInsertDomain(alwaysEnabled,
CustomAvailabilityDomain::Kind::AlwaysEnabled);
for (auto disabled : opts.AvailabilityDomains.DisabledDomains)
createAndInsertDomain(disabled, CustomAvailabilityDomain::Kind::Disabled);
for (auto dynamic : opts.AvailabilityDomains.DynamicDomains)
Expand Down
36 changes: 36 additions & 0 deletions test/Availability/availability_custom_domains.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// RUN: %target-typecheck-verify-swift \
// RUN: -enable-experimental-feature CustomAvailability \
// RUN: -define-enabled-availability-domain EnabledDomain \
// RUN: -define-always-enabled-availability-domain AlwaysEnabledDomain \
// RUN: -define-disabled-availability-domain DisabledDomain \
// RUN: -define-dynamic-availability-domain DynamicDomain

Expand All @@ -11,9 +12,15 @@ func alwaysAvailable() { }
@available(EnabledDomain)
func availableInEnabledDomain() { }

@available(AlwaysEnabledDomain)
func availableInAlwaysEnabledDomain() { }

@available(EnabledDomain, unavailable)
func unavailableInEnabledDomain() { } // expected-note * {{'unavailableInEnabledDomain()' has been explicitly marked unavailable here}}

@available(AlwaysEnabledDomain, unavailable)
func unavailableInAlwaysEnabledDomain() { } // expected-note * {{'unavailableInAlwaysEnabledDomain()' has been explicitly marked unavailable here}}

@available(DisabledDomain, unavailable)
func unavailableInDisabledDomain() { } // expected-note * {{'unavailableInDisabledDomain()' has been explicitly marked unavailable here}}

Expand Down Expand Up @@ -41,7 +48,9 @@ func testDeployment() { // expected-note 3 {{add '@available' attribute to enclo
alwaysAvailable()
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
// expected-note@-1 {{add 'if #available' version check}}
availableInAlwaysEnabledDomain()
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
unavailableInAlwaysEnabledDomain() // expected-error {{'unavailableInAlwaysEnabledDomain()' is unavailable}}
unavailableInDisabledDomain() // expected-error {{'unavailableInDisabledDomain()' is unavailable}}
deprecatedInDynamicDomain() // expected-warning {{'deprecatedInDynamicDomain()' is deprecated: Use something else}}
unavailableInDynamicDomain() // expected-error {{'unavailableInDynamicDomain()' is unavailable}}
Expand All @@ -53,10 +62,14 @@ func testDeployment() { // expected-note 3 {{add '@available' attribute to enclo
availableAndUnavailableInEnabledDomain() // expected-error {{'availableAndUnavailableInEnabledDomain()' is unavailable}}
}

// FIXME: [availability] Test @inlinable functions.

func testIfAvailable(_ truthy: Bool) { // expected-note 9 {{add '@available' attribute to enclosing global function}}
if #available(EnabledDomain) { // expected-note {{enclosing scope here}}
availableInEnabledDomain()
availableInAlwaysEnabledDomain()
unavailableInEnabledDomain() // expected-error {{'unavailableInEnabledDomain()' is unavailable}}
unavailableInAlwaysEnabledDomain() // expected-error {{'unavailableInAlwaysEnabledDomain()' is unavailable}}
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
// expected-note@-1 {{add 'if #available' version check}}
unavailableInDynamicDomain() // expected-error {{'unavailableInDynamicDomain()' is unavailable}}
Expand Down Expand Up @@ -133,6 +146,14 @@ func testIfAvailable(_ truthy: Bool) { // expected-note 9 {{add '@available' att
if #unavailable(EnabledDomain), #available(DynamicDomain) {
// expected-error@-1 {{#available and #unavailable cannot be in the same statement}}
}

if #available(AlwaysEnabledDomain) {
availableInAlwaysEnabledDomain()
unavailableInAlwaysEnabledDomain() // expected-error {{'unavailableInAlwaysEnabledDomain()' is unavailable}}
} else {
availableInAlwaysEnabledDomain()
unavailableInAlwaysEnabledDomain()
}
}

func testWhileAvailable() { // expected-note {{add '@available' attribute to enclosing global function}}
Expand Down Expand Up @@ -207,13 +228,28 @@ func testEnabledDomainUnavailable() { // expected-note {{add '@available' attrib
availableInUnknownDomain()
}

@available(AlwaysEnabledDomain)
func testAlwaysEnabledDomainAvailable() {
availableInAlwaysEnabledDomain()
unavailableInAlwaysEnabledDomain() // expected-error {{'unavailableInAlwaysEnabledDomain()' is unavailable}}
}

@available(AlwaysEnabledDomain, unavailable)
func testAlwaysEnabledDomainUnavailable() {
availableInAlwaysEnabledDomain()
unavailableInAlwaysEnabledDomain()
}

@available(*, unavailable)
func testUniversallyUnavailable() {
alwaysAvailable()
// FIXME: [availability] Diagnostic consistency: potentially unavailable declaration shouldn't be diagnosed
// in contexts that are unavailable to broader domains
availableInEnabledDomain() // expected-error {{'availableInEnabledDomain()' is only available in EnabledDomain}}
// expected-note@-1 {{add 'if #available' version check}}
unavailableInEnabledDomain()
availableInAlwaysEnabledDomain()
unavailableInAlwaysEnabledDomain()
unavailableInDisabledDomain()
deprecatedInDynamicDomain() // expected-warning {{'deprecatedInDynamicDomain()' is deprecated: Use something else}}
availableInDynamicDomain() // expected-error {{'availableInDynamicDomain()' is only available in DynamicDomain}}
Expand Down
22 changes: 22 additions & 0 deletions test/ClangImporter/Inputs/availability_custom_domains_other.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,26 @@ func availableInMediterranean() { }
func testOtherClangDecls() { // expected-note {{add '@available' attribute to enclosing global function}}
available_in_baltic() // expected-error {{'available_in_baltic()' is only available in Baltic}}
// expected-note@-1 {{add 'if #available' version check}}
available_in_bering() // ok, Bering is always available
unavailable_in_bering() // expected-error {{'unavailable_in_bering()' is unavailable}}
}

@available(Baltic)
func availableInBalticOther() {
available_in_baltic()
available_in_bering() // ok, Bering is always available
unavailable_in_bering() // expected-error {{'unavailable_in_bering()' is unavailable}}
}

@available(Bering)
func availableInBering() { // expected-note {{add '@available' attribute to enclosing global function}}
available_in_baltic() // expected-error {{'available_in_baltic()' is only available in Baltic}}
// expected-note@-1 {{add 'if #available' version check}}
available_in_bering()
unavailable_in_bering() // expected-error {{'unavailable_in_bering()' is unavailable}}
}

@available(Bering, unavailable)
func unavailableInBering() {
unavailable_in_bering()
}
Loading