Skip to content

Commit cce3e8a

Browse files
authored
Merge pull request #40149 from etcwilde/ewilde/concurrency/underscored-unavailablefromasync
Add `_unavailableFromAsync` attribute
2 parents c0f7143 + a84650b commit cce3e8a

File tree

17 files changed

+324
-8
lines changed

17 files changed

+324
-8
lines changed

docs/ReferenceGuides/UnderscoredAttributes.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,3 +634,8 @@ calls a runtime function which allocates memory or locks, respectively.
634634
The `@_noLocks` attribute implies `@_noAllocation` because a memory allocation
635635
also locks.
636636

637+
## `@_unavailableFromAsync`
638+
639+
Marks a synchronous API as being unavailable from asynchronous contexts. Direct
640+
usage of annotated API from asynchronous contexts will result in a warning from
641+
the compiler.

include/swift/AST/Attr.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,12 @@ CONTEXTUAL_SIMPLE_DECL_ATTR(_const, CompileTimeConst,
699699
ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove,
700700
126)
701701

702+
SIMPLE_DECL_ATTR(_unavailableFromAsync, UnavailableFromAsync,
703+
OnFunc | OnConstructor | UserInaccessible |
704+
ABIStableToAdd | ABIStableToRemove |
705+
APIBreakingToAdd | APIStableToRemove,
706+
127)
707+
702708
// If you're adding a new underscored attribute here, please document it in
703709
// docs/ReferenceGuides/UnderscoredAttributes.md.
704710

include/swift/AST/DeclContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ class alignas(1 << DeclContextAlignInBits) DeclContext
296296
/// Returns the kind of context this is.
297297
DeclContextKind getContextKind() const;
298298

299+
/// Returns whether this context asynchronous
300+
bool isAsyncContext() const;
301+
299302
/// Returns whether this context has value semantics.
300303
bool hasValueSemantics() const;
301304

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4705,6 +4705,15 @@ ERROR(actor_isolation_superclass_mismatch,none,
47054705
"%0 class %1 has different actor isolation from %2 superclass %3",
47064706
(ActorIsolation, DeclName, ActorIsolation, DeclName))
47074707

4708+
ERROR(async_decl_must_be_available_from_async,none,
4709+
"asynchronous %0 must be available from asynchronous contexts",
4710+
(DescriptiveDeclKind))
4711+
ERROR(async_named_decl_must_be_available_from_async,none,
4712+
"asynchronous %0 %1 must be available from asynchronous contexts",
4713+
(DescriptiveDeclKind, DeclName))
4714+
ERROR(async_unavailable_decl,none,
4715+
"%0 %1 is unavailable from asynchronous contexts", (DescriptiveDeclKind, DeclBaseName))
4716+
47084717
//------------------------------------------------------------------------------
47094718
// MARK: Type Check Types
47104719
//------------------------------------------------------------------------------

