Skip to content

Commit 3212d2e

Browse files
committed
Correct and improve objcImpl member matching
• `@objc` is now inferred on non-`final` members of @objcImplementation extensions • Diagnostics now suggest adding `private` to ObjC helper members, not `@objc`, in line with currently proposed behavior • Better diagnostic for members implemented in the wrong extension Part of rdar://103150189.
1 parent a169f37 commit 3212d2e

File tree

4 files changed

+115
-65
lines changed

4 files changed

+115
-65
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,14 +1587,20 @@ ERROR(member_of_objc_implementation_not_objc_or_final,none,
15871587
"%0 %1 does not match any %0 declared in the headers for %2; did you use "
15881588
"the %0's Swift name?",
15891589
(DescriptiveDeclKind, ValueDecl *, ValueDecl *))
1590-
NOTE(fixit_add_objc_for_objc_implementation,none,
1591-
"add '@objc' to define an Objective-C-compatible %0 not declared in "
1592-
"the header",
1590+
NOTE(fixit_add_private_for_objc_implementation,none,
1591+
"add 'private' or 'fileprivate' to define an Objective-C-compatible %0 "
1592+
"not declared in the header",
15931593
(DescriptiveDeclKind))
15941594
NOTE(fixit_add_final_for_objc_implementation,none,
15951595
"add 'final' to define a Swift %0 that cannot be overridden",
15961596
(DescriptiveDeclKind))
15971597

1598+
ERROR(objc_implementation_wrong_category,none,
1599+
"%0 %1 should be implemented in extension for "
1600+
"%select{main class interface|category %2}2, not "
1601+
"%select{main class interface|category %3}3",
1602+
(DescriptiveDeclKind, ValueDecl *, Identifier, Identifier))
1603+
15981604
ERROR(cdecl_not_at_top_level,none,
15991605
"@_cdecl can only be applied to global functions", ())
16001606
ERROR(cdecl_empty_name,none,

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,11 @@ Optional<ObjCReason> shouldMarkAsObjC(const ValueDecl *VD, bool allowImplicit) {
14511451
return ObjCReason(ObjCReason::MemberOfObjCProtocol);
14521452
}
14531453

1454+
// A member implementation of an @objcImplementation extension is @objc.
1455+
if (VD->isObjCMemberImplementation())
1456+
// FIXME: New ObjCReason::Kind?
1457+
return ObjCReason(ObjCReason::ImplicitlyObjC);
1458+
14541459
// A @nonobjc is not @objc, even if it is an override of an @objc, so check
14551460
// for @nonobjc first.
14561461
if (VD->getAttrs().hasAttribute<NonObjCAttr>() ||

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,24 +1243,96 @@ static void checkObjCImplementationMemberAvoidsVTable(ValueDecl *VD) {
12431243
if (!VD->isObjCMemberImplementation())
12441244
return;
12451245

1246-
if (VD->isObjC()) {
1247-
assert(isa<DestructorDecl>(VD) || VD->isDynamic() &&
1248-
"@objc decls in @_objcImplementations should be dynamic!");
1249-
return;
1250-
}
1246+
auto isNotRelevantInterfaceMember = [&](ValueDecl *member) -> bool {
1247+
return !member->hasClangNode()
1248+
|| VD->isInstanceMember() != member->isInstanceMember();
1249+
};
1250+
assert(!VD->hasClangNode() &&
1251+
"we assumed VD could not be in sameNameMembers!");
12511252

12521253
auto &diags = VD->getASTContext().Diags;
1253-
diags.diagnose(VD, diag::member_of_objc_implementation_not_objc_or_final,
1254-
VD->getDescriptiveKind(), VD, ED->getExtendedNominal());
1254+
auto extendedType = ED->getSelfClassDecl();
1255+
auto objcAttr = VD->getSemanticAttrs().getAttribute<ObjCAttr>();
1256+
TinyPtrVector<ValueDecl *> sameNameMembers;
1257+
1258+
// Look up by @objc(<selector>), if present
1259+
if (objcAttr && objcAttr->getName()) {
1260+
auto selectorLookup = extendedType->lookupDirect(*objcAttr->getName(),
1261+
VD->isInstanceMember());
1262+
// Loop upcasts from AbstractFunctionDecl to ValueDecl
1263+
for (ValueDecl *member : selectorLookup) {
1264+
if (auto accessor = dyn_cast<AccessorDecl>(member))
1265+
member = accessor->getStorage();
1266+
1267+
if (!isNotRelevantInterfaceMember(member)
1268+
&& !llvm::is_contained(sameNameMembers, member))
1269+
sameNameMembers.push_back(member);
1270+
}
1271+
}
1272+
1273+
// Fallback: Look up by Swift name
1274+
// If we tried the selector lookup but it failed, this will find near-matches
1275+
// we can suggest.
1276+
if (sameNameMembers.empty()) {
1277+
sameNameMembers = extendedType->lookupDirect(VD->getName());
1278+
}
1279+
1280+
// Filter matches down to only imported members of the same instance-ness.
1281+
if (!sameNameMembers.empty()) {
1282+
sameNameMembers.erase(llvm::remove_if(sameNameMembers,
1283+
isNotRelevantInterfaceMember),
1284+
sameNameMembers.end());
1285+
}
1286+
1287+
// If we didn't find any methods, diagnose.
1288+
if (sameNameMembers.empty()) {
1289+
diags.diagnose(VD, diag::member_of_objc_implementation_not_objc_or_final,
1290+
VD->getDescriptiveKind(), VD, ED->getExtendedNominal());
1291+
1292+
if (canBeRepresentedInObjC(VD))
1293+
diags.diagnose(VD, diag::fixit_add_private_for_objc_implementation,
1294+
VD->getDescriptiveKind())
1295+
.fixItInsert(VD->getAttributeInsertionLoc(true), "private ");
12551296

1256-
if (canBeRepresentedInObjC(VD))
1257-
diags.diagnose(VD, diag::fixit_add_objc_for_objc_implementation,
1297+
diags.diagnose(VD, diag::fixit_add_final_for_objc_implementation,
12581298
VD->getDescriptiveKind())
1259-
.fixItInsert(VD->getAttributeInsertionLoc(false), "@objc ");
1299+
.fixItInsert(VD->getAttributeInsertionLoc(true), "final ");
1300+
return;
1301+
}
1302+
1303+
// FIXME: Diagnose multiple matches?
1304+
1305+
// If we only found methods in the wrong extensions, diagnose.
1306+
auto expectedInterfaceDC = ED->getImplementedObjCContext();
1307+
auto selectedMethodIter = llvm::find_if(sameNameMembers,
1308+
[&](ValueDecl *member) {
1309+
return expectedInterfaceDC == member->getDeclContext();
1310+
});
1311+
if (selectedMethodIter == sameNameMembers.end()) {
1312+
Identifier expectedInterfaceName;
1313+
if (auto ED = dyn_cast<ExtensionDecl>(expectedInterfaceDC)) {
1314+
expectedInterfaceName = ED->getObjCCategoryName();
1315+
}
1316+
1317+
// Arbitarily assume sameNameMethods.front() is the correct declaration.
1318+
auto actualInterfaceDC = sameNameMembers.front()->getDeclContext();
1319+
Identifier actualInterfaceName;
1320+
if (auto ED = dyn_cast<ExtensionDecl>(actualInterfaceDC)) {
1321+
actualInterfaceName = ED->getObjCCategoryName();
1322+
}
1323+
1324+
diags.diagnose(VD, diag::objc_implementation_wrong_category,
1325+
VD->getDescriptiveKind(), VD,
1326+
actualInterfaceName, expectedInterfaceName);
1327+
1328+
// FIXME: Add fix-it to move implementation
1329+
1330+
return;
1331+
}
12601332

1261-
diags.diagnose(VD, diag::fixit_add_final_for_objc_implementation,
1262-
VD->getDescriptiveKind())
1263-
.fixItInsert(VD->getAttributeInsertionLoc(true), "final ");
1333+
assert(VD->isObjC());
1334+
assert(isa<DestructorDecl>(VD) || VD->isDynamic() &&
1335+
"@objc decls in @_objcImplementations should be dynamic!");
12641336
}
12651337

12661338
/// Build a default initializer string for the given pattern.

test/decl/ext/objc_implementation.swift

Lines changed: 16 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,15 @@
66
// expected-note@-1 {{previously implemented by extension here}}
77

88
func method(fromHeader1: CInt) {
9-
// FIXME: OK, provides an implementation for the header's method.
10-
// expected-error@-2 {{instance method 'method(fromHeader1:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
11-
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
12-
// expected-note@-4 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
9+
// OK, provides an implementation for the header's method.
1310
}
1411

1512
@objc func method(fromHeader2: CInt) {
1613
// OK, provides an implementation for the header's method.
1714
}
1815

1916
func categoryMethod(fromHeader3: CInt) {
20-
// FIXME: Should complain about the wrong category
21-
// expected-error@-2 {{instance method 'categoryMethod(fromHeader3:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
22-
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
23-
// expected-note@-4 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
17+
// expected-error@-1 {{instance method 'categoryMethod(fromHeader3:)' should be implemented in extension for category 'PresentAdditions', not main class interface}}
2418
}
2519

2620
@objc fileprivate func methodNot(fromHeader1: CInt) {
@@ -33,24 +27,18 @@
3327

3428
func methodNot(fromHeader3: CInt) {
3529
// expected-error@-1 {{instance method 'methodNot(fromHeader3:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
36-
// expected-note@-2 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
30+
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=private }}
3731
// expected-note@-3 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
3832
}
3933

4034
var propertyFromHeader1: CInt
41-
// FIXME: OK, provides an implementation with a stored property
42-
// expected-error@-2 {{property 'propertyFromHeader1' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?}}
43-
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible property not declared in the header}} {{3-3=@objc }}
44-
// expected-note@-4 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
35+
// OK, provides an implementation with a stored property
4536

4637
@objc var propertyFromHeader2: CInt
4738
// OK, provides an implementation with a stored property
4839

4940
var propertyFromHeader3: CInt {
50-
// FIXME: OK, provides an implementation with a computed property
51-
// expected-error@-2 {{property 'propertyFromHeader3' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?}}
52-
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible property not declared in the header}} {{3-3=@objc }}
53-
// expected-note@-4 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
41+
// OK, provides an implementation with a computed property
5442
get { return 1 }
5543
set {}
5644
}
@@ -73,19 +61,13 @@
7361
// FIXME: Should complain about final not fulfilling the @objc requirement
7462

