Skip to content

Commit 2c19e70

Browse files
committed
Sema: Initial implementation of 'reasync' body checking
This also implements rethrows checking for rethrows-by-conformance functions bodies, too.
1 parent 586885b commit 2c19e70

File tree

3 files changed

+177
-54
lines changed

3 files changed

+177
-54
lines changed

lib/Sema/TypeCheckEffects.cpp

Lines changed: 132 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,18 @@ class ApplyClassifier {
702702
}
703703
}
704704

705+
Classification classifyConformance(ProtocolConformanceRef conformanceRef,
706+
EffectKind kind) {
707+
if (conformanceRef.hasEffect(kind)) {
708+
// FIXME: Should be ::Always if its not one of our
709+
// input conformances
710+
return Classification::forConditional(kind,
711+
PotentialEffectReason::forConformance());
712+
}
713+
714+
return Classification();
715+
}
716+
705717
/// Check to see if the given function application throws or is async.
706718
Classification classifyApply(ApplyExpr *E) {
707719
if (isa<SelfApplyExpr>(E))
@@ -747,13 +759,8 @@ class ApplyClassifier {
747759
switch (fnRef.getPolymorphicEffectKind(kind)) {
748760
case PolymorphicEffectKind::ByConformance: {
749761
auto substitutions = fnRef.getSubstitutions();
750-
for (auto conformanceRef : substitutions.getConformances()) {
751-
if (conformanceRef.hasEffect(kind)) {
752-
result.merge(Classification::forConditional(kind,
753-
PotentialEffectReason::forConformance()));
754-
return;
755-
}
756-
}
762+
for (auto conformanceRef : substitutions.getConformances())
763+
result.merge(classifyConformance(conformanceRef, kind));
757764

758765
// 'ByConformance' is a superset of 'ByClosure', so check for
759766
// closure arguments too.
@@ -865,8 +872,8 @@ class ApplyClassifier {
865872

866873
// If we're currently doing rethrows-checking on the body of the
867874
// function which declares the parameter, it's rethrowing-only.
868-
if (kind == EffectKind::Throws &&
869-
param->getDeclContext() == RethrowsDC)
875+
auto *ParentDC = getPolymorphicEffectDeclContext(kind);
876+
if (ParentDC == param->getDeclContext())
870877
return Classification::forConditional(kind, reason);
871878

872879
// Otherwise, it throws unconditionally.
@@ -991,6 +998,18 @@ class ApplyClassifier {
991998
return ShouldRecurse;
992999
}
9931000

1001+
ShouldRecurse_t checkForEach(ForEachStmt *S) {
1002+
if (S->getTryLoc().isValid()) {
1003+
auto classification = Self.classifyConformance(
1004+
S->getSequenceConformance(), EffectKind::Throws);
1005+
IsInvalid |= classification.isInvalid();
1006+
ThrowKind = std::max(ThrowKind,
1007+
classification.getConditionalKind(EffectKind::Throws));
1008+
}
1009+
1010+
return ShouldRecurse;
1011+
}
1012+
9941013
ConditionalEffectKind checkExhaustiveDoBody(DoCatchStmt *S) {
9951014
// All errors thrown by the do body are caught, but any errors thrown
9961015
// by the catch bodies are bounded by the throwing kind of the do body.
@@ -1086,6 +1105,19 @@ class ApplyClassifier {
10861105
ShouldRecurse_t checkDoCatch(DoCatchStmt *S) {
10871106
return ShouldRecurse;
10881107
}
1108+
1109+
ShouldRecurse_t checkForEach(ForEachStmt *S) {
1110+
if (S->getAwaitLoc().isValid()) {
1111+
auto classification = Self.classifyConformance(
1112+
S->getSequenceConformance(),
1113+
EffectKind::Async);
1114+
IsInvalid |= classification.isInvalid();
1115+
AsyncKind = std::max(AsyncKind,
1116+
classification.getConditionalKind(EffectKind::Async));
1117+
}
1118+
1119+
return ShouldRecurse;
1120+
}
10891121
};
10901122

10911123
Optional<ConditionalEffectKind>
@@ -1325,22 +1357,43 @@ class Context {
13251357
}
13261358

13271359
/// Whether this is a function that rethrows.
1328-
bool isRethrows() const {
1329-
if (!HandlesErrors)
1330-
return false;
1331-
1332-
if (ErrorHandlingIgnoresFunction)
1333-
return false;
1334-
1360+
bool hasPolymorphicEffect(EffectKind kind) const {
13351361
if (!Function)
13361362
return false;
13371363

13381364
auto fn = Function->getAbstractFunctionDecl();
13391365
if (!fn)
13401366
return false;
13411367

1342-
return fn->getPolymorphicEffectKind(EffectKind::Throws)
1343-
== PolymorphicEffectKind::ByClosure;
1368+
switch (kind) {
1369+
case EffectKind::Throws:
1370+
if (!HandlesErrors)
1371+
return false;
1372+
1373+
if (ErrorHandlingIgnoresFunction)
1374+
return false;
1375+
1376+
break;
1377+
1378+
case EffectKind::Async:
1379+
if (!HandlesAsync)
1380+
return false;
1381+
1382+
break;
1383+
}
1384+
1385+
switch (fn->getPolymorphicEffectKind(kind)) {
1386+
case PolymorphicEffectKind::ByClosure:
1387+
case PolymorphicEffectKind::ByConformance:
1388+
return true;
1389+
1390+
case PolymorphicEffectKind::None:
1391+
case PolymorphicEffectKind::Always:
1392+
case PolymorphicEffectKind::Invalid:
1393+
return false;
1394+
}
1395+
1396+
llvm_unreachable("Bad polymorphic effect kind");
13441397
}
13451398

13461399
/// Whether this is an autoclosure.
@@ -1450,9 +1503,6 @@ class Context {
14501503

14511504
Kind getKind() const { return TheKind; }
14521505

1453-
bool handlesNothing() const {
1454-
return !HandlesErrors;
1455-
}
14561506
bool handlesThrows(ConditionalEffectKind errorKind) const {
14571507
switch (errorKind) {
14581508
case ConditionalEffectKind::None:
@@ -1465,17 +1515,30 @@ class Context {
14651515
// An operation that always throws can only be handled by an
14661516
// all-handling context.
14671517
case ConditionalEffectKind::Always:
1468-
return HandlesErrors && !isRethrows();
1518+
return HandlesErrors && !hasPolymorphicEffect(EffectKind::Throws);
14691519
}
14701520
llvm_unreachable("bad error kind");
14711521
}
14721522

1473-
bool handlesAsync() const {
1474-
return HandlesAsync;
1523+
bool handlesAsync(ConditionalEffectKind errorKind) const {
1524+
switch (errorKind) {
1525+
case ConditionalEffectKind::None:
1526+
return true;
1527+
1528+
// A call that's rethrowing-only can be handled by 'rethrows'.
1529+
case ConditionalEffectKind::Conditional:
1530+
return HandlesAsync;
1531+
1532+
// An operation that always throws can only be handled by an
1533+
// all-handling context.
1534+
case ConditionalEffectKind::Always:
1535+
return HandlesAsync && !hasPolymorphicEffect(EffectKind::Async);
1536+
}
1537+
llvm_unreachable("bad error kind");
14751538
}
14761539

1477-
DeclContext *getRethrowsDC() const {
1478-
if (!isRethrows())
1540+
DeclContext *getPolymorphicEffectDeclContext(EffectKind kind) const {
1541+
if (!hasPolymorphicEffect(kind))
14791542
return nullptr;
14801543

14811544
return Function->getAbstractFunctionDecl();
@@ -1585,8 +1648,10 @@ class Context {
15851648

15861649
// Allow the diagnostic to fire on the 'try' if we don't have
15871650
// anything else to say.
1588-
if (isTryCovered && !reason.hasPolymorphicEffect() &&
1589-
!isRethrows() && !isAutoClosure()) {
1651+
if (isTryCovered &&
1652+
!reason.hasPolymorphicEffect() &&
1653+
!hasPolymorphicEffect(EffectKind::Throws) &&
1654+
!isAutoClosure()) {
15901655
DiagnoseErrorOnTry = true;
15911656
return;
15921657
}
@@ -1618,7 +1683,7 @@ class Context {
16181683
return;
16191684
}
16201685

1621-
if (isRethrows()) {
1686+
if (hasPolymorphicEffect(EffectKind::Throws)) {
16221687
diagnoseThrowInLegalContext(Diags, E, isTryCovered, reason,
16231688
diag::throwing_call_in_rethrows_function,
16241689
diag::tryless_throwing_call_in_rethrows_function);
@@ -1657,7 +1722,7 @@ class Context {
16571722
return;
16581723
}
16591724

1660-
if (isRethrows()) {
1725+
if (hasPolymorphicEffect(EffectKind::Throws)) {
16611726
Diags.diagnose(S->getStartLoc(), diag::throw_in_rethrows_function);
16621727
return;
16631728
}
@@ -1831,6 +1896,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
18311896
ASTContext &Ctx;
18321897

18331898
DeclContext *RethrowsDC = nullptr;
1899+
DeclContext *ReasyncDC = nullptr;
18341900
Context CurContext;
18351901

18361902
class ContextFlags {
@@ -1917,13 +1983,15 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
19171983
CheckEffectsCoverage &Self;
19181984
Context OldContext;
19191985
DeclContext *OldRethrowsDC;
1986+
DeclContext *OldReasyncDC;
19201987
ContextFlags OldFlags;
19211988
ConditionalEffectKind OldMaxThrowingKind;
19221989

19231990
public:
19241991
ContextScope(CheckEffectsCoverage &self, Optional<Context> newContext)
19251992
: Self(self), OldContext(self.CurContext),
19261993
OldRethrowsDC(self.RethrowsDC),
1994+
OldReasyncDC(self.ReasyncDC),
19271995
OldFlags(self.Flags),
19281996
OldMaxThrowingKind(self.MaxThrowingKind) {
19291997
if (newContext) self.CurContext = *newContext;
@@ -1934,6 +2002,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
19342002

19352003
void enterSubFunction() {
19362004
Self.RethrowsDC = nullptr;
2005+
Self.ReasyncDC = nullptr;
19372006
}
19382007

19392008
void enterTry() {
@@ -2038,6 +2107,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
20382107
~ContextScope() {
20392108
Self.CurContext = OldContext;
20402109
Self.RethrowsDC = OldRethrowsDC;
2110+
Self.ReasyncDC = OldReasyncDC;
20412111
Self.Flags = OldFlags;
20422112
Self.MaxThrowingKind = OldMaxThrowingKind;
20432113
}
@@ -2048,9 +2118,14 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
20482118
: Ctx(ctx), CurContext(initialContext),
20492119
MaxThrowingKind(ConditionalEffectKind::None) {
20502120

2051-
if (auto rethrowsDC = initialContext.getRethrowsDC()) {
2121+
if (auto rethrowsDC = initialContext.getPolymorphicEffectDeclContext(
2122+
EffectKind::Throws)) {
20522123
RethrowsDC = rethrowsDC;
20532124
}
2125+
if (auto reasyncDC = initialContext.getPolymorphicEffectDeclContext(
2126+
EffectKind::Async)) {
2127+
ReasyncDC = reasyncDC;
2128+
}
20542129
}
20552130

20562131
/// Mark that the current context is top-level code with
@@ -2131,7 +2206,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
21312206

21322207
// If the enclosing context doesn't handle anything, use a
21332208
// specialized diagnostic about non-exhaustive catches.
2134-
if (CurContext.handlesNothing()) {
2209+
if (!CurContext.handlesThrows(ConditionalEffectKind::Conditional)) {
21352210
CurContext.setNonExhaustiveCatch(true);
21362211
}
21372212

@@ -2168,7 +2243,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
21682243

21692244
auto savedContext = CurContext;
21702245
if (doThrowingKind != ConditionalEffectKind::Always &&
2171-
CurContext.isRethrows()) {
2246+
CurContext.hasPolymorphicEffect(EffectKind::Throws)) {
21722247
// If this catch clause is reachable at all, it's because a function
21732248
// parameter throws. So let's temporarily state that the body is allowed
21742249
// to throw.
@@ -2186,6 +2261,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
21862261
// But if the expression didn't type-check, suppress diagnostics.
21872262
ApplyClassifier classifier;
21882263
classifier.RethrowsDC = RethrowsDC;
2264+
classifier.ReasyncDC = ReasyncDC;
21892265
auto classification = classifier.classifyApply(E);
21902266

21912267
checkThrowAsyncSite(E, /*requiresTry*/ true, classification);
@@ -2243,7 +2319,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
22432319

22442320
ShouldRecurse_t checkAsyncLet(PatternBindingDecl *patternBinding) {
22452321
// Diagnose async let in a context that doesn't handle async.
2246-
if (!CurContext.handlesAsync()) {
2322+
if (!CurContext.handlesAsync(ConditionalEffectKind::Always)) {
22472323
CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, patternBinding);
22482324
}
22492325

@@ -2319,14 +2395,12 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
23192395
break;
23202396

23212397
case ConditionalEffectKind::Conditional:
2322-
llvm_unreachable("Not supported yet\n");
2323-
23242398
case ConditionalEffectKind::Always:
23252399
// Remember that we've seen an async call.
23262400
Flags.set(ContextFlags::HasAnyAsyncSite);
23272401

23282402
// Diagnose async calls in a context that doesn't handle async.
2329-
if (!CurContext.handlesAsync()) {
2403+
if (!CurContext.handlesAsync(asyncKind)) {
23302404
CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, E);
23312405
}
23322406
// Diagnose async calls that are outside of an await context.
@@ -2382,7 +2456,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
23822456
// course we're in a context that could never handle an 'async'. Then, we
23832457
// produce an error.
23842458
if (!Flags.has(ContextFlags::HasAnyAsyncSite)) {
2385-
if (CurContext.handlesAsync())
2459+
if (CurContext.handlesAsync(ConditionalEffectKind::Conditional))
23862460
Ctx.Diags.diagnose(E->getAwaitLoc(), diag::no_async_in_await);
23872461
else
23882462
CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, E);
@@ -2407,7 +2481,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
24072481

24082482
// Diagnose all the call sites within a single unhandled 'try'
24092483
// at the same time.
2410-
} else if (CurContext.handlesNothing()) {
2484+
} else if (!CurContext.handlesThrows(ConditionalEffectKind::Conditional)) {
24112485
CurContext.diagnoseUnhandledTry(Ctx.Diags, E);
24122486
}
24132487

@@ -2448,16 +2522,27 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
24482522
}
24492523

24502524
ShouldRecurse_t checkForEach(ForEachStmt *S) {
2451-
// FIXME: Handle 'for try await' inside a 'rethrows'-by-conformance
2452-
// function
2453-
if (S->getTryLoc().isValid() &&
2454-
!CurContext.handlesThrows(ConditionalEffectKind::Always)) {
2455-
CurContext.diagnoseUnhandledThrowStmt(Ctx.Diags, S);
2525+
if (!S->getAwaitLoc().isValid())
2526+
return ShouldRecurse;
2527+
2528+
ApplyClassifier classifier;
2529+
classifier.RethrowsDC = RethrowsDC;
2530+
classifier.ReasyncDC = ReasyncDC;
2531+
2532+
if (S->getTryLoc().isValid()) {
2533+
auto classification = classifier.classifyConformance(
2534+
S->getSequenceConformance(), EffectKind::Throws);
2535+
auto throwsKind = classification.getConditionalKind(EffectKind::Throws);
2536+
if (!CurContext.handlesThrows(throwsKind))
2537+
CurContext.diagnoseUnhandledThrowStmt(Ctx.Diags, S);
24562538
}
2457-
if (S->getAwaitLoc().isValid() &&
2458-
!CurContext.handlesAsync()) {
2539+
2540+
auto classification = classifier.classifyConformance(
2541+
S->getSequenceConformance(), EffectKind::Async);
2542+
auto asyncKind = classification.getConditionalKind(EffectKind::Async);
2543+
if (!CurContext.handlesAsync(asyncKind))
24592544
CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, S);
2460-
}
2545+
24612546
return ShouldRecurse;
24622547
}
24632548
};

test/Concurrency/reasync.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,17 @@ func callReasyncWithAutoclosure2() async {
115115
await reasyncWithAutoclosure(computeValueAsync())
116116
// expected-error@-1 {{call is 'async' in an autoclosure argument that is not marked with 'await'}}
117117
}
118+
119+
//// Reasync body checking
120+
121+
// FIXME: Need tailored diagnostics to handle 'reasync' vs 'sync'.
122+
123+
func invalidReasyncBody(_: () async -> ()) reasync {
124+
// expected-note@-1 {{add 'async' to function 'invalidReasyncBody' to make it asynchronous}}
125+
_ = await computeValueAsync()
126+
// expected-error@-1 {{'async' in a function that does not support concurrency}}
127+
}
128+
129+
func validReasyncBody(_ fn: () async -> ()) reasync {
130+
await fn()
131+
}

0 commit comments

Comments
 (0)