Skip to content

Commit 87113d7

Browse files
authored
Merge pull request #37430 from xedin/rdar-77942193
[ConstraintSystem] Detect passing sync closure to async argument early
2 parents a6df5ac + ace4d3a commit 87113d7

File tree

2 files changed

+64
-1
lines changed

2 files changed

+64
-1
lines changed

lib/Sema/CSSimplify.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,31 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments(
14881488
}
14891489
}
14901490

1491+
// Detect that there is sync -> async mismatch early on for
1492+
// closure argument to avoid re-checking calls if there was
1493+
// an overload choice with synchronous parameter of the same
1494+
// shape e.g.
1495+
//
1496+
// func test(_: () -> Void) -> MyStruct {}
1497+
// func test(_: () async -> Void) -> MyStruct {}
1498+
//
1499+
// test({ ... }).<member>...
1500+
//
1501+
// Synchronous overload is always better in this case so there
1502+
// is no need to re-check follow-up `<member>`s and better
1503+
// to short-circuit this path early.
1504+
if (auto *fnType = paramTy->getAs<FunctionType>()) {
1505+
if (fnType->isAsync()) {
1506+
auto *typeVar = argTy->getAs<TypeVariableType>();
1507+
if (typeVar && typeVar->getImpl().isClosureType()) {
1508+
auto *locator = typeVar->getImpl().getLocator();
1509+
auto *closure = castToExpr<ClosureExpr>(locator->getAnchor());
1510+
if (!cs.getClosureType(closure)->isAsync())
1511+
cs.increaseScore(SK_SyncInAsync);
1512+
}
1513+
}
1514+
}
1515+
14911516
cs.addConstraint(
14921517
subKind, argTy, paramTy,
14931518
matchingAutoClosureResult
@@ -2037,7 +2062,20 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
20372062
return getTypeMatchFailure(locator);
20382063
}
20392064

2040-
increaseScore(SK_SyncInAsync);
2065+
bool forClosureInArgumentPosition = false;
2066+
if (auto last = locator.last()) {
2067+
forClosureInArgumentPosition =
2068+
last->is<LocatorPathElt::ApplyArgToParam>() &&
2069+
isa<ClosureExpr>(locator.trySimplifyToExpr());
2070+
}
2071+
2072+
// Since it's possible to infer `async` from the body of a
2073+
// closure, score for sync -> async mismatch is increased
2074+
// while solver is matching arguments to parameters to
2075+
// indicate than solution with such a mismatch is always
2076+
// worse than one with synchronous functions on both sides.
2077+
if (!forClosureInArgumentPosition)
2078+
increaseScore(SK_SyncInAsync);
20412079
}
20422080

20432081
// A @Sendable function can be a subtype of a non-@Sendable function.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %target-typecheck-verify-swift -debug-constraints 2>%t.err
2+
// RUN: %FileCheck %s < %t.err
3+
4+
// rdar://77942193 - adding async overload leads to expressions becoming "too complex"
5+
6+
struct Obj {
7+
func op<T>(_: T) {}
8+
func op(_: Int) {}
9+
}
10+
11+
// Three overloads of `filter_async` to avoid generic overload optimization
12+
13+
func filter_async<T>(fn1: () -> T) -> T { fn1() }
14+
func filter_async<T>(fn2: () async -> T) -> T { fatalError() }
15+
func filter_async(_: String) -> Void {}
16+
17+
var a: String? = nil
18+
19+
// CHECK: attempting disjunction choice $T0 bound to decl async_overload_filtering.(file).filter_async(fn2:)
20+
// CHECK-NEXT: overload set choice binding $T0 := {{.*}}
21+
// CHECK-NEXT: increasing score due to sync-in-asynchronous
22+
// CHECK-NEXT: solution is worse than the best solution
23+
filter_async {
24+
Obj()
25+
}.op("" + (a ?? "a"))

0 commit comments

Comments
 (0)