Skip to content

Commit 5ce7be7

Browse files
committed
[Concurrency] explicit nonisolated specification for closures
1 parent e96642c commit 5ce7be7

16 files changed

+134
-29
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,8 @@ ERROR(anon_closure_tuple_param_destructuring,none,
12961296
"closure tuple parameter does not support destructuring", ())
12971297
ERROR(expected_closure_parameter_name,none,
12981298
"expected the name of a closure parameter", ())
1299+
ERROR(nonisolated_closure_parameter_parentheses,none,
1300+
"the parameter list of a 'nonisolated' closure requires parentheses", ())
12991301
ERROR(expected_capture_specifier,none,
13001302
"expected 'weak', 'unowned', or no specifier in capture list", ())
13011303
ERROR(expected_capture_specifier_name,none,

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ EXPERIMENTAL_FEATURE(DynamicActorIsolation, false)
332332
// Allow for `switch` of noncopyable values to be borrowing or consuming.
333333
EXPERIMENTAL_FEATURE(BorrowingSwitch, true)
334334

335+
// Enable explicit isolation of closures.
336+
EXPERIMENTAL_FEATURE(ClosureIsolation, true)
337+
335338
// Enable isolated(any) attribute on function types.
336339
CONDITIONALLY_SUPPRESSIBLE_EXPERIMENTAL_FEATURE(IsolatedAny, true)
337340

include/swift/Parse/Parser.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class Parser {
114114
void operator=(const Parser&) = delete;
115115

116116
bool IsInputIncomplete = false;
117+
bool EnableParameterizedNonisolated = true; // HACK: for closures
117118
std::vector<Token> SplitTokens;
118119

119120
public:
@@ -1042,6 +1043,9 @@ class Parser {
10421043
bool IfConfigsAreDeclAttrs,
10431044
PatternBindingInitializer *initContext);
10441045

1046+
/// Parse the optional attributes before a closure declaration.
1047+
ParserStatus parseClosureDeclAttributeList(DeclAttributes &Attributes);
1048+
10451049
/// Parse the optional modifiers before a declaration.
10461050
ParserStatus parseDeclModifierList(DeclAttributes &Attributes,
10471051
SourceLoc &StaticLoc,

lib/AST/ASTDumper.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2792,10 +2792,13 @@ class PrintExpr : public ExprVisitor<PrintExpr, void, StringRef>,
27922792

27932793
switch (auto isolation = E->getActorIsolation()) {
27942794
case ActorIsolation::Unspecified:
2795-
case ActorIsolation::Nonisolated:
27962795
case ActorIsolation::NonisolatedUnsafe:
27972796
break;
27982797

2798+
case ActorIsolation::Nonisolated:
2799+
printFlag(true, "nonisolated", CapturesColor);
2800+
break;
2801+
27992802
case ActorIsolation::Erased:
28002803
printFlag(true, "dynamically_isolated", CapturesColor);
28012804
break;

lib/AST/FeatureSet.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,8 @@ static bool usesFeatureDynamicActorIsolation(Decl *decl) {
640640

641641
UNINTERESTING_FEATURE(BorrowingSwitch)
642642

643+
UNINTERESTING_FEATURE(ClosureIsolation)
644+
643645
static bool usesFeatureIsolatedAny(Decl *decl) {
644646
return usesTypeMatching(decl, [](Type type) {
645647
if (auto fnType = type->getAs<AnyFunctionType>()) {

lib/Parse/ParseDecl.cpp

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3877,11 +3877,14 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
38773877
break;
38783878
}
38793879
case DeclAttrKind::Nonisolated: {
3880-
auto isUnsafe =
3881-
parseSingleAttrOption<bool>(*this, Loc, AttrRange, AttrName, DK,
3882-
{{Context.Id_unsafe, true}}, false);
3883-
if (!isUnsafe) {
3884-
return makeParserSuccess();
3880+
std::optional<bool> isUnsafe(false);
3881+
if (EnableParameterizedNonisolated) {
3882+
isUnsafe =
3883+
parseSingleAttrOption<bool>(*this, Loc, AttrRange, AttrName, DK,
3884+
{{Context.Id_unsafe, true}}, *isUnsafe);
3885+
if (!isUnsafe) {
3886+
return makeParserSuccess();
3887+
}
38853888
}
38863889

38873890
if (!DiscardAttribute) {
@@ -5180,6 +5183,38 @@ ParserStatus Parser::parseDeclAttributeList(
51805183
return parseDeclAttributeList(Attributes, IfConfigsAreDeclAttrs, initContext);
51815184
}
51825185

5186+
// effectively parseDeclAttributeList but with selective modifier handling
5187+
ParserStatus Parser::parseClosureDeclAttributeList(DeclAttributes &Attributes) {
5188+
auto parsingNonisolated = [this] {
5189+
return Context.LangOpts.hasFeature(Feature::ClosureIsolation) &&
5190+
Tok.isContextualKeyword("nonisolated");
5191+
};
5192+
5193+
if (Tok.isNot(tok::at_sign, tok::pound_if) && !parsingNonisolated())
5194+
return makeParserSuccess();
5195+
5196+
PatternBindingInitializer *initContext = nullptr;
5197+
constexpr bool ifConfigsAreDeclAttrs = false;
5198+
ParserStatus Status;
5199+
while (Tok.isAny(tok::at_sign, tok::pound_if) || parsingNonisolated()) {
5200+
if (Tok.is(tok::at_sign)) {
5201+
SourceLoc AtEndLoc = Tok.getRange().getEnd();
5202+
SourceLoc AtLoc = consumeToken();
5203+
Status |= parseDeclAttribute(Attributes, AtLoc, AtEndLoc, initContext);
5204+
} else if (parsingNonisolated()) {
5205+
Status |=
5206+
parseNewDeclAttribute(Attributes, {}, DeclAttrKind::Nonisolated);
5207+
} else {
5208+
if (!ifConfigsAreDeclAttrs && !ifConfigContainsOnlyAttributes()) {
5209+
break;
5210+
}
5211+
Status |= parseIfConfigDeclAttributes(Attributes, ifConfigsAreDeclAttrs,
5212+
initContext);
5213+
}
5214+
}
5215+
return Status;
5216+
}
5217+
51835218
/// \verbatim
51845219
/// modifier-list
51855220
/// /* empty */

lib/Parse/ParseExpr.cpp

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,22 @@
1414
//
1515
//===----------------------------------------------------------------------===//
1616

17-
#include "swift/Parse/Parser.h"
1817
#include "swift/AST/ASTWalker.h"
1918
#include "swift/AST/Attr.h"
2019
#include "swift/AST/DiagnosticsParse.h"
2120
#include "swift/AST/TypeRepr.h"
21+
#include "swift/Basic/Defer.h"
2222
#include "swift/Basic/EditorPlaceholder.h"
23+
#include "swift/Basic/StringExtras.h"
2324
#include "swift/Parse/IDEInspectionCallbacks.h"
25+
#include "swift/Parse/Parser.h"
2426
#include "llvm/ADT/SmallString.h"
2527
#include "llvm/ADT/StringSwitch.h"
2628
#include "llvm/ADT/Twine.h"
27-
#include "swift/Basic/Defer.h"
28-
#include "swift/Basic/StringExtras.h"
2929
#include "llvm/Support/Compiler.h"
3030
#include "llvm/Support/SaveAndRestore.h"
3131
#include "llvm/Support/raw_ostream.h"
32+
#include <utility>
3233

3334
using namespace swift;
3435

@@ -2579,8 +2580,16 @@ ParserStatus Parser::parseClosureSignatureIfPresent(
25792580
BacktrackingScope backtrack(*this);
25802581

25812582
// Consume attributes.
2582-
while (Tok.is(tok::at_sign)) {
2583-
skipAnyAttribute();
2583+
auto parsingNonisolated = [this] {
2584+
return Context.LangOpts.hasFeature(Feature::ClosureIsolation) &&
2585+
Tok.isContextualKeyword("nonisolated");
2586+
};
2587+
while (Tok.is(tok::at_sign) || parsingNonisolated()) {
2588+
if (parsingNonisolated()) {
2589+
consumeToken();
2590+
} else {
2591+
skipAnyAttribute();
2592+
}
25842593
}
25852594

25862595
// Skip by a closure capture list if present.
@@ -2644,7 +2653,12 @@ ParserStatus Parser::parseClosureSignatureIfPresent(
26442653
return makeParserSuccess();
26452654
}
26462655
ParserStatus status;
2647-
(void)parseDeclAttributeList(attributes);
2656+
// 'nonisolated' cannot be parameterized in a closure due to ambiguity with
2657+
// closure parameters.
2658+
const auto entryNonisolatedState =
2659+
std::exchange(EnableParameterizedNonisolated, false);
2660+
(void)parseClosureDeclAttributeList(attributes);
2661+
EnableParameterizedNonisolated = entryNonisolatedState;
26482662

26492663
if (Tok.is(tok::l_square) && peekToken().is(tok::r_square)) {
26502664
SourceLoc lBracketLoc = consumeToken(tok::l_square);
@@ -2814,6 +2828,7 @@ ParserStatus Parser::parseClosureSignatureIfPresent(
28142828
} else {
28152829
// Parse identifier (',' identifier)*
28162830
SmallVector<ParamDecl*, 4> elements;
2831+
const auto parameterListLoc = Tok.getLoc();
28172832
bool HasNext;
28182833
do {
28192834
if (Tok.isNot(tok::identifier, tok::kw__, tok::code_complete)) {
@@ -2843,6 +2858,15 @@ ParserStatus Parser::parseClosureSignatureIfPresent(
28432858
} while (HasNext);
28442859

28452860
params = ParameterList::create(Context, elements);
2861+
2862+
if (Context.LangOpts.hasFeature(Feature::ClosureIsolation) && params &&
2863+
(params->size() > 0) && attributes.hasAttribute<NonisolatedAttr>()) {
2864+
diagnose(parameterListLoc,
2865+
diag::nonisolated_closure_parameter_parentheses)
2866+
.fixItInsert(parameterListLoc, "(")
2867+
.fixItInsert(Tok.getLoc(), ")");
2868+
status.setIsParseError();
2869+
}
28462870
}
28472871

28482872
TypeRepr *thrownTypeRepr = nullptr;
@@ -2973,13 +2997,13 @@ ParserResult<Expr> Parser::parseExprClosure() {
29732997
DeclAttributes attributes;
29742998
SourceRange bracketRange;
29752999
SmallVector<CaptureListEntry, 2> captureList;
2976-
VarDecl *capturedSelfDecl;
3000+
VarDecl *capturedSelfDecl = nullptr;
29773001
ParameterList *params = nullptr;
29783002
SourceLoc asyncLoc;
29793003
SourceLoc throwsLoc;
2980-
TypeExpr *thrownType;
3004+
TypeExpr *thrownType = nullptr;
29813005
SourceLoc arrowLoc;
2982-
TypeExpr *explicitResultType;
3006+
TypeExpr *explicitResultType = nullptr;
29833007
SourceLoc inLoc;
29843008
Status |= parseClosureSignatureIfPresent(
29853009
attributes, bracketRange, captureList, capturedSelfDecl, params, asyncLoc,

lib/Sema/TypeCheckAttr.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7374,6 +7374,13 @@ class ClosureAttributeChecker
73747374
// Nothing else to check.
73757375
}
73767376

7377+
void visitNonisolatedAttr(NonisolatedAttr *attr) {
7378+
if (attr->isUnsafe() ||
7379+
!ctx.LangOpts.hasFeature(Feature::ClosureIsolation)) {
7380+
visitDeclAttribute(attr);
7381+
}
7382+
}
7383+
73777384
void visitCustomAttr(CustomAttr *attr) {
73787385
// Check whether this custom attribute is the global actor attribute.
73797386
auto globalActorAttr = evaluateOrDefault(

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4050,6 +4050,13 @@ namespace {
40504050
if (Type globalActor = resolveGlobalActorType(explicitClosure))
40514051
return ActorIsolation::forGlobalActor(globalActor)
40524052
.withPreconcurrency(preconcurrency);
4053+
4054+
if (auto *attr =
4055+
explicitClosure->getAttrs().getAttribute<NonisolatedAttr>();
4056+
attr && ctx.LangOpts.hasFeature(Feature::ClosureIsolation)) {
4057+
return ActorIsolation::forNonisolated(attr->isUnsafe())
4058+
.withPreconcurrency(preconcurrency);
4059+
}
40534060
}
40544061

40554062
// If a closure has an isolated parameter, it is isolated to that

test/Concurrency/closure_isolation.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// RUN: %target-swift-frontend -dump-ast %s -disable-availability-checking | %FileCheck %s
1+
// RUN: %target-swift-frontend -dump-ast %s -disable-availability-checking -enable-experimental-feature ClosureIsolation | %FileCheck %s
22

33
// REQUIRES: concurrency
4+
// REQUIRES: asserts
45

56
func acceptClosure<T>(_: () -> T) { }
67
func acceptSendableClosure<T>(_: @Sendable () -> T) { }
@@ -45,6 +46,11 @@ extension MyActor {
4546
// CHECK: closure_expr
4647
// CHECK: actor_isolated
4748
acceptEscapingAsyncClosure { () async in print(self) }
49+
50+
// CHECK: acceptClosure
51+
// CHECK: closure_expr
52+
// CHECK: nonisolated
53+
acceptClosure { nonisolated in print() }
4854
}
4955
}
5056

0 commit comments

Comments
 (0)