7563
var readonlyPropertyFromHeader1: CInt
76-
// FIXME: OK, provides an implementation with a stored property that's nonpublicly settable
77-
// expected-error@-2 {{property 'readonlyPropertyFromHeader1' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?}}
78-
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible property not declared in the header}} {{3-3=@objc }}
79-
// expected-note@-4 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
64+
// OK, provides an implementation with a stored property that's nonpublicly settable
8065

8166
@objc var readonlyPropertyFromHeader2: CInt
8267
// OK, provides an implementation with a stored property that's nonpublicly settable
8368

8469
var readonlyPropertyFromHeader3: CInt {
85-
// FIXME: OK, provides an implementation with a computed property
86-
// expected-error@-2 {{property 'readonlyPropertyFromHeader3' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?}}
87-
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible property not declared in the header}} {{3-3=@objc }}
88-
// expected-note@-4 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
70+
// OK, provides an implementation with a computed property
8971
get { return 1 }
9072
set {}
9173
}
@@ -104,15 +86,15 @@
10486
get { return 1 }
10587
}
10688

107-
var propertyNotFromHeader1: CInt
89+
internal var propertyNotFromHeader1: CInt
10890
// expected-error@-1 {{property 'propertyNotFromHeader1' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?}}
109-
// expected-note@-2 {{add '@objc' to define an Objective-C-compatible property not declared in the header}} {{3-3=@objc }}
91+
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible property not declared in the header}} {{3-3=private }}
11092
// expected-note@-3 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
11193

