Skip to content

Commit 95abb73

Browse files
Merge pull request swiftlang#72175 from sophiapoirier/explicitly-nonisolated-closure
[Concurrency] explicit nonisolated specification for closures
2 parents 3deb5d4 + 5ce7be7 commit 95abb73

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
@@ -334,6 +334,9 @@ EXPERIMENTAL_FEATURE(DynamicActorIsolation, false)
334334
// Allow for `switch` of noncopyable values to be borrowing or consuming.
335335
EXPERIMENTAL_FEATURE(BorrowingSwitch, true)
336336

337+
// Enable explicit isolation of closures.
338+
EXPERIMENTAL_FEATURE(ClosureIsolation, true)
339+
337340
// Enable isolated(any) attribute on function types.
338341
CONDITIONALLY_SUPPRESSIBLE_EXPERIMENTAL_FEATURE(IsolatedAny, true)
339342

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
@@ -2794,10 +2794,13 @@ class PrintExpr : public ExprVisitor<PrintExpr, void, StringRef>,
27942794

27952795
switch (auto isolation = E->getActorIsolation()) {
27962796
case ActorIsolation::Unspecified:
2797-
case ActorIsolation::Nonisolated:
27982797
case ActorIsolation::NonisolatedUnsafe:
27992798
break;
28002799

2800+
case ActorIsolation::Nonisolated:
2801+
printFlag(true, "nonisolated", CapturesColor);
2802+
break;
2803+
28012804
case ActorIsolation::Erased:
28022805
printFlag(true, "dynamically_isolated", CapturesColor);
28032806
break;

lib/AST/FeatureSet.cpp

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

646646
UNINTERESTING_FEATURE(BorrowingSwitch)
647647

648+
UNINTERESTING_FEATURE(ClosureIsolation)
649+
648650
static bool usesFeatureIsolatedAny(Decl *decl) {
649651
return usesTypeMatching(decl, [](Type type) {
650652
if (auto fnType = type->getAs<AnyFunctionType>()) {

lib/Parse/ParseDecl.cpp

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3872,11 +3872,14 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
38723872
break;
38733873
}
38743874
case DeclAttrKind::Nonisolated: {
3875-
auto isUnsafe =
3876-
parseSingleAttrOption<bool>(*this, Loc, AttrRange, AttrName, DK,
3877-
{{Context.Id_unsafe, true}}, false);
3878-
if (!isUnsafe) {
3879-
return makeParserSuccess();
3875+
std::optional<bool> isUnsafe(false);
3876+
if (EnableParameterizedNonisolated) {
3877+
isUnsafe =
3878+
parseSingleAttrOption<bool>(*this, Loc, AttrRange, AttrName, DK,
3879+
{{Context.Id_unsafe, true}}, *isUnsafe);
3880+
if (!isUnsafe) {
3881+
return makeParserSuccess();
3882+
}
38803883
}
38813884

38823885
if (!DiscardAttribute) {
@@ -5175,6 +5178,38 @@ ParserStatus Parser::parseDeclAttributeList(
51755178
return parseDeclAttributeList(Attributes, IfConfigsAreDeclAttrs, initContext);
51765179
}
51775180

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

@@ -2550,8 +2551,16 @@ ParserStatus Parser::parseClosureSignatureIfPresent(
25502551
BacktrackingScope backtrack(*this);
25512552

25522553
// Consume attributes.
2553-
while (Tok.is(tok::at_sign)) {
2554-
skipAnyAttribute();
2554+
auto parsingNonisolated = [this] {
2555+
return Context.LangOpts.hasFeature(Feature::ClosureIsolation) &&
2556+
Tok.isContextualKeyword("nonisolated");
2557+
};
2558+
while (Tok.is(tok::at_sign) || parsingNonisolated()) {
2559+
if (parsingNonisolated()) {
2560+
consumeToken();
2561+
} else {
2562+
skipAnyAttribute();
2563+
}
25552564
}
25562565

25572566
// Skip by a closure capture list if present.
@@ -2615,7 +2624,12 @@ ParserStatus Parser::parseClosureSignatureIfPresent(
26152624
return makeParserSuccess();
26162625
}
26172626
ParserStatus status;
2618-
(void)parseDeclAttributeList(attributes);
2627+
// 'nonisolated' cannot be parameterized in a closure due to ambiguity with
2628+
// closure parameters.
2629+
const auto entryNonisolatedState =
2630+
std::exchange(EnableParameterizedNonisolated, false);
2631+
(void)parseClosureDeclAttributeList(attributes);
2632+
EnableParameterizedNonisolated = entryNonisolatedState;
26192633

26202634
if (Tok.is(tok::l_square) && peekToken().is(tok::r_square)) {
26212635
SourceLoc lBracketLoc = consumeToken(tok::l_square);
@@ -2785,6 +2799,7 @@ ParserStatus Parser::parseClosureSignatureIfPresent(
27852799
} else {
27862800
// Parse identifier (',' identifier)*
27872801
SmallVector<ParamDecl*, 4> elements;
2802+
const auto parameterListLoc = Tok.getLoc();
27882803
bool HasNext;
27892804
do {
27902805
if (Tok.isNot(tok::identifier, tok::kw__, tok::code_complete)) {
@@ -2814,6 +2829,15 @@ ParserStatus Parser::parseClosureSignatureIfPresent(
28142829
} while (HasNext);
28152830

28162831
params = ParameterList::create(Context, elements);
2832+
2833+
if (Context.LangOpts.hasFeature(Feature::ClosureIsolation) && params &&
2834+
(params->size() > 0) && attributes.hasAttribute<NonisolatedAttr>()) {
2835+
diagnose(parameterListLoc,
2836+
diag::nonisolated_closure_parameter_parentheses)
2837+
.fixItInsert(parameterListLoc, "(")
2838+
.fixItInsert(Tok.getLoc(), ")");
2839+
status.setIsParseError();
2840+
}
28172841
}
28182842

28192843
TypeRepr *thrownTypeRepr = nullptr;
@@ -2944,13 +2968,13 @@ ParserResult<Expr> Parser::parseExprClosure() {
29442968
DeclAttributes attributes;
29452969
SourceRange bracketRange;
29462970
SmallVector<CaptureListEntry, 2> captureList;
2947-
VarDecl *capturedSelfDecl;
2971+
VarDecl *capturedSelfDecl = nullptr;
29482972
ParameterList *params = nullptr;
29492973
SourceLoc asyncLoc;
29502974
SourceLoc throwsLoc;
2951-
TypeExpr *thrownType;
2975+
TypeExpr *thrownType = nullptr;
29522976
SourceLoc arrowLoc;
2953-
TypeExpr *explicitResultType;
2977+
TypeExpr *explicitResultType = nullptr;
29542978
SourceLoc inLoc;
29552979
Status |= parseClosureSignatureIfPresent(
29562980
attributes, bracketRange, captureList, capturedSelfDecl, params, asyncLoc,

lib/Sema/TypeCheckAttr.cpp

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

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

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4082,6 +4082,13 @@ namespace {
40824082
if (Type globalActor = resolveGlobalActorType(explicitClosure))
40834083
return ActorIsolation::forGlobalActor(globalActor)
40844084
.withPreconcurrency(preconcurrency);
4085+
4086+
if (auto *attr =
4087+
explicitClosure->getAttrs().getAttribute<NonisolatedAttr>();
4088+
attr && ctx.LangOpts.hasFeature(Feature::ClosureIsolation)) {
4089+
return ActorIsolation::forNonisolated(attr->isUnsafe())
4090+
.withPreconcurrency(preconcurrency);
4091+
}
40854092
}
40864093

40874094
// 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)