Skip to content

Commit 2d7e040

Browse files
committed
Sema: Fix an issue with NonisolatedNonsendingByDefault migration for closures
See the inline comments for more details. Depending on the closure's type signature, sometimes adding the attribute will break code. Fix this by also adding inferred effects to the signature in these cases.
1 parent 998a676 commit 2d7e040

File tree

2 files changed

+170
-60
lines changed

2 files changed

+170
-60
lines changed

lib/Sema/NonisolatedNonsendingByDefaultMigration.cpp

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/AST/Decl.h"
2222
#include "swift/AST/DiagnosticsSema.h"
2323
#include "swift/AST/Expr.h"
24+
#include "swift/AST/ParameterList.h"
2425
#include "swift/AST/TypeRepr.h"
2526
#include "swift/Basic/Assertions.h"
2627
#include "swift/Basic/Feature.h"
@@ -171,19 +172,81 @@ void NonisolatedNonsendingByDefaultMigrationTarget::diagnose() const {
171172
featureName, functionDecl)
172173
.fixItInsertAttribute(
173174
decl->getAttributeInsertionLoc(/*forModifier=*/false), &attr);
174-
} else if (closure) {
175-
ctx.Diags
176-
.diagnose(closure->getLoc(),
177-
diag::attr_execution_nonisolated_behavior_will_change_closure,
178-
featureName)
179-
.fixItAddAttribute(&attr, closure);
180-
} else {
175+
} else if (functionRepr) {
181176
ctx.Diags
182177
.diagnose(
183178
functionRepr->getStartLoc(),
184179
diag::attr_execution_nonisolated_behavior_will_change_typerepr,
185180
featureName)
186181
.fixItInsertAttribute(functionRepr->getStartLoc(), &attr);
182+
} else {
183+
auto diag = ctx.Diags.diagnose(
184+
closure->getLoc(),
185+
diag::attr_execution_nonisolated_behavior_will_change_closure,
186+
featureName);
187+
diag.fixItAddAttribute(&attr, closure);
188+
189+
// The following cases fail to compile together with `@concurrent` in
190+
// Swift 5 or Swift 6 mode due to parser and type checker behaviors:
191+
// 1. - Explicit parameter list
192+
// - Explicit result type
193+
// - No explicit `async` effect
194+
// 2. - Explicit parenthesized parameter list
195+
// - No capture list
196+
// - No explicit result type
197+
// - No explicit effect
198+
//
199+
// Work around these issues by adding inferred effects together with the
200+
// attribute.
201+
202+
// If there's an explicit `async` effect, we're good.
203+
if (closure->getAsyncLoc().isValid()) {
204+
return;
205+
}
206+
207+
auto *params = closure->getParameters();
208+
// FIXME: We need a better way to distinguish an implicit parameter list.
209+
bool hasExplicitParenthesizedParamList =
210+
params->getLParenLoc().isValid() &&
211+
params->getLParenLoc() != closure->getStartLoc();
212+
213+
// If the parameter list is implicit, we're good.
214+
if (!hasExplicitParenthesizedParamList) {
215+
if (params->size() == 0) {
216+
return;
217+
} else if ((*params)[0]->isImplicit()) {
218+
return;
219+
}
220+
}
221+
222+
// At this point we must proceed if there is an explicit result type.
223+
// If there is both no explicit result type and the second case does not
224+
// apply for any other reason, we're good.
225+
if (!closure->hasExplicitResultType() &&
226+
(!hasExplicitParenthesizedParamList ||
227+
closure->getBracketRange().isValid() ||
228+
closure->getThrowsLoc().isValid())) {
229+
return;
230+
}
231+
232+
// Compute the insertion location.
233+
SourceLoc effectsInsertionLoc = closure->getThrowsLoc();
234+
if (effectsInsertionLoc.isInvalid() && closure->hasExplicitResultType()) {
235+
effectsInsertionLoc = closure->getArrowLoc();
236+
}
237+
238+
if (effectsInsertionLoc.isInvalid()) {
239+
effectsInsertionLoc = closure->getInLoc();
240+
}
241+
242+
ASSERT(effectsInsertionLoc);
243+
244+
std::string fixIt = "async ";
245+
if (closure->getThrowsLoc().isInvalid() && closure->isBodyThrowing()) {
246+
fixIt += "throws ";
247+
}
248+
249+
diag.fixItInsert(effectsInsertionLoc, fixIt);
187250
}
188251
}
189252

test/Concurrency/attr_execution/adoption_mode.swift