112-
@objc var propertyNotFromHeader2: CInt
94+
@objc private var propertyNotFromHeader2: CInt
11395
// OK, provides a nonpublic but ObjC-compatible stored property
11496

115-
@objc var propertyNotFromHeader3: CInt {
97+
@objc fileprivate var propertyNotFromHeader3: CInt {
11698
// OK, provides a nonpublic but ObjC-compatible computed property
11799
get { return 1 }
118100
set {}
@@ -147,25 +129,16 @@
147129
// expected-note@-1 {{previously implemented by extension here}}
148130

149131
func method(fromHeader3: CInt) {
150-
// FIXME: Should complain about wrong category
151-
// expected-error@-2 {{instance method 'method(fromHeader3:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
152-
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
153-
// expected-note@-4 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
132+
// expected-error@-1 {{instance method 'method(fromHeader3:)' should be implemented in extension for main class interface, not category 'PresentAdditions'}}
154133
}
155134

156135
var propertyFromHeader7: CInt {
157-
// FIXME: Should complain about wrong category
158-
// expected-error@-2 {{property 'propertyFromHeader7' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?}}
159-
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible property not declared in the header}} {{3-3=@objc }}
160-
// expected-note@-4 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
136+
// expected-error@-1 {{property 'propertyFromHeader7' should be implemented in extension for main class interface, not category 'PresentAdditions'}}
161137
get { return 1 }
162138
}
163139

