Skip to content

Commit 586885b

Browse files
committed
Sema: Preliminary implementation of 'reasync' call site checking
Part of <rdar://problem/71098795>.
1 parent 6655f51 commit 586885b

File tree

2 files changed

+162
-65
lines changed

2 files changed

+162
-65
lines changed

lib/Sema/TypeCheckEffects.cpp

Lines changed: 77 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -717,16 +717,11 @@ class ApplyClassifier {
717717
auto fnType = type->getAs<AnyFunctionType>();
718718
if (!fnType) return Classification::forInvalidCode();
719719

720-
Classification result;
721-
722-
if (fnType->isAsync() || E->implicitlyAsync())
723-
result = Classification::forUnconditional(
724-
EffectKind::Async,
725-
PotentialEffectReason::forApply());
726-
727-
// If the function doesn't throw at all, we're done here.
728-
if (!fnType->isThrowing()) {
729-
return result;
720+
// If the function doesn't have any effects, we're done here.
721+
if (!fnType->isThrowing() &&
722+
!fnType->isAsync() &&
723+
!E->implicitlyAsync()) {
724+
return Classification();
730725
}
731726

732727
// Decompose the application.
@@ -739,58 +734,86 @@ class ApplyClassifier {
739734
return Classification::forInvalidCode();
740735
}
741736

742-
// Handle rethrowing functions.
743-
switch (fnRef.getPolymorphicEffectKind(EffectKind::Throws)) {
744-
case PolymorphicEffectKind::ByConformance: {
745-
auto substitutions = fnRef.getSubstitutions();
746-
for (auto conformanceRef : substitutions.getConformances()) {
747-
if (conformanceRef.hasEffect(EffectKind::Throws)) {
748-
result.merge(Classification::forConditional(EffectKind::Throws,
749-
PotentialEffectReason::forConformance()));
750-
return result;
737+
Classification result;
738+
739+
auto classifyApplyEffect = [&](EffectKind kind) {
740+
if (!fnType->hasEffect(kind) &&
741+
!(kind == EffectKind::Async &&
742+
E->implicitlyAsync())) {
743+
return;
744+
}
745+
746+
// Handle rethrowing and reasync functions.
747+
switch (fnRef.getPolymorphicEffectKind(kind)) {
748+
case PolymorphicEffectKind::ByConformance: {
749+
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+
}
751756
}
757+
758+
// 'ByConformance' is a superset of 'ByClosure', so check for
759+
// closure arguments too.
760+
LLVM_FALLTHROUGH;
752761
}
753762

754-
// 'ByConformance' is a superset of 'ByClosure', so check for
755-
// closure arguments too.
756-
LLVM_FALLTHROUGH;
757-
}
763+
case PolymorphicEffectKind::ByClosure: {
764+
// We need to walk the original parameter types in parallel
765+
// because it only counts for rethrows/reasync purposes if it
766+
// lines up with a throws/async function parameter in the
767+
// original type.
768+
auto *origType = fnRef.getType()->getAs<AnyFunctionType>();
769+
if (!origType) {
770+
result.merge(Classification::forInvalidCode());
771+
return;
772+
}
758773

759-
case PolymorphicEffectKind::ByClosure: {
760-
// We need to walk the original parameter types in parallel
761-
// because it only counts for 'rethrows' purposes if it lines up
762-
// with a throwing function parameter in the original type.
763-
auto *origType = fnRef.getType()->getAs<AnyFunctionType>();
764-
if (!origType)
765-
return Classification::forInvalidCode();
774+
// Use the most significant result from the arguments.
775+
auto params = origType->getParams();
776+
if (params.size() != args.size()) {
777+
result.merge(Classification::forInvalidCode());
778+
return;
779+
}
766780

767-
// Use the most significant result from the arguments.
768-
auto params = origType->getParams();
769-
if (params.size() != args.size())
770-
return Classification::forInvalidCode();
781+
for (unsigned i = 0, e = params.size(); i < e; ++i) {
782+
result.merge(classifyArgument(args[i],
783+
params[i].getParameterType(),
784+
kind));
785+
}
771786

772-
for (unsigned i = 0, e = params.size(); i < e; ++i) {
773-
result.merge(classifyArgument(args[i],
774-
params[i].getParameterType(),
775-
EffectKind::Throws));
787+
return;
776788
}
777789

778-
return result;
779-
}
790+
case PolymorphicEffectKind::None:
791+
case PolymorphicEffectKind::Always:
792+
case PolymorphicEffectKind::Invalid:
793+
break;
794+
}
780795

781-
default:
782-
break;
783-
}
796+
// Try to classify the implementation of functions that we have
797+
// local knowledge of.
798+
//
799+
// An autoclosure callee here only appears in a narrow case where
800+
// we're in the initializer of an 'async let'.
801+
if (fnRef.isAutoClosure()) {
802+
result.merge(Classification::forUnconditional(
803+
kind, PotentialEffectReason::forApply()));
804+
} else {
805+
result.merge(
806+
classifyFunctionBody(fnRef,
807+
PotentialEffectReason::forApply(),
808+
kind));
809+
assert(result.getConditionalKind(kind)
810+
!= ConditionalEffectKind::None &&
811+
"body classification decided function had no effect?");
812+
}
813+
};
784814

785-
// Try to classify the implementation of functions that we have
786-
// local knowledge of.
787-
result.merge(
788-
classifyFunctionBody(fnRef,
789-
PotentialEffectReason::forApply(),
790-
EffectKind::Throws));
791-
assert(result.getConditionalKind(EffectKind::Throws)
792-
!= ConditionalEffectKind::None &&
793-
"body classification decided function was no-throw");
815+
classifyApplyEffect(EffectKind::Throws);
816+
classifyApplyEffect(EffectKind::Async);
794817

795818
return result;
796819
}
@@ -813,23 +836,12 @@ class ApplyClassifier {
813836
}
814837

815838
private:
816-
/// Classify a throwing function according to our local knowledge of
817-
/// its implementation.
818-
///
819-
/// For the most part, this only distinguishes between Throws and
820-
/// RethrowingOnly. But it can return Invalid if a type-checking
821-
/// failure prevents it from deciding that, and it can return None
822-
/// if the function is an autoclosure that simply doesn't throw at all.
839+
/// Classify a throwing or async function according to our local
840+
/// knowledge of its implementation.
823841
Classification
824842
classifyFunctionBody(const AbstractFunction &fn,
825843
PotentialEffectReason reason,
826844
EffectKind kind) {
827-
// If we're not checking a 'rethrows' context, we don't need to
828-
// distinguish between 'throws' and 'rethrows'. But don't even
829-
// trust 'throws' for autoclosures.
830-
if (!getPolymorphicEffectDeclContext(kind) && !fn.isAutoClosure())
831-
return Classification::forUnconditional(kind, reason);
832-
833845
switch (fn.getKind()) {
834846
case AbstractFunction::Opaque:
835847
return Classification::forUnconditional(kind, reason);

test/Concurrency/reasync.swift

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency
22
// REQUIRES: concurrency
33

4+
//// Basic definitions and parsing
5+
46
func reasyncFunction(_: () async -> ()) reasync {}
57

68
func reasyncRethrowsFunction(_: () async throws -> ()) reasync rethrows {}
@@ -30,3 +32,86 @@ class Derived : Base {
3032
override func reasyncMethod(_: () async -> ()) async {}
3133
// expected-error@-1 {{override of 'reasync' method should also be 'reasync'}}
3234
}
35+
36+
//// Reasync call site checking
37+
38+
func asyncFunction() async {}
39+
40+
func callReasyncFunction() async {
41+
reasyncFunction { }
42+
await reasyncFunction { } // expected-warning {{no calls to 'async' functions occur within 'await' expression}}
43+
44+
reasyncFunction { await asyncFunction() } // expected-error {{call is 'async' but is not marked with 'await'}}
45+
await reasyncFunction { await asyncFunction() }
46+
}
47+
48+
enum HorseError : Error {
49+
case colic
50+
}
51+
52+
func callReasyncRethrowsFunction() async throws {
53+
reasyncRethrowsFunction { }
54+
await reasyncRethrowsFunction { }
55+
// expected-warning@-1 {{no calls to 'async' functions occur within 'await' expression}}
56+
try reasyncRethrowsFunction { }
57+
// expected-warning@-1 {{no calls to throwing functions occur within 'try' expression}}
58+
try await reasyncRethrowsFunction { }
59+
// expected-warning@-1 {{no calls to 'async' functions occur within 'await' expression}}
60+
// expected-warning@-2 {{no calls to throwing functions occur within 'try' expression}}
61+
62+
reasyncRethrowsFunction { await asyncFunction() }
63+
// expected-error@-1 {{call is 'async' but is not marked with 'await'}}
64+
await reasyncRethrowsFunction { await asyncFunction() }
65+
try reasyncRethrowsFunction { await asyncFunction() }
66+
// expected-error@-1 {{call is 'async' but is not marked with 'await'}}
67+
// expected-warning@-2 {{no calls to throwing functions occur within 'try' expression}}
68+
try await reasyncRethrowsFunction { await asyncFunction() }
69+
// expected-warning@-1 {{no calls to throwing functions occur within 'try' expression}}
70+
71+
reasyncRethrowsFunction { throw HorseError.colic }
72+
// expected-error@-1 {{call can throw but is not marked with 'try'}}
73+
// expected-note@-2 {{call is to 'rethrows' function, but argument function can throw}}
74+
await reasyncRethrowsFunction { throw HorseError.colic }
75+
// expected-error@-1 {{call can throw but is not marked with 'try'}}
76+
// expected-note@-2 {{call is to 'rethrows' function, but argument function can throw}}
77+
// expected-warning@-3 {{no calls to 'async' functions occur within 'await' expression}}
78+
try reasyncRethrowsFunction { throw HorseError.colic }
79+
try await reasyncRethrowsFunction { throw HorseError.colic }
80+
// expected-warning@-1 {{no calls to 'async' functions occur within 'await' expression}}
81+
82+
reasyncRethrowsFunction { await asyncFunction(); throw HorseError.colic }
83+
// expected-error@-1 {{call can throw but is not marked with 'try'}}
84+
// expected-note@-2 {{call is to 'rethrows' function, but argument function can throw}}
85+
// expected-error@-3 {{call is 'async' but is not marked with 'await'}}
86+
await reasyncRethrowsFunction { await asyncFunction(); throw HorseError.colic }
87+
// expected-error@-1 {{call can throw but is not marked with 'try'}}
88+
// expected-note@-2 {{call is to 'rethrows' function, but argument function can throw}}
89+
try reasyncRethrowsFunction { await asyncFunction(); throw HorseError.colic }
90+
// expected-error@-1 {{call is 'async' but is not marked with 'await'}}
91+
try await reasyncRethrowsFunction { await asyncFunction(); throw HorseError.colic }
92+
}
93+
94+
func computeValue() -> Int {}
95+
func computeValueAsync() async -> Int {}
96+
97+
func reasyncWithAutoclosure(_: @autoclosure () async -> Int) reasync {}
98+
99+
func callReasyncWithAutoclosure1() {
100+
// expected-note@-1 2{{add 'async' to function 'callReasyncWithAutoclosure1()' to make it asynchronous}}
101+
// expected-note@-2 2{{add '@asyncHandler' to function 'callReasyncWithAutoclosure1()' to create an implicit asynchronous context}}
102+
reasyncWithAutoclosure(computeValue())
103+
await reasyncWithAutoclosure(await computeValueAsync())
104+
// expected-error@-1 {{'async' in a function that does not support concurrency}}
105+
106+
await reasyncWithAutoclosure(computeValueAsync())
107+
// expected-error@-1 {{call is 'async' in an autoclosure argument that is not marked with 'await'}}
108+
// expected-error@-2 {{'async' in a function that does not support concurrency}}
109+
}
110+
111+
func callReasyncWithAutoclosure2() async {
112+
reasyncWithAutoclosure(computeValue())
113+
await reasyncWithAutoclosure(await computeValueAsync())
114+
115+
await reasyncWithAutoclosure(computeValueAsync())
116+
// expected-error@-1 {{call is 'async' in an autoclosure argument that is not marked with 'await'}}
117+
}

0 commit comments

Comments
 (0)