Skip to content

Commit 3a2f12a

Browse files
committed
Allow stored properties in @_objcImpls
Stored properties are only allowed in the extension implementing the class's main interface, not its categories. This also means banning `@objc final`, which is unenforceable anyway when ObjC subclasses are allowed, and therefore allowing `@objc let` and `@objc static` properties to be overridden if they're declared in objcImplementations.
1 parent 0678a38 commit 3a2f12a

File tree

10 files changed

+284
-35
lines changed

10 files changed

+284
-35
lines changed

include/swift/AST/Decl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,6 +1501,11 @@ class ExtensionDecl final : public GenericContext, public Decl,
15011501
/// behaviors for its members.
15021502
bool isObjCImplementation() const;
15031503

1504+
/// Returns the name of the category specified by the \c \@_objcImplementation
1505+
/// attribute, or \c None if the name is invalid. Do not call unless
1506+
/// \c isObjCImplementation() returns \c true.
1507+
Optional<Identifier> getCategoryNameForObjCImplementation() const;
1508+
15041509
/// Returns the \c clang::ObjCCategoryDecl or \c clang::ObjCInterfaceDecl
15051510
/// implemented by this extension.
15061511
///

include/swift/AST/DeclContext.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,11 @@ class IterableDeclContext {
824824
/// abstractions on top of member loading, such as a name lookup table.
825825
DeclRange getCurrentMembersWithoutLoading() const;
826826

827+
/// Return the context that contains the actual implemented members. This
828+
/// is \em usually just \c this, but if \c this is an imported class or
829+
/// category, it may be a Swift extension instead.
830+
IterableDeclContext *getImplementationContext();
831+
827832
/// Add a member to this context.
828833
///
829834
/// If the hint decl is specified, the new decl is inserted immediately

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,6 +1511,10 @@ 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 *))
15141518

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

