Skip to content

Commit 41f74e4

Browse files
authored
Merge pull request #34952 from DougGregor/convert-sync-to-async
[Concurrency] Allow synchronous functions to be a subtype of 'async' functions
2 parents af25d75 + 3530dc6 commit 41f74e4

File tree

14 files changed

+139
-15
lines changed

14 files changed

+139
-15
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,10 @@ ERROR(throws_functiontype_mismatch,none,
551551
"invalid conversion from throwing function of type %0 to "
552552
"non-throwing function type %1", (Type, Type))
553553

554+
ERROR(async_functiontype_mismatch,none,
555+
"invalid conversion from 'async' function of type %0 to "
556+
"synchronous function type %1", (Type, Type))
557+
554558
// Key-path expressions.
555559
ERROR(expr_keypath_no_objc_runtime,none,
556560
"'#keyPath' can only be used with the Objective-C runtime", ())

include/swift/Sema/CSFix.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,26 @@ class DropThrowsAttribute final : public ContextualMismatch {
630630
ConstraintLocator *locator);
631631
};
632632

633+
/// This is a contextual mismatch between async and non-async
634+
/// function types, repair it by dropping `async` attribute.
635+
class DropAsyncAttribute final : public ContextualMismatch {
636+
DropAsyncAttribute(ConstraintSystem &cs, FunctionType *fromType,
637+
FunctionType *toType, ConstraintLocator *locator)
638+
: ContextualMismatch(cs, fromType, toType, locator) {
639+
assert(fromType->isAsync() != toType->isAsync());
640+
}
641+
642+
public:
643+
std::string getName() const override { return "drop 'async' attribute"; }
644+
645+
bool diagnose(const Solution &solution, bool asNote = false) const override;
646+
647+
static DropAsyncAttribute *create(ConstraintSystem &cs,
648+
FunctionType *fromType,
649+
FunctionType *toType,
650+
ConstraintLocator *locator);
651+
};
652+
633653
/// Append 'as! T' to force a downcast to the specified type.
634654
class ForceDowncast final : public ContextualMismatch {
635655
ForceDowncast(ConstraintSystem &cs, Type fromType, Type toType,

lib/SIL/IR/TypeLowering.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3015,6 +3015,10 @@ TypeConverter::checkForABIDifferences(SILModule &M,
30153015
// trivial.
30163016
if (auto fnTy1 = type1.getAs<SILFunctionType>()) {
30173017
if (auto fnTy2 = type2.getAs<SILFunctionType>()) {
3018+
// Async/synchronous conversions always need a thunk.
3019+
if (fnTy1->isAsync() != fnTy2->isAsync())
3020+
return ABIDifference::NeedsThunk;
3021+
30183022
// @convention(block) is a single retainable pointer so optionality
30193023
// change is allowed.
30203024
if (optionalityChange)

lib/Sema/CSDiagnostics.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5762,6 +5762,12 @@ bool ThrowingFunctionConversionFailure::diagnoseAsError() {
57625762
return true;
57635763
}
57645764

5765+
bool AsyncFunctionConversionFailure::diagnoseAsError() {
5766+
emitDiagnostic(diag::async_functiontype_mismatch, getFromType(),
5767+
getToType());
5768+
return true;
5769+
}
5770+
57655771
bool InOutConversionFailure::diagnoseAsError() {
57665772
auto *locator = getLocator();
57675773
auto path = locator->getPath();

lib/Sema/CSDiagnostics.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,27 @@ class ThrowingFunctionConversionFailure final : public ContextualFailure {
809809
bool diagnoseAsError() override;
810810
};
811811

812+
/// Diagnose failures related to conversion between 'async' function type
813+
/// and a synchronous one e.g.
814+
///
815+
/// ```swift
816+
/// func foo<T>(_ t: T) async -> Void {}
817+
/// let _: (Int) -> Void = foo // `foo` can't be implictly converted to
818+
/// // synchronous function type `(Int) -> Void`
819+
/// ```
820+
class AsyncFunctionConversionFailure final : public ContextualFailure {
821+
public:
822+
AsyncFunctionConversionFailure(const Solution &solution, Type fromType,
823+
Type toType, ConstraintLocator *locator)
824+
: ContextualFailure(solution, fromType, toType, locator) {
825+
auto fnType1 = fromType->castTo<FunctionType>();
826+
auto fnType2 = toType->castTo<FunctionType>();
827+
assert(fnType1->isAsync() != fnType2->isAsync());
828+
}
829+
830+
bool diagnoseAsError() override;
831+
};
832+
812833
/// Diagnose failures related attempt to implicitly convert types which
813834
/// do not support such implicit converstion.
814835
/// "as" or "as!" has to be specified explicitly in cases like that.

lib/Sema/CSFix.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,21 @@ DropThrowsAttribute *DropThrowsAttribute::create(ConstraintSystem &cs,
11401140
DropThrowsAttribute(cs, fromType, toType, locator);
11411141
}
11421142

1143+
bool DropAsyncAttribute::diagnose(const Solution &solution,
1144+
bool asNote) const {
1145+
AsyncFunctionConversionFailure failure(solution, getFromType(),
1146+
getToType(), getLocator());
1147+
return failure.diagnose(asNote);
1148+
}
1149+
1150+
DropAsyncAttribute *DropAsyncAttribute::create(ConstraintSystem &cs,
1151+
FunctionType *fromType,
1152+
FunctionType *toType,
1153+
ConstraintLocator *locator) {
1154+
return new (cs.getAllocator())
1155+
DropAsyncAttribute(cs, fromType, toType, locator);
1156+
}
1157+
11431158
bool IgnoreContextualType::diagnose(const Solution &solution,
11441159
bool asNote) const {
11451160
ContextualFailure failure(solution, getFromType(), getToType(), getLocator());

lib/Sema/CSSimplify.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,9 +1873,19 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
18731873
}
18741874
}
18751875

1876-
// 'async' and non-'async' function types are not compatible.
1877-
if (func1->isAsync() != func2->isAsync())
1878-
return getTypeMatchFailure(locator);
1876+
// A synchronous function can be a subtype of an 'async' function.
1877+
if (func1->isAsync() != func2->isAsync()) {
1878+
// Cannot drop 'async'.
1879+
if (func1->isAsync() || kind < ConstraintKind::Subtype) {
1880+
if (!shouldAttemptFixes())
1881+
return getTypeMatchFailure(locator);
1882+
1883+
auto *fix = DropAsyncAttribute::create(*this, func1, func2,
1884+
getConstraintLocator(locator));
1885+
if (recordFix(fix))
1886+
return getTypeMatchFailure(locator);
1887+
}
1888+
}
18791889

18801890
// A non-@noescape function type can be a subtype of a @noescape function
18811891
// type.

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,8 +713,8 @@ swift::matchWitness(
713713
}
714714

715715
// If the witness is 'async', the requirement must be.
716-
if (witnessFnType->getExtInfo().isAsync() !=
717-
reqFnType->getExtInfo().isAsync()) {
716+
if (witnessFnType->getExtInfo().isAsync() &&
717+
!reqFnType->getExtInfo().isAsync()) {
718718
return RequirementMatch(witness, MatchKind::AsyncConflict);
719719
}
720720

test/Concurrency/async_tasks.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func buyVegetables(shoppingList: [String]) async throws -> [Vegetable] {
4343
func test_unsafeContinuations() async {
4444
// the closure should not allow async operations;
4545
// after all: if you have async code, just call it directly, without the unsafe continuation
46-
let _: String = Task.withUnsafeContinuation { continuation in // expected-error{{cannot convert value of type '(_) async -> ()' to expected argument type '(UnsafeContinuation<String>) -> Void'}}
46+
let _: String = Task.withUnsafeContinuation { continuation in // expected-error{{invalid conversion from 'async' function of type '(UnsafeContinuation<String>) async -> Void' to synchronous function type '(UnsafeContinuation<String>) -> Void'}}
4747
let s = await someAsyncFunc() // rdar://70610141 for getting a better error message here
4848
continuation.resume(returning: s)
4949
}

test/Constraints/async.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
func doAsynchronously() async { }
66
func doSynchronously() { }
77

8-
func testNonConversions() async {
9-
let _: () -> Void = doAsynchronously // expected-error{{cannot convert value of type '() async -> ()' to specified type '() -> Void'}}
10-
let _: () async -> Void = doSynchronously // expected-error{{cannot convert value of type '() -> ()' to specified type '() async -> Void'}}
8+
func testConversions() async {
9+
let _: () -> Void = doAsynchronously // expected-error{{invalid conversion from 'async' function of type '() async -> ()' to synchronous function type '() -> Void'}}
10+
let _: () async -> Void = doSynchronously // okay
1111
}
1212

1313
// Overloading
@@ -94,3 +94,24 @@ func testPassAsyncClosure() {
9494
let b = takesAsyncClosure { overloadedSame() } // expected-warning{{synchronous is no fun}}
9595
let _: Double = b // expected-error{{convert value of type 'String'}}
9696
}
97+
98+
struct FunctionTypes {
99+
var syncNonThrowing: () -> Void
100+
var syncThrowing: () throws -> Void
101+
var asyncNonThrowing: () async -> Void
102+
var asyncThrowing: () async throws -> Void
103+
104+
mutating func demonstrateConversions() {
105+
// Okay to add 'async' and/or 'throws'
106+
asyncNonThrowing = syncNonThrowing
107+
asyncThrowing = syncThrowing
108+
syncThrowing = syncNonThrowing
109+
asyncThrowing = asyncNonThrowing
110+
111+
// Error to remove 'async' or 'throws'
112+
syncNonThrowing = asyncNonThrowing // expected-error{{invalid conversion}}
113+
syncThrowing = asyncThrowing // expected-error{{invalid conversion}}
114+
syncNonThrowing = syncThrowing // expected-error{{invalid conversion}}
115+
asyncNonThrowing = syncThrowing // expected-error{{invalid conversion}}
116+
}
117+
}

test/SILGen/async_conversion.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -swift-version 5 -enable-experimental-concurrency | %FileCheck %s
2+
// REQUIRES: concurrency
3+
4+
func f(_: Int, _: String) -> String? { nil }
5+
6+
// CHECK-LABEL: sil hidden [ossa] @$s4testAAyyF : $@convention(thin) () -> () {
7+
func test() {
8+
// CHECK: [[F:%.*]] = function_ref @$s4test1fySSSgSi_SStF : $@convention(thin) (Int, @guaranteed String) -> @owned Optional<String>
9+
// CHECK: [[THICK_F:%.*]] = thin_to_thick_function [[F]] : $@convention(thin) (Int, @guaranteed String) -> @owned Optional<String> to $@callee_guaranteed (Int, @guaranteed String) -> @owned Optional<String>
10+
// CHECK: [[THUNK:%.*]] = function_ref @$sSiS2SSgIegygo_SiSSAAIegHygo_TR : $@convention(thin) @async (Int, @guaranteed String, @guaranteed @callee_guaranteed (Int, @guaranteed String) -> @owned Optional<String>) -> @owned Optional<String>
11+
// CHECK: partial_apply [callee_guaranteed] [[THUNK]]([[THICK_F]]) : $@convention(thin) @async (Int, @guaranteed String, @guaranteed @callee_guaranteed (Int, @guaranteed String) -> @owned Optional<String>) -> @owned Optional<String>
12+
let _: (Int, String) async -> String? = f
13+
}
14+
15+
protocol P {
16+
func f(_: Int, _: String) async -> String?
17+
}
18+
19+
struct X: P {
20+
// CHECK-LABEL: sil private [transparent] [thunk] [ossa] @$s4test1XVAA1PA2aDP1fySSSgSi_SStYFTW : $@convention(witness_method: P) @async (Int, @guaranteed String, @in_guaranteed X) -> @owned Optional<String>
21+
// CHECK: function_ref @$s4test1XV1fySSSgSi_SStF : $@convention(method) (Int, @guaranteed String, X) -> @owned Optional<String>
22+
func f(_: Int, _: String) -> String? { nil }
23+
}

test/Serialization/async.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
import def_async
1010

1111
func testDoSomethingBig() {
12-
let _: () -> Int = doSomethingBig // expected-error{{cannot convert value of type '() async -> Int' to specified type '() -> Int'}}
12+
let _: () -> Int = doSomethingBig // expected-error{{invalid conversion from 'async' function of type '() async -> Int' to synchronous function type '() -> Int'}}
1313
}

test/decl/func/async.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ struct ConformsToP1: P1 { // expected-error{{type 'ConformsToP1' does not confor
3333
}
3434

3535
protocol P2 {
36-
func f() async // expected-note{{protocol requires function 'f()' with type '() async -> ()'; do you want to add a stub?}}
36+
func f() async
3737
}
3838

39-
struct ConformsToP2: P2 { // expected-error{{type 'ConformsToP2' does not conform to protocol 'P2'}}
40-
func f() { } // expected-note{{candidate is not 'async', but protocol requirement is}}
39+
struct ConformsToP2: P2 {
40+
func f() { } // okay
4141
}

test/expr/unary/async_await.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@ func testClosure() {
6565
await getInt()
6666
}
6767

68-
let _: () -> Int = closure // expected-error{{cannot convert value of type '() async -> Int' to specified type '() -> Int'}}
68+
let _: () -> Int = closure // expected-error{{invalid conversion from 'async' function of type '() async -> Int' to synchronous function type '() -> Int'}}
6969

7070
let closure2 = { () async -> Int in
7171
print("here")
7272
return await getInt()
7373
}
7474

75-
let _: () -> Int = closure2 // expected-error{{cannot convert value of type '() async -> Int' to specified type '() -> Int'}}
75+
let _: () -> Int = closure2 // expected-error{{invalid conversion from 'async' function of type '() async -> Int' to synchronous function type '() -> Int'}}
7676
}
7777

7878
// Nesting async and await together

0 commit comments

Comments
 (0)