Skip to content

[Concurrency] Allow synchronous functions to be a subtype of 'async' functions #34952

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,10 @@ ERROR(throws_functiontype_mismatch,none,
"invalid conversion from throwing function of type %0 to "
"non-throwing function type %1", (Type, Type))

ERROR(async_functiontype_mismatch,none,
"invalid conversion from 'async' function of type %0 to "
"synchronous function type %1", (Type, Type))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"of type" maybe?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I sorts like it this way? We trying to convert to a synchronous function type


// Key-path expressions.
ERROR(expr_keypath_no_objc_runtime,none,
"'#keyPath' can only be used with the Objective-C runtime", ())
Expand Down
20 changes: 20 additions & 0 deletions include/swift/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,26 @@ class DropThrowsAttribute final : public ContextualMismatch {
ConstraintLocator *locator);
};

/// This is a contextual mismatch between async and non-async
/// function types, repair it by dropping `async` attribute.
class DropAsyncAttribute final : public ContextualMismatch {
DropAsyncAttribute(ConstraintSystem &cs, FunctionType *fromType,
FunctionType *toType, ConstraintLocator *locator)
: ContextualMismatch(cs, fromType, toType, locator) {
assert(fromType->isAsync() != toType->isAsync());
}

public:
std::string getName() const override { return "drop 'async' attribute"; }

bool diagnose(const Solution &solution, bool asNote = false) const override;

static DropAsyncAttribute *create(ConstraintSystem &cs,
FunctionType *fromType,
FunctionType *toType,
ConstraintLocator *locator);
};

