Skip to content

Commit 79e2697

Browse files
committed
Handle async vs. completion-handler mismatches in @_objcImplementation.
Fixes rdar://108160837.
1 parent 9436899 commit 79e2697

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

Diff for: lib/Sema/TypeCheckDeclObjC.cpp

+69-4
Original file line numberDiff line numberDiff line change
@@ -2882,6 +2882,15 @@ class ObjCImplementationChecker {
28822882
/// Candidates with their explicit ObjC names, if any.
28832883
llvm::SmallDenseMap<ValueDecl *, ObjCSelector, 16> unmatchedCandidates;
28842884

2885+
/// Key that can be used to uniquely identify a particular Objective-C
2886+
/// method.
2887+
using ObjCMethodKey = std::pair<ObjCSelector, char>;
2888+
2889+
/// Mapping from Objective-C methods to the set of requirements within this
2890+
/// protocol that have the same selector and instance/class designation.
2891+
llvm::SmallDenseMap<ObjCMethodKey, TinyPtrVector<AbstractFunctionDecl *>, 4>
2892+
objcMethodRequirements;
2893+
28852894
public:
28862895
ObjCImplementationChecker(ExtensionDecl *ext)
28872896
: diags(ext->getASTContext().Diags)
@@ -2903,6 +2912,10 @@ class ObjCImplementationChecker {
29032912
}
29042913

29052914
private:
2915+
auto getObjCMethodKey(AbstractFunctionDecl *func) const -> ObjCMethodKey {
2916+
return ObjCMethodKey(func->getObjCSelector(), func->isInstanceMember());
2917+
}
2918+
29062919
void addRequirements(IterableDeclContext *idc) {
29072920
assert(idc->getDecl()->hasClangNode());
29082921
for (Decl *_member : idc->getMembers()) {
@@ -2918,6 +2931,10 @@ class ObjCImplementationChecker {
29182931

29192932
auto inserted = unmatchedRequirements.insert(member);
29202933
assert(inserted && "objc interface member added twice?");
2934+
2935+
if (auto func = dyn_cast<AbstractFunctionDecl>(member)) {
2936+
objcMethodRequirements[getObjCMethodKey(func)].push_back(func);
2937+
}
29212938
}
29222939
}
29232940

@@ -3011,6 +3028,19 @@ class ObjCImplementationChecker {
30113028
}
30123029
};
30133030

3031+
/// Determine whether the set of matched requirements are ambiguous for the
3032+
/// given candidate.
3033+
bool areRequirementsAmbiguous(const BestMatchList &reqs, ValueDecl *cand) {
3034+
if (reqs.matches.size() != 2)
3035+
return reqs.matches.size() > 2;
3036+
3037+
bool firstIsAsyncAlternative =
3038+
matchesAsyncAlternative(reqs.matches[0], cand);
3039+
bool secondIsAsyncAlternative =
3040+
matchesAsyncAlternative(reqs.matches[1], cand);
3041+
return firstIsAsyncAlternative == secondIsAsyncAlternative;
3042+
}
3043+
30143044
void matchRequirementsAtThreshold(MatchOutcome threshold) {
30153045
SmallString<32> scratch;
30163046

@@ -3070,11 +3100,14 @@ class ObjCImplementationChecker {
30703100
// removing them.
30713101
requirementsToRemove.set_union(matchedRequirements.matches);
30723102

3073-
if (matchedRequirements.matches.size() == 1) {
3103+
if (!areRequirementsAmbiguous(matchedRequirements, cand)) {
30743104
// Note that this is BestMatchList::insert(), so it'll only keep the
30753105
// matches with the best outcomes.
3076-
matchesByRequirement[matchedRequirements.matches.front()]
3077-
.insert(cand, matchedRequirements.currentOutcome);
3106+
for (auto req : matchedRequirements.matches) {
3107+
matchesByRequirement[req]
3108+
.insert(cand, matchedRequirements.currentOutcome);
3109+
}
3110+
30783111
continue;
30793112
}
30803113

@@ -3156,6 +3189,35 @@ class ObjCImplementationChecker {
31563189
unmatchedCandidates.erase(cand);
31573190
}
31583191

3192+
/// Whether the candidate matches the async alternative of the given
3193+
/// requirement.
3194+
bool matchesAsyncAlternative(ValueDecl *req, ValueDecl *cand) const {
3195+
auto reqFunc = dyn_cast<AbstractFunctionDecl>(req);
3196+
if (!reqFunc)
3197+
return false;
3198+
3199+
auto candFunc = dyn_cast<AbstractFunctionDecl>(cand);
3200+
if (!candFunc)
3201+
return false;
3202+
3203+
if (reqFunc->hasAsync() == candFunc->hasAsync())
3204+
return false;
3205+
3206+
auto otherReqFuncs =
3207+
objcMethodRequirements.find(getObjCMethodKey(reqFunc));
3208+
if (otherReqFuncs == objcMethodRequirements.end())
3209+
return false;
3210+
3211+
for (auto otherReqFunc : otherReqFuncs->second) {
3212+
if (otherReqFunc->getName() == cand->getName() &&
3213+
otherReqFunc->hasAsync() == candFunc->hasAsync() &&
3214+
req->getObjCRuntimeName() == cand->getObjCRuntimeName())
3215+
return true;
3216+
}
3217+
3218+
return false;
3219+
}
3220+
31593221
MatchOutcome matches(ValueDecl *req, ValueDecl *cand,
31603222
ObjCSelector explicitObjCName) const {
31613223
bool hasObjCNameMatch =
@@ -3173,7 +3235,10 @@ class ObjCImplementationChecker {
31733235
&& req->getObjCRuntimeName() != explicitObjCName)
31743236
return MatchOutcome::WrongExplicitObjCName;
31753237

3176-
if (!hasSwiftNameMatch)
3238+
// If the ObjC selectors matched but the Swift names do not, and these are
3239+
// functions with mismatched 'async', check whether the "other" requirement
3240+
// (the completion-handler or async version)'s Swift name matches.
3241+
if (!hasSwiftNameMatch && !matchesAsyncAlternative(req, cand))
31773242
return MatchOutcome::WrongSwiftName;
31783243

31793244
if (!hasObjCNameMatch)

Diff for: test/decl/ext/Inputs/objc_implementation.h

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@import Foundation;
2+
13
@interface ObjCBaseClass
24

35

@@ -83,6 +85,12 @@
8385

8486
@end
8587

88+
@interface ObjCClass (Effects)
89+
- (void)doSomethingAsynchronousWithCompletionHandler:(void (^ _Nonnull)(id _Nullable result, NSError * _Nullable error))completionHandler;
90+
- (void)doSomethingElseAsynchronousWithCompletionHandler:(void (^ _Nullable)(id _Nonnull result))completionHandler;
91+
- (void)doSomethingFunAndAsynchronousWithCompletionHandler:(void (^ _Nonnull)(id _Nullable result, NSError * _Nullable error))completionHandler;
92+
@end
93+
8694
@interface ObjCSubclass : ObjCClass
8795

8896
- (void)subclassMethodFromHeader1:(int)param;

Diff for: test/decl/ext/objc_implementation.swift

+15
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,21 @@
276276
}
277277
}
278278

279+
@_objcImplementation(Effects) extension ObjCClass {
280+
@available(SwiftStdlib 5.1, *)
281+
public func doSomethingAsynchronous() async throws -> Any {
282+
return self
283+
}
284+
285+
@available(SwiftStdlib 5.1, *)
286+
public func doSomethingElseAsynchronous() async -> Any {
287+
return self
288+
}
289+
290+
public func doSomethingFunAndAsynchronous(completionHandler: @escaping (Any?, Error?) -> Void) {
291+
}
292+
}
293+
279294
@_objcImplementation extension ObjCClass {}
280295
// expected-error@-1 {{duplicate implementation of Objective-C class 'ObjCClass'}}
281296

0 commit comments

Comments
 (0)