Skip to content

Commit 4f731e5

Browse files
committed
Merge remote-tracking branch 'origin/main' into rebranch
2 parents 4d8f83a + cb1c6e4 commit 4f731e5

File tree

7 files changed

+146
-89
lines changed

7 files changed

+146
-89
lines changed

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 126 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2419,13 +2419,6 @@ bool swift::diagnoseUnintendedObjCMethodOverrides(SourceFile &sf) {
24192419
return diagnosedAny;
24202420
}
24212421

2422-
/// Retrieve the source file for the given Objective-C member conflict.
2423-
static TinyPtrVector<AbstractFunctionDecl *>
2424-
getObjCMethodConflictDecls(const SourceFile::ObjCMethodConflict &conflict) {
2425-
return conflict.typeDecl->lookupDirect(conflict.selector,
2426-
conflict.isInstanceMethod);
2427-
}
2428-
24292422
static ObjCAttr *getObjCAttrIfFromAccessNote(ValueDecl *VD) {
24302423
if (auto objc = VD->getAttrs().getAttribute<ObjCAttr>())
24312424
if (objc->getAddedByAccessNote())
@@ -2437,95 +2430,138 @@ static ObjCAttr *getObjCAttrIfFromAccessNote(ValueDecl *VD) {
24372430
return nullptr;
24382431
}
24392432

2433+
static bool hasCustomObjCName(AbstractFunctionDecl *afd) {
2434+
if (auto objc = afd->getAttrs().getAttribute<ObjCAttr>())
2435+
return objc->hasName();
2436+
return false;
2437+
}
2438+
2439+
/// Retrieve the methods involved in a specific Objective-C selector
2440+
/// conflict. The list will be sorted so that the first method is the "best" one
2441+
/// and the others can be diagnosed as conflicts with that one.
2442+
static TinyPtrVector<AbstractFunctionDecl *>
2443+
getObjCMethodConflictDecls(const SourceFile::ObjCMethodConflict &conflict) {
2444+
// Look up all methods involved in the conflict.
2445+
auto methods = conflict.typeDecl->lookupDirect(conflict.selector,
2446+
conflict.isInstanceMethod);
2447+
2448+
// Erase any invalid or stub declarations. We don't want to complain about
2449+
// them, because we might already have complained about redeclarations
2450+
// based on Swift matching.
2451+
llvm::erase_if(methods, [](AbstractFunctionDecl *afd) -> bool {
2452+
if (afd->isInvalid())
2453+
return true;
2454+
2455+
if (auto ad = dyn_cast<AccessorDecl>(afd))
2456+
return ad->getStorage()->isInvalid();
2457+
2458+
if (auto *ctor = dyn_cast<ConstructorDecl>(afd)) {
2459+
if (ctor->hasStubImplementation())
2460+
return true;
2461+
}
2462+
return false;
2463+
});
2464+
2465+
// Sort the conflicting methods from the "strongest" claim to the "weakest".
2466+
// This puts the "best" method at methods.front() so that others will be
2467+
// diagnosed as conflicts with that one, and it helps ensure that individual
2468+
// methods in a conflict set are diagnosed in a deterministic order.
2469+
llvm::stable_sort(methods,
2470+
[](AbstractFunctionDecl *a, AbstractFunctionDecl *b) {
2471+
#define RULE(aCriterion, bCriterion) do { \
2472+
bool _aCriterion = (aCriterion), _bCriterion = (bCriterion); \
2473+
if (!_aCriterion && _bCriterion) \
2474+
return false; \
2475+
if (_aCriterion && !_bCriterion) \
2476+
return true; \
2477+
} while (0)
2478+
2479+
// Is one of these from Objective-C and the other from Swift?
2480+
// NOTE: Inserting another rule above this will break the hasClangNode()
2481+
// filtering below.
2482+
RULE(a->hasClangNode(),
2483+
b->hasClangNode());
2484+
2485+
// Is one of these async and the other not?
2486+
RULE(a->hasAsync(),
2487+
b->hasAsync());
2488+
2489+
// Is one of these explicit and the other from an access note?
2490+
RULE(!getObjCAttrIfFromAccessNote(a),
2491+
!getObjCAttrIfFromAccessNote(b));
2492+
2493+
// Is one of these from the main decl and the other an extension?
2494+
RULE(!isa<ExtensionDecl>(a->getDeclContext()),
2495+
!isa<ExtensionDecl>(b->getDeclContext()));
2496+
2497+
// Does one of these use plain @objc and the other @objc(selector)?
2498+
RULE(!hasCustomObjCName(a),
2499+
!hasCustomObjCName(b));
2500+
2501+
// Neither has a "stronger" claim, so just try to put them in some sort of
2502+
// consistent order.
2503+
OrderDeclarations ordering;
2504+
return ordering(a, b);
2505+
2506+
#undef RULE
2507+
});
2508+
2509+
// If the best method is imported from ObjC, eliminate any other imported ObjC
2510+
// methods. Selector conflicts between imported ObjC methods are spurious;
2511+
// they're just the same ObjC method being imported under different names with
2512+
// different ImportNameVersions.
2513+
if (!methods.empty() && methods.front()->hasClangNode())
2514+
llvm::erase_if(methods, [&](AbstractFunctionDecl *afd) {
2515+
return afd != methods.front() && afd->hasClangNode();
2516+
});
2517+
2518+
return methods;
2519+
}
2520+
24402521
bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {
24412522
// If there were no conflicts, we're done.
24422523
if (sf.ObjCMethodConflicts.empty())
24432524
return false;
24442525

24452526
auto &Ctx = sf.getASTContext();
24462527
DiagnosticStateRAII diagState(Ctx.Diags);
2447-
OrderDeclarations ordering;
24482528

2449-
// Sort the set of conflicts so we get a deterministic order for
2450-
// diagnostics. We use the first conflicting declaration in each set to
2451-
// perform the sort.
2452-
llvm::SmallVector<SourceFile::ObjCMethodConflict, 4> localConflicts;
2453-
llvm::copy(sf.ObjCMethodConflicts, std::back_inserter(localConflicts));
2454-
std::sort(localConflicts.begin(), localConflicts.end(),
2455-
[&](const SourceFile::ObjCMethodConflict &lhs,
2456-
const SourceFile::ObjCMethodConflict &rhs) {
2457-
return ordering(getObjCMethodConflictDecls(lhs)[1],
2458-
getObjCMethodConflictDecls(rhs)[1]);
2459-
});
2460-
2461-
// Diagnose each conflict.
2462-
bool anyConflicts = false;
2463-
for (const auto &conflict : localConflicts) {
2529+
// Build a list of all the conflicts and the methods involved in them.
2530+
using ConflictSet = std::pair<SourceFile::ObjCMethodConflict,
2531+
TinyPtrVector<AbstractFunctionDecl *>>;
2532+
llvm::SmallVector<ConflictSet, 4> conflictSets;
2533+
for (auto conflict : sf.ObjCMethodConflicts) {
24642534
auto methods = getObjCMethodConflictDecls(conflict);
2465-
2466-
// Erase any invalid or stub declarations. We don't want to complain about
2467-
// them, because we might already have complained about redeclarations
2468-
// based on Swift matching.
2469-
llvm::erase_if(methods, [](AbstractFunctionDecl *afd) -> bool {
2470-
if (afd->isInvalid())
2471-
return true;
2472-
2473-
if (auto ad = dyn_cast<AccessorDecl>(afd))
2474-
return ad->getStorage()->isInvalid();
2475-
2476-
if (auto *ctor = dyn_cast<ConstructorDecl>(afd)) {
2477-
if (ctor->hasStubImplementation())
2478-
return true;
2479-
}
2480-
return false;
2481-
});
2482-
24832535
if (methods.size() < 2)
24842536
continue;
2537+
conflictSets.emplace_back(conflict, methods);
2538+
}
2539+
2540+
// Sort the set of conflicts so the different conflict sets are diagnosed in
2541+
// the same order. We use the first conflicting declaration in each set to
2542+
// perform the sort.
2543+
llvm::stable_sort(conflictSets,
2544+
[](const ConflictSet &lhs, const ConflictSet &rhs) {
2545+
OrderDeclarations ordering;
2546+
return ordering(lhs.second[1], rhs.second[1]);
2547+
});
24852548

2549+
// Diagnose each conflict.
2550+
bool anyConflicts = false;
2551+
for (const auto &conflictSet : conflictSets) {
24862552
// Diagnose the conflict.
24872553
anyConflicts = true;
2554+
2555+
const auto &conflict = conflictSet.first;
2556+
const auto &methods = conflictSet.second;
24882557

2489-
/// If true, \p a has a "weaker" claim on the selector than \p b, and the
2490-
/// conflict diagnostic should appear on \p a instead of \p b.
2491-
auto areBackwards =
2492-
[&](AbstractFunctionDecl *a, AbstractFunctionDecl *b) -> bool {
2493-
#define RULE(aCriterion, bCriterion) do { \
2494-
bool _aCriterion = (aCriterion), _bCriterion = (bCriterion); \
2495-
if (!_aCriterion && _bCriterion) \
2496-
return true; \
2497-
if (_aCriterion && !_bCriterion) \
2498-
return false; \
2499-
} while (0)
2500-
2501-
// Is one of these from an access note?
2502-
RULE(getObjCAttrIfFromAccessNote(a),
2503-
getObjCAttrIfFromAccessNote(b));
2504-
2505-
// Is one of these from the main declaration?
2506-
RULE(!isa<ExtensionDecl>(a->getDeclContext()),
2507-
!isa<ExtensionDecl>(b->getDeclContext()));
2508-
2509-
// Are these from different source files? If so, fall back to the order in
2510-
// which the declarations were type checked.
2511-
// FIXME: This is gross and nondeterministic.
2512-
if (a->getParentSourceFile() != b->getParentSourceFile())
2513-
return false;
2514-
2515-
// Handle them in source order.
2516-
return !ordering(a, b);
2517-
2518-
#undef RULE
2519-
};
2520-
2521-
MutableArrayRef<AbstractFunctionDecl *> methodsRef(methods);
2522-
if (areBackwards(methods[0], methods[1]))
2523-
std::swap(methodsRef[0], methodsRef[1]);
2524-
2558+
ArrayRef<AbstractFunctionDecl *> methodsRef(methods);
25252559
auto originalMethod = methods.front();
2526-
auto conflictingMethods = methodsRef.slice(1);
2527-
25282560
auto origDiagInfo = getObjCMethodDiagInfo(originalMethod);
2561+
bool originalIsImportedAsync = originalMethod->hasClangNode() &&
2562+
originalMethod->hasAsync();
2563+
2564+
auto conflictingMethods = methodsRef.slice(1);
25292565
for (auto conflictingDecl : conflictingMethods) {
25302566
auto diagInfo = getObjCMethodDiagInfo(conflictingDecl);
25312567

@@ -2534,16 +2570,24 @@ bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {
25342570
if (auto accessor = dyn_cast<AccessorDecl>(originalMethod))
25352571
originalDecl = accessor->getStorage();
25362572

2573+
// In Swift 5.7, we discovered cases which inadvertently bypassed selector
2574+
// conflict checking and have to be diagnosed as warnings in Swift 5:
2575+
2576+
// * Selectors for imported methods with async variants.
2577+
bool breakingInSwift5 = originalIsImportedAsync;
2578+
2579+
// * Protocol requirements
2580+
if (!isa<ClassDecl>(conflict.typeDecl))
2581+
breakingInSwift5 = true;
2582+
25372583
bool redeclSame = (diagInfo == origDiagInfo);
25382584
auto diag = Ctx.Diags.diagnose(conflictingDecl,
25392585
redeclSame ? diag::objc_redecl_same
25402586
: diag::objc_redecl,
25412587
diagInfo.first, diagInfo.second,
25422588
origDiagInfo.first, origDiagInfo.second,
25432589
conflict.selector);
2544-
2545-
// Protocols weren't checked for selector conflicts in 5.0.
2546-
diag.warnUntilSwiftVersionIf(!isa<ClassDecl>(conflict.typeDecl), 6);
2590+
diag.warnUntilSwiftVersionIf(breakingInSwift5, 6);
25472591

25482592
auto objcAttr = getObjCAttrIfFromAccessNote(conflictingDecl);
25492593
swift::softenIfAccessNote(conflictingDecl, objcAttr, diag);

test/Concurrency/Inputs/Delegate.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,10 @@
2121
-(void)myAsyncMethod:(void (^ _Nullable)(NSError * _Nullable, NSString * _Nullable))completionHandler;
2222
@end
2323

24+
@interface Delegate (SwiftImpls)
25+
26+
- (void)makeRequestFromSwift:(Request * _Nonnull)request completionHandler:(void (^ _Nullable)(void))handler;
27+
28+
@end
29+
2430
#endif

test/Concurrency/objc_async_overload.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,10 @@ extension Delegate {
5353
}
5454
}
5555
}
56+
57+
// rdar://95887113 - Implementing an ObjC category method in Swift is not strictly valid, but should be tolerated
58+
59+
extension Delegate {
60+
@objc public func makeRequest(fromSwift: Request, completionHandler: (() -> Void)?) {}
61+
// expected-warning@-1 {{method 'makeRequest(fromSwift:completionHandler:)' with Objective-C selector 'makeRequestFromSwift:completionHandler:' conflicts with method 'makeRequest(fromSwift:)' with the same Objective-C selector; this is an error in Swift 6}}
62+
}

test/decl/Inputs/objc_redeclaration_multi_2.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ extension Redecl1 {
55
@objc(init)
66
func initialize() { } // expected-error{{method 'initialize()' with Objective-C selector 'init' conflicts with initializer 'init()' with the same Objective-C selector}}
77

8-
@objc func method2() { } // expected-error{{method 'method2()' with Objective-C selector 'method2' conflicts with method 'method2_alias()' with the same Objective-C selector}}
8+
@objc func method2() { } // expected-note{{method 'method2()' declared here}}
99
}
1010

1111
@objc class Redecl2 {

test/decl/objc_redeclaration.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ extension Redecl1 {
2626
@objc var method1_var_alias: Int {
2727
@objc(method1) get { return 5 } // expected-error{{getter for 'method1_var_alias' with Objective-C selector 'method1' conflicts with method 'method1()' with the same Objective-C selector}}
2828

29-
@objc(method2:) set { } // expected-note{{setter for 'method1_var_alias' declared here}}
29+
@objc(method2:) set { } // expected-error{{setter for 'method1_var_alias' with Objective-C selector 'method2:' conflicts with method 'method2' with the same Objective-C selector}}
3030
}
3131

3232
@objc subscript (i: Int) -> Redecl1 {
@@ -37,7 +37,7 @@ extension Redecl1 {
3737

3838
extension Redecl1 {
3939
@objc
40-
func method2(_ x: Int) { } // expected-error{{method 'method2' with Objective-C selector 'method2:' conflicts with setter for 'method1_var_alias' with the same Objective-C selector}}
40+
func method2(_ x: Int) { } // expected-note{{method 'method2' declared here}}
4141

4242
@objc(objectAtIndexedSubscript:)
4343
func indexed(_ x: Int) { } // expected-error{{method 'indexed' with Objective-C selector 'objectAtIndexedSubscript:' conflicts with subscript getter with the same Objective-C selector}}

test/decl/objc_redeclaration_multi.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ extension Redecl2 {
1616
}
1717

1818
extension Redecl1 {
19-
@objc(method2) func method2_alias() { } // expected-note{{method 'method2_alias()' declared here}}
19+
@objc(method2) func method2_alias() { } // expected-error{{method 'method2_alias()' with Objective-C selector 'method2' conflicts with method 'method2()' with the same Objective-C selector}}
2020
}

test/decl/protocol/objc.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ class C8SubRW2: C8Base {
261261

262262
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
263263
@objc protocol P9 {
264-
@objc(custom:) func f(_: Any) // expected-note 2 {{method 'f' declared here}}
265-
@objc(custom:) func g(_: Any) // expected-warning {{method 'g' with Objective-C selector 'custom:' conflicts with method 'f' with the same Objective-C selector; this is an error in Swift 6}}
266-
@objc(custom:) func h() async // expected-warning {{method 'h()' with Objective-C selector 'custom:' conflicts with method 'f' with the same Objective-C selector; this is an error in Swift 6}}
264+
@objc(custom:) func f(_: Any) // expected-warning {{method 'f' with Objective-C selector 'custom:' conflicts with method 'h()' with the same Objective-C selector; this is an error in Swift 6}}
265+
@objc(custom:) func g(_: Any) // expected-warning {{method 'g' with Objective-C selector 'custom:' conflicts with method 'h()' with the same Objective-C selector; this is an error in Swift 6}}
266+
@objc(custom:) func h() async // expected-note 2 {{method 'h()' declared here}}
267267
}

0 commit comments

Comments
 (0)