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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ _**Note:** This is in reverse chronological order, so newer entries are added to

## Swift 5.7

* [SE-0340][]:

It is now possible to make declarations unavailable from use in asynchronous
contexts with the `@available(*, noasync)` attribute.

This is to protect the consumers of an API against undefined behavior that can
occur when the API uses thread-local storage, or encourages using thread-local
storage, across suspension points, or protect developers against holding locks
across suspension points which may lead to undefined behavior, priority
inversions, or deadlocks.

* [SE-0343][]:

Top-level scripts support asynchronous calls.
Expand Down Expand Up @@ -9083,6 +9094,7 @@ Swift 1.0
[SE-0341]: <https://github.com/apple/swift-evolution/blob/main/proposals/0341-opaque-parameters.md>
[SE-0336]: <https://github.com/apple/swift-evolution/blob/main/proposals/0336-distributed-actor-isolation.md>
[SE-0343]: <https://github.com/apple/swift-evolution/blob/main/proposals/0343-top-level-concurrency.md>
[SE-0340]: <https://github.com/apple/swift-evolution/blob/main/proposals/0340-swift-noasync.md>

[SR-75]: <https://bugs.swift.org/browse/SR-75>
[SR-106]: <https://bugs.swift.org/browse/SR-106>
Expand Down
10 changes: 10 additions & 0 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,8 @@ enum class PlatformAgnosticAvailabilityKind {
PackageDescriptionVersionSpecific,
/// The declaration is unavailable for other reasons.
Unavailable,
/// The declaration is unavailable from asynchronous contexts
NoAsync,
};