/// Append 'as! T' to force a downcast to the specified type.
class ForceDowncast final : public ContextualMismatch {
ForceDowncast(ConstraintSystem &cs, Type fromType, Type toType,
Expand Down
4 changes: 4 additions & 0 deletions lib/SIL/IR/TypeLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3014,6 +3014,10 @@ TypeConverter::checkForABIDifferences(SILModule &M,
// trivial.
if (auto fnTy1 = type1.getAs<SILFunctionType>()) {
if (auto fnTy2 = type2.getAs<SILFunctionType>()) {
// Async/synchronous conversions always need a thunk.
if (fnTy1->isAsync() != fnTy2->isAsync())
return ABIDifference::NeedsThunk;

// @convention(block) is a single retainable pointer so optionality
// change is allowed.
if (optionalityChange)
Expand Down
6 changes: 6 additions & 0 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5762,6 +5762,12 @@ bool ThrowingFunctionConversionFailure::diagnoseAsError() {
return true;
}

bool AsyncFunctionConversionFailure::diagnoseAsError() {
emitDiagnostic(diag::async_functiontype_mismatch, getFromType(),
getToType());
return true;
}

bool InOutConversionFailure::diagnoseAsError() {
auto *locator = getLocator();
auto path = locator->getPath();
Expand Down
21 changes: 21 additions & 0 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,27 @@ class ThrowingFunctionConversionFailure final : public ContextualFailure {
bool diagnoseAsError() override;
};

/// Diagnose failures related to conversion between 'async' function type
/// and a synchronous one e.g.
///
/// ```swift
/// func foo<T>(_ t: T) async -> Void {}
/// let _: (Int) -> Void = foo // `foo` can't be implictly converted to
/// // synchronous function type `(Int) -> Void`
/// ```
class AsyncFunctionConversionFailure final : public ContextualFailure {
public:
AsyncFunctionConversionFailure(const Solution &solution, Type fromType,
Type toType, ConstraintLocator *locator)
: ContextualFailure(solution, fromType, toType, locator) {
auto fnType1 = fromType->castTo<FunctionType>();
auto fnType2 = toType->castTo<FunctionType>();
assert(fnType1->isAsync() != fnType2->isAsync());
}

bool diagnoseAsError() override;
};

/// Diagnose failures related attempt to implicitly convert types which
/// do not support such implicit converstion.
/// "as" or "as!" has to be specified explicitly in cases like that.
Expand Down
15 changes: 15 additions & 0 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,21 @@ DropThrowsAttribute *DropThrowsAttribute::create(ConstraintSystem &cs,
DropThrowsAttribute(cs, fromType, toType, locator);
}

bool DropAsyncAttribute::diagnose(const Solution &solution,
bool asNote) const {
AsyncFunctionConversionFailure failure(solution, getFromType(),
getToType(), getLocator());
return failure.diagnose(asNote);
}

DropAsyncAttribute *DropAsyncAttribute::create(ConstraintSystem &cs,
FunctionType *fromType,
FunctionType *toType,
ConstraintLocator *locator) {
return new (cs.getAllocator())
DropAsyncAttribute(cs, fromType, toType, locator);
}

bool IgnoreContextualType::diagnose(const Solution &solution,
bool asNote) const {
ContextualFailure failure(solution, getFromType(), getToType(), getLocator());
Expand Down
16 changes: 13 additions & 3 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1873,9 +1873,19 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
}
}

// 'async' and non-'async' function types are not compatible.
if (func1->isAsync() != func2->isAsync())
return getTypeMatchFailure(locator);
// A synchronous function can be a subtype of an 'async' function.
if (func1->isAsync() != func2->isAsync()) {
// Cannot drop 'async'.
if (func1->isAsync() || kind < ConstraintKind::Subtype) {
if (!shouldAttemptFixes())
return getTypeMatchFailure(locator);

auto *fix = DropAsyncAttribute::create(*this, func1, func2,
getConstraintLocator(locator));
if (recordFix(fix))
return getTypeMatchFailure(locator);
}
}

// A non-@noescape function type can be a subtype of a @noescape function
// type.
Expand Down
4 changes: 2 additions & 2 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -713,8 +713,8 @@ swift::matchWitness(
}

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

Expand Down
2 changes: 1 addition & 1 deletion test/Concurrency/async_tasks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func buyVegetables(shoppingList: [String]) async throws -> [Vegetable] {
func test_unsafeContinuations() async {
// the closure should not allow async operations;
// after all: if you have async code, just call it directly, without the unsafe continuation
let _: String = Task.withUnsafeContinuation { continuation in // expected-error{{cannot convert value of type '(_) async -> ()' to expected argument type '(UnsafeContinuation<String>) -> Void'}}
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'}}
let s = await someAsyncFunc() // rdar://70610141 for getting a better error message here
continuation.resume(returning: s)
}
Expand Down
27 changes: 24 additions & 3 deletions test/Constraints/async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
func doAsynchronously() async { }
func doSynchronously() { }

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

// Overloading
Expand Down Expand Up @@ -94,3 +94,24 @@ func testPassAsyncClosure() {
let b = takesAsyncClosure { overloadedSame() } // expected-warning{{synchronous is no fun}}
let _: Double = b // expected-error{{convert value of type 'String'}}
}

struct FunctionTypes {
var syncNonThrowing: () -> Void
var syncThrowing: () throws -> Void
var asyncNonThrowing: () async -> Void
var asyncThrowing: () async throws -> Void

mutating func demonstrateConversions() {
// Okay to add 'async' and/or 'throws'
asyncNonThrowing = syncNonThrowing
asyncThrowing = syncThrowing
syncThrowing = syncNonThrowing
asyncThrowing = asyncNonThrowing

// Error to remove 'async' or 'throws'
syncNonThrowing = asyncNonThrowing // expected-error{{invalid conversion}}
syncThrowing = asyncThrowing // expected-error{{invalid conversion}}
syncNonThrowing = syncThrowing // expected-error{{invalid conversion}}
asyncNonThrowing = syncThrowing // expected-error{{invalid conversion}}
}
}
23 changes: 23 additions & 0 deletions test/SILGen/async_conversion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -swift-version 5 -enable-experimental-concurrency | %FileCheck %s
// REQUIRES: concurrency

func f(_: Int, _: String) -> String? { nil }

// CHECK-LABEL: sil hidden [ossa] @$s4testAAyyF : $@convention(thin) () -> () {
func test() {
// CHECK: [[F:%.*]] = function_ref @$s4test1fySSSgSi_SStF : $@convention(thin) (Int, @guaranteed String) -> @owned Optional<String>
// 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>
// 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>
// 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>
let _: (Int, String) async -> String? = f
}

protocol P {
func f(_: Int, _: String) async -> String?
}

struct X: P {
// CHECK-LABEL: sil private [transparent] [thunk] [ossa] @$s4test1XVAA1PA2aDP1fySSSgSi_SStYFTW : $@convention(witness_method: P) @async (Int, @guaranteed String, @in_guaranteed X) -> @owned Optional<String>
// CHECK: function_ref @$s4test1XV1fySSSgSi_SStF : $@convention(method) (Int, @guaranteed String, X) -> @owned Optional<String>
func f(_: Int, _: String) -> String? { nil }
}
2 changes: 1 addition & 1 deletion test/Serialization/async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
import def_async

func testDoSomethingBig() {
let _: () -> Int = doSomethingBig // expected-error{{cannot convert value of type '() async -> Int' to specified type '() -> Int'}}
let _: () -> Int = doSomethingBig // expected-error{{invalid conversion from 'async' function of type '() async -> Int' to synchronous function type '() -> Int'}}
}
6 changes: 3 additions & 3 deletions test/decl/func/async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ struct ConformsToP1: P1 { // expected-error{{type 'ConformsToP1' does not confor
}

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

struct ConformsToP2: P2 { // expected-error{{type 'ConformsToP2' does not conform to protocol 'P2'}}
func f() { } // expected-note{{candidate is not 'async', but protocol requirement is}}
struct ConformsToP2: P2 {
func f() { } // okay
}
4 changes: 2 additions & 2 deletions test/expr/unary/async_await.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ func testClosure() {
await getInt()
}

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

let closure2 = { () async -> Int in
print("here")
return await getInt()
}

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

// Nesting async and await together
Expand Down