Skip to content

Commit e04ec5a

Browse files
committed
Weaken some type checks for @preconcurrency decls
In an async context, trying to pass a non-`@Sendable` function to an `@Sendable` parameter or trying to assign a `@MainActor` method to a non-`@MainActor`-typed variable were hard errors. We now think that this a mistake for `@preconcurrency` APIs in Swift 5 mode, as it hinders retroactive adoption of `@Sendable` and `@MainActor` by libraries. This PR weakens these errors to warnings *only* when the decl which contains the attribute in its type signature is `@preconcurrency` and *only* when in Swift 5 mode (with or without -warn-concurrency). For non-`@preconcurrency` decls, it is still an error. Fixes <rdar://88703266>.
1 parent d7d3fe3 commit e04ec5a

File tree

4 files changed

+43
-9
lines changed

4 files changed

+43
-9
lines changed

lib/Sema/CSSimplify.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2259,6 +2259,20 @@ static bool fixExtraneousArguments(ConstraintSystem &cs,
22592259
/*impact=*/numExtraneous * 2);
22602260
}
22612261

2262+
bool hasPreconcurrencyCallee(ConstraintSystem *cs,
2263+
ConstraintLocatorBuilder locator) {
2264+
if (cs->getASTContext().isSwiftVersionAtLeast(6))
2265+
// Swift 6 mode does not reduce errors to warnings.
2266+
return false;
2267+
2268+
auto calleeLocator = cs->getCalleeLocator(cs->getConstraintLocator(locator));
2269+
auto calleeOverload = cs->findSelectedOverloadFor(calleeLocator);
2270+
if (!calleeOverload || !calleeOverload->choice.isDecl())
2271+
return false;
2272+
2273+
return calleeOverload->choice.getDecl()->preconcurrency();
2274+
}
2275+
22622276
ConstraintSystem::TypeMatchResult
22632277
ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
22642278
ConstraintKind kind, TypeMatchOptions flags,
@@ -2308,7 +2322,8 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
23082322

23092323
/// Whether to downgrade to a concurrency warning.
23102324
auto isConcurrencyWarning = [&] {
2311-
if (contextRequiresStrictConcurrencyChecking(DC, GetClosureType{*this}))
2325+
if (contextRequiresStrictConcurrencyChecking(DC, GetClosureType{*this})
2326+
&& !hasPreconcurrencyCallee(this, locator))
23122327
return false;
23132328

23142329
switch (kind) {

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,26 +1195,29 @@ void ConstraintSystem::print(raw_ostream &out) const {
11951195
out << choice.getBaseType()->getString(PO) << ".";
11961196
out << choice.getDecl()->getBaseName() << ": "
11971197
<< resolved.boundType->getString(PO) << " == "
1198-
<< resolved.openedType->getString(PO) << "\n";
1198+
<< resolved.openedType->getString(PO);
11991199
break;
12001200

12011201
case OverloadChoiceKind::KeyPathApplication:
12021202
out << "key path application root "
1203-
<< choice.getBaseType()->getString(PO) << "\n";
1203+
<< choice.getBaseType()->getString(PO);
12041204
break;
12051205

12061206
case OverloadChoiceKind::DynamicMemberLookup:
12071207
case OverloadChoiceKind::KeyPathDynamicMemberLookup:
1208-
out << "dynamic member lookup:"
1209-
<< choice.getBaseType()->getString(PO) << " name="
1210-
<< choice.getName() << "\n";
1208+
out << "dynamic member lookup: "
1209+
<< choice.getBaseType()->getString(PO) << " name="
1210+
<< choice.getName();
12111211
break;
12121212

12131213
case OverloadChoiceKind::TupleIndex:
12141214
out << "tuple " << choice.getBaseType()->getString(PO) << " index "
1215-
<< choice.getTupleIndex() << "\n";
1215+
<< choice.getTupleIndex();
12161216
break;
12171217
}
1218+
out << " for ";
1219+
elt.first->dump(&getASTContext().SourceMgr, out);
1220+
out << "\n";
12181221
}
12191222
out << "\n";
12201223
}

test/Concurrency/objc_async_overload.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,15 @@ func asyncWithAwait() async {
2929
await d.makeRequest2(r)
3030
await d.makeRequest3(r)
3131
}
32+
33+
// rdar://88703266 - Swift 5 mode should warn, not error, if an imported
34+
// completion handler's implicit `@Sendable` isn't respected.
35+
extension Delegate {
36+
nonisolated func makeRequest(_ req: Request??, completionHandler: (() -> Void)? = nil) {
37+
// expected-note@-1 {{parameter 'completionHandler' is implicitly non-sendable}}
38+
if let req = (req ?? nil) {
39+
makeRequest1(req, completionHandler: completionHandler)
40+
// expected-warning@-1 {{passing non-sendable parameter 'completionHandler' to function expecting a @Sendable closure}}
41+
}
42+
}
43+
}

test/Concurrency/predates_concurrency.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ struct X {
2222

2323
@MainActor func onMainActor() { }
2424

25-
func testInAsync(x: X) async {
25+
func testInAsync(x: X, plainClosure: () -> Void) async { // expected-note 2{{parameter 'plainClosure' is implicitly non-sendable}}
2626
let _: Int = unsafelySendableClosure // expected-error{{type '(@Sendable () -> Void) -> ()'}}
2727
let _: Int = unsafelyMainActorClosure // expected-error{{type '(@MainActor () -> Void) -> ()'}}
2828
let _: Int = unsafelyDoEverythingClosure // expected-error{{type '(@MainActor @Sendable () -> Void) -> ()'}}
@@ -35,6 +35,10 @@ func testInAsync(x: X) async {
3535

3636
let _: Int = x[{ onMainActor() }] // expected-error{{type '@Sendable () -> Void'}}
3737
let _: Int = X[statically: { onMainActor() }] // expected-error{{type '@Sendable () -> Void'}}
38+
39+
unsafelySendableClosure(plainClosure) // expected-warning {{passing non-sendable parameter 'plainClosure' to function expecting a @Sendable closure}}
40+
unsafelyMainActorClosure(plainClosure)
41+
unsafelyDoEverythingClosure(plainClosure) // expected-warning {{passing non-sendable parameter 'plainClosure' to function expecting a @Sendable closure}}
3842
}
3943

4044
func testElsewhere(x: X) {
@@ -86,7 +90,7 @@ func testCallsWithAsync() async {
8690
onMainActorAlways() // expected-error{{expression is 'async' but is not marked with 'await'}}
8791
// expected-note@-1{{calls to global function 'onMainActorAlways()' from outside of its actor context are implicitly asynchronous}}
8892

89-
let _: () -> Void = onMainActorAlways // expected-error{{converting function value of type '@MainActor () -> ()' to '() -> Void' loses global actor 'MainActor'}}
93+
let _: () -> Void = onMainActorAlways // expected-warning{{converting function value of type '@MainActor () -> ()' to '() -> Void' loses global actor 'MainActor'}}
9094

9195
let c = MyModelClass() // expected-error{{expression is 'async' but is not marked with 'await'}}
9296
// expected-note@-1{{calls to initializer 'init()' from outside of its actor context are implicitly asynchronous}}

0 commit comments

Comments
 (0)