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

Diff for: include/swift/AST/DiagnosticsParse.def

+2
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,

Diff for: include/swift/Basic/Features.def

+3
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

Diff for: include/swift/Parse/Parser.h

+4
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,

Diff for: lib/AST/ASTDumper.cpp

+4-1
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;

Diff for: lib/AST/FeatureSet.cpp

+2
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>()) {

Diff for: lib/Parse/ParseDecl.cpp

+40-5
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 */

Diff for: lib/Parse/ParseExpr.cpp

+33-9
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,

Diff for: lib/Sema/TypeCheckAttr.cpp

+7
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(

Diff for: lib/Sema/TypeCheckConcurrency.cpp

+7
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

Diff for: test/Concurrency/closure_isolation.swift

+7-1
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

Diff for: test/Frontend/dump-parse.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ escaping({ $0 })
6767
// CHECK-AST: (declref_expr type="(@escaping (Int) -> Int) -> ()"
6868
// CHECK-AST-NEXT: (argument_list
6969
// CHECK-AST-NEXT: (argument
70-
// CHECK-AST-NEXT: (closure_expr type="(Int) -> Int" {{.*}} discriminator=0 escaping single_expression
70+
// CHECK-AST-NEXT: (closure_expr type="(Int) -> Int" {{.*}} discriminator=0 nonisolated escaping single_expression
7171

7272
func nonescaping(_: (Int) -> Int) {}
7373
nonescaping({ $0 })
7474
// CHECK-AST: (declref_expr type="((Int) -> Int) -> ()"
7575
// CHECK-AST-NEXT: (argument_list
7676
// CHECK-AST-NEXT: (argument
77-
// CHECK-AST-NEXT: (closure_expr type="(Int) -> Int" {{.*}} discriminator=1 single_expression
77+
// CHECK-AST-NEXT: (closure_expr type="(Int) -> Int" {{.*}} discriminator=1 nonisolated single_expression
7878

7979
// CHECK-LABEL: (struct_decl range=[{{.+}}] "MyStruct")
8080
struct MyStruct {}

Diff for: test/Parse/trailing_closures.swift

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
// RUN: %target-typecheck-verify-swift
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-feature ClosureIsolation
2+
3+
// REQUIRES: asserts
24

35
func foo<T, U>(a: () -> T, b: () -> U) {}
46

@@ -120,3 +122,13 @@ struct TrickyTest {
120122
3
121123
}
122124
}
125+
126+
struct IsolationTest {
127+
func acceptClosureWithParameter(_: (Int) -> Int) {}
128+
129+
func test() {
130+
acceptClosureWithParameter {
131+
nonisolated parameter in return parameter // expected-error {{the parameter list of a 'nonisolated' closure requires parentheses}} {{19-19=(}} {{29-29=)}}
132+
}
133+
}
134+
}

Diff for: test/Sema/property_wrappers.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,17 @@ public class W_58201<Value> {
7070
struct S1_58201 {
7171
// CHECK: argument_list implicit labels="wrappedValue:"
7272
// CHECK-NEXT: argument label="wrappedValue"
73-
// CHECK-NEXT: autoclosure_expr implicit type="() -> Bool?" discriminator=0 captures=(<opaque_value> ) escaping
74-
// CHECK: autoclosure_expr implicit type="() -> Bool?" discriminator=1 escaping
73+
// CHECK-NEXT: autoclosure_expr implicit type="() -> Bool?" discriminator=0 nonisolated captures=(<opaque_value> ) escaping
74+
// CHECK: autoclosure_expr implicit type="() -> Bool?" discriminator=1 nonisolated escaping
7575
@W_58201 var a: Bool?
7676
}
7777

7878
// CHECK-LABEL: struct_decl{{.*}}S2_58201
7979
struct S2_58201 {
8080
// CHECK: argument_list implicit labels="wrappedValue:"
8181
// CHECK-NEXT: argument label="wrappedValue"
82-
// CHECK-NEXT: autoclosure_expr implicit type="() -> Bool" location={{.*}}.swift:[[@LINE+2]]:26 range=[{{.+}}] discriminator=0 captures=(<opaque_value> ) escaping
83-
// CHECK: autoclosure_expr implicit type="() -> Bool" location={{.*}}.swift:[[@LINE+1]]:26 range=[{{.+}}] discriminator=1 escaping
82+
// CHECK-NEXT: autoclosure_expr implicit type="() -> Bool" location={{.*}}.swift:[[@LINE+2]]:26 range=[{{.+}}] discriminator=0 nonisolated captures=(<opaque_value> ) escaping
83+
// CHECK: autoclosure_expr implicit type="() -> Bool" location={{.*}}.swift:[[@LINE+1]]:26 range=[{{.+}}] discriminator=1 nonisolated escaping
8484
@W_58201 var b: Bool = false
8585
}
8686

Diff for: test/expr/capture/dynamic_self.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class Android {
66
func clone() -> Self {
7-
// CHECK: closure_expr type="() -> Self" {{.*}} discriminator=0 captures=(<dynamic_self> self<direct>)
7+
// CHECK: closure_expr type="() -> Self" {{.*}} discriminator=0 nonisolated captures=(<dynamic_self> self<direct>)
88
let fn = { return self }
99
return fn()
1010
}

Diff for: test/expr/capture/generic_params.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ func doSomething<T>(_ t: T) {}
66

77
func outerGeneric<T>(t: T, x: AnyObject) {
88
// Simple case -- closure captures outer generic parameter
9-
// CHECK: closure_expr type="() -> ()" {{.*}} discriminator=0 captures=(<generic> t<direct>) escaping single_expression
9+
// CHECK: closure_expr type="() -> ()" {{.*}} discriminator=0 nonisolated captures=(<generic> t<direct>) escaping single_expression
1010
_ = { doSomething(t) }
1111

1212
// Special case -- closure does not capture outer generic parameters
13-
// CHECK: closure_expr type="() -> ()" {{.*}} discriminator=1 captures=(x<direct>) escaping single_expression
13+
// CHECK: closure_expr type="() -> ()" {{.*}} discriminator=1 nonisolated captures=(x<direct>) escaping single_expression
1414
_ = { doSomething(x) }
1515

1616
// Special case -- closure captures outer generic parameter, but it does not
1717
// appear as the type of any expression
18-
// CHECK: closure_expr type="() -> ()" {{.*}} discriminator=2 captures=(<generic> x<direct>)
18+
// CHECK: closure_expr type="() -> ()" {{.*}} discriminator=2 nonisolated captures=(<generic> x<direct>)
1919
_ = { if x is T {} }
2020

2121
// Nested generic functions always capture outer generic parameters, even if

Diff for: test/expr/capture/nested.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
// CHECK: func_decl{{.*}}"foo2(_:)"
44
func foo2(_ x: Int) -> (Int) -> (Int) -> Int {
5-
// CHECK: closure_expr type="(Int) -> (Int) -> Int" {{.*}} discriminator=0 captures=(x)
5+
// CHECK: closure_expr type="(Int) -> (Int) -> Int" {{.*}} discriminator=0 nonisolated captures=(x)
66
return {(bar: Int) -> (Int) -> Int in
7-
// CHECK: closure_expr type="(Int) -> Int" {{.*}} discriminator=0 captures=(x<direct>, bar<direct>)
7+
// CHECK: closure_expr type="(Int) -> Int" {{.*}} discriminator=0 nonisolated captures=(x<direct>, bar<direct>)
88
return {(bas: Int) -> Int in
99
return x + bar + bas
1010
}

0 commit comments

Comments
 (0)