Skip to content

Commit f558d01

Browse files
authored
Merge pull request #76462 from beccadax/objcimpl-the-legacy-i-leave-you
Mimic old objcImpl behavior for early adopters
2 parents 1401510 + 0d0a022 commit f558d01

File tree

7 files changed

+196
-55
lines changed

7 files changed

+196
-55
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,6 +1823,17 @@ ERROR(attr_implementation_category_goes_on_objc_attr,none,
18231823
"'@implementation'",
18241824
())
18251825

1826+
WARNING(objc_implementation_will_become_objc,none,
1827+
"%kind0 will become implicitly '@objc' after adopting '@objc "
1828+
"@implementation'; add '%select{@nonobjc|final}1' to keep the current "
1829+
"behavior",
1830+
(const ValueDecl *, /*suggestFinal=*/bool))
1831+
WARNING(objc_implementation_will_become_nonobjc,none,
1832+
"%kind0 will no longer be implicitly '@objc' after adopting '@objc "
1833+
"@implementation'; add '@objc' to keep the current "
1834+
"behavior",
1835+
(const ValueDecl *))
1836+
18261837
ERROR(member_of_objc_implementation_not_objc_or_final,none,
18271838
"%kind0 does not match any %kindonly0 declared in the headers for %1; "
18281839
"did you use the %kindonly0's Swift name?",
@@ -1831,12 +1842,10 @@ NOTE(fixit_add_private_for_objc_implementation,none,
18311842
"add 'private' or 'fileprivate' to define an Objective-C-compatible %0 "
18321843
"not declared in the header",
18331844
(DescriptiveDeclKind))
1834-
NOTE(fixit_add_final_for_objc_implementation,none,
1835-
"add 'final' to define a Swift %0 that cannot be overridden",
1836-
(DescriptiveDeclKind))
1837-
NOTE(fixit_add_nonobjc_for_objc_implementation,none,
1838-
"add '@nonobjc' to define a Swift-only %0",
1839-
(DescriptiveDeclKind))
1845+
NOTE(fixit_add_nonobjc_or_final_for_objc_implementation,none,
1846+
"add '%select{@nonobjc|final}1' to define a Swift-only %0"
1847+
"%select{| that cannot be overridden}1",
1848+
(DescriptiveDeclKind, /*suggestFinal=*/bool))
18401849

18411850
ERROR(objc_implementation_wrong_category,none,
18421851
"%kind0 should be implemented in extension for "

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 134 additions & 33 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`
@@ -1390,8 +1412,9 @@ static std::optional<ObjCReason> shouldMarkClassAsObjC(const ClassDecl *CD) {
13901412
}
13911413

13921414
/// Figure out if a declaration should be exported to Objective-C.
1393-
static std::optional<ObjCReason> shouldMarkAsObjC(const ValueDecl *VD,
1394-
bool allowImplicit) {
1415+
static std::optional<ObjCReason>
1416+
shouldMarkAsObjC(const ValueDecl *VD, bool allowImplicit,
1417+
ObjCImplementationAttr *implAttr, bool isImplEarlyAdopter) {
13951418
// If Objective-C interoperability is disabled, nothing gets marked as @objc.
13961419
if (!VD->getASTContext().LangOpts.EnableObjCInterop)
13971420
return std::nullopt;
@@ -1493,6 +1516,13 @@ static std::optional<ObjCReason> shouldMarkAsObjC(const ValueDecl *VD,
14931516
return ObjCReason(ObjCReason::MemberOfObjCProtocol);
14941517
}
14951518

1519+
// Emulating old logic:
1520+
// A member implementation of an @objcImplementation extension is @objc...
1521+
// for early adopters only. (There's new logic for non-early adopters below.)
1522+
if (isImplEarlyAdopter && VD->isObjCMemberImplementation())
1523+
return ObjCReason(ObjCReason::MemberOfObjCImplementationExtension,
1524+
implAttr);
1525+
14961526
// A @nonobjc is not @objc, even if it is an override of an @objc, so check
14971527
// for @nonobjc first.
14981528
if (VD->getAttrs().hasAttribute<NonObjCAttr>() ||
@@ -1501,21 +1531,28 @@ static std::optional<ObjCReason> shouldMarkAsObjC(const ValueDecl *VD,
15011531
.hasAttribute<NonObjCAttr>()))
15021532
return std::nullopt;
15031533

1504-
if (isMemberOfObjCImplementationExtension(VD)) {
1505-
// A `final` member of an @objc @implementation extension is not @objc.
1506-
if (VD->isFinal())
1507-
return std::nullopt;
1508-
// Other members get a special reason.
1509-
if (canInferImplicitObjC(/*allowAnyAccess*/true)) {
1510-
auto ext = VD->getDeclContext()->getAsDecl();
1511-
auto attr = ext->getAttrs()
1512-
.getAttribute<ObjCImplementationAttr>(/*AllowInvalid=*/true);
1513-
return ObjCReason(ObjCReason::MemberOfObjCImplementationExtension, attr);
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:
1542+
// A non-`final` member of an @objc @implementation extension is @objc
1543+
// with a special reason.
1544+
if (!VD->isFinal() && canInferImplicitObjC(/*allowAnyAccess*/true)) {
1545+
if (!isImplEarlyAdopter)
1546+
return ObjCReason(ObjCReason::MemberOfObjCImplementationExtension,
1547+
implAttr);
15141548
}
1549+
break;
1550+
1551+
case ObjCExtensionKind::NormalExtension:
1552+
if (canInferImplicitObjC(/*allowAnyAccess*/true))
1553+
return ObjCReason(ObjCReason::MemberOfObjCExtension);
1554+
break;
15151555
}
1516-
else if (isMemberOfObjCClassExtension(VD) &&
1517-
canInferImplicitObjC(/*allowAnyAccess*/true))
1518-
return ObjCReason(ObjCReason::MemberOfObjCExtension);
15191556

15201557
if (isMemberOfObjCMembersClass(VD) &&
15211558
canInferImplicitObjC(/*allowAnyAccess*/false))
@@ -1535,6 +1572,63 @@ static std::optional<ObjCReason> shouldMarkAsObjC(const ValueDecl *VD,
15351572
return std::nullopt;
15361573
}
15371574

1575+
static std::optional<ObjCReason> shouldMarkAsObjC(const ValueDecl *VD,
1576+
bool allowImplicit) {
1577+
// The real logic is in the four-argument version above. This wrapper just
1578+
// detects future `@objc @implementation` behavior changes and warns about
1579+
// them.
1580+
1581+
ObjCImplementationAttr *implAttr = nullptr;
1582+
if (auto ctxDecl = VD->getDeclContext()->getAsDecl())
1583+
implAttr = ctxDecl->getAttrs().getAttribute<ObjCImplementationAttr>(
1584+
/*AllowInvalid=*/true);
1585+
1586+
bool isEarlyAdopter = implAttr ? implAttr->isEarlyAdopter() : false;
1587+
1588+
auto currentlyObjC = shouldMarkAsObjC(VD, allowImplicit,
1589+
implAttr, isEarlyAdopter);
1590+
1591+
// If we weren't an early adopter, there's no differences to detect.
1592+
if (!isEarlyAdopter)
1593+
return currentlyObjC;
1594+
1595+
// Re-run the logic, but with the early adopter flag forced to `false`.
1596+
auto eventuallyObjC = shouldMarkAsObjC(VD, allowImplicit,
1597+
implAttr, /*isEarlyAdopter=*/false);
1598+
1599+
// Would that have behaved differently? If so, warn about it so the developer
1600+
// knows they should update their code.
1601+
if (currentlyObjC.has_value() == eventuallyObjC.has_value())
1602+
return currentlyObjC;
1603+
1604+
bool suggestFinal = !isa<ConstructorDecl>(VD);
1605+
1606+
if (eventuallyObjC.has_value()) {
1607+
ASSERT(!currentlyObjC.has_value());
1608+
1609+
VD->diagnose(diag::objc_implementation_will_become_objc, VD, suggestFinal)
1610+
.fixItInsert(VD->getAttributeInsertionLoc(suggestFinal),
1611+
suggestFinal ? "final " : "@nonobjc ");
1612+
1613+
VD->diagnose(diag::make_decl_objc, VD->getDescriptiveKind())
1614+
.fixItInsert(VD->getAttributeInsertionLoc(false), "@objc ");
1615+
} else {
1616+
ASSERT(currentlyObjC.has_value());
1617+
1618+
VD->diagnose(diag::objc_implementation_will_become_nonobjc, VD)
1619+
.fixItInsert(VD->getAttributeInsertionLoc(false), "@objc ");
1620+
1621+
VD->diagnose(diag::fixit_add_nonobjc_or_final_for_objc_implementation,
1622+
VD->getDescriptiveKind(), suggestFinal)
1623+
.fixItInsert(VD->getAttributeInsertionLoc(suggestFinal),
1624+
suggestFinal ? "final " : "@nonobjc ");
1625+
}
1626+
1627+
implAttr->setHasInvalidImplicitLangAttrs();
1628+
1629+
return currentlyObjC;
1630+
}
1631+
15381632
/// Determine whether the given type is a C integer type.
15391633
static bool isCIntegerType(Type type) {
15401634
auto nominal = type->getAnyNominal();
@@ -2412,6 +2506,11 @@ bool swift::diagnoseUnintendedObjCMethodOverrides(SourceFile &sf) {
24122506
continue;
24132507
}
24142508

2509+
// Skip declarations that are member implementations;
2510+
// ObjCImplmentationChecker will ensure they match a redeclared method.
2511+
if (method->isObjCMemberImplementation())
2512+
continue;
2513+
24152514
auto classDecl = method->getDeclContext()->getSelfClassDecl();
24162515
if (!classDecl)
24172516
continue; // error-recovery path, only
@@ -3263,6 +3362,9 @@ class ObjCImplementationChecker {
32633362
continue;
32643363

32653364
if (auto VD = dyn_cast<ValueDecl>(member)) {
3365+
// Force isObjC() to make sure hasInvalidImplicitLangAttrs() is set.
3366+
(void)VD->isObjC();
3367+
32663368
// Skip non-member implementations.
32673369
// FIXME: Should we consider them if only rejected for access level?
32683370
if (!VD->isObjCMemberImplementation()) {
@@ -3283,7 +3385,8 @@ class ObjCImplementationChecker {
32833385
return ObjCSelector(VD->getASTContext(), 0, { ident });
32843386
}
32853387
if (auto objcAttr = VD->getAttrs().getAttribute<ObjCAttr>())
3286-
return objcAttr->getName().value_or(ObjCSelector());
3388+
if (!objcAttr->isNameImplicit())
3389+
return objcAttr->getName().value_or(ObjCSelector());
32873390
return ObjCSelector();
32883391
}
32893392

@@ -3856,21 +3959,19 @@ class ObjCImplementationChecker {
38563959
}
38573960

38583961
// Initializers can't be 'final', but they can be '@nonobjc'
3859-
if (isa<ConstructorDecl>(cand))
3860-
diagnose(cand, diag::fixit_add_nonobjc_for_objc_implementation,
3861-
cand->getDescriptiveKind())
3862-
.fixItInsert(cand->getAttributeInsertionLoc(false), "@nonobjc ");
3863-
else
3864-
diagnose(cand, diag::fixit_add_final_for_objc_implementation,
3865-
cand->getDescriptiveKind())
3866-
.fixItInsert(cand->getAttributeInsertionLoc(true), "final ");
3962+
bool suggestFinal = !isa<ConstructorDecl>(cand);
3963+
diagnose(cand, diag::fixit_add_nonobjc_or_final_for_objc_implementation,
3964+
cand->getDescriptiveKind(), suggestFinal)
3965+
.fixItInsert(cand->getAttributeInsertionLoc(suggestFinal),
3966+
suggestFinal ? "final " : "@nonobjc ");
38673967
}
38683968
}
38693969

38703970
void diagnoseEarlyAdopterDeprecation() {
38713971
// Only encourage use of @implementation for early adopters, and only when
38723972
// there are no mismatches that they might be working around with it.
3873-
if (hasDiagnosed || !getAttr()->isEarlyAdopter())
3973+
if (hasDiagnosed || !getAttr()->isEarlyAdopter()
3974+
|| getAttr()->hasInvalidImplicitLangAttrs())
38743975
return;
38753976

38763977
// Only encourage adoption if the corresponding language feature is enabled.

test/SILOptimizer/let_properties_opts_objc.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public func testObjcInterface(_ x: ObjcInterface) -> Int {
1818

1919
// Test that private @objc constants aren't optimized, but instead continue to pass through ObjC message dispatch.
2020
@_objcImplementation extension ObjcInterfaceConstInit {
21-
private let constant: Int = 0
21+
@objc private let constant: Int = 0
2222

2323
// CHECK-LABEL: sil hidden @$sSo22ObjcInterfaceConstInitC4testE0E15PrivateConstantSiyF : $@convention(method) (@guaranteed ObjcInterfaceConstInit) -> Int {
2424
// CHECK: objc_method %0 : $ObjcInterfaceConstInit, #ObjcInterfaceConstInit.constant!getter.foreign

test/Serialization/objc_implementation.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import objc_implementation
1818
@_objcImplementation extension ObjCImpl {
1919
// CHECK-DAG: func cannotBeObjCMethod(_ value: Int?)
2020
private func cannotBeObjCMethod(_ value: Int?) {}
21-
// expected-warning@-1 {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
21+
// expected-warning@-1 {{instance method 'cannotBeObjCMethod' will become implicitly '@objc' after adopting '@objc @implementation'; add 'final' to keep the current behavior}} {{3-3=final }}
22+
// expected-note@-2 {{add '@objc' to expose this instance method to Objective-C}} {{3-3=@objc }}
2223

2324
// CHECK-DAG: @objc func goodMethod()
2425
@objc public func goodMethod() {}

test/decl/ext/Inputs/objc_implementation.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
- (void)superclassMethod:(int)param;
1414
@property (assign) int superclassProperty;
1515

16+
+ (void)superclassClassMethod;
17+
1618
@end
1719

1820
@protocol ObjCProto
@@ -214,6 +216,10 @@
214216
@protocol EmptyObjCProto
215217
@end
216218

219+
@interface ObjCClassWithWeirdSwiftAttributeCombo : ObjCBaseClass
220+
221+
@end
222+
217223
#endif
218224

219225
void CImplFunc1(int param);

test/decl/ext/objc_implementation.swift

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ protocol EmptySwiftProto {}
2626
// FIXME: should emit expected-DISABLED-error@-1 {{instance method 'categoryMethod(fromHeader3:)' should be implemented in extension for category 'PresentAdditions', not main class interface}}
2727
// FIXME: 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?}}
2828
// FIXME: expected-note@-3 {{add 'private' or 'fileprivate' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=private }}
29-
// FIXME: expected-note@-4 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
29+
// FIXME: expected-note@-4 {{add 'final' to define a Swift-only instance method that cannot be overridden}} {{3-3=final }}
3030
}
3131

3232
@objc fileprivate func methodNot(fromHeader1: CInt) {
@@ -40,7 +40,7 @@ protocol EmptySwiftProto {}
4040
func methodNot(fromHeader3: CInt) {
4141
// 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?}}
4242
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=private }}
43-
// expected-note@-3 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
43+
// expected-note@-3 {{add 'final' to define a Swift-only instance method that cannot be overridden}} {{3-3=final }}
4444
}
4545

4646
var methodFromHeader5: CInt
@@ -116,7 +116,7 @@ protocol EmptySwiftProto {}
116116
internal var propertyNotFromHeader1: CInt
117117
// expected-error@-1 {{property 'propertyNotFromHeader1' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?}}
118118
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible property not declared in the header}} {{3-11=private}}
119-
// expected-note@-3 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
119+
// expected-note@-3 {{add 'final' to define a Swift-only property that cannot be overridden}} {{3-3=final }}
120120

121121
@objc private var propertyNotFromHeader2: CInt
122122
// OK, provides a nonpublic but ObjC-compatible stored property
@@ -240,14 +240,14 @@ protocol EmptySwiftProto {}
240240
// FIXME: should emit expected-DISABLED-error@-1 {{instance method 'method(fromHeader3:)' should be implemented in extension for main class interface, not category 'PresentAdditions'}}
241241
// FIXME: 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?}}
242242
// FIXME: expected-note@-3 {{add 'private' or 'fileprivate' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=private }}
243-
// FIXME: expected-note@-4 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
243+
// FIXME: expected-note@-4 {{add 'final' to define a Swift-only instance method that cannot be overridden}} {{3-3=final }}
244244
}
245245

246246
var propertyFromHeader7: CInt {
247247
// FIXME: should emit expected-DISABLED-error@-1 {{property 'propertyFromHeader7' should be implemented in extension for main class interface, not category 'PresentAdditions'}}
248248
// FIXME: expected-error@-2 {{property 'propertyFromHeader7' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?}}
249249
// FIXME: expected-note@-3 {{add 'private' or 'fileprivate' to define an Objective-C-compatible property not declared in the header}} {{3-3=private }}
250-
// FIXME: expected-note@-4 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
250+
// FIXME: expected-note@-4 {{add 'final' to define a Swift-only property that cannot be overridden}} {{3-3=final }}
251251
get { return 1 }
252252
}
253253

@@ -270,7 +270,7 @@ protocol EmptySwiftProto {}
270270
func categoryMethodNot(fromHeader3: CInt) {
271271
// 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?}}
272272
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible instance method not declared in the header}} {{3-3=private }}
273-
// expected-note@-3 {{add 'final' to define a Swift instance method that cannot be overridden}} {{3-3=final }}
273+
// expected-note@-3 {{add 'final' to define a Swift-only instance method that cannot be overridden}} {{3-3=final }}
274274
}
275275

276276
var categoryPropertyFromHeader1: CInt
@@ -446,6 +446,17 @@ protocol EmptySwiftProto {}
446446
// expected-error@-1 {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
447447
// expected-note@-2 {{protocol-constrained type containing protocol 'EmptySwiftProto' cannot be represented in Objective-C}}
448448
}
449+
450+
override public static func superclassClassMethod() {
451+
// rdar://136113393: `static override` could make a non-`@objc` override
452+
// of an `@objc` member when using new syntax.
453+
}
454+
}
455+
456+
@objc @implementation extension ObjCClassWithWeirdSwiftAttributeCombo {
457+
static func staticFnPreviouslyTreatedAsAtObjcExtensionMember() {
458+
// OK
459+
}
449460
}
450461

451462
// Intentionally using `@_objcImplementation` for this test; do not upgrade!

0 commit comments

Comments
 (0)