Skip to content

Commit 9964b97

Browse files
authored
Merge pull request #35662 from kavon/objc-async-conformance-bugs
[concurrency] fixes for conformance to async ObjC protocol requirements
2 parents 9d982be + ff54f3b commit 9964b97

File tree

7 files changed

+130
-13
lines changed

7 files changed

+130
-13
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4169,8 +4169,8 @@ ERROR(actor_isolated_objc,none,
41694169
"actor-isolated %0 %1 cannot be @objc",
41704170
(DescriptiveDeclKind, DeclName))
41714171
NOTE(protocol_witness_async_conflict,none,
4172-
"candidate is %select{not |}0'async', but protocol requirement is%select{| not}0",
4173-
(bool))
4172+
"candidate is %select{not |}0'async', but%select{| @objc}1 protocol requirement is%select{| not}0",
4173+
(bool, bool))
41744174
ERROR(async_autoclosure_nonasync_function,none,
41754175
"'async' autoclosure parameter in a non-'async' function", ())
41764176

@@ -4218,9 +4218,6 @@ ERROR(objc_ambiguous_async_convention,none,
42184218
NOTE(objc_ambiguous_async_convention_candidate,none,
42194219
"%0 provides async here", (DeclName))
42204220

4221-
ERROR(satisfy_async_objc,none,
4222-
"satisfying an asychronous @objc %select{method|initializer}0 with "
4223-
"a synchronous %select{method|initializer}0 is not supported", (bool))
42244221
ERROR(async_objc_dynamic_self,none,
42254222
"asynchronous method returning 'Self' cannot be '@objc'", ())
42264223

lib/Sema/TypeCheckAttr.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,8 +1169,11 @@ void AttributeChecker::visitObjCAttr(ObjCAttr *attr) {
11691169
if (CD->isObjCZeroParameterWithLongSelector())
11701170
numParameters = 0; // Something like "init(foo: ())"
11711171

1172-
// A throwing method has an error parameter.
1173-
if (func->hasThrows())
1172+
// An async method, even if it is also 'throws', has
1173+
// one additional completion handler parameter in ObjC.
1174+
if (func->hasAsync())
1175+
++numParameters;
1176+
else if (func->hasThrows()) // A throwing method has an error parameter.
11741177
++numParameters;
11751178

11761179
unsigned numArgumentNames = objcName->getNumArgs();

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,11 +1795,8 @@ void markAsObjC(ValueDecl *D, ObjCReason reason,
17951795

17961796
// Attach the foreign async convention.
17971797
if (inheritedAsyncConvention) {
1798-
if (!method->hasAsync())
1799-
method->diagnose(diag::satisfy_async_objc,
1800-
isa<ConstructorDecl>(method));
1801-
else
1802-
method->setForeignAsyncConvention(*inheritedAsyncConvention);
1798+
assert(method->hasAsync() && "async objc req offered for sync witness?");
1799+
method->setForeignAsyncConvention(*inheritedAsyncConvention);
18031800

18041801
} else if (method->hasAsync()) {
18051802
assert(asyncConvention && "Missing async convention");

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,12 @@ swift::matchWitness(
731731
return RequirementMatch(witness, MatchKind::AsyncConflict);
732732
}
733733

734+
// If witness is sync, the requirement cannot be @objc and 'async'
735+
if (!witnessFnType->getExtInfo().isAsync() &&
736+
(req->isObjC() && reqFnType->getExtInfo().isAsync())) {
737+
return RequirementMatch(witness, MatchKind::AsyncConflict);
738+
}
739+
734740
// If the witness is 'throws', the requirement must be.
735741
if (witnessFnType->getExtInfo().isThrowing() &&
736742
!reqFnType->getExtInfo().isThrowing()) {
@@ -2381,7 +2387,8 @@ diagnoseMatch(ModuleDecl *module, NormalProtocolConformance *conformance,
23812387

23822388
case MatchKind::AsyncConflict:
23832389
diags.diagnose(match.Witness, diag::protocol_witness_async_conflict,
2384-
cast<AbstractFunctionDecl>(match.Witness)->hasAsync());
2390+
cast<AbstractFunctionDecl>(match.Witness)->hasAsync(),
2391+
req->isObjC());
23852392
break;
23862393

23872394
case MatchKind::ThrowsConflict:

test/ClangImporter/objc_async_conformance.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,57 @@ class C1: ConcurrentProtocol {
1111

1212
func askUser(toJumpThroughHoop hoop: String) async -> String { "hello" }
1313
}
14+
15+
// try to conform to an objc protocol that has both a sync and async requirement
16+
// that has the same name, and both requirements are optional.
17+
class C2 : NSObject, OptionalObserver {}
18+
extension C2 {
19+
func hello(_ session: NSObject) -> Bool { true }
20+
}
21+
22+
// a version of C2 that requires both sync and async methods (differing only by
23+
// completion handler) in ObjC, is not possible to conform to with 'async' in
24+
// a Swift protocol
25+
class C3 : NSObject, RequiredObserver {}
26+
extension C3 {
27+
func hello() -> Bool { true } // expected-note {{'hello()' previously declared here}}
28+
func hello() async -> Bool { true } // expected-error {{invalid redeclaration of 'hello()'}}
29+
}
30+
31+
// the only way to conform to 'RequiredObserver' in Swift is to not use 'async'
32+
class C4 : NSObject, RequiredObserver {}
33+
extension C4 {
34+
func hello() -> Bool { true }
35+
func hello(_ completion : @escaping (Bool) -> Void) -> Void { completion(true) }
36+
}
37+
38+
///////
39+
// selector conflicts
40+
41+
// attempting to satisfy the ObjC async requirement in two ways simultaenously
42+
// is problematic due to a clash in selector names on this ObjC-compatible type
43+
class SelectorConflict : NSObject, RequiredObserverOnlyCompletion {
44+
func hello() async -> Bool { true } // expected-note {{method 'hello()' declared here}}
45+
46+
// expected-error@+1 {{method 'hello' with Objective-C selector 'hello:' conflicts with method 'hello()' with the same Objective-C selector}}
47+
func hello(_ completion : @escaping (Bool) -> Void) -> Void { completion(true) }
48+
}
49+
50+
// making either one of the two methods nonobjc fixes it:
51+
class SelectorOK1 : NSObject, RequiredObserverOnlyCompletion {
52+
@nonobjc func hello() async -> Bool { true }
53+
func hello(_ completion : @escaping (Bool) -> Void) -> Void { completion(true) }
54+
}
55+
56+
class SelectorOK2 : NSObject, RequiredObserverOnlyCompletion {
57+
func hello() async -> Bool { true }
58+
@nonobjc func hello(_ completion : @escaping (Bool) -> Void) -> Void { completion(true) }
59+
}
60+
61+
// additional coverage for situation like C4, where the method names don't
62+
// clash on the ObjC side, but they do on Swift side, BUT their ObjC selectors
63+
// differ, so it's OK.
64+
class Rock : NSObject, Rollable {
65+
func roll(completionHandler: @escaping () -> Void) { completionHandler() }
66+
func roll() { roll(completionHandler: {}) }
67+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency
2+
// REQUIRES: objc_interop
3+
// REQUIRES: concurrency
4+
5+
import Foundation
6+
7+
// async objc requirement, sync witness
8+
@objc protocol Tracker {
9+
func track(event: String) async // expected-note {{protocol requires function 'track(event:)' with type '(String) async -> ()'; do you want to add a stub?}}
10+
}
11+
class Dog: NSObject, Tracker { // expected-error {{type 'Dog' does not conform to protocol 'Tracker'}}
12+
func track(event: String) {} // expected-note {{candidate is not 'async', but @objc protocol requirement is}}
13+
}
14+
15+
// sync objc requirement, async witness
16+
@objc protocol Runner {
17+
func run(event: String) // expected-note {{protocol requires function 'run(event:)' with type '(String) -> ()'; do you want to add a stub?}}
18+
}
19+
class Athlete: NSObject, Runner { // expected-error {{type 'Athlete' does not conform to protocol 'Runner'}}
20+
func run(event: String) async {} // expected-note {{candidate is 'async', but @objc protocol requirement is not}}
21+
}
22+
23+
24+
// async swift protocol, sync witness
25+
protocol Snacker {
26+
func snack(food: String) async
27+
}
28+
29+
class Foodie: Snacker {
30+
func snack(food: String) {}
31+
}
32+
33+
34+
// sync swift protocol, async witness
35+
protocol Backer {
36+
func back(stonk: String) // expected-note {{protocol requires function 'back(stonk:)' with type '(String) -> ()'; do you want to add a stub?}}
37+
}
38+
39+
class Investor: Backer { // expected-error {{type 'Investor' does not conform to protocol 'Backer'}}
40+
func back(stonk: String) async {} // expected-note {{candidate is 'async', but protocol requirement is not}}
41+
}

test/Inputs/clang-importer-sdk/usr/include/ObjCConcurrency.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,24 @@ typedef void (^CompletionHandler)(NSString * _Nullable, NSString * _Nullable_res
7777
-(void)missingAtAttributeMethod __attribute__((__swift_attr__("asyncHandler")));
7878
@end
7979

80+
@protocol OptionalObserver <NSObject>
81+
@optional
82+
- (void)hello:(NSObject *)session completion:(void (^)(BOOL answer))completion;
83+
- (BOOL)hello:(NSObject *)session;
84+
@end
85+
86+
@protocol RequiredObserverOnlyCompletion <NSObject>
87+
- (void)hello:(void (^)(BOOL answer))completion;
88+
@end
89+
90+
@protocol RequiredObserver <RequiredObserverOnlyCompletion>
91+
- (BOOL)hello;
92+
@end
93+
94+
@protocol Rollable <NSObject>
95+
- (void)rollWithCompletionHandler: (void (^)(void))completionHandler;
96+
@end
97+
8098
#define MAGIC_NUMBER 42
8199

82100
#pragma clang assume_nonnull end

0 commit comments

Comments
 (0)