Skip to content

Commit 21ec80f

Browse files
[Sema] Improving implicit closure capture diagnostic wording
1 parent 3a9c801 commit 21ec80f

File tree

5 files changed

+93
-26
lines changed

5 files changed

+93
-26
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,6 +1965,9 @@ NOTE(non_sendable_nominal,none,
19651965
NOTE(add_nominal_sendable_conformance,none,
19661966
"consider making %0 %1 conform to the 'Sendable' protocol",
19671967
(DescriptiveDeclKind, DeclName))
1968+
NOTE(add_generic_parameter_sendable_conformance,none,
1969+
"consider making generic parameter %0 conform to the 'Sendable' protocol",
1970+
(Type))
19681971
REMARK(add_predates_concurrency_import,none,
19691972
"add '@preconcurrency' to %select{suppress|treat}0 "
19701973
"'Sendable'-related %select{warnings|errors}0 from module %1"
@@ -4613,6 +4616,12 @@ ERROR(concurrent_access_of_inout_param,none,
46134616
ERROR(non_sendable_capture,none,
46144617
"capture of %1 with non-sendable type %0 in a `@Sendable` closure",
46154618
(Type, DeclName))
4619+
ERROR(implicit_async_let_non_sendable_capture,none,
4620+
"capture of %1 with non-sendable type %0 in 'async let' binding",
4621+
(Type, DeclName))
4622+
ERROR(implicit_non_sendable_capture,none,
4623+
"implicit capture of %1 requires that %0 conforms to `Sendable`",
4624+
(Type, DeclName))
46164625

46174626
NOTE(actor_isolated_sync_func,none,
46184627
"calls to %0 %1 from outside of its actor context are "

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -569,18 +569,27 @@ static void addSendableFixIt(
569569
const NominalTypeDecl *nominal, InFlightDiagnostic &diag, bool unchecked) {
570570
if (nominal->getInherited().empty()) {
571571
SourceLoc fixItLoc = nominal->getBraces().Start;
572-
if (unchecked)
573-
diag.fixItInsert(fixItLoc, ": @unchecked Sendable");
574-
else
575-
diag.fixItInsert(fixItLoc, ": Sendable");
572+
diag.fixItInsert(fixItLoc,
573+
unchecked ? ": @unchecked Sendable" : ": Sendable");
576574
} else {
577-
ASTContext &ctx = nominal->getASTContext();
578-
SourceLoc fixItLoc = nominal->getInherited().back().getSourceRange().End;
579-
fixItLoc = Lexer::getLocForEndOfToken(ctx.SourceMgr, fixItLoc);
580-
if (unchecked)
581-
diag.fixItInsert(fixItLoc, ", @unchecked Sendable");
582-
else
583-
diag.fixItInsert(fixItLoc, ", Sendable");
575+
auto fixItLoc = nominal->getInherited().back().getLoc();
576+
diag.fixItInsertAfter(fixItLoc,
577+
unchecked ? ", @unchecked Sendable" : ", Sendable");
578+
}
579+
}
580+
581+
/// Add Fix-It text for the given generic param declaration type to adopt
582+
/// Sendable.
583+
static void addSendableFixIt(const GenericTypeParamDecl *genericArgument,
584+
InFlightDiagnostic &diag, bool unchecked) {
585+
if (genericArgument->getInherited().empty()) {
586+
auto fixItLoc = genericArgument->getLoc();
587+
diag.fixItInsertAfter(fixItLoc,
588+
unchecked ? ": @unchecked Sendable" : ": Sendable");
589+
} else {
590+
auto fixItLoc = genericArgument->getInherited().back().getLoc();
591+
diag.fixItInsertAfter(fixItLoc,
592+
unchecked ? ", @unchecked Sendable" : ", Sendable");
584593
}
585594
}
586595

@@ -874,6 +883,18 @@ static bool diagnoseSingleNonSendableType(
874883
nominal->diagnose(
875884
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
876885
nominal->getName());
886+
} else if (auto genericArchetype = type->getAs<ArchetypeType>()) {
887+
auto interfaceType = genericArchetype->getInterfaceType();
888+
if (auto genericParamType =
889+
interfaceType->getAs<GenericTypeParamType>()) {
890+
auto *genericParamTypeDecl = genericParamType->getDecl();
891+
if (genericParamTypeDecl &&
892+
genericParamTypeDecl->getModuleContext() == module) {
893+
auto diag = genericParamTypeDecl->diagnose(
894+
diag::add_generic_parameter_sendable_conformance, type);
895+
addSendableFixIt(genericParamTypeDecl, diag, /*unchecked=*/false);
896+
}
897+
}
877898
}
878899

879900
return false;
@@ -1527,6 +1548,7 @@ namespace {
15271548
SmallVector<const DeclContext *, 4> contextStack;
15281549
SmallVector<ApplyExpr*, 4> applyStack;
15291550
SmallVector<std::pair<OpaqueValueExpr *, Expr *>, 4> opaqueValues;
1551+
SmallVector<const PatternBindingDecl *, 2> patternBindingStack;
15301552

15311553
/// Keeps track of the capture context of variables that have been
15321554
/// explicitly captured in closures.
@@ -1539,6 +1561,10 @@ namespace {
15391561
using MutableVarParent
15401562
= llvm::PointerUnion<InOutExpr *, LoadExpr *, AssignExpr *>;
15411563

1564+
const PatternBindingDecl *getTopPatternBindingDecl() const {
1565+
return patternBindingStack.empty() ? nullptr : patternBindingStack.back();
1566+
}
1567+
15421568
/// Mapping from mutable variable reference exprs, or inout expressions,
15431569
/// to the parent expression, when that parent is either a load or
15441570
/// an inout expr.
@@ -1694,9 +1720,24 @@ namespace {
16941720
Type type = getDeclContext()
16951721
->mapTypeIntoContext(decl->getInterfaceType())
16961722
->getReferenceStorageReferent();
1697-
diagnoseNonSendableTypes(
1698-
type, getDeclContext(), capture.getLoc(),
1699-
diag::non_sendable_capture, decl->getName());
1723+
1724+
if (closure->isImplicit()) {
1725+
auto *patternBindingDecl = getTopPatternBindingDecl();
1726+
if (patternBindingDecl && patternBindingDecl->isAsyncLet()) {
1727+
diagnoseNonSendableTypes(
1728+
type, getDeclContext(), capture.getLoc(),
1729+
diag::implicit_async_let_non_sendable_capture, decl->getName());
1730+
} else {
1731+
// Fallback to a generic implicit capture missing sendable
1732+
// conformance diagnostic.
1733+
diagnoseNonSendableTypes(type, getDeclContext(), capture.getLoc(),
1734+
diag::implicit_non_sendable_capture,
1735+
decl->getName());
1736+
}
1737+
} else {
1738+
diagnoseNonSendableTypes(type, getDeclContext(), capture.getLoc(),
1739+
diag::non_sendable_capture, decl->getName());
1740+
}
17001741
}
17011742
}
17021743

@@ -1759,6 +1800,10 @@ namespace {
17591800
contextStack.push_back(func);
17601801
}
17611802

1803+
if (auto *PBD = dyn_cast<PatternBindingDecl>(decl)) {
1804+
patternBindingStack.push_back(PBD);
1805+
}
1806+
17621807
return true;
17631808
}
17641809

@@ -1768,6 +1813,11 @@ namespace {
17681813
contextStack.pop_back();
17691814
}
17701815

1816+
if (auto *PBD = dyn_cast<PatternBindingDecl>(decl)) {
1817+
assert(patternBindingStack.back() == PBD);
1818+
patternBindingStack.pop_back();
1819+
}
1820+
17711821
return true;
17721822
}
17731823

test/Concurrency/concurrent_value_checking.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ var concurrentFuncVar: (@Sendable (NotConcurrent) -> Void)? = nil
255255
// ----------------------------------------------------------------------
256256
func acceptConcurrentUnary<T>(_: @Sendable (T) -> T) { }
257257

258-
func concurrentClosures<T>(_: T) {
258+
func concurrentClosures<T>(_: T) { // expected-note{{consider making generic parameter 'T' conform to the 'Sendable' protocol}} {{26-26=: Sendable}}
259259
acceptConcurrentUnary { (x: T) in
260260
_ = x // ok
261261
acceptConcurrentUnary { _ in x } // expected-warning{{capture of 'x' with non-sendable type 'T' in a `@Sendable` closure}}
@@ -269,7 +269,7 @@ struct S1: Sendable {
269269
var nc: NotConcurrent // expected-warning{{stored property 'nc' of 'Sendable'-conforming struct 'S1' has non-sendable type 'NotConcurrent'}}
270270
}
271271

272-
struct S2<T>: Sendable {
272+
struct S2<T>: Sendable { // expected-note{{consider making generic parameter 'T' conform to the 'Sendable' protocol}} {{12-12=: Sendable}}
273273
var nc: T // expected-warning{{stored property 'nc' of 'Sendable'-conforming generic struct 'S2' has non-sendable type 'T'}}
274274
}
275275

test/Concurrency/sr15049.swift

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,33 @@ func testAsyncSequence1Sendable<Seq: AsyncSequence>(_ seq: Seq) async throws whe
1919
async let _ = seq.reduce(0) { $0 + $1 } // OK
2020
}
2121

22-
// TODO(diagnostics) [SR-16092]: Add a tailored wording for implicit autoclosure captures in sendable warnings, because
23-
// from user perspective there is no closure capture, so diagnostic can be confusing.
24-
func testAsyncSequenceTypedPattern<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int {
22+
func testAsyncSequenceTypedPattern<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int { // expected-note{{consider making generic parameter 'Seq' conform to the 'Sendable' protocol}} {{54-54=, Sendable}}
2523
async let result: Int = seq.reduce(0) { $0 + $1 } // OK
2624
// expected-warning@-1{{immutable value 'result' was never used; consider replacing with '_' or removing it}}
27-
// expected-warning@-2{{capture of 'seq' with non-sendable type 'Seq' in a `@Sendable` closure}}
25+
// expected-warning@-2{{capture of 'seq' with non-sendable type 'Seq' in 'async let' binding}}
2826
}
2927

30-
func testAsyncSequenceTypedPattern1<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int {
28+
func testAsyncSequenceTypedPattern1<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int { // expected-note{{consider making generic parameter 'Seq' conform to the 'Sendable' protocol}} {{55-55=, Sendable}}
3129
async let _: Int = seq.reduce(0) { $0 + $1 } // OK
32-
// expected-warning@-1{{capture of 'seq' with non-sendable type 'Seq' in a `@Sendable` closure}}
30+
// expected-warning@-1{{capture of 'seq' with non-sendable type 'Seq' in 'async let' binding}}
3331
}
3432

35-
func testAsyncSequence<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int {
33+
func testAsyncSequence<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int { // expected-note{{consider making generic parameter 'Seq' conform to the 'Sendable' protocol}} {{42-42=, Sendable}}
3634
async let result = seq.reduce(0) { $0 + $1 } // OK
3735
// expected-warning@-1{{initialization of immutable value 'result' was never used; consider replacing with assignment to '_' or removing it}}
38-
// expected-warning@-2{{capture of 'seq' with non-sendable type 'Seq' in a `@Sendable` closure}}
36+
// expected-warning@-2{{capture of 'seq' with non-sendable type 'Seq' in 'async let' binding}}
3937
}
4038

41-
func testAsyncSequence1<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int {
39+
func testAsyncSequence1<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int { // expected-note{{consider making generic parameter 'Seq' conform to the 'Sendable' protocol}} {{43-43=, Sendable}}
4240
async let _ = seq.reduce(0) { $0 + $1 } // OK
43-
// expected-warning@-1{{capture of 'seq' with non-sendable type 'Seq' in a `@Sendable` closure}}
41+
// expected-warning@-1{{capture of 'seq' with non-sendable type 'Seq' in 'async let' binding}}
42+
}
43+
44+
func testAsyncSequence3<Seq>(_ seq: Seq) async throws where Seq: AsyncSequence, Seq.Element == Int { // expected-note{{consider making generic parameter 'Seq' conform to the 'Sendable' protocol}} {{28-28=: Sendable}}
45+
async let result = seq // expected-warning{{capture of 'seq' with non-sendable type 'Seq' in 'async let' binding}}
46+
//expected-warning@-1{{initialization of immutable value 'result' was never used; consider replacing with assignment to '_' or removing it}}
47+
}
48+
49+
func testAsyncSequence4<Seq>(_ seq: Seq) async throws where Seq: AsyncSequence, Seq.Element == Int { // expected-note{{consider making generic parameter 'Seq' conform to the 'Sendable' protocol}} {{28-28=: Sendable}}
50+
async let _ = seq // expected-warning{{capture of 'seq' with non-sendable type 'Seq' in 'async let' binding}}
4451
}

test/Distributed/distributed_protocol_isolation.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ protocol Server {
127127
func send<Message: Codable>(message: Message) async throws -> String
128128
}
129129
actor MyServer : Server {
130+
// expected-note@+1{{consider making generic parameter 'Message' conform to the 'Sendable' protocol}} {{29-29=, Sendable}}
130131
func send<Message: Codable>(message: Message) throws -> String { "" } // expected-warning{{non-sendable type 'Message' in parameter of actor-isolated instance method 'send(message:)' satisfying non-isolated protocol requirement cannot cross actor boundary}}
131132
}
132133

0 commit comments

Comments
 (0)