mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[Concurrency] Diagnose mutating accesses to locals from concurrent code.
Replace the existing warning about any access to a local variable from concurrently-executing code with a more tailored error: concurrently-executing code may read a mutable varable, but cannot modify it. This is safe so long as we either always do by-value captures in concurrent closures or we ensure that no mutation of that variable can occur after the point of capture. We'll follow up with one of those. For now... be careful out there. Since we're promoting this to an error, narrow it down to concurrent closures and local functions, dropping the assumption that escaping closures "may execute concurrently."
This commit is contained in:
@@ -181,6 +181,17 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this function is @concurrent.
|
||||||
|
bool isConcurrent() const {
|
||||||
|
if (!hasType())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (auto *fnType = getType()->getAs<AnyFunctionType>())
|
||||||
|
return fnType->isConcurrent();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool isObjC() const {
|
bool isObjC() const {
|
||||||
if (auto afd = TheFunction.dyn_cast<AbstractFunctionDecl *>()) {
|
if (auto afd = TheFunction.dyn_cast<AbstractFunctionDecl *>()) {
|
||||||
return afd->isObjC();
|
return afd->isObjC();
|
||||||
|
|||||||
@@ -4253,9 +4253,8 @@ ERROR(global_actor_from_nonactor_context,none,
|
|||||||
ERROR(actor_isolated_partial_apply,none,
|
ERROR(actor_isolated_partial_apply,none,
|
||||||
"actor-isolated %0 %1 can not be partially applied",
|
"actor-isolated %0 %1 can not be partially applied",
|
||||||
(DescriptiveDeclKind, DeclName))
|
(DescriptiveDeclKind, DeclName))
|
||||||
WARNING(concurrent_access_local,none,
|
ERROR(concurrent_access_local,none,
|
||||||
"local %0 %1 is unsafe to reference in code that may execute "
|
"use of local %0 %1 in concurrently-executing code",
|
||||||
"concurrently",
|
|
||||||
(DescriptiveDeclKind, DeclName))
|
(DescriptiveDeclKind, DeclName))
|
||||||
ERROR(actor_isolated_from_concurrent_closure,none,
|
ERROR(actor_isolated_from_concurrent_closure,none,
|
||||||
"actor-isolated %0 %1 cannot be referenced from a concurrent closure",
|
"actor-isolated %0 %1 cannot be referenced from a concurrent closure",
|
||||||
@@ -4272,6 +4271,9 @@ ERROR(actor_isolated_from_escaping_closure,none,
|
|||||||
ERROR(local_function_executed_concurrently,none,
|
ERROR(local_function_executed_concurrently,none,
|
||||||
"concurrently-executed %0 %1 must be marked as '@concurrent'",
|
"concurrently-executed %0 %1 must be marked as '@concurrent'",
|
||||||
(DescriptiveDeclKind, DeclName))
|
(DescriptiveDeclKind, DeclName))
|
||||||
|
ERROR(concurrent_mutation_of_local_capture,none,
|
||||||
|
"mutation of captured %0 %1 in concurrently-executing code",
|
||||||
|
(DescriptiveDeclKind, DeclName))
|
||||||
NOTE(concurrent_access_here,none,
|
NOTE(concurrent_access_here,none,
|
||||||
"access in concurrently-executed code here", ())
|
"access in concurrently-executed code here", ())
|
||||||
NOTE(actor_isolated_sync_func,none,
|
NOTE(actor_isolated_sync_func,none,
|
||||||
|
|||||||
@@ -697,6 +697,12 @@ namespace {
|
|||||||
|
|
||||||
ConcurrentExecutionChecker concurrentExecutionChecker;
|
ConcurrentExecutionChecker concurrentExecutionChecker;
|
||||||
|
|
||||||
|
using MutableVarParent = llvm::PointerUnion<InOutExpr *, LoadExpr *>;
|
||||||
|
|
||||||
|
/// Mapping from mutable local variables to the parent expression, when
|
||||||
|
/// that parent is either a load or a inout expression.
|
||||||
|
llvm::SmallDenseMap<DeclRefExpr *, MutableVarParent, 4> mutableLocalVarParent;
|
||||||
|
|
||||||
const DeclContext *getDeclContext() const {
|
const DeclContext *getDeclContext() const {
|
||||||
return contextStack.back();
|
return contextStack.back();
|
||||||
}
|
}
|
||||||
@@ -709,6 +715,33 @@ namespace {
|
|||||||
useContext, defContext);
|
useContext, defContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If the subexpression is a reference to a mutable local variable from a
|
||||||
|
/// different context, record its parent. We'll query this as part of
|
||||||
|
/// capture semantics in concurrent functions.
|
||||||
|
void recordMutableVarParent(MutableVarParent parent, Expr *subExpr) {
|
||||||
|
auto declRef = dyn_cast<DeclRefExpr>(subExpr);
|
||||||
|
if (!declRef)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto var = dyn_cast_or_null<VarDecl>(declRef->getDecl());
|
||||||
|
if (!var)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Only mutable variables matter.
|
||||||
|
if (!var->supportsMutation())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Only mutable variables outside of the current context. This is an
|
||||||
|
// optimization, because the parent map won't be queried in this case, and
|
||||||
|
// it is the most common case for variables to be referenced in their
|
||||||
|
// own context.
|
||||||
|
if (var->getDeclContext() == getDeclContext())
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert(mutableLocalVarParent[declRef].isNull());
|
||||||
|
mutableLocalVarParent[declRef] = parent;
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ActorIsolationChecker(const DeclContext *dc) : ctx(dc->getASTContext()) {
|
ActorIsolationChecker(const DeclContext *dc) : ctx(dc->getASTContext()) {
|
||||||
contextStack.push_back(dc);
|
contextStack.push_back(dc);
|
||||||
@@ -777,6 +810,12 @@ namespace {
|
|||||||
if (auto inout = dyn_cast<InOutExpr>(expr)) {
|
if (auto inout = dyn_cast<InOutExpr>(expr)) {
|
||||||
if (!applyStack.empty())
|
if (!applyStack.empty())
|
||||||
diagnoseInOutArg(applyStack.back(), inout, false);
|
diagnoseInOutArg(applyStack.back(), inout, false);
|
||||||
|
|
||||||
|
recordMutableVarParent(inout, inout->getSubExpr());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto load = dyn_cast<LoadExpr>(expr)) {
|
||||||
|
recordMutableVarParent(load, load->getSubExpr());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
|
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
|
||||||
@@ -786,7 +825,8 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (auto declRef = dyn_cast<DeclRefExpr>(expr)) {
|
if (auto declRef = dyn_cast<DeclRefExpr>(expr)) {
|
||||||
checkNonMemberReference(declRef->getDeclRef(), declRef->getLoc());
|
checkNonMemberReference(
|
||||||
|
declRef->getDeclRef(), declRef->getLoc(), declRef);
|
||||||
return { true, expr };
|
return { true, expr };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,6 +905,11 @@ namespace {
|
|||||||
applyStack.pop_back();
|
applyStack.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear out the mutable local variable parent map on the way out.
|
||||||
|
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr)) {
|
||||||
|
mutableLocalVarParent.erase(declRefExpr);
|
||||||
|
}
|
||||||
|
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1215,7 +1260,8 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check a reference to a local or global.
|
/// Check a reference to a local or global.
|
||||||
bool checkNonMemberReference(ConcreteDeclRef valueRef, SourceLoc loc) {
|
bool checkNonMemberReference(
|
||||||
|
ConcreteDeclRef valueRef, SourceLoc loc, DeclRefExpr *declRefExpr) {
|
||||||
if (!valueRef)
|
if (!valueRef)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -1233,22 +1279,37 @@ namespace {
|
|||||||
value, loc, isolation.getGlobalActor());
|
value, loc, isolation.getGlobalActor());
|
||||||
|
|
||||||
case ActorIsolationRestriction::LocalCapture:
|
case ActorIsolationRestriction::LocalCapture:
|
||||||
if (!shouldDiagnoseExistingDataRaces(getDeclContext()))
|
// Check whether we are in a context that will not execute concurrently
|
||||||
|
// with the context of 'self'. If not, it's safe.
|
||||||
|
if (!mayExecuteConcurrentlyWith(
|
||||||
|
getDeclContext(), isolation.getLocalContext()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check whether we are in a context that will not execute concurrently
|
// Check whether this is a local variable, in which case we can
|
||||||
// with the context of 'self'.
|
// determine whether it was captured by value.
|
||||||
if (mayExecuteConcurrentlyWith(
|
if (auto var = dyn_cast<VarDecl>(value)) {
|
||||||
getDeclContext(), isolation.getLocalContext())) {
|
auto parent = mutableLocalVarParent[declRefExpr];
|
||||||
|
|
||||||
|
// If we have an immediate load of this variable, the by-value
|
||||||
|
// capture in a concurrent function will guarantee that this is
|
||||||
|
// safe.
|
||||||
|
if (parent.dyn_cast<LoadExpr *>())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Otherwise, we have concurrent mutation. Complain.
|
||||||
ctx.Diags.diagnose(
|
ctx.Diags.diagnose(
|
||||||
loc, diag::concurrent_access_local,
|
loc, diag::concurrent_mutation_of_local_capture,
|
||||||
value->getDescriptiveKind(), value->getName());
|
var->getDescriptiveKind(), var->getName());
|
||||||
value->diagnose(
|
|
||||||
diag::kind_declared_here, value->getDescriptiveKind());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
// Concurrent access to some other local.
|
||||||
|
ctx.Diags.diagnose(
|
||||||
|
loc, diag::concurrent_access_local,
|
||||||
|
value->getDescriptiveKind(), value->getName());
|
||||||
|
value->diagnose(
|
||||||
|
diag::kind_declared_here, value->getDescriptiveKind());
|
||||||
|
return true;
|
||||||
|
|
||||||
case ActorIsolationRestriction::Unsafe:
|
case ActorIsolationRestriction::Unsafe:
|
||||||
return diagnoseReferenceToUnsafeGlobal(value, loc);
|
return diagnoseReferenceToUnsafeGlobal(value, loc);
|
||||||
@@ -1535,6 +1596,10 @@ SourceLoc ConcurrentExecutionChecker::getConcurrentReferenceLoc(
|
|||||||
enclosingBody = enclosingFunc->getBody();
|
enclosingBody = enclosingFunc->getBody();
|
||||||
else if (auto enclosingClosure = dyn_cast<ClosureExpr>(enclosingDC))
|
else if (auto enclosingClosure = dyn_cast<ClosureExpr>(enclosingDC))
|
||||||
enclosingBody = enclosingClosure->getBody();
|
enclosingBody = enclosingClosure->getBody();
|
||||||
|
else if (auto enclosingTopLevelCode = dyn_cast<TopLevelCodeDecl>(enclosingDC))
|
||||||
|
enclosingBody = enclosingTopLevelCode->getBody();
|
||||||
|
else
|
||||||
|
return SourceLoc();
|
||||||
|
|
||||||
assert(enclosingBody && "Cannot have a local function here");
|
assert(enclosingBody && "Cannot have a local function here");
|
||||||
ConcurrentLocalRefWalker walker(*this, localFunc);
|
ConcurrentLocalRefWalker walker(*this, localFunc);
|
||||||
@@ -1548,10 +1613,8 @@ bool ConcurrentExecutionChecker::mayExecuteConcurrentlyWith(
|
|||||||
// Walk the context chain from the use to the definition.
|
// Walk the context chain from the use to the definition.
|
||||||
while (useContext != defContext) {
|
while (useContext != defContext) {
|
||||||
// If we find a concurrent closure... it can be run concurrently.
|
// If we find a concurrent closure... it can be run concurrently.
|
||||||
// NOTE: We also classify escaping closures this way, which detects more
|
|
||||||
// problematic cases.
|
|
||||||
if (auto closure = dyn_cast<AbstractClosureExpr>(useContext)) {
|
if (auto closure = dyn_cast<AbstractClosureExpr>(useContext)) {
|
||||||
if (isEscapingClosure(closure) || isConcurrentClosure(closure))
|
if (isConcurrentClosure(closure))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ extension MyActor {
|
|||||||
|
|
||||||
// Closures.
|
// Closures.
|
||||||
let localConstant = 17
|
let localConstant = 17
|
||||||
var localVar = 17 // expected-note 4{{var declared here}}
|
var localVar = 17
|
||||||
|
|
||||||
// Non-escaping closures are okay.
|
// Non-escaping closures are okay.
|
||||||
acceptClosure {
|
acceptClosure {
|
||||||
@@ -142,7 +142,8 @@ extension MyActor {
|
|||||||
acceptConcurrentClosure {
|
acceptConcurrentClosure {
|
||||||
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent closure}}
|
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent closure}}
|
||||||
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent closure}}
|
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent closure}}
|
||||||
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
|
_ = localVar // okay
|
||||||
|
localVar = 25 // expected-error{{mutation of captured var 'localVar' in concurrently-executing code}}
|
||||||
_ = localConstant
|
_ = localConstant
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +151,7 @@ extension MyActor {
|
|||||||
acceptEscapingClosure {
|
acceptEscapingClosure {
|
||||||
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from an '@escaping' closure}}
|
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from an '@escaping' closure}}
|
||||||
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from an '@escaping' closure}}
|
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from an '@escaping' closure}}
|
||||||
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
|
_ = localVar // okay, don't complain about escaping
|
||||||
_ = localConstant
|
_ = localConstant
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +159,8 @@ extension MyActor {
|
|||||||
@concurrent func localFn1() {
|
@concurrent func localFn1() {
|
||||||
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent function}}
|
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent function}}
|
||||||
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent function}}
|
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent function}}
|
||||||
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
|
_ = localVar // okay
|
||||||
|
localVar = 25 // expected-error{{mutation of captured var 'localVar' in concurrently-executing code}}
|
||||||
_ = localConstant
|
_ = localConstant
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +168,8 @@ extension MyActor {
|
|||||||
acceptClosure {
|
acceptClosure {
|
||||||
_ = text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent function}}
|
_ = text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent function}}
|
||||||
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent function}}
|
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent function}}
|
||||||
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
|
_ = localVar // okay
|
||||||
|
localVar = 25 // expected-error{{mutation of captured var 'localVar' in concurrently-executing code}}
|
||||||
_ = localConstant
|
_ = localConstant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,11 +324,12 @@ func testGlobalRestrictions(actor: MyActor) async {
|
|||||||
// Global mutable state cannot be accessed.
|
// Global mutable state cannot be accessed.
|
||||||
_ = mutableGlobal // expected-warning{{reference to var 'mutableGlobal' is not concurrency-safe because it involves shared mutable state}}
|
_ = mutableGlobal // expected-warning{{reference to var 'mutableGlobal' is not concurrency-safe because it involves shared mutable state}}
|
||||||
|
|
||||||
// Local mutable variables cannot be accessed from concurrently-executing
|
// Local mutable variables cannot be modified from concurrently-executing
|
||||||
// code.
|
// code.
|
||||||
var i = 17 // expected-note{{var declared here}}
|
var i = 17
|
||||||
acceptEscapingClosure {
|
acceptConcurrentClosure {
|
||||||
i = 42 // expected-warning{{local var 'i' is unsafe to reference in code that may execute concurrently}}
|
_ = i
|
||||||
|
i = 42 // expected-error{{mutation of captured var 'i' in concurrently-executing code}}
|
||||||
}
|
}
|
||||||
print(i)
|
print(i)
|
||||||
}
|
}
|
||||||
@@ -335,15 +339,14 @@ func testGlobalRestrictions(actor: MyActor) async {
|
|||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
func checkLocalFunctions() async {
|
func checkLocalFunctions() async {
|
||||||
var i = 0
|
var i = 0
|
||||||
var j = 0 // expected-note{{var declared here}}
|
var j = 0
|
||||||
|
|
||||||
func local1() {
|
func local1() {
|
||||||
i = 17
|
i = 17
|
||||||
}
|
}
|
||||||
|
|
||||||
func local2() { // expected-error{{concurrently-executed local function 'local2()' must be marked as '@concurrent'}}{{3-3=@concurrent }}
|
func local2() { // expected-error{{concurrently-executed local function 'local2()' must be marked as '@concurrent'}}{{3-3=@concurrent }}
|
||||||
j = 42 // expected-warning{{local var 'j' is unsafe to reference in code that may execute concurrently}}
|
j = 42 // expected-error{{mutation of captured var 'j' in concurrently-executing code}}
|
||||||
// FIXME: the above should be an error as well
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Okay to call locally.
|
// Okay to call locally.
|
||||||
@@ -357,22 +360,22 @@ func checkLocalFunctions() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Escaping closures can make the local function execute concurrently.
|
// Escaping closures can make the local function execute concurrently.
|
||||||
acceptEscapingClosure {
|
acceptConcurrentClosure {
|
||||||
local2() // expected-note{{access in concurrently-executed code here}}
|
local2() // expected-note{{access in concurrently-executed code here}}
|
||||||
}
|
}
|
||||||
|
|
||||||
print(i)
|
print(i)
|
||||||
print(j)
|
print(j)
|
||||||
|
|
||||||
var k = 17 // expected-note{{var declared here}}
|
var k = 17
|
||||||
func local4() {
|
func local4() {
|
||||||
acceptEscapingClosure {
|
acceptConcurrentClosure {
|
||||||
local3() // expected-note{{access in concurrently-executed code here}}
|
local3() // expected-note{{access in concurrently-executed code here}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func local3() { // expected-error{{concurrently-executed local function 'local3()' must be marked as '@concurrent'}}
|
func local3() { // expected-error{{concurrently-executed local function 'local3()' must be marked as '@concurrent'}}
|
||||||
k = 25 // expected-warning{{local var 'k' is unsafe to reference in code that may execute concurrently}}
|
k = 25 // expected-error{{mutation of captured var 'k' in concurrently-executing code}}
|
||||||
}
|
}
|
||||||
|
|
||||||
print(k)
|
print(k)
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ actor class MyActor {
|
|||||||
async let z = synchronous()
|
async let z = synchronous()
|
||||||
// expected-error @-1{{actor-isolated instance method 'synchronous()' cannot be referenced from 'async let' initializer}}
|
// expected-error @-1{{actor-isolated instance method 'synchronous()' cannot be referenced from 'async let' initializer}}
|
||||||
|
|
||||||
var localText = text // expected-note{{var declared here}}
|
var localText = text
|
||||||
async let w = localText.removeLast()
|
async let w = localText.removeLast()
|
||||||
// expected-warning@-1{{local var 'localText' is unsafe to reference in code that may execute concurrently}}
|
// expected-error@-1{{mutation of captured var 'localText' in concurrently-executing code}}
|
||||||
|
|
||||||
_ = await x
|
_ = await x
|
||||||
_ = await y
|
_ = await y
|
||||||
|
|||||||
@@ -53,3 +53,43 @@ func closures() {
|
|||||||
acceptsConcurrent(closure1) // expected-error{{converting non-concurrent function value to '@concurrent (Int) -> Int' may introduce data races}}
|
acceptsConcurrent(closure1) // expected-error{{converting non-concurrent function value to '@concurrent (Int) -> Int' may introduce data races}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mutation of captured locals from within @concurrent functions.
|
||||||
|
extension Int {
|
||||||
|
mutating func makeNegative() {
|
||||||
|
self = -self
|
||||||
|
}
|
||||||
|
|
||||||
|
func printMe() {
|
||||||
|
print(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mutationOfLocal() {
|
||||||
|
var localInt = 17
|
||||||
|
acceptsConcurrent { i in
|
||||||
|
// Non-mutating accesses are okay
|
||||||
|
print(localInt + 17)
|
||||||
|
localInt.printMe()
|
||||||
|
|
||||||
|
// Mutations of locally-defined variables are fine.
|
||||||
|
var localResult = localInt + 1
|
||||||
|
print(localResult)
|
||||||
|
|
||||||
|
_ = {
|
||||||
|
localResult = localResult + 1
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Mutations of captured variables executing concurrently are bad.
|
||||||
|
localInt = 17 // expected-error{{mutation of captured var 'localInt' in concurrently-executing code}}
|
||||||
|
localInt += 1 // expected-error{{mutation of captured var 'localInt' in concurrently-executing code}}
|
||||||
|
localInt.makeNegative() // expected-error{{mutation of captured var 'localInt' in concurrently-executing code}}
|
||||||
|
|
||||||
|
_ = {
|
||||||
|
localInt = localInt + 12 // expected-error{{mutation of captured var 'localInt' in concurrently-executing code}}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return i + localInt
|
||||||
|
}
|
||||||
|
|
||||||
|
localInt = 20
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user