Skip to content

Commit 44f6214

Browse files
authored
Merge pull request #35798 from DougGregor/capture-var-in-concurrent-closure
2 parents ff08ddb + 4bd7bed commit 44f6214

File tree

3 files changed

+111
-46
lines changed

3 files changed

+111
-46
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,11 +1001,13 @@ namespace {
10011001

10021002
ConcurrentExecutionChecker concurrentExecutionChecker;
10031003

1004+
using MutableVarSource = llvm::PointerUnion<DeclRefExpr *, InOutExpr *>;
10041005
using MutableVarParent = llvm::PointerUnion<InOutExpr *, LoadExpr *>;
10051006

1006-
/// Mapping from mutable local variables to the parent expression, when
1007-
/// that parent is either a load or a inout expression.
1008-
llvm::SmallDenseMap<DeclRefExpr *, MutableVarParent, 4> mutableLocalVarParent;
1007+
/// Mapping from mutable local variables or inout expressions to the
1008+
/// parent expression, when that parent is either a load or a inout expression.
1009+
llvm::SmallDenseMap<MutableVarSource, MutableVarParent, 4>
1010+
mutableLocalVarParent;
10091011

10101012
const DeclContext *getDeclContext() const {
10111013
return contextStack.back();
@@ -1022,28 +1024,66 @@ namespace {
10221024
/// If the subexpression is a reference to a mutable local variable from a
10231025
/// different context, record its parent. We'll query this as part of
10241026
/// capture semantics in concurrent functions.
1025-
void recordMutableVarParent(MutableVarParent parent, Expr *subExpr) {
1026-
auto declRef = dyn_cast<DeclRefExpr>(subExpr);
1027-
if (!declRef)
1028-
return;
1027+
///
1028+
/// \returns true if we recorded anything, false otherwise.
1029+
bool recordMutableVarParent(MutableVarParent parent, Expr *subExpr) {
1030+
subExpr = subExpr->getValueProvidingExpr();
1031+
1032+
if (auto declRef = dyn_cast<DeclRefExpr>(subExpr)) {
1033+
auto var = dyn_cast_or_null<VarDecl>(declRef->getDecl());
1034+
if (!var)
1035+
return false;
10291036

1030-
auto var = dyn_cast_or_null<VarDecl>(declRef->getDecl());
1031-
if (!var)
1032-
return;
1037+
// Only mutable variables matter.
1038+
if (!var->supportsMutation())
1039+
return false;
10331040

1034-
// Only mutable variables matter.
1035-
if (!var->supportsMutation())
1036-
return;
1041+
// Only mutable variables outside of the current context. This is an
1042+
// optimization, because the parent map won't be queried in this case, and
1043+
// it is the most common case for variables to be referenced in their
1044+
// own context.
1045+
if (var->getDeclContext() == getDeclContext())
1046+
return false;
10371047

1038-
// Only mutable variables outside of the current context. This is an
1039-
// optimization, because the parent map won't be queried in this case, and
1040-
// it is the most common case for variables to be referenced in their
1041-
// own context.
1042-
if (var->getDeclContext() == getDeclContext())
1043-
return;
1048+
assert(mutableLocalVarParent[declRef].isNull());
1049+
mutableLocalVarParent[declRef] = parent;
1050+
return true;
1051+
}
1052+
1053+
// For a member reference, try to record a parent for the base
1054+
// expression.
1055+
if (auto memberRef = dyn_cast<MemberRefExpr>(subExpr)) {
1056+
return recordMutableVarParent(parent, memberRef->getBase());
1057+
}
1058+
1059+
// For a subscript, try to record a parent for the base expression.
1060+
if (auto subscript = dyn_cast<SubscriptExpr>(subExpr)) {
1061+
return recordMutableVarParent(parent, subscript->getBase());
1062+
}
10441063

1045-
assert(mutableLocalVarParent[declRef].isNull());
1046-
mutableLocalVarParent[declRef] = parent;
1064+
// Look through postfix '!'.
1065+
if (auto force = dyn_cast<ForceValueExpr>(subExpr)) {
1066+
return recordMutableVarParent(parent, force->getSubExpr());
1067+
}
1068+
1069+
// Look through postfix '?'.
1070+
if (auto bindOpt = dyn_cast<BindOptionalExpr>(subExpr)) {
1071+
return recordMutableVarParent(parent, bindOpt->getSubExpr());
1072+
}
1073+
1074+
if (auto optEval = dyn_cast<OptionalEvaluationExpr>(subExpr)) {
1075+
return recordMutableVarParent(parent, optEval->getSubExpr());
1076+
}
1077+
1078+
// & expressions can be embedded for references to mutable variables
1079+
// or subscribes inside a struct/enum.
1080+
if (auto inout = dyn_cast<InOutExpr>(subExpr)) {
1081+
// Record the parent of the inout so we don't look at it again later.
1082+
mutableLocalVarParent[inout] = parent;
1083+
return recordMutableVarParent(parent, inout->getSubExpr());
1084+
}
1085+
1086+
return false;
10471087
}
10481088

10491089
public:
@@ -1127,7 +1167,8 @@ namespace {
11271167
if (!applyStack.empty())
11281168
diagnoseInOutArg(applyStack.back(), inout, false);
11291169

1130-
recordMutableVarParent(inout, inout->getSubExpr());
1170+
if (mutableLocalVarParent.count(inout) == 0)
1171+
recordMutableVarParent(inout, inout->getSubExpr());
11311172
}
11321173

11331174
if (auto load = dyn_cast<LoadExpr>(expr)) {
@@ -1225,6 +1266,9 @@ namespace {
12251266
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr)) {
12261267
mutableLocalVarParent.erase(declRefExpr);
12271268
}
1269+
if (auto *inoutExpr = dyn_cast<InOutExpr>(expr)) {
1270+
mutableLocalVarParent.erase(inoutExpr);
1271+
}
12281272

12291273
return expr;
12301274
}

test/Concurrency/concurrentfunction_capturediagnostics.swift

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,9 @@ struct NonTrivialValueType {
143143
func testCaseNonTrivialValue() {
144144
var i = NonTrivialValueType(17, Klass())
145145
f {
146-
// Currently emits a typechecker level error due to some sort of bug in the type checker.
147-
// print(i.i + 17)
148-
// print(i.i + 18)
149-
// print(i.i + 19)
146+
print(i.i + 17)
147+
print(i.i + 18)
148+
print(i.i + 19)
150149
}
151150

152151
i.i = 20
@@ -155,28 +154,27 @@ func testCaseNonTrivialValue() {
155154
// We only emit a warning here since we use the last write.
156155
//
157156
// TODO: Should we emit for all writes?
158-
i.i.addOne() // xpected-warning {{'i' mutated after capture by concurrent closure}}
159-
// xpected-note @-14 {{variable defined here}}
160-
// xpected-note @-14 {{variable captured by concurrent closure}}
161-
// xpected-note @-14 {{capturing use}}
162-
// xpected-note @-14 {{capturing use}}
163-
// xpected-note @-14 {{capturing use}}
157+
i.i.addOne() // expected-warning {{'i' mutated after capture by concurrent closure}}
158+
// expected-note @-14 {{variable defined here}}
159+
// expected-note @-14 {{variable captured by concurrent closure}}
160+
// expected-note @-14 {{capturing use}}
161+
// expected-note @-14 {{capturing use}}
162+
// expected-note @-14 {{capturing use}}
164163
}
165164

166165
func testCaseNonTrivialValueInout() {
167166
var i = NonTrivialValueType(17, Klass())
168167
f {
169-
// Currently emits a typechecker level error due to some sort of bug in the type checker.
170-
// print(i.i + 17)
171-
// print(i.k ?? "none")
168+
print(i.i + 17)
169+
print(i.k ?? "none")
172170
}
173171

174172
// We only emit a warning here since we use the last write.
175-
inoutUserOptKlass(&i.k) // xpected-warning {{'i' mutated after capture by concurrent closure}}
176-
// xpected-note @-8 {{variable defined here}}
177-
// xpected-note @-8 {{variable captured by concurrent closure}}
178-
// xpected-note @-8 {{capturing use}}
179-
// xpected-note @-8 {{capturing use}}
173+
inoutUserOptKlass(&i.k) // expected-warning {{'i' mutated after capture by concurrent closure}}
174+
// expected-note @-8 {{variable defined here}}
175+
// expected-note @-8 {{variable captured by concurrent closure}}
176+
// expected-note @-8 {{capturing use}}
177+
// expected-note @-8 {{capturing use}}
180178
}
181179

182180
protocol MyProt {
@@ -187,9 +185,8 @@ protocol MyProt {
187185
func testCaseAddressOnlyAllocBoxToStackable<T : MyProt>(i : T) {
188186
var i2 = i
189187
f {
190-
// Currently emits an error due to some sort of bug in the type checker.
191-
// print(i2.i + 17)
192-
// print(i2.k ?? "none")
188+
print(i2.i + 17)
189+
print(i2.k ?? "none")
193190
}
194191

195192
// TODO: Make sure we emit these once we support address only types!
@@ -206,9 +203,8 @@ func testCaseAddressOnlyNoAllocBoxToStackable<T : MyProt>(i : T) {
206203
let f2 = F()
207204
var i2 = i
208205
f2.useConcurrent {
209-
// Currently emits a typechecker level error due to some sort of bug in the type checker.
210-
// print(i2.i + 17)
211-
// print(i2.k ?? "none")
206+
print(i2.i + 17)
207+
print(i2.k ?? "none")
212208
}
213209

214210
// TODO: Make sure we emit these once we support address only types!

test/attr/attr_concurrent.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,28 @@ func mutationOfLocal() {
9393

9494
localInt = 20
9595
}
96+
97+
struct NonTrivialValueType {
98+
var int: Int = 0
99+
var array: [Int] = []
100+
var optArray: [Int]? = nil
101+
}
102+
103+
func testCaseNonTrivialValue() {
104+
var i = NonTrivialValueType()
105+
var j = 0
106+
acceptsConcurrent { value in
107+
print(i.int)
108+
print(i.array[0])
109+
print(i.array[j])
110+
print(i.optArray?[j] ?? 0)
111+
print(i.optArray![j])
112+
113+
i.int = 5 // expected-error{{mutation of captured var 'i' in concurrently-executing code}}
114+
i.array[0] = 5 // expected-error{{mutation of captured var 'i' in concurrently-executing code}}
115+
116+
return value
117+
}
118+
119+
j = 17
120+
}

0 commit comments

Comments
 (0)