/// Defines the @available attribute.
Expand Down Expand Up @@ -702,6 +704,9 @@ class AvailableAttr : public DeclAttribute {
/// Whether this is an unconditionally deprecated entity.
bool isUnconditionallyDeprecated() const;

/// Whether this is a noasync attribute.
bool isNoAsync() const;

/// Returns the platform-agnostic availability.
PlatformAgnosticAvailabilityKind getPlatformAgnosticAvailability() const {
return PlatformAgnostic;
Expand Down Expand Up @@ -2261,6 +2266,11 @@ class DeclAttributes {
/// a declaration will be deprecated in the future, or null otherwise.
const AvailableAttr *getSoftDeprecated(const ASTContext &ctx) const;

/// Returns the first @available attribute that indicates
/// a declaration is unavailable from asynchronous contexts, or null
/// otherwise.
const AvailableAttr *getNoAsync(const ASTContext &ctx) const;

SWIFT_DEBUG_DUMPER(dump(const Decl *D = nullptr));
void print(ASTPrinter &Printer, const PrintOptions &Options,
const Decl *D = nullptr) const;
Expand Down
5 changes: 2 additions & 3 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1497,9 +1497,8 @@ ERROR(attr_unsupported_on_target, none,
// availability
ERROR(attr_availability_platform,none,
"expected platform name or '*' for '%0' attribute", (StringRef))
ERROR(attr_availability_unavailable_deprecated,none,
"'%0' attribute cannot be both unconditionally 'unavailable' and "
"'deprecated'", (StringRef))
ERROR(attr_availability_multiple_kinds ,none,
"'%0' attribute cannot be both '%1' and '%2'", (StringRef, StringRef, StringRef))

WARNING(attr_availability_invalid_duplicate,none,
"'%0' argument has already been specified", (StringRef))
Expand Down
4 changes: 2 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4848,8 +4848,8 @@ ERROR(async_named_decl_must_be_available_from_async,none,
"asynchronous %0 %1 must be available from asynchronous contexts",
(DescriptiveDeclKind, DeclName))
ERROR(async_unavailable_decl,none,
"%0 %1 is unavailable from asynchronous contexts%select{|; %3}2",
(DescriptiveDeclKind, DeclBaseName, bool, StringRef))
"%0 %1 is unavailable from asynchronous contexts%select{|; %2}2",
(DescriptiveDeclKind, DeclBaseName, StringRef))

//------------------------------------------------------------------------------
// MARK: String Processing
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/PrintOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ struct PrintOptions {
/// Whether to print generic requirements in a where clause.
bool PrintGenericRequirements = true;

/// Suppress emitting @available(*, noasync)
bool SuppressNoAsyncAvailabilityAttr = false;

/// How to print opaque return types.
enum class OpaqueReturnTypePrintingMode {
/// 'some P1 & P2'.
Expand Down
1 change: 1 addition & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ LANGUAGE_FEATURE(BuiltinAssumeAlignment, 0, "Builtin.assumeAlignment", true)
SUPPRESSIBLE_LANGUAGE_FEATURE(UnsafeInheritExecutor, 0, "@_unsafeInheritExecutor", true)
SUPPRESSIBLE_LANGUAGE_FEATURE(PrimaryAssociatedTypes, 0, "Primary associated types", true)
SUPPRESSIBLE_LANGUAGE_FEATURE(UnavailableFromAsync, 0, "@_unavailableFromAsync", true)
SUPPRESSIBLE_LANGUAGE_FEATURE(NoAsyncAvailability, 340, "@available(*, noasync)", true)

#undef SUPPRESSIBLE_LANGUAGE_FEATURE
#undef LANGUAGE_FEATURE
12 changes: 12 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3015,6 +3015,18 @@ suppressingFeatureUnavailableFromAsync(PrintOptions &options,
options.ExcludeAttrList.resize(originalExcludeAttrCount);
}

static bool usesFeatureNoAsyncAvailability(Decl *decl) {
return decl->getAttrs().getNoAsync(decl->getASTContext()) != nullptr;
}

static void
suppressingFeatureNoAsyncAvailability(PrintOptions &options,
llvm::function_ref<void()> action) {
llvm::SaveAndRestore<PrintOptions> orignalOptions(options);
options.SuppressNoAsyncAvailabilityAttr = true;
action();
}

/// Suppress the printing of a particular feature.
static void suppressingFeature(PrintOptions &options, Feature feature,
llvm::function_ref<void()> action) {
Expand Down
55 changes: 54 additions & 1 deletion lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ DeclAttributes::findMostSpecificActivePlatform(const ASTContext &ctx) const{
continue;

// We have an attribute that is active for the platform, but
// is it more specific than our curent best?
// is it more specific than our current best?
if (!bestAttr || inheritsAvailabilityFromPlatform(avAttr->Platform,
bestAttr->Platform)) {
bestAttr = avAttr;
Expand Down Expand Up @@ -356,6 +356,48 @@ DeclAttributes::getSoftDeprecated(const ASTContext &ctx) const {
return conditional;
}

const AvailableAttr *DeclAttributes::getNoAsync(const ASTContext &ctx) const {
const AvailableAttr *bestAttr = nullptr;
for (const DeclAttribute *attr : *this) {
if (const AvailableAttr *avAttr = dyn_cast<AvailableAttr>(attr)) {
if (avAttr->isInvalid())
continue;

if (avAttr->getPlatformAgnosticAvailability() ==
PlatformAgnosticAvailabilityKind::NoAsync) {
// An API may only be unavailable on specific platforms.
// If it doesn't have a platform associated with it, then it's
// unavailable for all platforms, so we should include it. If it does
// have a platform and we are not that platform, then it doesn't apply
// to us.
const bool isGoodForPlatform =
(avAttr->hasPlatform() && avAttr->isActivePlatform(ctx)) ||
!avAttr->hasPlatform();

if (!isGoodForPlatform)
continue;

if (!bestAttr) {
// If there is no best attr selected
// and the attr either has an active platform, or doesn't have one at
// all, select it.
bestAttr = avAttr;
} else if (bestAttr && avAttr->hasPlatform() &&
bestAttr->hasPlatform() &&
inheritsAvailabilityFromPlatform(avAttr->Platform,
bestAttr->Platform)) {
// if they both have a viable platform, use the better one
bestAttr = avAttr;
} else if (avAttr->hasPlatform() && !bestAttr->hasPlatform()) {
// Use the one more specific
bestAttr = avAttr;
}
}
}
}
return bestAttr;
}

void DeclAttributes::dump(const Decl *D) const {
StreamPrinter P(llvm::errs());
PrintOptions PO = PrintOptions::printDeclarations();
Expand Down Expand Up @@ -394,6 +436,7 @@ static bool isShortAvailable(const DeclAttribute *DA) {
case PlatformAgnosticAvailabilityKind::Deprecated:
case PlatformAgnosticAvailabilityKind::Unavailable:
case PlatformAgnosticAvailabilityKind::UnavailableInSwift:
case PlatformAgnosticAvailabilityKind::NoAsync:
return false;
case PlatformAgnosticAvailabilityKind::None:
case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific:
Expand Down Expand Up @@ -771,6 +814,8 @@ static void printAvailableAttr(const AvailableAttr *Attr, ASTPrinter &Printer,
Printer << ", unavailable";
else if (Attr->isUnconditionallyDeprecated())
Printer << ", deprecated";
else if (Attr->isNoAsync())
Printer << ", noasync";

if (Attr->Introduced)
Printer << ", introduced: " << Attr->Introduced.getValue().getAsString();
Expand Down Expand Up @@ -974,6 +1019,8 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,

case DAK_Available: {
auto Attr = cast<AvailableAttr>(this);
if (Options.SuppressNoAsyncAvailabilityAttr && Attr->isNoAsync())
return false;
if (!Options.PrintSPIs && Attr->IsSPI) {
assert(Attr->hasPlatform());
assert(Attr->Introduced.hasValue());
Expand Down Expand Up @@ -1705,6 +1752,7 @@ bool AvailableAttr::isUnconditionallyUnavailable() const {
case PlatformAgnosticAvailabilityKind::Deprecated:
case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific:
case PlatformAgnosticAvailabilityKind::PackageDescriptionVersionSpecific:
case PlatformAgnosticAvailabilityKind::NoAsync:
return false;

case PlatformAgnosticAvailabilityKind::Unavailable:
Expand All @@ -1722,6 +1770,7 @@ bool AvailableAttr::isUnconditionallyDeprecated() const {
case PlatformAgnosticAvailabilityKind::UnavailableInSwift:
case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific:
case PlatformAgnosticAvailabilityKind::PackageDescriptionVersionSpecific:
case PlatformAgnosticAvailabilityKind::NoAsync:
return false;

case PlatformAgnosticAvailabilityKind::Deprecated:
Expand All @@ -1731,6 +1780,10 @@ bool AvailableAttr::isUnconditionallyDeprecated() const {
llvm_unreachable("Unhandled PlatformAgnosticAvailabilityKind in switch.");
}

bool AvailableAttr::isNoAsync() const {
return PlatformAgnostic == PlatformAgnosticAvailabilityKind::NoAsync;
}

llvm::VersionTuple AvailableAttr::getActiveVersion(const ASTContext &ctx) const {
if (isLanguageVersionSpecific()) {
return ctx.LangOpts.EffectiveLanguageVersion;
Expand Down
5 changes: 3 additions & 2 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7533,9 +7533,10 @@ AbstractFunctionDecl *AbstractFunctionDecl::getAsyncAlternative() const {
// rename parameter, falling back to the first with a rename. Note that
// `getAttrs` is in reverse source order, so the last attribute is the
// first in source
if (!attr->Rename.empty() && (attr->Platform == PlatformKind::none ||
!avAttr))
if (!attr->Rename.empty() &&
(attr->Platform == PlatformKind::none || !avAttr) && !attr->isNoAsync()) {
avAttr = attr;
}
}

auto *renamedDecl = evaluateOrDefault(
Expand Down
66 changes: 50 additions & 16 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,23 +327,48 @@ ParserResult<AvailableAttr> Parser::parseExtendedAvailabilitySpecList(
++ParamIndex;

enum {
IsMessage, IsRenamed,
IsIntroduced, IsDeprecated, IsObsoleted,
IsMessage,
IsRenamed,
IsIntroduced,
IsDeprecated,
IsObsoleted,
IsUnavailable,
IsNoAsync,
IsInvalid
} ArgumentKind = IsInvalid;

if (Tok.is(tok::identifier)) {
ArgumentKind =
llvm::StringSwitch<decltype(ArgumentKind)>(ArgumentKindStr)
.Case("message", IsMessage)
.Case("renamed", IsRenamed)
.Case("introduced", IsIntroduced)
.Case("deprecated", IsDeprecated)
.Case("obsoleted", IsObsoleted)
.Case("unavailable", IsUnavailable)
.Default(IsInvalid);
}
ArgumentKind = llvm::StringSwitch<decltype(ArgumentKind)>(ArgumentKindStr)
.Case("message", IsMessage)
.Case("renamed", IsRenamed)
.Case("introduced", IsIntroduced)
.Case("deprecated", IsDeprecated)
.Case("obsoleted", IsObsoleted)
.Case("unavailable", IsUnavailable)
.Case("noasync", IsNoAsync)
.Default(IsInvalid);
}

auto platformAgnosticKindToStr = [](PlatformAgnosticAvailabilityKind kind) {
switch (kind) {
case PlatformAgnosticAvailabilityKind::None:
return "none";
case PlatformAgnosticAvailabilityKind::Deprecated:
return "deprecated";
case PlatformAgnosticAvailabilityKind::Unavailable:
return "unavailable";
case PlatformAgnosticAvailabilityKind::NoAsync:
return "noasync";

// These are possible platform agnostic availability kinds.
// I'm not sure what their spellings are at the moment, so I'm
// crashing instead of handling them.
case PlatformAgnosticAvailabilityKind::UnavailableInSwift:
case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific:
case PlatformAgnosticAvailabilityKind::PackageDescriptionVersionSpecific:
llvm_unreachable("Unknown availability kind for parser");
}
};

if (ArgumentKind == IsInvalid) {
diagnose(ArgumentLoc, diag::attr_availability_expected_option, AttrName)
Expand Down Expand Up @@ -419,8 +444,8 @@ ParserResult<AvailableAttr> Parser::parseExtendedAvailabilitySpecList(
case IsDeprecated:
if (!findAttrValueDelimiter()) {
if (PlatformAgnostic != PlatformAgnosticAvailabilityKind::None) {
diagnose(Tok, diag::attr_availability_unavailable_deprecated,
AttrName);
diagnose(Tok, diag::attr_availability_multiple_kinds, AttrName,
"deprecated", platformAgnosticKindToStr(PlatformAgnostic));
}

PlatformAgnostic = PlatformAgnosticAvailabilityKind::Deprecated;
Expand Down Expand Up @@ -467,12 +492,21 @@ ParserResult<AvailableAttr> Parser::parseExtendedAvailabilitySpecList(

case IsUnavailable:
if (PlatformAgnostic != PlatformAgnosticAvailabilityKind::None) {
diagnose(Tok, diag::attr_availability_unavailable_deprecated, AttrName);
diagnose(Tok, diag::attr_availability_multiple_kinds, AttrName,
"unavailable", platformAgnosticKindToStr(PlatformAgnostic));
}

PlatformAgnostic = PlatformAgnosticAvailabilityKind::Unavailable;
break;

case IsNoAsync:
if (PlatformAgnostic != PlatformAgnosticAvailabilityKind::None) {
diagnose(Tok, diag::attr_availability_multiple_kinds, AttrName,
"noasync", platformAgnosticKindToStr(PlatformAgnostic));
}
PlatformAgnostic = PlatformAgnosticAvailabilityKind::NoAsync;
break;

case IsInvalid:
llvm_unreachable("handled above");
}
Expand Down
29 changes: 29 additions & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1640,6 +1640,35 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *attr) {
}
}

if (attr->isNoAsync()) {
const DeclContext * dctx = dyn_cast<DeclContext>(D);
bool isAsyncDeclContext = dctx && dctx->isAsyncContext();

if (const AbstractStorageDecl *decl = dyn_cast<AbstractStorageDecl>(D)) {
const AccessorDecl * accessor = decl->getEffectfulGetAccessor();
isAsyncDeclContext |= accessor && accessor->isAsyncContext();
}

if (isAsyncDeclContext) {
if (const ValueDecl *vd = dyn_cast<ValueDecl>(D)) {
D->getASTContext().Diags.diagnose(
D->getLoc(), diag::async_named_decl_must_be_available_from_async,
D->getDescriptiveKind(), vd->getName());
} else {
D->getASTContext().Diags.diagnose(
D->getLoc(), diag::async_decl_must_be_available_from_async,
D->getDescriptiveKind());
}
}

// deinit's may not be unavailable from async contexts
if (isa<DestructorDecl>(D)) {
D->getASTContext().Diags.diagnose(
D->getLoc(), diag::invalid_decl_attribute, attr);
}

}

if (!attr->hasPlatform() || !attr->isActivePlatform(Ctx) ||
!attr->Introduced.hasValue()) {
return;
Expand Down
Loading