Skip to content

Commit 0d0a022

Browse files
committed
Restore explicit @objc objcImpl behavior for early adopters
The combination `@objc @_objcImplementation extension` used to be treated like a normal `@objc extension`. Restore that behavior for early adopters.
1 parent d0a2564 commit 0d0a022

File tree

3 files changed

+57
-12
lines changed

3 files changed

+57
-12
lines changed

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,17 +1262,39 @@ bool swift::canBeRepresentedInObjC(const ValueDecl *decl) {
12621262

12631263
#pragma mark "@objc declaration handling"
12641264

1265-
/// Whether this declaration is a member of a class extension marked @objc.
1266-
static bool isMemberOfObjCClassExtension(const ValueDecl *VD) {
1265+
enum class ObjCExtensionKind : uint8_t {
1266+
/// Not an @objc extension of any kind.
1267+
None,
1268+
/// This should be treated as an @objc @implementation extension.
1269+
ImplementationExtension,
1270+
/// This should be treated as an ordinary @objc extension.
1271+
NormalExtension,
1272+
};
1273+
1274+
static ObjCExtensionKind
1275+
classifyObjCExtensionContext(const ValueDecl *VD, bool isEarlyAdopter) {
12671276
auto ext = dyn_cast<ExtensionDecl>(VD->getDeclContext());
1268-
if (!ext) return false;
1277+
if (!ext || !ext->getSelfClassDecl())
1278+
return ObjCExtensionKind::None;
12691279

1270-
return ext->getSelfClassDecl() && ext->getAttrs().hasAttribute<ObjCAttr>();
1271-
}
1280+
auto objCAttr = ext->getAttrs().getAttribute<ObjCAttr>();
1281+
if (!objCAttr)
1282+
return ObjCExtensionKind::None;
1283+
1284+
if (ext->isObjCImplementation()) {
1285+
// Only apply objcImpl-specific logic at this point in the algorithm if the
1286+
// user wrote the new syntax.
1287+
if (!isEarlyAdopter)
1288+
return ObjCExtensionKind::ImplementationExtension;
1289+
1290+
// Here's the tricky case: If the `@objc` attribute is implicit, then it was
1291+
// added by the parser when it saw the early adopter syntax, so we should
1292+
// pretend it isn't there!
1293+
if (objCAttr->isImplicit())
1294+
return ObjCExtensionKind::None;
1295+
}
12721296

1273-
static bool isMemberOfObjCImplementationExtension(const ValueDecl *VD) {
1274-
return isMemberOfObjCClassExtension(VD) &&
1275-
cast<ExtensionDecl>(VD->getDeclContext())->isObjCImplementation();
1297+
return ObjCExtensionKind::NormalExtension;
12761298
}
12771299

12781300
/// Whether this declaration is a member of a class with the `@objcMembers`
@@ -1509,18 +1531,28 @@ shouldMarkAsObjC(const ValueDecl *VD, bool allowImplicit,
15091531
.hasAttribute<NonObjCAttr>()))
15101532
return std::nullopt;
15111533

1512-
if (isMemberOfObjCImplementationExtension(VD)) {
1534+
// Handle @objc extensions of various sorts.
1535+
// The `ImplementationExtension` case is only taken if we are *not* an early
1536+
// adopter, though which of the other two it will take instead is complicated.
1537+
switch (classifyObjCExtensionContext(VD, isImplEarlyAdopter)) {
1538+
case ObjCExtensionKind::None:
1539+
break;
1540+
1541+
case ObjCExtensionKind::ImplementationExtension:
15131542
// A non-`final` member of an @objc @implementation extension is @objc
15141543
// with a special reason.
15151544
if (!VD->isFinal() && canInferImplicitObjC(/*allowAnyAccess*/true)) {
15161545
if (!isImplEarlyAdopter)
15171546
return ObjCReason(ObjCReason::MemberOfObjCImplementationExtension,
15181547
implAttr);
15191548
}
1549+
break;
1550+
1551+
case ObjCExtensionKind::NormalExtension:
1552+
if (canInferImplicitObjC(/*allowAnyAccess*/true))
1553+
return ObjCReason(ObjCReason::MemberOfObjCExtension);
1554+
break;
15201555
}
1521-
else if (isMemberOfObjCClassExtension(VD) &&
1522-
canInferImplicitObjC(/*allowAnyAccess*/true))
1523-
return ObjCReason(ObjCReason::MemberOfObjCExtension);
15241556

15251557
if (isMemberOfObjCMembersClass(VD) &&
15261558
canInferImplicitObjC(/*allowAnyAccess*/false))

test/decl/ext/objc_implementation.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,12 @@ protocol EmptySwiftProto {}
453453
}
454454
}
455455

456+
@objc @implementation extension ObjCClassWithWeirdSwiftAttributeCombo {
457+
static func staticFnPreviouslyTreatedAsAtObjcExtensionMember() {
458+
// OK
459+
}
460+
}
461+
456462
// Intentionally using `@_objcImplementation` for this test; do not upgrade!
457463
@_objcImplementation(EmptyCategory) extension ObjCClass {
458464
// expected-warning@-1 {{'@_objcImplementation' is deprecated; use '@implementation' instead}} {{1-36=@implementation}} {{1-1=@objc(EmptyCategory) }}

test/decl/ext/objc_implementation_early_adopter.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,13 @@ protocol EmptySwiftProto {}
454454
}
455455
}
456456

457+
@objc @_objcImplementation extension ObjCClassWithWeirdSwiftAttributeCombo {
458+
static func staticFnPreviouslyTreatedAsAtObjcExtensionMember() {
459+
// expected-warning@-1 {{static method 'staticFnPreviouslyTreatedAsAtObjcExtensionMember()' will no longer be implicitly '@objc' after adopting '@objc @implementation'; add '@objc' to keep the current behavior}} {{3-3=@objc }}
460+
// expected-note@-2 {{add 'final' to define a Swift-only static method that cannot be overridden}} {{3-3=final }}
461+
}
462+
}
463+
457464
// Intentionally using `@_objcImplementation` for this test; do not upgrade!
458465
@_objcImplementation(EmptyCategory) extension ObjCClass {
459466
// expected-warning@-1 {{'@_objcImplementation' is deprecated; use '@implementation' instead}} {{1-36=@implementation}} {{1-1=@objc(EmptyCategory) }}

0 commit comments

Comments
 (0)