164140
func categoryMethod(fromHeader1: CInt) {
165-
// FIXME: OK, provides an implementation for the header's method.
166-
// expected-error@-2 {{instance method 'categoryMethod(fromHeader1:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
167-
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
168-
// expected-note@-4 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
141+
// OK, provides an implementation for the header's method.
169142
}
170143

171144
@objc func categoryMethod(fromHeader2: CInt) {
@@ -182,24 +155,18 @@
182155

183156
func categoryMethodNot(fromHeader3: CInt) {
184157
// expected-error@-1 {{instance method 'categoryMethodNot(fromHeader3:)' does not match any instance method declared in the headers for 'ObjCClass'; did you use the instance method's Swift name?}}
185-
// expected-note@-2 {{add '@objc' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=@objc }}
158+
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=private }}
186159
// expected-note@-3 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
187160
}
188161

189162
var categoryPropertyFromHeader1: CInt
190163
// expected-error@-1 {{extensions must not contain stored properties}}
191-
// FIXME: expected-error@-2 {{property 'categoryPropertyFromHeader1' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?}}
192-
// FIXME: expected-note@-3 {{add '@objc' to define an Objective-C-compatible property not declared in the header}} {{3-3=@objc }}
193-
// FIXME: expected-note@-4 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
194164

195165
@objc var categoryPropertyFromHeader2: CInt
196166
// expected-error@-1 {{extensions must not contain stored properties}}
197167

198168
var categoryPropertyFromHeader3: CInt {
199-
// FIXME: OK, provides an implementation with a computed property
200-
// expected-error@-2 {{property 'categoryPropertyFromHeader3' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?}}
201-
// expected-note@-3 {{add '@objc' to define an Objective-C-compatible property not declared in the header}} {{3-3=@objc }}
202-
// expected-note@-4 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
169+
// OK, provides an implementation with a computed property
203170
get { return 1 }
204171
set {}
205172
}

0 commit comments

Comments
 (0)