Skip to content

Commit 50cddda

Browse files
committed
[Concurrency] Enable implicit conversion from synchronous -> asynchronous.
1 parent 1798e66 commit 50cddda

File tree

10 files changed

+107
-10
lines changed

10 files changed

+107
-10
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/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.

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/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/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)