include/swift/AST/Expr.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3601,6 +3601,13 @@ class AbstractClosureExpr : public DeclContext, public Expr {
36013601
/// Only valid when \c hasSingleExpressionBody() is true.
36023602
Expr *getSingleExpressionBody() const;
36033603

3604+
/// Whether this closure has a body
3605+
bool hasBody() const;
3606+
3607+
/// Returns the body of closures that have a body
3608+
/// returns nullptr if the closure doesn't have a body
3609+
BraceStmt *getBody() const;
3610+
36043611
ClosureActorIsolation getActorIsolation() const { return actorIsolation; }
36053612

36063613
void setActorIsolation(ClosureActorIsolation actorIsolation) {

lib/AST/DeclContext.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,32 @@ bool DeclContext::isClassConstrainedProtocolExtension() const {
12081208
return false;
12091209
}
12101210

1211+
bool DeclContext::isAsyncContext() const {
1212+
switch (getContextKind()) {
1213+
case DeclContextKind::Initializer:
1214+
case DeclContextKind::TopLevelCodeDecl:
1215+
case DeclContextKind::EnumElementDecl:
1216+
case DeclContextKind::ExtensionDecl:
1217+
case DeclContextKind::SerializedLocal:
1218+
case DeclContextKind::Module:
1219+
case DeclContextKind::FileUnit:
1220+
case DeclContextKind::GenericTypeDecl:
1221+
return false;
1222+
case DeclContextKind::AbstractClosureExpr:
1223+
return cast<AbstractClosureExpr>(this)->isBodyAsync();
1224+
case DeclContextKind::AbstractFunctionDecl: {
1225+
const AbstractFunctionDecl *function = cast<AbstractFunctionDecl>(this);
1226+
return function->hasAsync();
1227+
}
1228+
case DeclContextKind::SubscriptDecl: {
1229+
AccessorDecl *getter =
1230+
cast<SubscriptDecl>(this)->getAccessor(AccessorKind::Get);
1231+
return getter != nullptr && getter->hasAsync();
1232+
}
1233+
}
1234+
llvm_unreachable("Unhandled DeclContextKind switch");
1235+
}
1236+
12111237
SourceLoc swift::extractNearestSourceLoc(const DeclContext *dc) {
12121238
switch (dc->getContextKind()) {
12131239
case DeclContextKind::Module:

lib/AST/Expr.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,6 +1674,26 @@ void AbstractClosureExpr::setParameterList(ParameterList *P) {
16741674
P->setDeclContextOfParamDecls(this);
16751675
}
16761676

1677+
bool AbstractClosureExpr::hasBody() const {
1678+
switch (getKind()) {
1679+
case ExprKind::Closure:
1680+
case ExprKind::AutoClosure:
1681+
return true;
1682+
default:
1683+
return false;
1684+
}
1685+
}
1686+
1687+
BraceStmt * AbstractClosureExpr::getBody() const {
1688+
if (!hasBody())
1689+
return nullptr;
1690+
if (const AutoClosureExpr *autocls = dyn_cast<AutoClosureExpr>(this))
1691+
return autocls->getBody();
1692+
if (const ClosureExpr *cls = dyn_cast<ClosureExpr>(this))
1693+
return cls->getBody();
1694+
llvm_unreachable("Unknown closure expression");
1695+
}
1696+
16771697
Type AbstractClosureExpr::getResultType(
16781698
llvm::function_ref<Type(Expr *)> getType) const {
16791699
auto *E = const_cast<AbstractClosureExpr *>(this);

lib/Sema/TypeCheckAttr.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
270270
void visitNonisolatedAttr(NonisolatedAttr *attr);
271271

272272
void visitNoImplicitCopyAttr(NoImplicitCopyAttr *attr);
273+
274+
void visitUnavailableFromAsyncAttr(UnavailableFromAsyncAttr *attr);
273275
};
274276

275277
} // end anonymous namespace
@@ -2081,8 +2083,7 @@ SynthesizeMainFunctionRequest::evaluate(Evaluator &evaluator,
20812083
}
20822084

20832085
auto where = ExportContext::forDeclSignature(D);
2084-
diagnoseDeclAvailability(mainFunction, attr->getRange(), nullptr,
2085-
where, None);
2086+
diagnoseDeclAvailability(mainFunction, attr->getRange(), nullptr, where, None);
20862087

20872088
auto *const func = FuncDecl::createImplicit(
20882089
context, StaticSpellingKind::KeywordStatic,
@@ -5678,6 +5679,23 @@ void AttributeChecker::visitReasyncAttr(ReasyncAttr *attr) {
56785679
attr->setInvalid();
56795680
}
56805681

5682+
void AttributeChecker::visitUnavailableFromAsyncAttr(
5683+
UnavailableFromAsyncAttr *attr) {
5684+
if (DeclContext *dc = dyn_cast<DeclContext>(D)) {
5685+
if (dc->isAsyncContext()) {
5686+
if (ValueDecl *vd = dyn_cast<ValueDecl>(D)) {
5687+
D->getASTContext().Diags.diagnose(
5688+
D->getLoc(), diag::async_named_decl_must_be_available_from_async,
5689+
D->getDescriptiveKind(), vd->getName());
5690+
} else {
5691+
D->getASTContext().Diags.diagnose(
5692+
D->getLoc(), diag::async_decl_must_be_available_from_async,
5693+
D->getDescriptiveKind());
5694+
}
5695+
}
5696+
}
5697+
}
5698+
56815699
namespace {
56825700

56835701
class ClosureAttributeChecker

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2842,6 +2842,22 @@ class ExprAvailabilityWalker : public ASTWalker {
28422842
E->getLoc(), Where);
28432843
}
28442844

2845+
if (AbstractClosureExpr *closure = dyn_cast<AbstractClosureExpr>(E)) {
2846+
// Multi-statement closures are collected by ExprWalker::rewriteFunction
2847+
// and checked by ExprWalker::processDelayed in CSApply.cpp.
2848+
// Single-statement closures only have the attributes checked
2849+
// by TypeChecker::checkClosureAttributes in that rewriteFunction.
2850+
// Multi-statement closures will be checked explicitly later (as the decl
2851+
// context in the Where). Single-expression closures will not be
2852+
// revisited, and are not automatically set as the context of the 'where'.
2853+
// Don't double-check multi-statement closures, but do check
2854+
// single-statement closures, setting the closure as the decl context.
2855+
if (closure->hasSingleExpressionBody()) {
2856+
walkAbstractClosure(closure);
2857+
return skipChildren();
2858+
}
2859+
}
2860+
28452861
return visitChildren();
28462862
}
28472863

@@ -2853,13 +2869,13 @@ class ExprAvailabilityWalker : public ASTWalker {
28532869
}
28542870

28552871
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
2872+
28562873
// We end up here when checking the output of the result builder transform,
28572874
// which includes closures that are not "separately typechecked" and yet
28582875
// contain statements and declarations. We need to walk them recursively,
28592876
// since these availability for these statements is not diagnosed from
28602877
// typeCheckStmt() as usual.
2861-
diagnoseStmtAvailability(S, Where.getDeclContext(),
2862-
/*walkRecursively=*/true);
2878+
diagnoseStmtAvailability(S, Where.getDeclContext(), /*walkRecursively=*/true);
28632879
return std::make_pair(false, S);
28642880
}
28652881

@@ -2978,6 +2994,21 @@ class ExprAvailabilityWalker : public ASTWalker {
29782994
walkInContext(E, E->getSubExpr(), MemberAccessContext::InOut);
29792995
}
29802996

2997+
/// Walk an abstract closure expression, checking for availability
2998+
void walkAbstractClosure(AbstractClosureExpr *closure) {
2999+
// Do the walk with the closure set as the decl context of the 'where'
3000+
auto where = ExportContext::forFunctionBody(closure, closure->getStartLoc());
3001+
if (where.isImplicit())
3002+
return;
3003+
ExprAvailabilityWalker walker(where);
3004+
3005+
// Manually dive into the body
3006+
closure->getBody()->walk(walker);
3007+
3008+
return;
3009+
}
3010+
3011+
29813012
/// Walk the given expression in the member access context.
29823013
void walkInContext(Expr *baseExpr, Expr *E,
29833014
MemberAccessContext AccessContext) {
@@ -3038,8 +3069,8 @@ class ExprAvailabilityWalker : public ASTWalker {
30383069

30393070
Flags &= DeclAvailabilityFlag::ForInout;
30403071
Flags |= DeclAvailabilityFlag::ContinueOnPotentialUnavailability;
3041-
if (diagnoseDeclAvailability(D, ReferenceRange, /*call*/nullptr,
3042-
Where, Flags))
3072+
if (diagnoseDeclAvailability(D, ReferenceRange, /*call*/ nullptr, Where,
3073+
Flags))
30433074
return;
30443075
}
30453076
};
@@ -3074,6 +3105,56 @@ bool ExprAvailabilityWalker::diagnoseDeclRefAvailability(
30743105
return false;
30753106
}
30763107

3108+
/// Diagnose uses of API annotated '@unavailableFromAsync' when used from
3109+
/// asynchronous contexts.
3110+
/// Returns true if a diagnostic was emitted, false otherwise.
3111+
static bool
3112+
diagnoseDeclUnavailableFromAsync(const ValueDecl *D, SourceRange R,
3113+
const Expr *call, const ExportContext &Where) {
3114+
// FIXME: I don't think this is right, but I don't understand the issue well
3115+
// enough to fix it properly. If the decl context is an abstract
3116+
// closure, we need it to have a type assigned to it before we can
3117+
// determine whether it is an asynchronous context. It will crash
3118+
// when we go to check without one. In TypeChecker::typeCheckExpression
3119+
// (TypeCheckConstraints.cpp:403), we apply a solution before calling
3120+
// `performSyntacticDiagnosticsForTarget`, which eventually calls
3121+
// down to this function. Under most circumstances, the context that
3122+
// we're in is typechecked at that point and has a type assigned.
3123+
// When working with specific result builders, the solution applied
3124+
// results in an expression with an unset type. In these cases, the
3125+
// application makes its way into `ConstraintSystem::applySolution` for
3126+
// closures (CSClosure.cpp:1356). The type is computed, but is
3127+
// squirreled away in the constrain system to be applied once the
3128+
// checks (including this one) approve of the decls within the decl
3129+
// context before applying the type to the expression. It might be
3130+
// possible to drive the constraint solver through the availability
3131+
// checker and into us so that we can ask for it, but that feels wrong
3132+
// too.
3133+
// This behavior is demonstrated by the first use of the `tuplify`
3134+
// function in `testExistingPatternsInCaseStatements` in
3135+
// `test/Constraints/result_builder.swift`.
3136+
const AbstractClosureExpr *declCtxAsExpr =
3137+
dyn_cast<AbstractClosureExpr>(Where.getDeclContext());
3138+
if (declCtxAsExpr && !declCtxAsExpr->getType()) {
3139+
return false;
3140+
}
3141+
3142+
// If we are in a synchronous context, don't check it
3143+
if (!Where.getDeclContext()->isAsyncContext())
3144+
return false;
3145+
if (!D->getAttrs().hasAttribute<UnavailableFromAsyncAttr>())
3146+
return false;
3147+
3148+
ASTContext &ctx = Where.getDeclContext()->getASTContext();
3149+
SourceLoc diagLoc = call ? call->getLoc() : R.Start;
3150+
ctx.Diags
3151+
.diagnose(diagLoc, diag::async_unavailable_decl, D->getDescriptiveKind(),
3152+
D->getBaseName())
3153+
.warnUntilSwiftVersion(6);
3154+
D->diagnose(diag::decl_declared_here, D->getName());
3155+
return true;
3156+
}
3157+
30773158
/// Diagnose uses of unavailable declarations. Returns true if a diagnostic
30783159
/// was emitted.
30793160
bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R,
@@ -3107,6 +3188,9 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R,
31073188
if (diagnoseExplicitUnavailability(D, R, Where, call, Flags))
31083189
return true;
31093190

3191+
if (diagnoseDeclUnavailableFromAsync(D, R, call, Where))
3192+
return true;
3193+
31103194
// Make sure not to diagnose an accessor's deprecation if we already
31113195
// complained about the property/subscript.
31123196
bool isAccessorWithDeprecatedStorage =

lib/Sema/TypeCheckAvailability.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ namespace swift {
2727
class ApplyExpr;
2828
class AvailableAttr;
2929
class Expr;
30+
class ClosureExpr;
3031
class InFlightDiagnostic;
3132
class Decl;
3233
class ProtocolConformanceRef;

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,7 @@ namespace {
15591559
UNINTERESTING_ATTR(InheritActorContext)
15601560
UNINTERESTING_ATTR(Isolated)
15611561
UNINTERESTING_ATTR(NoImplicitCopy)
1562+
UNINTERESTING_ATTR(UnavailableFromAsync)
15621563

15631564
UNINTERESTING_ATTR(TypeSequence)
15641565
UNINTERESTING_ATTR(CompileTimeConst)

lib/Serialization/ModuleFormat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5656
/// describe what change you made. The content of this comment isn't important;
5757
/// it just ensures a conflict if two people change the module format.
5858
/// Don't worry about adhering to the 80-column limit for this line.
59-
const uint16_t SWIFTMODULE_VERSION_MINOR = 646; // hasHermeticSealAtLink option
59+
const uint16_t SWIFTMODULE_VERSION_MINOR = 647; // _unavailableFromAsync attr
6060

6161
/// A standard hash seed used for all string hashes in a serialized module.
6262
///

test/ClangImporter/objc_async.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ func testSlowServer(slowServer: SlowServer) async throws {
4545
let _: Int = await slowServer.bestName("hello")
4646
let _: Int = await slowServer.customize("hello")
4747

48+
slowServer.unavailableMethod() // expected-warning{{'unavailableMethod' is unavailable from asynchronous contexts}}
49+
4850
let _: String = await slowServer.dance("slide")
4951
let _: String = await slowServer.__leap(17)
5052

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@_unavailableFromAsync
2+
public func unavailableFunction() { }

0 commit comments

Comments
 (0)