Skip to content

Commit 7eb57d1

Browse files
committed
Re-allow @objc final
Treating this as forbidden was incorrect; an `@objc final` method is simply one that isn’t a member implementation but does have an ObjC entry point. Formalize and centralize the definition of what a member implementation is, tweak it so that it’s basically “non-final and internal or greater”, and permit `@objc final`. Also remove the inference of `final` on `let`s in @_objcImpl extensions.
1 parent 8b71053 commit 7eb57d1

File tree

7 files changed

+30
-55
lines changed

7 files changed

+30
-55
lines changed

include/swift/AST/Decl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2379,6 +2379,10 @@ class ValueDecl : public Decl {
23792379
/// Asserts if this is not a member of a protocol.
23802380
bool isProtocolRequirement() const;
23812381

2382+
/// Return true if this is a member implementation for an \c @_objcImplementation
2383+
/// extension.
2384+
bool isObjCMemberImplementation() const;
2385+
23822386
void setUserAccessible(bool Accessible) {
23832387
Bits.ValueDecl.IsUserAccessible = Accessible;
23842388
}

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,10 +1511,6 @@ ERROR(attr_objc_implementation_category_not_found,none,
15111511
NOTE(attr_objc_implementation_fixit_remove_category_name,none,
15121512
"remove arguments to implement the main '@interface' for this class",
15131513
())
1514-
ERROR(attr_objc_implementation_no_objc_final,none,
1515-
"%0 %1 cannot be 'final' because Objective-C subclasses of %2 can "
1516-
"override it",
1517-
(DescriptiveDeclKind, ValueDecl *, ValueDecl *))
15181514

15191515
ERROR(member_of_objc_implementation_not_objc_or_final,none,
15201516
"%0 %1 does not match any %0 declared in the headers for %2; did you use "

lib/AST/Decl.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3739,18 +3739,23 @@ static bool checkAccessUsingAccessScopes(const DeclContext *useDC,
37393739
/// Checks if \p VD is an ObjC member implementation:
37403740
///
37413741
/// \li It's in an \c \@_objcImplementation extension
3742-
/// \li It's \c \@objc
3742+
/// \li It's not explicitly \c final
37433743
/// \li Its access level is not \c private or \c fileprivate
37443744
static bool
37453745
isObjCMemberImplementation(const ValueDecl *VD,
37463746
llvm::function_ref<AccessLevel()> getAccessLevel) {
37473747
if (auto ED = dyn_cast<ExtensionDecl>(VD->getDeclContext()))
3748-
if (ED->isObjCImplementation())
3749-
return VD->isObjC() && getAccessLevel() >= AccessLevel::Internal;
3748+
if (ED->isObjCImplementation() && !isa<TypeDecl>(VD))
3749+
return !VD->isFinal() && getAccessLevel() >= AccessLevel::Internal;
37503750

37513751
return false;
37523752
}
37533753

3754+
bool ValueDecl::isObjCMemberImplementation() const {
3755+
return ::isObjCMemberImplementation(
3756+
this, [&]() { return this->getFormalAccess(); });
3757+
}
3758+
37543759
/// Checks if \p VD may be used from \p useDC, taking \@testable and \@_spi
37553760
/// imports into account.
37563761
///

lib/Sema/TypeCheckDecl.cpp

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -811,22 +811,6 @@ IsFinalRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
811811
if (!cls)
812812
return false;
813813

814-
// Objective-C doesn't have a way to prevent overriding, so if this member
815-
// is accessible from ObjC and it's possible to subclass its parent from ObjC,
816-
// `final` doesn't make sense even when inferred.
817-
//
818-
// FIXME: This is technically true iff `!cls->hasKnownSwiftImplementation()`,
819-
// but modeling that would be source-breaking, so instead we only
820-
// enforce it in `@_objcImplementation extension`s.
821-
if (auto ext = dyn_cast<ExtensionDecl>(decl->getDeclContext()))
822-
if (ext->isObjCImplementation() && decl->isObjC()) {
823-
if (explicitFinalAttr)
824-
diagnoseAndRemoveAttr(decl, explicitFinalAttr,
825-
diag::attr_objc_implementation_no_objc_final,
826-
decl->getDescriptiveKind(), decl, cls);
827-
return false;
828-
}
829-
830814
switch (decl->getKind()) {
831815
case DeclKind::Var: {
832816
// Properties are final if they are declared 'static' or a 'let'
@@ -854,6 +838,12 @@ IsFinalRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
854838
return true;
855839

856840
if (VD->isLet()) {
841+
// If this `let` is in an `@_objcImplementation extension`, don't
842+
// infer `final` unless it is written explicitly.
843+
auto ed = dyn_cast<ExtensionDecl>(VD->getDeclContext());
844+
if (!explicitFinalAttr && ed && ed->isObjCImplementation())
845+
return false;
846+
857847
if (VD->getFormalAccess() == AccessLevel::Open) {
858848
auto &context = decl->getASTContext();
859849
auto diagID = diag::implicitly_final_cannot_be_open;
@@ -974,6 +964,11 @@ IsDynamicRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
974964
return true;
975965
}
976966

967+
// @_objcImplementation extension member implementations are implicitly
968+
// dynamic.
969+
if (decl->isObjCMemberImplementation())
970+
return true;
971+
977972
if (auto accessor = dyn_cast<AccessorDecl>(decl)) {
978973
// Runtime-replaceable accessors are dynamic when their storage declaration
979974
// is dynamic and they were explicitly defined or they are implicitly defined
@@ -1005,12 +1000,6 @@ IsDynamicRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
10051000
if (isa<ExtensionDecl>(dc) && dc->getSelfClassDecl())
10061001
return true;
10071002

1008-
// @objc declarations in @_objcImplementation extensions are implicitly
1009-
// dynamic.
1010-
if (auto ED = dyn_cast_or_null<ExtensionDecl>(dc->getAsDecl()))
1011-
if (ED->isObjCImplementation())
1012-
return true;
1013-
10141003
// If any of the declarations overridden by this declaration are dynamic
10151004
// or were imported from Objective-C, this declaration is dynamic.
10161005
// Don't do this if the declaration is not exposed to Objective-C; that's

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,26 +1881,6 @@ static ObjCSelector inferObjCName(ValueDecl *decl) {
18811881
return *decl->getObjCRuntimeName(true);
18821882
}
18831883

1884-
static bool isObjCImplementation(AbstractFunctionDecl *method,
1885-
ObjCSelector methodName) {
1886-
auto ext = dyn_cast<ExtensionDecl>(method->getDeclContext());
1887-
if (!ext || !ext->isObjCImplementation())
1888-
return false;
1889-
1890-
auto contextInterface =
1891-
dyn_cast<IterableDeclContext>(ext->getImplementedObjCDecl());
1892-
1893-
for (auto otherMember : contextInterface->getMembers()) {
1894-
auto otherMethod = dyn_cast<AbstractFunctionDecl>(otherMember);
1895-
if (!otherMethod) continue;
1896-
1897-
if (otherMethod->getObjCSelector() == methodName)
1898-
return true;
1899-
}
1900-
1901-
return false;
1902-
}
1903-
19041884
/// Mark the given declaration as being Objective-C compatible (or
19051885
/// not) as appropriate.
19061886
///
@@ -2062,7 +2042,7 @@ void markAsObjC(ValueDecl *D, ObjCReason reason,
20622042

20632043
// Record the method in the type, if it's a member of one.
20642044
if (auto tyDecl = D->getDeclContext()->getSelfNominalTypeDecl()) {
2065-
if (!isObjCImplementation(method, selector))
2045+
if (!method->isObjCMemberImplementation())
20662046
tyDecl->recordObjCMethod(method, selector);
20672047
}
20682048

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,8 +1230,11 @@ static void checkObjCImplementationMemberAvoidsVTable(ValueDecl *VD) {
12301230
!ED->getSelfClassDecl()->hasKnownSwiftImplementation() &&
12311231
"@_objcImplementation on non-class or Swift class?");
12321232

1233-
if (VD->isSemanticallyFinal() || VD->isObjC()) {
1234-
assert(isa<DestructorDecl>(VD) || !VD->isObjC() || VD->isDynamic() &&
1233+
if (!VD->isObjCMemberImplementation())
1234+
return;
1235+
1236+
if (VD->isObjC()) {
1237+
assert(isa<DestructorDecl>(VD) || VD->isDynamic() &&
12351238
"@objc decls in @_objcImplementations should be dynamic!");
12361239
return;
12371240
}

test/decl/ext/objc_implementation.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
// OK, provides a Swift-only stored property
123123

124124
@objc final var propertyNotFromHeader5: CInt
125-
// expected-error@-1 {{property 'propertyNotFromHeader5' cannot be 'final' because Objective-C subclasses of 'ObjCClass' can override it}} {{9-15=}}
125+
// OK, @objc final is weird but supported, not a member impl
126126
}
127127

128128
// FIXME: Should complain about categoryMethodFromHeader4:
@@ -227,7 +227,6 @@ func usesAreNotAmbiguous(obj: ObjCClass) {
227227

228228
obj.methodNot(fromHeader1: 1)
229229
obj.methodNot(fromHeader2: 2)
230-
obj.methodNot(fromHeader3: 3)
231230

232231
obj.categoryMethod(fromHeader1: 1)
233232
obj.categoryMethod(fromHeader2: 2)
@@ -236,5 +235,4 @@ func usesAreNotAmbiguous(obj: ObjCClass) {
236235

237236
obj.categoryMethodNot(fromHeader1: 1)
238237
obj.categoryMethodNot(fromHeader2: 2)
239-
obj.categoryMethodNot(fromHeader3: 3)
240238
}

0 commit comments

Comments
 (0)