Skip to content

Commit 47c6303

Browse files
committed
Revert "Increase determinism of selector conflict errors"
This reverts commit 7cbbc8e.
1 parent 854996a commit 47c6303

File tree

5 files changed

+106
-128
lines changed

5 files changed

+106
-128
lines changed

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 99 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,12 @@ namespace {
22572257
/// Produce a deterministic ordering of the given declarations.
22582258
struct OrderDeclarations {
22592259
bool operator()(ValueDecl *lhs, ValueDecl *rhs) const {
2260+
// If one declaration is imported from ObjC and the other is native Swift,
2261+
// put the imported Clang one first.
2262+
if (lhs->hasClangNode() != rhs->hasClangNode()) {
2263+
return lhs->hasClangNode();
2264+
}
2265+
22602266
// If the declarations come from different modules, order based on the
22612267
// module.
22622268
ModuleDecl *lhsModule = lhs->getDeclContext()->getParentModule();
@@ -2419,6 +2425,13 @@ bool swift::diagnoseUnintendedObjCMethodOverrides(SourceFile &sf) {
24192425
return diagnosedAny;
24202426
}
24212427

2428+
/// Retrieve the source file for the given Objective-C member conflict.
2429+
static TinyPtrVector<AbstractFunctionDecl *>
2430+
getObjCMethodConflictDecls(const SourceFile::ObjCMethodConflict &conflict) {
2431+
return conflict.typeDecl->lookupDirect(conflict.selector,
2432+
conflict.isInstanceMethod);
2433+
}
2434+
24222435
static ObjCAttr *getObjCAttrIfFromAccessNote(ValueDecl *VD) {
24232436
if (auto objc = VD->getAttrs().getAttribute<ObjCAttr>())
24242437
if (objc->getAddedByAccessNote())
@@ -2430,138 +2443,99 @@ static ObjCAttr *getObjCAttrIfFromAccessNote(ValueDecl *VD) {
24302443
return nullptr;
24312444
}
24322445

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-
25212446
bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {
25222447
// If there were no conflicts, we're done.
25232448
if (sf.ObjCMethodConflicts.empty())
25242449
return false;
25252450

25262451
auto &Ctx = sf.getASTContext();
25272452
DiagnosticStateRAII diagState(Ctx.Diags);
2453+
OrderDeclarations ordering;
25282454

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) {
2534-
auto methods = getObjCMethodConflictDecls(conflict);
2535-
if (methods.size() < 2)
2536-
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
2455+
// Sort the set of conflicts so we get a deterministic order for
2456+
// diagnostics. We use the first conflicting declaration in each set to
25422457
// 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-
});
2458+
llvm::SmallVector<SourceFile::ObjCMethodConflict, 4> localConflicts;
2459+
llvm::copy(sf.ObjCMethodConflicts, std::back_inserter(localConflicts));
2460+
std::sort(localConflicts.begin(), localConflicts.end(),
2461+
[&](const SourceFile::ObjCMethodConflict &lhs,
2462+
const SourceFile::ObjCMethodConflict &rhs) {
2463+
return ordering(getObjCMethodConflictDecls(lhs)[1],
2464+
getObjCMethodConflictDecls(rhs)[1]);
2465+
});
25482466

25492467
// Diagnose each conflict.
25502468
bool anyConflicts = false;
2551-
for (const auto &conflictSet : conflictSets) {
2469+
for (const auto &conflict : localConflicts) {
2470+
auto methods = getObjCMethodConflictDecls(conflict);
2471+
2472+
// Erase any invalid or stub declarations. We don't want to complain about
2473+
// them, because we might already have complained about redeclarations
2474+
// based on Swift matching.
2475+
llvm::erase_if(methods, [](AbstractFunctionDecl *afd) -> bool {
2476+
if (afd->isInvalid())
2477+
return true;
2478+
2479+
if (auto ad = dyn_cast<AccessorDecl>(afd))
2480+
return ad->getStorage()->isInvalid();
2481+
2482+
if (auto *ctor = dyn_cast<ConstructorDecl>(afd)) {
2483+
if (ctor->hasStubImplementation())
2484+
return true;
2485+
}
2486+
return false;
2487+
});
2488+
2489+
if (methods.size() < 2)
2490+
continue;
2491+
25522492
// Diagnose the conflict.
25532493
anyConflicts = true;
2554-
2555-
const auto &conflict = conflictSet.first;
2556-
const auto &methods = conflictSet.second;
25572494

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

2531+
MutableArrayRef<AbstractFunctionDecl *> methodsRef(methods);
2532+
if (areBackwards(methods[0], methods[1]))
2533+
std::swap(methodsRef[0], methodsRef[1]);
2534+
2535+
auto originalMethod = methods.front();
25642536
auto conflictingMethods = methodsRef.slice(1);
2537+
2538+
auto origDiagInfo = getObjCMethodDiagInfo(originalMethod);
25652539
for (auto conflictingDecl : conflictingMethods) {
25662540
auto diagInfo = getObjCMethodDiagInfo(conflictingDecl);
25672541

@@ -2570,13 +2544,17 @@ bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {
25702544
if (auto accessor = dyn_cast<AccessorDecl>(originalMethod))
25712545
originalDecl = accessor->getStorage();
25722546

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:
2547+
// Conflicts between two imported ObjC methods are caused by the same
2548+
// clang decl having several Swift names.
2549+
if (originalDecl->hasClangNode() && conflictingDecl->hasClangNode())
2550+
continue;
25752551

2576-
// * Selectors for imported methods with async variants.
2577-
bool breakingInSwift5 = originalIsImportedAsync;
2578-
2579-
// * Protocol requirements
2552+
bool breakingInSwift5 = false;
2553+
// Imported ObjC async methods weren't fully checked for selector
2554+
// conflicts until 5.7.
2555+
if (originalMethod->hasClangNode() && originalMethod->hasAsync())
2556+
breakingInSwift5 = true;
2557+
// Protocols weren't checked for selector conflicts in 5.0.
25802558
if (!isa<ClassDecl>(conflict.typeDecl))
25812559
breakingInSwift5 = true;
25822560

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-note{{method 'method2()' declared here}}
8+
@objc func method2() { } // expected-error{{method 'method2()' with Objective-C selector 'method2' conflicts with method 'method2_alias()' with the same Objective-C selector}}
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-error{{setter for 'method1_var_alias' with Objective-C selector 'method2:' conflicts with method 'method2' with the same Objective-C selector}}
29+
@objc(method2:) set { } // expected-note{{setter for 'method1_var_alias' declared here}}
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-note{{method 'method2' declared here}}
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}}
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-error{{method 'method2_alias()' with Objective-C selector 'method2' conflicts with method 'method2()' with the same Objective-C selector}}
19+
@objc(method2) func method2_alias() { } // expected-note{{method 'method2_alias()' declared here}}
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-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}}
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}}
267267
}

0 commit comments

Comments
 (0)