Skip to content

Commit bb29c35

Browse files
authored
Merge pull request #41828 from beccadax/a-breach-of-protocol-can-cause-a-conflict
Check protocols for selector conflicts
2 parents aa00a58 + 06949a4 commit bb29c35

File tree

12 files changed

+97
-63
lines changed

12 files changed

+97
-63
lines changed

include/swift/AST/ASTContext.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -988,11 +988,11 @@ class ASTContext final {
988988
/// one.
989989
void loadExtensions(NominalTypeDecl *nominal, unsigned previousGeneration);
990990

991-
/// Load the methods within the given class that produce
991+
/// Load the methods within the given type that produce
992992
/// Objective-C class or instance methods with the given selector.
993993
///
994-
/// \param classDecl The class in which we are searching for @objc methods.
995-
/// The search only considers this class and its extensions; not any
994+
/// \param tyDecl The type in which we are searching for @objc methods.
995+
/// The search only considers this type and its extensions; not any
996996
/// superclasses.
997997
///
998998
/// \param selector The selector to search for.
@@ -1010,7 +1010,11 @@ class ASTContext final {
10101010
///
10111011
/// \param swiftOnly If true, only loads methods from imported Swift modules,
10121012
/// skipping the Clang importer.
1013-
void loadObjCMethods(ClassDecl *classDecl, ObjCSelector selector,
1013+
///
1014+
/// \note Passing a protocol is supported, but currently a no-op, because
1015+
/// Objective-C protocols cannot be extended in ways that make the ObjC method
1016+
/// lookup table relevant.
1017+
void loadObjCMethods(NominalTypeDecl *tyDecl, ObjCSelector selector,
10141018
bool isInstanceMethod, unsigned previousGeneration,
10151019
llvm::TinyPtrVector<AbstractFunctionDecl *> &methods,
10161020
bool swiftOnly = false);

include/swift/AST/Decl.h

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3277,6 +3277,7 @@ class AssociatedTypeDecl : public AbstractTypeParamDecl {
32773277
};
32783278

32793279
class MemberLookupTable;
3280+
class ObjCMethodLookupTable;
32803281
class ConformanceLookupTable;
32813282

32823283
// Kinds of pointer types.
@@ -3382,6 +3383,12 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
33823383
getSatisfiedProtocolRequirementsForMember(const ValueDecl *Member,
33833384
bool Sorted) const;
33843385

3386+
ObjCMethodLookupTable *ObjCMethodLookup = nullptr;
3387+
3388+
/// Create the Objective-C method lookup table, or return \c false if this
3389+
/// kind of type cannot have Objective-C methods.
3390+
bool createObjCMethodLookup();
3391+
33853392
friend class ASTContext;
33863393
friend class MemberLookupTable;
33873394
friend class ConformanceLookupTable;
@@ -3534,6 +3541,24 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
35343541

35353542
void setConformanceLoader(LazyMemberLoader *resolver, uint64_t contextData);
35363543

3544+
/// Look in this type and its extensions (but not any of its protocols or
3545+
/// superclasses) for declarations with a given Objective-C selector.
3546+
///
3547+
/// Note that this can find methods, initializers, deinitializers,
3548+
/// getters, and setters.
3549+
///
3550+
/// \param selector The Objective-C selector of the method we're
3551+
/// looking for.
3552+
///
3553+
/// \param isInstance Whether we are looking for an instance method
3554+
/// (vs. a class method).
3555+
TinyPtrVector<AbstractFunctionDecl *> lookupDirect(ObjCSelector selector,
3556+
bool isInstance);
3557+
3558+
/// Record the presence of an @objc method with the given selector. No-op if
3559+
/// the type is of a kind which cannot contain @objc methods.
3560+
void recordObjCMethod(AbstractFunctionDecl *method, ObjCSelector selector);
3561+
35373562
/// Is this the decl for Optional<T>?
35383563
bool isOptionalDecl() const;
35393564

@@ -3957,13 +3982,7 @@ using AncestryOptions = OptionSet<AncestryFlags>;
39573982
/// The type of the decl itself is a MetatypeType; use getDeclaredType()
39583983
/// to get the declared type ("Complex" in the above example).
39593984
class ClassDecl final : public NominalTypeDecl {
3960-
class ObjCMethodLookupTable;
3961-
39623985
SourceLoc ClassLoc;
3963-
ObjCMethodLookupTable *ObjCMethodLookup = nullptr;
3964-
3965-
/// Create the Objective-C member lookup table.
3966-
void createObjCMethodLookup();
39673986

39683987
struct {
39693988
/// The superclass decl and a bit to indicate whether the
@@ -4228,25 +4247,6 @@ class ClassDecl final : public NominalTypeDecl {
42284247
/// the Objective-C runtime.
42294248
StringRef getObjCRuntimeName(llvm::SmallVectorImpl<char> &buffer) const;
42304249

4231-
using NominalTypeDecl::lookupDirect;
4232-
4233-
/// Look in this class and its extensions (but not any of its protocols or
4234-
/// superclasses) for declarations with a given Objective-C selector.
4235-
///
4236-
/// Note that this can find methods, initializers, deinitializers,
4237-
/// getters, and setters.
4238-
///
4239-
/// \param selector The Objective-C selector of the method we're
4240-
/// looking for.
4241-
///
4242-
/// \param isInstance Whether we are looking for an instance method
4243-
/// (vs. a class method).
4244-
TinyPtrVector<AbstractFunctionDecl *> lookupDirect(ObjCSelector selector,
4245-
bool isInstance);
4246-
4247-
/// Record the presence of an @objc method with the given selector.
4248-
void recordObjCMethod(AbstractFunctionDecl *method, ObjCSelector selector);
4249-
42504250
// Implement isa/cast/dyncast/etc.
42514251
static bool classof(const Decl *D) {
42524252
return D->getKind() == DeclKind::Class;

include/swift/AST/SourceFile.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,8 @@ class SourceFile final : public FileUnit {
243243
std::vector<ObjCUnsatisfiedOptReq> ObjCUnsatisfiedOptReqs;
244244

245245
/// A selector that is used by two different declarations in the same class.
246-
/// Fields: classDecl, selector, isInstanceMethod.
247-
using ObjCMethodConflict = std::tuple<ClassDecl *, ObjCSelector, bool>;
246+
/// Fields: typeDecl, selector, isInstanceMethod.
247+
using ObjCMethodConflict = std::tuple<NominalTypeDecl *, ObjCSelector, bool>;
248248

249249
/// List of Objective-C member conflicts we have found during type checking.
250250
std::vector<ObjCMethodConflict> ObjCMethodConflicts;

lib/AST/ASTContext.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,11 +1906,24 @@ void ASTContext::loadExtensions(NominalTypeDecl *nominal,
19061906
}
19071907

19081908
void ASTContext::loadObjCMethods(
1909-
ClassDecl *classDecl, ObjCSelector selector, bool isInstanceMethod,
1909+
NominalTypeDecl *tyDecl, ObjCSelector selector, bool isInstanceMethod,
19101910
unsigned previousGeneration,
19111911
llvm::TinyPtrVector<AbstractFunctionDecl *> &methods, bool swiftOnly) {
19121912
PrettyStackTraceSelector stackTraceSelector("looking for", selector);
1913-
PrettyStackTraceDecl stackTraceDecl("...in", classDecl);
1913+
PrettyStackTraceDecl stackTraceDecl("...in", tyDecl);
1914+
1915+
// @objc protocols cannot have @objc extension members, so if we've recorded
1916+
// everything in the protocol definition, we've recorded everything. And we
1917+
// only ever use the ObjCSelector version of `NominalTypeDecl::lookupDirect()`
1918+
// on protocols in primary file typechecking, so we don't care about protocols
1919+
// that need to be loaded from files.
1920+
// TODO: Rework `ModuleLoader::loadObjCMethods()` to support protocols too if
1921+
// selector-based `NominalTypeDecl::lookupDirect()` ever needs to work
1922+
// in more situations.
1923+
ClassDecl *classDecl = dyn_cast<ClassDecl>(tyDecl);
1924+
if (!classDecl)
1925+
return;
1926+
19141927
for (auto &loader : getImpl().ModuleLoaders) {
19151928
// Ignore the Clang importer if we've been asked for Swift-only results.
19161929
if (swiftOnly && loader.get() == getClangModuleLoader())

lib/AST/NameLookup.cpp

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,10 +1125,10 @@ namespace {
11251125

11261126
/// Class member lookup table, which is a member lookup table with a second
11271127
/// table for lookup based on Objective-C selector.
1128-
class ClassDecl::ObjCMethodLookupTable
1128+
class swift::ObjCMethodLookupTable
11291129
: public llvm::DenseMap<std::pair<ObjCSelector, char>,
11301130
StoredObjCMethods>,
1131-
public ASTAllocated<ClassDecl::ObjCMethodLookupTable>
1131+
public ASTAllocated<ObjCMethodLookupTable>
11321132
{};
11331133

11341134
MemberLookupTable::MemberLookupTable(ASTContext &ctx) {
@@ -1483,23 +1483,27 @@ DirectLookupRequest::evaluate(Evaluator &evaluator,
14831483
includeAttrImplements);
14841484
}
14851485

1486-
void ClassDecl::createObjCMethodLookup() {
1486+
bool NominalTypeDecl::createObjCMethodLookup() {
14871487
assert(!ObjCMethodLookup && "Already have an Objective-C member table");
1488+
1489+
// Most types cannot have ObjC methods.
1490+
if (!(isa<ClassDecl>(this) || isa<ProtocolDecl>(this)))
1491+
return false;
1492+
14881493
auto &ctx = getASTContext();
14891494
ObjCMethodLookup = new (ctx) ObjCMethodLookupTable();
14901495

14911496
// Register a cleanup with the ASTContext to call the lookup table
14921497
// destructor.
1493-
ctx.addCleanup([this]() {
1494-
this->ObjCMethodLookup->~ObjCMethodLookupTable();
1495-
});
1498+
ctx.addDestructorCleanup(*ObjCMethodLookup);
1499+
1500+
return true;
14961501
}
14971502

14981503
TinyPtrVector<AbstractFunctionDecl *>
1499-
ClassDecl::lookupDirect(ObjCSelector selector, bool isInstance) {
1500-
if (!ObjCMethodLookup) {
1501-
createObjCMethodLookup();
1502-
}
1504+
NominalTypeDecl::lookupDirect(ObjCSelector selector, bool isInstance) {
1505+
if (!ObjCMethodLookup && !createObjCMethodLookup())
1506+
return {};
15031507

15041508
// If any modules have been loaded since we did the search last (or if we
15051509
// hadn't searched before), look in those modules, too.
@@ -1514,11 +1518,10 @@ ClassDecl::lookupDirect(ObjCSelector selector, bool isInstance) {
15141518
return stored.Methods;
15151519
}
15161520

1517-
void ClassDecl::recordObjCMethod(AbstractFunctionDecl *method,
1518-
ObjCSelector selector) {
1519-
if (!ObjCMethodLookup) {
1520-
createObjCMethodLookup();
1521-
}
1521+
void NominalTypeDecl::recordObjCMethod(AbstractFunctionDecl *method,
1522+
ObjCSelector selector) {
1523+
if (!ObjCMethodLookup && !createObjCMethodLookup())
1524+
return;
15221525

15231526
// Record the method.
15241527
bool isInstanceMethod = method->isObjCInstanceMethod();

lib/ClangImporter/ImportDecl.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4815,12 +4815,12 @@ namespace {
48154815
decl->setIsDynamic(true);
48164816

48174817
// If the declaration we attached the 'objc' attribute to is within a
4818-
// class, record it in the class.
4818+
// type, record it in the type.
48194819
if (auto contextTy = decl->getDeclContext()->getDeclaredInterfaceType()) {
4820-
if (auto classDecl = contextTy->getClassOrBoundGenericClass()) {
4820+
if (auto tyDecl = contextTy->getNominalOrBoundGenericNominal()) {
48214821
if (auto method = dyn_cast<AbstractFunctionDecl>(decl)) {
48224822
if (name)
4823-
classDecl->recordObjCMethod(method, *name);
4823+
tyDecl->recordObjCMethod(method, *name);
48244824
}
48254825
}
48264826
}

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2008,9 +2008,9 @@ void markAsObjC(ValueDecl *D, ObjCReason reason,
20082008
}
20092009
}
20102010

2011-
// Record the method in the class, if it's a member of one.
2012-
if (auto classDecl = D->getDeclContext()->getSelfClassDecl()) {
2013-
classDecl->recordObjCMethod(method, selector);
2011+
// Record the method in the type, if it's a member of one.
2012+
if (auto tyDecl = D->getDeclContext()->getSelfNominalTypeDecl()) {
2013+
tyDecl->recordObjCMethod(method, selector);
20142014
}
20152015

20162016
// Record the method in the source file.
@@ -2406,11 +2406,11 @@ bool swift::diagnoseUnintendedObjCMethodOverrides(SourceFile &sf) {
24062406
/// Retrieve the source file for the given Objective-C member conflict.
24072407
static TinyPtrVector<AbstractFunctionDecl *>
24082408
getObjCMethodConflictDecls(const SourceFile::ObjCMethodConflict &conflict) {
2409-
ClassDecl *classDecl = std::get<0>(conflict);
2409+
NominalTypeDecl *typeDecl = std::get<0>(conflict);
24102410
ObjCSelector selector = std::get<1>(conflict);
24112411
bool isInstanceMethod = std::get<2>(conflict);
24122412

2413-
return classDecl->lookupDirect(selector, isInstanceMethod);
2413+
return typeDecl->lookupDirect(selector, isInstanceMethod);
24142414
}
24152415

24162416
static ObjCAttr *getObjCAttrIfFromAccessNote(ValueDecl *VD) {
@@ -2447,6 +2447,7 @@ bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {
24472447
// Diagnose each conflict.
24482448
bool anyConflicts = false;
24492449
for (const auto &conflict : localConflicts) {
2450+
NominalTypeDecl *tyDecl = std::get<0>(conflict);
24502451
ObjCSelector selector = std::get<1>(conflict);
24512452

24522453
auto methods = getObjCMethodConflictDecls(conflict);
@@ -2530,6 +2531,9 @@ bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {
25302531
origDiagInfo.first, origDiagInfo.second,
25312532
selector);
25322533

2534+
// Protocols weren't checked for selector conflicts in 5.0.
2535+
diag.warnUntilSwiftVersionIf(!isa<ClassDecl>(tyDecl), 6);
2536+
25332537
auto objcAttr = getObjCAttrIfFromAccessNote(conflictingDecl);
25342538
swift::softenIfAccessNote(conflictingDecl, objcAttr, diag);
25352539
if (objcAttr)

test/ClangImporter/objc_bridging_custom.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,9 @@ protocol TestProto {
159159
@objc optional func testUnmigrated(a: NSRuncingMode, b: Refrigerator, c: NSCoding) // expected-note {{here}} {{none}}
160160
@objc optional func testPartialMigrated(a: NSRuncingMode, b: Refrigerator) // expected-note {{here}} {{none}}
161161

162-
@objc optional subscript(a a: Refrigerator) -> Refrigerator? { get } // expected-note {{here}} {{none}}
162+
@objc optional subscript(a a: Refrigerator) -> Refrigerator? { get } // expected-note 2 {{here}} {{none}}
163163
@objc optional subscript(generic a: ManufacturerInfo<NSString>) -> ManufacturerInfo<NSString>? { get } // expected-note {{here}} {{none}}
164+
// expected-warning@-1 {{subscript getter with Objective-C selector 'objectForKeyedSubscript:' conflicts with previous declaration with the same Objective-C selector; this is an error in Swift 6}}
164165

165166
@objc optional var prop: Refrigerator? { get } // expected-note {{here}} {{none}}
166167
@objc optional var propGeneric: ManufacturerInfo<NSString>? { get } // expected-note {{here}} {{none}}

test/Constraints/common_type_objc.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
import Foundation
77

88
@objc protocol P {
9-
func foo(_ i: Int) -> Double
10-
func foo(_ d: Double) -> Double
9+
func foo(_ i: Int) -> Double // expected-note {{'foo' previously declared here}}
10+
func foo(_ d: Double) -> Double // expected-warning {{method 'foo' with Objective-C selector 'foo:' conflicts with previous declaration with the same Objective-C selector; this is an error in Swift 6}}
1111

12-
@objc optional func opt(_ i: Int) -> Int
13-
@objc optional func opt(_ d: Double) -> Int
12+
@objc optional func opt(_ i: Int) -> Int // expected-note {{'opt' previously declared here}}
13+
@objc optional func opt(_ d: Double) -> Int // expected-warning {{method 'opt' with Objective-C selector 'opt:' conflicts with previous declaration with the same Objective-C selector; this is an error in Swift 6}}
1414
}
1515

1616
func testOptional(obj: P) {

test/Constraints/overload_filtering_objc.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import Foundation
1010

1111
@objc protocol P {
12-
func foo(_ i: Int) -> Int
13-
func foo(_ d: Double) -> Int
12+
func foo(_ i: Int) -> Int // expected-note {{'foo' previously declared here}}
13+
func foo(_ d: Double) -> Int // expected-warning {{method 'foo' with Objective-C selector 'foo:' conflicts with previous declaration with the same Objective-C selector; this is an error in Swift 6}}
1414

1515
@objc optional func opt(_ i: Int) -> Int
1616
@objc optional func opt(double: Double) -> Int

test/attr/attr_objc.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,9 +364,11 @@ protocol subject_containerObjCProtocol1 {
364364
@objc // access-note-move{{subject_containerObjCProtocol2}}
365365
protocol subject_containerObjCProtocol2 {
366366
init(a: Int)
367+
// expected-note@-1 {{'init' previously declared here}}
367368

368369
@objc // FIXME: Access notes can't distinguish between init(a:) overloads
369370
init(a: Double)
371+
// expected-warning@-1 {{initializer 'init(a:)' with Objective-C selector 'initWithA:' conflicts with previous declaration with the same Objective-C selector; this is an error in Swift 6}}
370372

371373
func func1() -> Int
372374
@objc // access-note-move{{subject_containerObjCProtocol2.func1_()}}

test/decl/protocol/objc.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,3 +258,10 @@ class C8SubRW: C8Base {
258258
class C8SubRW2: C8Base {
259259
var prop: Int = 0
260260
}
261+
262+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
263+
@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}}
267+
}

0 commit comments

Comments
 (0)