Skip to content

Commit 1b4f9b0

Browse files
authored
Merge pull request #41661 from apple/SR-15049-async-diag
[SR-15049][Diagnostics] Infer throwing in async let patter synthesized auto closure for SelfCallExpr initialization
2 parents b8684af + e57cf51 commit 1b4f9b0

File tree

15 files changed

+180
-21
lines changed

15 files changed

+180
-21
lines changed

include/swift/AST/Pattern.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ class alignas(8) Pattern : public ASTAllocated<Pattern> {
7979
IsLet : 1
8080
);
8181

82+
SWIFT_INLINE_BITFIELD(AnyPattern, Pattern, 1,
83+
/// True if this is an "async let _" pattern.
84+
IsAsyncLet : 1);
85+
8286
} Bits;
8387

8488
Pattern(PatternKind kind) {
@@ -371,8 +375,10 @@ class AnyPattern : public Pattern {
371375
SourceLoc Loc;
372376

373377
public:
374-
explicit AnyPattern(SourceLoc Loc)
375-
: Pattern(PatternKind::Any), Loc(Loc) { }
378+
explicit AnyPattern(SourceLoc Loc, bool IsAsyncLet = false)
379+
: Pattern(PatternKind::Any), Loc(Loc) {
380+
Bits.AnyPattern.IsAsyncLet = static_cast<uint64_t>(IsAsyncLet);
381+
}
376382

377383
static AnyPattern *createImplicit(ASTContext &Context) {
378384
auto *AP = new (Context) AnyPattern(SourceLoc());
@@ -383,6 +389,16 @@ class AnyPattern : public Pattern {
383389
SourceLoc getLoc() const { return Loc; }
384390
SourceRange getSourceRange() const { return Loc; }
385391

392+
/// True if this is an "async let _ pattern since `async let _` could be a
393+
/// subPattern of a \c TypedPattern represented as \c AnyPattern e.g.
394+
/// "async let _: Type = <expr>" or simply just an \c AnyPattern in
395+
/// "async let _ = <expr>" case.
396+
bool isAsyncLet() const { return bool(Bits.AnyPattern.IsAsyncLet); }
397+
398+
void setIsAsyncLet() {
399+
Bits.AnyPattern.IsAsyncLet = static_cast<uint64_t>(true);
400+
}
401+
386402
static bool classof(const Pattern *P) {
387403
return P->getKind() == PatternKind::Any;
388404
}

include/swift/Parse/Parser.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ class Parser {
160160
IVOLP_InLet
161161
} InVarOrLetPattern = IVOLP_NotInVarOrLet;
162162

163+
/// Whether this context has an async attribute.
164+
bool InPatternWithAsyncAttribute = false;
165+
163166
bool InPoundLineEnvironment = false;
164167
bool InPoundIfEnvironment = false;
165168
/// Do not call \c addUnvalidatedDeclWithOpaqueResultType when in an inactive

lib/AST/ASTDumper.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,9 @@ namespace {
375375
PrintWithColorRAII(OS, ParenthesisColor) << ')';
376376
}
377377
void visitAnyPattern(AnyPattern *P) {
378+
if (P->isAsyncLet()) {
379+
printCommon(P, "async_let ");
380+
}
378381
printCommon(P, "pattern_any");
379382
PrintWithColorRAII(OS, ParenthesisColor) << ')';
380383
}

lib/AST/Decl.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,6 +1745,18 @@ bool PatternBindingDecl::isAsyncLet() const {
17451745
if (auto var = getAnchoringVarDecl(0))
17461746
return var->isAsyncLet();
17471747

1748+
// Check for "async let _: <Type> = <expression>" pattern.
1749+
auto *pattern = getPatternList()[0].getPattern();
1750+
if (auto *typedPattern = dyn_cast<TypedPattern>(pattern)) {
1751+
auto *anyPattern = dyn_cast<AnyPattern>(typedPattern->getSubPattern());
1752+
return anyPattern && anyPattern->isAsyncLet();
1753+
}
1754+
1755+
// Check for "async let _ = <expression>" pattern.
1756+
if (auto *anyPattern = dyn_cast<AnyPattern>(pattern)) {
1757+
return anyPattern->isAsyncLet();
1758+
}
1759+
17481760
return false;
17491761
}
17501762

lib/Parse/ParseDecl.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6959,6 +6959,11 @@ Parser::parseDeclVar(ParseDeclOptions Flags,
69596959
llvm::SaveAndRestore<decltype(InVarOrLetPattern)>
69606960
T(InVarOrLetPattern, isLet ? IVOLP_InLet : IVOLP_InVar);
69616961

6962+
// Track whether we are parsing an 'async let' pattern.
6963+
const auto hasAsyncAttr = Attributes.hasAttribute<AsyncAttr>();
6964+
llvm::SaveAndRestore<bool> AsyncAttr(InPatternWithAsyncAttribute,
6965+
hasAsyncAttr);
6966+
69626967
auto patternRes = parseTypedPattern();
69636968
if (patternRes.hasCodeCompletion())
69646969
return makeResult(makeParserCodeCompletionStatus());
@@ -7643,7 +7648,11 @@ Parser::parseDeclEnumCase(ParseDeclOptions Flags,
76437648
llvm::SaveAndRestore<decltype(InVarOrLetPattern)>
76447649
T(InVarOrLetPattern, Parser::IVOLP_InMatchingPattern);
76457650
parseMatchingPattern(/*isExprBasic*/false);
7646-
7651+
7652+
// Reset async attribute in parser context.
7653+
llvm::SaveAndRestore<bool> AsyncAttr(InPatternWithAsyncAttribute,
7654+
false);
7655+
76477656
if (consumeIf(tok::colon)) {
76487657
backtrack.cancelBacktrack();
76497658
diagnose(CaseLoc, diag::case_outside_of_switch, "case");

lib/Parse/ParseExpr.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2917,7 +2917,10 @@ ParserResult<Expr> Parser::parseExprClosure() {
29172917
// reset our state to not be in a pattern for any recursive pattern parses.
29182918
llvm::SaveAndRestore<decltype(InVarOrLetPattern)>
29192919
T(InVarOrLetPattern, IVOLP_NotInVarOrLet);
2920-
2920+
2921+
// Reset async attribute in parser context.
2922+
llvm::SaveAndRestore<bool> AsyncAttr(InPatternWithAsyncAttribute, false);
2923+
29212924
// Parse the opening left brace.
29222925
SourceLoc leftBrace = consumeToken();
29232926

lib/Parse/ParsePattern.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,8 +1119,8 @@ ParserResult<Pattern> Parser::parsePattern() {
11191119
switch (Tok.getKind()) {
11201120
case tok::l_paren:
11211121
return parsePatternTuple();
1122-
1123-
case tok::kw__:
1122+
1123+
case tok::kw__: {
11241124
// Normally, '_' is invalid in type context for patterns, but they show up
11251125
// in interface files as the name for type members that are non-public.
11261126
// Treat them as an implicitly synthesized NamedPattern with a nameless
@@ -1134,8 +1134,12 @@ ParserResult<Pattern> Parser::parsePattern() {
11341134
return makeParserResult(NamedPattern::createImplicit(Context, VD));
11351135
}
11361136
PatternCtx.setCreateSyntax(SyntaxKind::WildcardPattern);
1137-
return makeParserResult(new (Context) AnyPattern(consumeToken(tok::kw__)));
1138-
1137+
1138+
const auto isAsyncLet =
1139+
InPatternWithAsyncAttribute && introducer == VarDecl::Introducer::Let;
1140+
return makeParserResult(
1141+
new (Context) AnyPattern(consumeToken(tok::kw__), isAsyncLet));
1142+
}
11391143
case tok::identifier: {
11401144
PatternCtx.setCreateSyntax(SyntaxKind::IdentifierPattern);
11411145
Identifier name;
@@ -1174,7 +1178,10 @@ ParserResult<Pattern> Parser::parsePattern() {
11741178
// In our recursive parse, remember that we're in a var/let pattern.
11751179
llvm::SaveAndRestore<decltype(InVarOrLetPattern)>
11761180
T(InVarOrLetPattern, isLet ? IVOLP_InLet : IVOLP_InVar);
1177-
1181+
1182+
// Reset async attribute in parser context.
1183+
llvm::SaveAndRestore<bool> AsyncAttr(InPatternWithAsyncAttribute, false);
1184+
11781185
ParserResult<Pattern> subPattern = parsePattern();
11791186
if (subPattern.hasCodeCompletion())
11801187
return makeParserCodeCompletionResult<Pattern>();
@@ -1384,6 +1391,9 @@ ParserResult<Pattern> Parser::parseMatchingPatternAsLetOrVar(bool isLet,
13841391
llvm::SaveAndRestore<decltype(InVarOrLetPattern)>
13851392
T(InVarOrLetPattern, isLet ? IVOLP_InLet : IVOLP_InVar);
13861393

1394+
// Reset async attribute in parser context.
1395+
llvm::SaveAndRestore<bool> AsyncAttr(InPatternWithAsyncAttribute, false);
1396+
13871397
ParserResult<Pattern> subPattern = parseMatchingPattern(isExprBasic);
13881398
if (subPattern.isNull())
13891399
return nullptr;

lib/Parse/ParseStmt.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1562,6 +1562,10 @@ Parser::parseStmtConditionElement(SmallVectorImpl<StmtConditionElement> &result,
15621562
// In our recursive parse, remember that we're in a matching pattern.
15631563
llvm::SaveAndRestore<decltype(InVarOrLetPattern)>
15641564
T(InVarOrLetPattern, IVOLP_InMatchingPattern);
1565+
1566+
// Reset async attribute in parser context.
1567+
llvm::SaveAndRestore<bool> AsyncAttr(InPatternWithAsyncAttribute, false);
1568+
15651569
ThePattern = parseMatchingPattern(/*isExprBasic*/ true);
15661570
} else if (Tok.is(tok::kw_case)) {
15671571
ConditionCtxt.setCreateSyntax(SyntaxKind::Unknown);
@@ -1580,7 +1584,10 @@ Parser::parseStmtConditionElement(SmallVectorImpl<StmtConditionElement> &result,
15801584
// In our recursive parse, remember that we're in a var/let pattern.
15811585
llvm::SaveAndRestore<decltype(InVarOrLetPattern)>
15821586
T(InVarOrLetPattern, wasLet ? IVOLP_InLet : IVOLP_InVar);
1583-
1587+
1588+
// Reset async attribute in parser context.
1589+
llvm::SaveAndRestore<bool> AsyncAttr(InPatternWithAsyncAttribute, false);
1590+
15841591
ThePattern = parseMatchingPattern(/*isExprBasic*/ true);
15851592

15861593
if (ThePattern.isNonNull()) {
@@ -2234,6 +2241,10 @@ ParserResult<Stmt> Parser::parseStmtForEach(LabeledStmtInfo LabelInfo) {
22342241
if (consumeIf(tok::kw_case)) {
22352242
llvm::SaveAndRestore<decltype(InVarOrLetPattern)>
22362243
T(InVarOrLetPattern, Parser::IVOLP_InMatchingPattern);
2244+
2245+
// Reset async attribute in parser context.
2246+
llvm::SaveAndRestore<bool> AsyncAttr(InPatternWithAsyncAttribute, false);
2247+
22372248
pattern = parseMatchingPattern(/*isExprBasic*/true);
22382249
pattern = parseOptionalPatternTypeAnnotation(pattern);
22392250
} else if (!IsCStyleFor || Tok.is(tok::kw_var)) {

lib/Sema/TypeCheckEffects.cpp

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,12 @@ class ApplyClassifier {
724724
DeclContext *RethrowsDC = nullptr;
725725
DeclContext *ReasyncDC = nullptr;
726726

727+
// Indicates if `classifyApply` will attempt to classify SelfApplyExpr
728+
// because that should be done only in certain contexts like when infering
729+
// if "async let" implicit auto closure wrapping initialize expression can
730+
// throw.
731+
bool ClassifySelfApplyExpr = false;
732+
727733
DeclContext *getPolymorphicEffectDeclContext(EffectKind kind) const {
728734
switch (kind) {
729735
case EffectKind::Throws: return RethrowsDC;
@@ -745,16 +751,29 @@ class ApplyClassifier {
745751

746752
/// Check to see if the given function application throws or is async.
747753
Classification classifyApply(ApplyExpr *E) {
748-
if (isa<SelfApplyExpr>(E)) {
749-
assert(!E->isImplicitlyAsync());
750-
return Classification();
751-
}
752-
753754
// An apply expression is a potential throw site if the function throws.
754755
// But if the expression didn't type-check, suppress diagnostics.
755756
if (!E->getType() || E->getType()->hasError())
756757
return Classification::forInvalidCode();
757758

759+
if (auto *SAE = dyn_cast<SelfApplyExpr>(E)) {
760+
assert(!E->isImplicitlyAsync());
761+
762+
if (ClassifySelfApplyExpr) {
763+
// Do not consider throw properties in SelfAssignExpr with an implicit
764+
// conversion base.
765+
if (isa<ImplicitConversionExpr>(SAE->getBase()))
766+
return Classification();
767+
768+
auto fnType = E->getType()->getAs<AnyFunctionType>();
769+
if (fnType && fnType->isThrowing()) {
770+
return Classification::forUnconditional(
771+
EffectKind::Throws, PotentialEffectReason::forApply());
772+
}
773+
}
774+
return Classification();
775+
}
776+
758777
auto type = E->getFn()->getType();
759778
if (!type) return Classification::forInvalidCode();
760779
auto fnType = type->getAs<AnyFunctionType>();
@@ -2957,6 +2976,8 @@ void TypeChecker::checkPropertyWrapperEffects(
29572976
}
29582977

29592978
bool TypeChecker::canThrow(Expr *expr) {
2960-
return (ApplyClassifier().classifyExpr(expr, EffectKind::Throws)
2961-
== ConditionalEffectKind::Always);
2979+
ApplyClassifier classifier;
2980+
classifier.ClassifySelfApplyExpr = true;
2981+
return (classifier.classifyExpr(expr, EffectKind::Throws) ==
2982+
ConditionalEffectKind::Always);
29622983
}

lib/Serialization/Deserialization.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,13 @@ Expected<Pattern *> ModuleFile::readPattern(DeclContext *owningDC) {
347347
}
348348
case decls_block::ANY_PATTERN: {
349349
TypeID typeID;
350+
bool isAsyncLet;
350351

351-
AnyPatternLayout::readRecord(scratch, typeID);
352+
AnyPatternLayout::readRecord(scratch, typeID, isAsyncLet);
352353
auto result = AnyPattern::createImplicit(getContext());
354+
if (isAsyncLet) {
355+
result->setIsAsyncLet();
356+
}
353357
recordPatternType(result, getType(typeID));
354358
restoreOffset.reset();
355359
return result;

lib/Serialization/ModuleFormat.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5656
/// describe what change you made. The content of this comment isn't important;
5757
/// it just ensures a conflict if two people change the module format.
5858
/// Don't worry about adhering to the 80-column limit for this line.
59-
const uint16_t SWIFTMODULE_VERSION_MINOR = 685; // Primary associated types
59+
const uint16_t SWIFTMODULE_VERSION_MINOR = 686; // async let bit encoded in any_pattern
6060

6161
/// A standard hash seed used for all string hashes in a serialized module.
6262
///
@@ -1608,7 +1608,8 @@ namespace decls_block {
16081608

16091609
using AnyPatternLayout = BCRecordLayout<
16101610
ANY_PATTERN,
1611-
TypeIDField // type
1611+
TypeIDField, // type
1612+
BCFixed<1> // isAsyncLet
16121613
// FIXME: is the type necessary?
16131614
>;
16141615

lib/Serialization/Serialization.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3091,8 +3091,10 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
30913091
}
30923092
case PatternKind::Any: {
30933093
unsigned abbrCode = S.DeclTypeAbbrCodes[AnyPatternLayout::Code];
3094+
auto anyPattern = cast<AnyPattern>(pattern);
30943095
AnyPatternLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode,
3095-
S.addTypeRef(getPatternType()));
3096+
S.addTypeRef(getPatternType()),
3097+
anyPattern->isAsyncLet());
30963098
break;
30973099
}
30983100
case PatternKind::Typed: {

test/Concurrency/sr15049.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// RUN: %target-typecheck-verify-swift -disable-availability-checking
2+
// REQUIRES: concurrency
3+
4+
func testAsyncSequenceTypedPatternSendable<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int, Seq: Sendable {
5+
async let result: Int = seq.reduce(0) { $0 + $1 } // OK
6+
// expected-warning@-1{{immutable value 'result' was never used; consider replacing with '_' or removing it}}
7+
}
8+
9+
func testAsyncSequenceTypedPattern1Sendable<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int, Seq: Sendable {
10+
async let _: Int = seq.reduce(0) { $0 + $1 } // OK
11+
}
12+
13+
func testAsyncSequenceSendable<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int, Seq: Sendable {
14+
async let result = seq.reduce(0) { $0 + $1 } // OK
15+
// expected-warning@-1{{initialization of immutable value 'result' was never used; consider replacing with assignment to '_' or removing it}}
16+
}
17+
18+
func testAsyncSequence1Sendable<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int, Seq: Sendable {
19+
async let _ = seq.reduce(0) { $0 + $1 } // OK
20+
}
21+
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 {
25+
async let result: Int = seq.reduce(0) { $0 + $1 } // OK
26+
// 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}}
28+
}
29+
30+
func testAsyncSequenceTypedPattern1<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int {
31+
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}}
33+
}
34+
35+
func testAsyncSequence<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int {
36+
async let result = seq.reduce(0) { $0 + $1 } // OK
37+
// 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}}
39+
}
40+
41+
func testAsyncSequence1<Seq: AsyncSequence>(_ seq: Seq) async throws where Seq.Element == Int {
42+
async let _ = seq.reduce(0) { $0 + $1 } // OK
43+
// expected-warning@-1{{capture of 'seq' with non-sendable type 'Seq' in a `@Sendable` closure}}
44+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
11
public func doSomethingBig() async -> Int { return 0 }
2+
3+
@_alwaysEmitIntoClient
4+
public func doSerializedAsyncLet() async {
5+
async let _ = ()
6+
}
7+
8+
@_alwaysEmitIntoClient
9+
public func doSerializedAsyncLetTyped() async {
10+
async let _ : () = ()
11+
}

test/Serialization/async.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,13 @@ import def_async
1111
func testDoSomethingBig() {
1212
let _: () -> Int = doSomethingBig // expected-error{{invalid conversion from 'async' function of type '() async -> Int' to synchronous function type '() -> Int'}}
1313
}
14+
15+
// Verify serialization of async let _ = <expression> pattern
16+
func testSerializedAsyncLet() async {
17+
await doSerializedAsyncLet()
18+
}
19+
20+
// Verify serialization of async let _ : <type> = <expression> pattern
21+
func testSerializedAsyncLetTyped() async {
22+
await doSerializedAsyncLetTyped()
23+
}

0 commit comments

Comments
 (0)