lib/AST/Decl.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,17 +1471,15 @@ bool ExtensionDecl::isObjCImplementation() const {
14711471

14721472
const clang::ObjCContainerDecl *
14731473
ExtensionDecl::getInterfaceForObjCImplementation() const {
1474-
assert(isObjCImplementation());
1475-
1476-
auto attr = getAttrs().getAttribute<ObjCImplementationAttr>();
1477-
if (attr->isCategoryNameInvalid())
1474+
auto categoryName = getCategoryNameForObjCImplementation();
1475+
if (!categoryName)
14781476
return nullptr;
14791477

14801478
auto CD = dyn_cast_or_null<ClassDecl>(getExtendedNominal());
14811479
if (!CD)
14821480
return nullptr;
14831481

1484-
auto importedDecl = CD->getImportedObjCCategory(attr->CategoryName);
1482+
auto importedDecl = CD->getImportedObjCCategory(*categoryName);
14851483
if (!importedDecl)
14861484
return nullptr;
14871485

@@ -1490,6 +1488,17 @@ ExtensionDecl::getInterfaceForObjCImplementation() const {
14901488
return cast<clang::ObjCContainerDecl>(importedDecl->getClangDecl());
14911489
}
14921490

1491+
Optional<Identifier>
1492+
ExtensionDecl::getCategoryNameForObjCImplementation() const {
1493+
assert(isObjCImplementation());
1494+
1495+
auto attr = getAttrs().getAttribute<ObjCImplementationAttr>();
1496+
if (attr->isCategoryNameInvalid())
1497+
return None;
1498+
1499+
return attr->CategoryName;
1500+
}
1501+
14931502
PatternBindingDecl::PatternBindingDecl(SourceLoc StaticLoc,
14941503
StaticSpellingKind StaticSpelling,
14951504
SourceLoc VarLoc,

lib/ClangImporter/ClangImporter.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5032,6 +5032,31 @@ TinyPtrVector<ValueDecl *> ClangRecordMemberLookup::evaluate(
50325032
return result;
50335033
}
50345034

5035+
IterableDeclContext *IterableDeclContext::getImplementationContext() {
5036+
// Only ClangNodes have @_objcImplementation extensions.
5037+
if (!getDecl()->hasClangNode())
5038+
return this;
5039+
5040+
ClassDecl *classDecl = getAsGenericContext()->getSelfClassDecl();
5041+
if (!classDecl)
5042+
return this;
5043+
5044+
// Figure out the category name of the extension that will match this decl.
5045+
Identifier categoryName{};
5046+
if (getIterableContextKind() == IterableDeclContextKind::ExtensionDecl) {
5047+
auto category = cast<clang::ObjCCategoryDecl>(getDecl()->getClangDecl());
5048+
categoryName = getASTContext().getIdentifier(category->getName());
5049+
}
5050+
5051+
for (ExtensionDecl *ext : classDecl->getExtensions()) {
5052+
if (ext->isObjCImplementation()
5053+
&& ext->getCategoryNameForObjCImplementation() == categoryName)
5054+
return ext;
5055+
}
5056+
5057+
return this;
5058+
}
5059+
50355060
Decl *ClangCategoryLookupRequest::
50365061
evaluate(Evaluator &evaluator, ClangCategoryLookupDescriptor desc) const {
50375062
const ClassDecl *CD = desc.classDecl;

lib/Sema/TypeCheckDecl.cpp

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -288,15 +288,16 @@ enum class ImplicitlyFinalReason : unsigned {
288288
}
289289

290290
static bool inferFinalAndDiagnoseIfNeeded(ValueDecl *D, ClassDecl *cls,
291+
FinalAttr *explicitFinalAttr,
291292
StaticSpellingKind staticSpelling) {
292293
// Are there any reasons to infer 'final'? Prefer 'static' over the class
293294
// being final for the purposes of diagnostics.
294295
Optional<ImplicitlyFinalReason> reason;
295296
if (staticSpelling == StaticSpellingKind::KeywordStatic) {
296297
reason = ImplicitlyFinalReason::Static;
297298

298-
if (auto finalAttr = D->getAttrs().getAttribute<FinalAttr>()) {
299-
auto finalRange = finalAttr->getRange();
299+
if (explicitFinalAttr) {
300+
auto finalRange = explicitFinalAttr->getRange();
300301
if (finalRange.isValid()) {
301302
auto &context = D->getASTContext();
302303
context.Diags.diagnose(finalRange.Start, diag::static_decl_already_final)
@@ -802,13 +803,30 @@ PrimaryAssociatedTypesRequest::evaluate(Evaluator &evaluator,
802803

803804
bool
804805
IsFinalRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
806+
auto explicitFinalAttr = decl->getAttrs().getAttribute<FinalAttr>();
805807
if (isa<ClassDecl>(decl))
806-
return decl->getAttrs().hasAttribute<FinalAttr>();
808+
return explicitFinalAttr;
807809

808810
auto cls = decl->getDeclContext()->getSelfClassDecl();
809811
if (!cls)
810812
return false;
811813

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+
812830
switch (decl->getKind()) {
813831
case DeclKind::Var: {
814832
// Properties are final if they are declared 'static' or a 'let'
@@ -831,7 +849,8 @@ IsFinalRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
831849
// If this variable is a class member, mark it final if the
832850
// class is final, or if it was declared with 'let'.
833851
auto *PBD = VD->getParentPatternBinding();
834-
if (PBD && inferFinalAndDiagnoseIfNeeded(decl, cls, PBD->getStaticSpelling()))
852+
if (PBD && inferFinalAndDiagnoseIfNeeded(decl, cls, explicitFinalAttr,
853+
PBD->getStaticSpelling()))
835854
return true;
836855

837856
if (VD->isLet()) {
@@ -856,7 +875,8 @@ IsFinalRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
856875
case DeclKind::Func: {
857876
// Methods declared 'static' are final.
858877
auto staticSpelling = cast<FuncDecl>(decl)->getStaticSpelling();
859-
if (inferFinalAndDiagnoseIfNeeded(decl, cls, staticSpelling))
878+
if (inferFinalAndDiagnoseIfNeeded(decl, cls, explicitFinalAttr,
879+
staticSpelling))
860880
return true;
861881
break;
862882
}
@@ -889,7 +909,8 @@ IsFinalRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
889909
case DeclKind::Subscript: {
890910
// Member subscripts.
891911
auto staticSpelling = cast<SubscriptDecl>(decl)->getStaticSpelling();
892-
if (inferFinalAndDiagnoseIfNeeded(decl, cls, staticSpelling))
912+
if (inferFinalAndDiagnoseIfNeeded(decl, cls, explicitFinalAttr,
913+
staticSpelling))
893914
return true;
894915
break;
895916
}
@@ -898,10 +919,7 @@ IsFinalRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
898919
break;
899920
}
900921

901-
if (decl->getAttrs().hasAttribute<FinalAttr>())
902-
return true;
903-
904-
return false;
922+
return explicitFinalAttr;
905923
}
906924

907925
bool IsMoveOnlyRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,10 +1217,24 @@ static void checkDynamicSelfType(ValueDecl *decl, Type type) {
12171217
/// extension, it is either `final` or `@objc` (which may have been inferred by
12181218
/// checking whether it shadows an imported declaration).
12191219
static void checkObjCImplementationMemberAvoidsVTable(ValueDecl *VD) {
1220+
// We check the properties instead of their accessors.
1221+
if (isa<AccessorDecl>(VD))
1222+
return;
1223+
1224+
// Are we in an @_objcImplementation extension?
12201225
auto ED = dyn_cast<ExtensionDecl>(VD->getDeclContext());
1221-
if (!ED || !ED->isObjCImplementation()) return;
1226+
if (!ED || !ED->isObjCImplementation())
1227+
return;
12221228

1223-
if (VD->isSemanticallyFinal() || VD->isObjC()) return;
1229+
assert(ED->getSelfClassDecl() &&
1230+
!ED->getSelfClassDecl()->hasKnownSwiftImplementation() &&
1231+
"@_objcImplementation on non-class or Swift class?");
1232+
1233+
if (VD->isSemanticallyFinal() || VD->isObjC()) {
1234+
assert(!VD->isObjC() || VD->isDynamic() &&
1235+
"@objc decls in @_objcImplementations should be dynamic!");
1236+
return;
1237+
}
12241238

12251239
auto &diags = VD->getASTContext().Diags;
12261240
diags.diagnose(VD, diag::member_of_objc_implementation_not_objc_or_final,

lib/Sema/TypeCheckStorage.cpp

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -93,24 +93,27 @@ static bool contextAllowsPatternBindingWithoutVariables(DeclContext *dc) {
9393
return true;
9494
}
9595

96-
static bool hasStoredProperties(NominalTypeDecl *decl) {
96+
static bool hasStoredProperties(NominalTypeDecl *decl,
97+
IterableDeclContext *implDecl) {
9798
bool isForeignReferenceTy =
9899
isa<ClassDecl>(decl) && cast<ClassDecl>(decl)->isForeignReferenceType();
99100

100101
return (isa<StructDecl>(decl) ||
101102
(isa<ClassDecl>(decl) &&
102-
(!decl->hasClangNode() || isForeignReferenceTy)));
103+
(!decl->hasClangNode() || isForeignReferenceTy
104+
|| (decl != implDecl))));
103105
}
104106

105-
static void computeLoweredStoredProperties(NominalTypeDecl *decl) {
107+
static void computeLoweredStoredProperties(NominalTypeDecl *decl,
108+
IterableDeclContext *implDecl) {
106109
// If declaration has a type wrapper, make sure that
107110
// `$_storage` property is synthesized.
108111
if (decl->hasTypeWrapper())
109112
(void)decl->getTypeWrapperProperty();
110113

111114
// Just walk over the members of the type, forcing backing storage
112-
// for lazy properties, property and type wrappers to be synthesized.
113-
for (auto *member : decl->getMembers()) {
115+
// for lazy properties and property wrappers to be synthesized.
116+
for (auto *member : implDecl->getMembers()) {
114117
auto *var = dyn_cast<VarDecl>(member);
115118
if (!var || var->isStatic())
116119
continue;
@@ -160,6 +163,7 @@ static void computeLoweredStoredProperties(NominalTypeDecl *decl) {
160163
/// in a deterministic order.
161164
static void enumerateStoredPropertiesAndMissing(
162165
NominalTypeDecl *decl,
166+
IterableDeclContext *implDecl,
163167
llvm::function_ref<void(VarDecl *)> addStoredProperty,
164168
llvm::function_ref<void(MissingMemberDecl *)> addMissing) {
165169
// If we have a distributed actor, find the id and actorSystem
@@ -169,7 +173,7 @@ static void enumerateStoredPropertiesAndMissing(
169173
VarDecl *distributedActorSystem = nullptr;
170174
if (decl->isDistributedActor()) {
171175
ASTContext &ctx = decl->getASTContext();
172-
for (auto *member : decl->getMembers()) {
176+
for (auto *member : implDecl->getMembers()) {
173177
if (auto *var = dyn_cast<VarDecl>(member)) {
174178
if (!var->isStatic() && var->hasStorage()) {
175179
if (var->getName() == ctx.Id_id) {
@@ -190,7 +194,7 @@ static void enumerateStoredPropertiesAndMissing(
190194
addStoredProperty(distributedActorSystem);
191195
}
192196

193-
for (auto *member : decl->getMembers()) {
197+
for (auto *member : implDecl->getMembers()) {
194198
if (auto *var = dyn_cast<VarDecl>(member)) {
195199
if (!var->isStatic() && var->hasStorage()) {
196200
// Skip any properties that we already emitted explicitly
@@ -209,20 +213,29 @@ static void enumerateStoredPropertiesAndMissing(
209213
}
210214
}
211215

216+
static bool isInSourceFile(IterableDeclContext *idc) {
217+
const DeclContext *dc = idc->getAsGenericContext();
218+
return isa<SourceFile>(dc->getModuleScopeContext());
219+
}
220+
212221
ArrayRef<VarDecl *>
213222
StoredPropertiesRequest::evaluate(Evaluator &evaluator,
214223
NominalTypeDecl *decl) const {
215-
if (!hasStoredProperties(decl))
224+
// If this is an imported class with an @_objcImplementation extension, get
225+
// members from the extension instead.
226+
IterableDeclContext *implDecl = decl->getImplementationContext();
227+
228+
if (!hasStoredProperties(decl, implDecl))
216229
return ArrayRef<VarDecl *>();
217230

218231
SmallVector<VarDecl *, 4> results;
219232

220233
// Unless we're in a source file we don't have to do anything
221234
// special to lower lazy properties and property wrappers.
222-
if (isa<SourceFile>(decl->getModuleScopeContext()))
223-
computeLoweredStoredProperties(decl);
235+
if (isInSourceFile(implDecl))
236+
computeLoweredStoredProperties(decl, implDecl);
224237

225-
enumerateStoredPropertiesAndMissing(decl,
238+
enumerateStoredPropertiesAndMissing(decl, implDecl,
226239
[&](VarDecl *var) {
227240
results.push_back(var);
228241
},
@@ -234,17 +247,21 @@ StoredPropertiesRequest::evaluate(Evaluator &evaluator,
234247
ArrayRef<Decl *>
235248
StoredPropertiesAndMissingMembersRequest::evaluate(Evaluator &evaluator,
236249
NominalTypeDecl *decl) const {
237-
if (!hasStoredProperties(decl))
250+
// If this is an imported class with an @_objcImplementation extension, get
251+
// members from the extension instead.
252+
IterableDeclContext *implDecl = decl->getImplementationContext();
253+
254+
if (!hasStoredProperties(decl, implDecl))
238255
return ArrayRef<Decl *>();
239256

240257
SmallVector<Decl *, 4> results;
241258

242259
// Unless we're in a source file we don't have to do anything
243260
// special to lower lazy properties and property wrappers.
244-
if (isa<SourceFile>(decl->getModuleScopeContext()))
245-
computeLoweredStoredProperties(decl);
261+
if (isInSourceFile(implDecl))
262+
computeLoweredStoredProperties(decl, implDecl);
246263

247-
enumerateStoredPropertiesAndMissing(decl,
264+
enumerateStoredPropertiesAndMissing(decl, implDecl,
248265
[&](VarDecl *var) {
249266
results.push_back(var);
250267
},
@@ -3295,8 +3312,17 @@ static void finishStorageImplInfo(AbstractStorageDecl *storage,
32953312
if (isa<EnumDecl>(dc)) {
32963313
storage->diagnose(diag::enum_stored_property);
32973314
info = StorageImplInfo::getMutableComputed();
3298-
} else if (isa<ExtensionDecl>(dc) &&
3299-
!storage->getAttrs().getAttribute<DynamicReplacementAttr>()) {
3315+
} else if (auto ext = dyn_cast<ExtensionDecl>(dc)) {
3316+
// Extensions can dynamically replace a stored property.
3317+
if (storage->getAttrs().getAttribute<DynamicReplacementAttr>())
3318+
return;
3319+
3320+
// @_objcImplementation extensions on a non-category can declare stored
3321+
// properties; StoredPropertiesRequest knows to look for them there.
3322+
if (ext->isObjCImplementation() &&
3323+
ext->getCategoryNameForObjCImplementation() == Identifier())
3324+
return;
3325+
33003326
storage->diagnose(diag::extension_stored_property);
33013327

33023328
info = (info.supportsMutation()

0 commit comments

Comments
 (0)