Lines changed: 100 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,15 @@ do {
240240

241241
// MARK: Closures
242242
do {
243+
@concurrent
244+
func asyncOnly(_: Int, _: Int) async {}
245+
@concurrent
246+
@Sendable
247+
func asyncThrows(_: Int, _: Int) async throws {}
248+
249+
nonisolated
250+
func test(_: @concurrent (Int, Int) async throws -> Void) {}
251+
243252
nonisolated
244253
func nonisolatedF() {
245254
let x = 0
@@ -249,59 +258,97 @@ do {
249258
let _ = { @concurrent () async -> Void in }
250259
let _ = { @MainActor () async -> Void in }
251260

252-
// expected-warning@+1:13 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{15-15=@concurrent }}{{none}}
253-
let _ = { () async -> Void in }
254-
// expected-warning@+1:13 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{15-15=@concurrent }}{{none}}
255-
let _ = { [x] () async -> Void in _ = x }
256-
257-
// expected-warning@+1:13 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{14-14= @concurrent in }}{{none}}
258-
let _ = {await globalAsyncF()}
259-
// expected-warning@+1:13 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{14-14=@concurrent }}{{none}}
260-
let _ = {[x] in await globalAsyncF(); _ = x}
261-
262-
// expected-warning@+1:13 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{14-14= @concurrent in }}{{none}}
263-
let _ = {
264-
await globalAsyncF()
265-
}
266-
// expected-warning@+1:13 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{14-14= @concurrent in }}{{none}}
267-
let _ = {
268-
await globalAsyncF()
269-
func takesInts(_: Int...) {}
270-
takesInts($0, $1, $2)
271-
}
272-
273-
// expected-warning@+1:13 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{25-25=@concurrent }}{{none}}
274-
let _ = { @Sendable in
275-
await globalAsyncF()
276-
}
277-
// expected-warning@+1:13 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{25-25=@concurrent }}{{none}}
278-
let _ = { @Sendable [x] in
279-
_ = x
280-
await globalAsyncF()
281-
}
282-
283-
// expected-warning@+2:18 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async function type to be treated as specified to run on the caller's actor; use '@concurrent' to preserve behavior}}{{18-18=@concurrent }}{{none}}
284-
// expected-warning@+1:45 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{47-47=@concurrent }}{{none}}
285-
var closure: (Int, Int) async -> Void = { a, b in
286-
await globalAsyncF()
287-
}
288-
// expected-warning@+1:15 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{17-17=@concurrent }}{{none}}
289-
closure = { [x] a, b in _ = x }
290-
// expected-warning@+1:15 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{+1:7-7=@concurrent }}{{none}}
291-
closure = {
292-
a, b async in ()
293-
}
294-
// expected-warning@+1:15 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{+1:7-7=@concurrent }}{{none}}
295-
closure = {
296-
[x] a, b async in _ = x
297-
}
298-
// expected-warning@+1:15 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{17-17=@concurrent }}{{none}}
299-
closure = { (a, b) in () }
300-
301-
// expected-warning@+1:15 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{17-17=@concurrent }}{{none}}
302-
closure = { [x] (a, b) in _ = x }
303-
304-
let _ = closure
261+
// Make sure all of these case compile with the fix-its applied.
262+
//
263+
// Cases where we add inferred effects to ensure it compiles with '@concurrent'.
264+
265+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{34-34=async throws }}{{none}}
266+
test { (a: consuming Int, b) in try await asyncThrows(a, b) }
267+
test { @concurrent (a: consuming Int, b) async throws in try await asyncThrows(a, b) }
268+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{29-29=async throws }}{{none}}
269+
test { (a: Int, b: Int) in try await asyncThrows(a, b) }
270+
test { @concurrent (a: Int, b: Int) async throws in try await asyncThrows(a, b) }
271+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{22-22=@concurrent }}{{39-39=async throws }}{{none}}
272+
test { @Sendable (a: Int, b: Int) in try await asyncThrows(a, b) }
273+
test { @Sendable @concurrent (a: Int, b: Int) async throws in try await asyncThrows(a, b) }
274+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{24-24=async throws }}{{none}}
275+
test { (a: Int, b) in try await asyncThrows(a, b) }
276+
test { @concurrent (a: Int, b) async throws in try await asyncThrows(a, b) }
277+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{19-19=async throws }}{{none}}
278+
test { (a, b) in try await asyncThrows(a, b) }
279+
test { @concurrent (a, b) async throws in try await asyncThrows(a, b) }
280+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{19-19=async }}{{none}}
281+
test { (a, b) throws -> Void in try await asyncThrows(a, b) }
282+
test { @concurrent (a, b) async throws -> Void in try await asyncThrows(a, b) }
283+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{23-23=async }}{{none}}
284+
test { [x] (a, b) throws -> Void in try await asyncThrows(a, b + x) }
285+
test { @concurrent [x] (a, b) async throws -> Void in try await asyncThrows(a, b + x) }
286+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{19-19=async throws }}{{none}}
287+
test { (a, b) -> Void in try await asyncThrows(a, b) }
288+
test { @concurrent (a, b) async throws -> Void in try await asyncThrows(a, b) }
289+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{23-23=async throws }}{{none}}
290+
test { [x] (a, b) -> Void in try await asyncThrows(a, b + x) }
291+
test { @concurrent [x] (a, b) async throws -> Void in try await asyncThrows(a, b + x) }
292+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{17-17=async throws }}{{none}}
293+
test { a, b -> Void in try await asyncThrows(a, b) }
294+
test { @concurrent a, b async throws -> Void in try await asyncThrows(a, b) }
295+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{17-17=async throws }}{{none}}
296+
test { _, _ -> Void in try await asyncThrows(1, 2) }
297+
test { @concurrent _, _ async throws -> Void in try await asyncThrows(1, 2) }
298+
299+
// Cases that will compile with '@concurrent' as is.
300+
301+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
302+
test { (a: Int, b: Int) throws in try await asyncThrows(a, b) }
303+
test { @concurrent (a: Int, b: Int) throws in try await asyncThrows(a, b) }
304+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
305+
test { (a, b) throws in try await asyncThrows(a, b) }
306+
test { @concurrent (a, b) throws in try await asyncThrows(a, b) }
307+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
308+
test { (_, _) throws in try await asyncThrows(1, 2) }
309+
test { @concurrent (_, _) throws in try await asyncThrows(1, 2) }
310+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
311+
test { (a, b) async throws in try await asyncThrows(a, b) }
312+
test { @concurrent (a, b) async throws in try await asyncThrows(a, b) }
313+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
314+
test { (a, b) async in await asyncOnly(a, b) }
315+
test { @concurrent (a, b) async in await asyncOnly(a, b) }
316+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
317+
test { a, b async -> Void in await asyncOnly(a, b) }
318+
test { @concurrent a, b async -> Void in await asyncOnly(a, b) }
319+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
320+
test { a, b in try await asyncThrows(a, b) }
321+
test { @concurrent a, b in try await asyncThrows(a, b) }
322+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
323+
test { a, b throws in try await asyncThrows(a, b) }
324+
test { @concurrent a, b throws in try await asyncThrows(a, b) }
325+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
326+
test { a, b async in await asyncOnly(a, b) }
327+
test { @concurrent a, b async in await asyncOnly(a, b) }
328+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{11-11= @concurrent in }}{{none}}
329+
test { try await asyncThrows($0, $1) }
330+
test { @concurrent in try await asyncThrows($0, $1) }
331+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
332+
test { [x] in try await asyncThrows($0, $1 + x) }
333+
test { @concurrent [x] in try await asyncThrows($0, $1 + x) }
334+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{22-22=@concurrent }}{{none}}
335+
test { @Sendable [x] in try await asyncThrows($0, $1 + x) }
336+
test { @Sendable @concurrent [x] in try await asyncThrows($0, $1 + x) }
337+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
338+
test { [x] a, b in try await asyncThrows(a, b + x) }
339+
test { @concurrent [x] a, b in try await asyncThrows(a, b + x) }
340+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
341+
test { [x] a, b async in await asyncOnly(a, b + x) }
342+
test { @concurrent [x] a, b async in await asyncOnly(a, b + x) }
343+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
344+
test { [x] a, b throws in try await asyncThrows(a, b + x) }
345+
test { @concurrent [x] a, b throws in try await asyncThrows(a, b + x) }
346+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
347+
test { [x] (a, b) in try await asyncThrows(a, b + x) }
348+
test { @concurrent [x] (a, b) in try await asyncThrows(a, b + x) }
349+
// expected-warning@+1:10 {{feature 'NonisolatedNonsendingByDefault' will cause nonisolated async closure to run on the caller's actor; use '@concurrent' to preserve behavior}}{{12-12=@concurrent }}{{none}}
350+
test { [x] (a: Int, _: Int) in try await asyncThrows(a, x) }
351+
test { @concurrent [x] (a: Int, _: Int) in try await asyncThrows(a, x) }
305352
}
306353

307354
@MainActor

0 commit comments

Comments
 (0)