Skip to content

Commit 1bc045a

Browse files
committed
Require @objc to be used with @implementation
…for extensions. This change also removes @implementation(CategoryName); you should attach the category name to the @objc attribute instead. And there are small changes to how much checking the compiler will do on an @objc @implementation after the decl checker has discovered a problem with it.
1 parent cf541c1 commit 1bc045a

File tree

16 files changed

+213
-104
lines changed

16 files changed

+213
-104
lines changed

include/swift/AST/Attr.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2422,6 +2422,9 @@ class DocumentationAttr: public DeclAttribute {
24222422

24232423
class ObjCImplementationAttr final : public DeclAttribute {
24242424
public:
2425+
/// Name of the category being implemented. This should only be used with
2426+
/// the early adopter \@\_objcImplementation syntax, but we support it there
2427+
/// for backwards compatibility.
24252428
Identifier CategoryName;
24262429

24272430
ObjCImplementationAttr(Identifier CategoryName, SourceLoc AtLoc,

include/swift/AST/DiagnosticsSema.def

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,9 @@ ERROR(expr_selector_not_objc,none,
763763
NOTE(make_decl_objc,none,
764764
"add '@objc' to expose this %0 to Objective-C",
765765
(DescriptiveDeclKind))
766+
NOTE(make_decl_objc_for_implementation,none,
767+
"add '@objc' to implement an Objective-C %0",
768+
(DescriptiveDeclKind))
766769

767770
// Selectors-as-string-literals.
768771
WARNING(selector_literal_invalid,none,
@@ -1760,10 +1763,6 @@ WARNING(objc_implementation_early_spelling_deprecated,none,
17601763
ERROR(attr_objc_implementation_must_be_unconditional,none,
17611764
"only unconditional extensions can implement an Objective-C '@interface'",
17621765
())
1763-
ERROR(attr_objc_implementation_must_extend_class,none,
1764-
"cannot mark extension of %kind0 with '@_objcImplementation'; it is not "
1765-
"an imported Objective-C class",
1766-
(ValueDecl *))
17671766
ERROR(attr_objc_implementation_must_be_imported,none,
17681767
"'@_objcImplementation' cannot be used to extend %kind0 because it was "
17691768
"defined by a Swift 'class' declaration, not an imported Objective-C "
@@ -1800,6 +1799,14 @@ ERROR(attr_objc_implementation_raise_minimum_deployment_target,none,
18001799
"'@implementation' of an Objective-C class requires a minimum deployment "
18011800
"target of at least %0 %1",
18021801
(StringRef, llvm::VersionTuple))
1802+
ERROR(attr_implementation_requires_language,none,
1803+
"'@implementation' used without specifying the language being "
1804+
"implemented",
1805+
())
1806+
ERROR(attr_implementation_category_goes_on_objc_attr,none,
1807+
"Objective-C category should be specified on '@objc', not "
1808+
"'@implementation'",
1809+
())
18031810

18041811
ERROR(member_of_objc_implementation_not_objc_or_final,none,
18051812
"%kind0 does not match any %kindonly0 declared in the headers for %1; "
@@ -6295,7 +6302,7 @@ ERROR(objc_extension_not_class,none,
62956302
"'@objc' can only be applied to an extension of a class", ())
62966303

62976304
// If you change this, also change enum ObjCReason
6298-
#define OBJC_ATTR_SELECT "select{marked @_cdecl|marked dynamic|marked @objc|marked @objcMembers|marked @IBOutlet|marked @IBAction|marked @IBSegueAction|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override|an implementation of an @objc requirement|marked @IBInspectable|marked @GKInspectable|in an @objc extension of a class (without @nonobjc)|in an @_objcImplementation extension of a class (without final or @nonobjc)|marked @objc by an access note}"
6305+
#define OBJC_ATTR_SELECT "select{marked @_cdecl|marked dynamic|marked @objc|marked @objcMembers|marked @IBOutlet|marked @IBAction|marked @IBSegueAction|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override|an implementation of an @objc requirement|marked @IBInspectable|marked @GKInspectable|in an @objc extension of a class (without @nonobjc)|in an @objc @implementation extension of a class (without final or @nonobjc)|marked @objc by an access note}"
62996306

63006307
ERROR(objc_invalid_on_var,none,
63016308
"property cannot be %" OBJC_ATTR_SELECT "0 "

lib/AST/Decl.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3717,12 +3717,6 @@ Identifier ExtensionDecl::getObjCCategoryName() const {
37173717
return Identifier();
37183718
}
37193719

3720-
// Fall back to @_objcImplementation attribute.
3721-
if (auto implAttr =
3722-
getAttrs().getAttribute<ObjCImplementationAttr>(/*AllowInvalid=*/true)) {
3723-
return implAttr->CategoryName;
3724-
}
3725-
37263720
// Not a category, evidently.
37273721
return Identifier();
37283722
}

lib/ClangImporter/ClangImporter.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5982,7 +5982,7 @@ constructResult(const llvm::TinyPtrVector<Decl *> &interfaces,
59825982
auto &diags = interfaces.front()->getASTContext().Diags;
59835983
for (auto extraImpl : llvm::ArrayRef<Decl *>(impls).drop_front()) {
59845984
auto attr = extraImpl->getAttrs().getAttribute<ObjCImplementationAttr>();
5985-
attr->setCategoryNameInvalid();
5985+
attr->setInvalid();
59865986

59875987
// @objc @implementations for categories are diagnosed as category
59885988
// conflicts, so we're only concerned with main class bodies and
@@ -5999,10 +5999,18 @@ constructResult(const llvm::TinyPtrVector<Decl *> &interfaces,
59995999
return ObjCInterfaceAndImplementation(interfaces, impls.front());
60006000
}
60016001

6002-
static bool isCategoryNameValid(ExtensionDecl *ext) {
6003-
auto attr = ext->getAttrs()
6004-
.getAttribute<ObjCImplementationAttr>(/*AllowInvalid=*/true);
6005-
return attr && !attr->isCategoryNameInvalid();
6002+
static bool isImplValid(ExtensionDecl *ext) {
6003+
auto attr = ext->getAttrs().getAttribute<ObjCImplementationAttr>();
6004+
6005+
if (!attr)
6006+
return false;
6007+
6008+
// Clients using the stable syntax shouldn't have a category name on the attr.
6009+
// This is diagnosed in AttributeChecker::visitObjCImplementationAttr().
6010+
if (!attr->isEarlyAdopter() && !attr->CategoryName.empty())
6011+
return false;
6012+
6013+
return !attr->isCategoryNameInvalid();
60066014
}
60076015

60086016
static ObjCInterfaceAndImplementation
@@ -6020,7 +6028,7 @@ findContextInterfaceAndImplementation(DeclContext *dc) {
60206028

60216029
if (auto ext = dyn_cast<ExtensionDecl>(dc)) {
60226030
assert(ext);
6023-
if (!ext->hasClangNode() && !isCategoryNameValid(ext))
6031+
if (!ext->hasClangNode() && !isImplValid(ext))
60246032
return {};
60256033

60266034
categoryName = ext->getObjCCategoryName();
@@ -6038,7 +6046,7 @@ findContextInterfaceAndImplementation(DeclContext *dc) {
60386046
for (ExtensionDecl *ext : classDecl->getExtensions()) {
60396047
if (ext->isObjCImplementation()
60406048
&& ext->getObjCCategoryName() == categoryName
6041-
&& isCategoryNameValid(ext))
6049+
&& isImplValid(ext))
60426050
implDecls.push_back(ext);
60436051
}
60446052

lib/Parse/ParseDecl.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7124,6 +7124,20 @@ Parser::parseDeclExtension(ParseDeclOptions Flags, DeclAttributes &Attributes) {
71247124
status |= whereStatus;
71257125
}
71267126

7127+
// @implementation requires an explicit @objc attribute, but
7128+
// @_objcImplementation didn't. Insert one if necessary.
7129+
auto implAttr = Attributes.getAttribute<ObjCImplementationAttr>();
7130+
if (implAttr && implAttr->isEarlyAdopter()
7131+
&& !Attributes.hasAttribute<ObjCAttr>()) {
7132+
ObjCAttr *objcAttr;
7133+
if (implAttr->CategoryName.empty())
7134+
objcAttr = ObjCAttr::createUnnamedImplicit(Context);
7135+
else
7136+
objcAttr = ObjCAttr::createNullary(Context, implAttr->CategoryName,
7137+
/*isNameImplicit=*/false);
7138+
Attributes.add(objcAttr);
7139+
}
7140+
71277141
ExtensionDecl *ext = ExtensionDecl::create(Context, ExtensionLoc,
71287142
extendedType.getPtrOrNull(),
71297143
Context.AllocateCopy(Inherited),

lib/Sema/TypeCheckAttr.cpp

Lines changed: 79 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,7 +1536,9 @@ void AttributeChecker::visitNonObjCAttr(NonObjCAttr *attr) {
15361536

15371537
static bool hasObjCImplementationFeature(Decl *D, ObjCImplementationAttr *attr,
15381538
Feature requiredFeature) {
1539-
if (D->getASTContext().LangOpts.hasFeature(requiredFeature))
1539+
auto &ctx = D->getASTContext();
1540+
1541+
if (ctx.LangOpts.hasFeature(requiredFeature))
15401542
return true;
15411543

15421544
// Allow the use of @_objcImplementation *without* Feature::ObjCImplementation
@@ -1547,29 +1549,81 @@ static bool hasObjCImplementationFeature(Decl *D, ObjCImplementationAttr *attr,
15471549

15481550
// Either you're using Feature::ObjCImplementation without the early adopter
15491551
// syntax, or you're using Feature::CImplementation. Either way, no go.
1550-
swift::diagnoseAndRemoveAttr(D, attr, diag::requires_experimental_feature,
1551-
attr->getAttrName(), attr->isDeclModifier(),
1552-
getFeatureName(requiredFeature));
1552+
ctx.Diags.diagnose(attr->getLocation(), diag::requires_experimental_feature,
1553+
attr->getAttrName(), attr->isDeclModifier(),
1554+
getFeatureName(requiredFeature));
15531555
return false;
15541556
}
15551557

1558+
static SourceRange getArgListRange(ASTContext &Ctx, DeclAttribute *attr) {
1559+
// attr->getRange() covers the attr name and argument list; adjust it to
1560+
// exclude the first token.
1561+
auto newStart = Lexer::getLocForEndOfToken(Ctx.SourceMgr,
1562+
attr->getRange().Start);
1563+
if (attr->getRange().contains(newStart)) {
1564+
return SourceRange(newStart, attr->getRange().End);
1565+
}
1566+
return SourceRange();
1567+
}
1568+
15561569
void AttributeChecker::
15571570
visitObjCImplementationAttr(ObjCImplementationAttr *attr) {
1571+
DeclAttribute * langAttr =
1572+
D->getAttrs().getAttribute<ObjCAttr>(/*AllowInvalid=*/true);
1573+
if (!langAttr)
1574+
langAttr = D->getAttrs().getAttribute<CDeclAttr>(/*AllowInvalid=*/true);
1575+
1576+
if (!langAttr) {
1577+
diagnose(attr->getLocation(), diag::attr_implementation_requires_language);
1578+
1579+
// If this is an extension, suggest '@objc'.
1580+
if (auto ED = dyn_cast<ExtensionDecl>(D)) {
1581+
auto diag = diagnose(attr->getLocation(),
1582+
diag::make_decl_objc_for_implementation,
1583+
D->getDescriptiveKind());
1584+
1585+
ObjCSelector correctSelector(Ctx, 0, {attr->CategoryName});
1586+
fixDeclarationObjCName(diag, ED, ObjCSelector(), correctSelector);
1587+
}
1588+
1589+
return;
1590+
}
1591+
15581592
if (auto ED = dyn_cast<ExtensionDecl>(D)) {
15591593
if (!hasObjCImplementationFeature(D, attr, Feature::ObjCImplementation))
15601594
return;
15611595

1596+
auto objcLangAttr = dyn_cast<ObjCAttr>(langAttr);
1597+
assert(objcLangAttr && "extension with @_cdecl or another lang attr???");
1598+
1599+
// Only early adopters should specify the category name on the attribute;
1600+
// the stabilized syntax uses @objc(CustomName) for that.
1601+
if (!attr->isEarlyAdopter() && !attr->CategoryName.empty()) {
1602+
auto diag = diagnose(attr->getLocation(),
1603+
diag::attr_implementation_category_goes_on_objc_attr);
1604+
1605+
ObjCSelector correctSelector(Ctx, 0, {attr->CategoryName});
1606+
auto argListRange = getArgListRange(Ctx, attr);
1607+
if (argListRange.isValid()) {
1608+
diag.fixItRemove(argListRange);
1609+
fixDeclarationObjCName(diag, ED, objcLangAttr->getName(),
1610+
correctSelector);
1611+
}
1612+
objcLangAttr->setName(correctSelector, /*implicit=*/false);
1613+
1614+
attr->setCategoryNameInvalid();
1615+
1616+
return;
1617+
}
1618+
15621619
if (ED->isConstrainedExtension())
15631620
diagnoseAndRemoveAttr(attr,
15641621
diag::attr_objc_implementation_must_be_unconditional);
15651622

15661623
auto CD = dyn_cast<ClassDecl>(ED->getExtendedNominal());
15671624
if (!CD) {
1568-
diagnoseAndRemoveAttr(attr,
1569-
diag::attr_objc_implementation_must_extend_class,
1570-
ED->getExtendedNominal());
1571-
ED->getExtendedNominal()->diagnose(diag::decl_declared_here,
1572-
ED->getExtendedNominal());
1625+
// This will be diagnosed as diag::objc_extension_not_class.
1626+
attr->setInvalid();
15731627
return;
15741628
}
15751629

@@ -1581,35 +1635,36 @@ visitObjCImplementationAttr(ObjCImplementationAttr *attr) {
15811635
}
15821636

15831637
if (!CD->hasSuperclass()) {
1584-
diagnoseAndRemoveAttr(attr, diag::attr_objc_implementation_must_have_super,
1585-
CD);
1638+
diagnoseAndRemoveAttr(attr,
1639+
diag::attr_objc_implementation_must_have_super, CD);
15861640
CD->diagnose(diag::decl_declared_here, CD);
15871641
return;
15881642
}
15891643

15901644
if (CD->isTypeErasedGenericClass()) {
1591-
diagnoseAndRemoveAttr(attr, diag::objc_implementation_cannot_have_generics,
1592-
CD);
1645+
diagnoseAndRemoveAttr(attr,
1646+
diag::objc_implementation_cannot_have_generics, CD);
15931647
CD->diagnose(diag::decl_declared_here, CD);
1648+
return;
15941649
}
15951650

15961651
if (!attr->isCategoryNameInvalid() && !ED->getImplementedObjCDecl()) {
15971652
diagnose(attr->getLocation(),
15981653
diag::attr_objc_implementation_category_not_found,
1599-
attr->CategoryName, CD);
1600-
1601-
// attr->getRange() covers the attr name and argument list; adjust it to
1602-
// exclude the first token.
1603-
auto newStart = Lexer::getLocForEndOfToken(Ctx.SourceMgr,
1604-
attr->getRange().Start);
1605-
if (attr->getRange().contains(newStart)) {
1606-
auto argListRange = SourceRange(newStart, attr->getRange().End);
1654+
ED->getObjCCategoryName(), CD);
1655+
1656+
SourceRange argListRange;
1657+
if (attr->CategoryName.empty())
1658+
argListRange = getArgListRange(Ctx, langAttr);
1659+
else
1660+
argListRange = getArgListRange(Ctx, attr);
1661+
if (argListRange.isValid()) {
16071662
diagnose(attr->getLocation(),
16081663
diag::attr_objc_implementation_fixit_remove_category_name)
16091664
.fixItRemove(argListRange);
16101665
}
16111666

1612-
attr->setCategoryNameInvalid();
1667+
attr->setInvalid();
16131668

16141669
return;
16151670
}
@@ -1634,12 +1689,8 @@ visitObjCImplementationAttr(ObjCImplementationAttr *attr) {
16341689
diagnose(attr->getLocation(),
16351690
diag::attr_objc_implementation_no_category_for_func, AFD);
16361691

1637-
// attr->getRange() covers the attr name and argument list; adjust it to
1638-
// exclude the first token.
1639-
auto newStart = Lexer::getLocForEndOfToken(Ctx.SourceMgr,
1640-
attr->getRange().Start);
1641-
if (attr->getRange().contains(newStart)) {
1642-
auto argListRange = SourceRange(newStart, attr->getRange().End);
1692+
auto argListRange = getArgListRange(Ctx, attr);
1693+
if (argListRange.isValid()) {
16431694
diagnostic.fixItRemove(argListRange);
16441695
}
16451696

lib/Sema/TypeCheckDecl.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -960,11 +960,6 @@ IsDynamicRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
960960
return true;
961961
}
962962

963-
// @_objcImplementation extension member implementations are implicitly
964-
// dynamic.
965-
if (decl->isObjCMemberImplementation())
966-
return true;
967-
968963
if (auto accessor = dyn_cast<AccessorDecl>(decl)) {
969964
// Runtime-replaceable accessors are dynamic when their storage declaration
970965
// is dynamic and they were explicitly defined or they are implicitly defined

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,11 @@ static bool isMemberOfObjCClassExtension(const ValueDecl *VD) {
12491249
return ext->getSelfClassDecl() && ext->getAttrs().hasAttribute<ObjCAttr>();
12501250
}
12511251

1252+
static bool isMemberOfObjCImplementationExtension(const ValueDecl *VD) {
1253+
return isMemberOfObjCClassExtension(VD) &&
1254+
cast<ExtensionDecl>(VD->getDeclContext())->isObjCImplementation();
1255+
}
1256+
12521257
/// Whether this declaration is a member of a class with the `@objcMembers`
12531258
/// attribute.
12541259
static bool isMemberOfObjCMembersClass(const ValueDecl *VD) {
@@ -1471,14 +1476,6 @@ static std::optional<ObjCReason> shouldMarkAsObjC(const ValueDecl *VD,
14711476
return ObjCReason(ObjCReason::MemberOfObjCProtocol);
14721477
}
14731478

1474-
// A member implementation of an @objcImplementation extension is @objc.
1475-
if (VD->isObjCMemberImplementation()) {
1476-
auto ext = VD->getDeclContext()->getAsDecl();
1477-
auto attr = ext->getAttrs()
1478-
.getAttribute<ObjCImplementationAttr>(/*AllowInvalid=*/true);
1479-
return ObjCReason(ObjCReason::MemberOfObjCImplementationExtension, attr);
1480-
}
1481-
14821479
// A @nonobjc is not @objc, even if it is an override of an @objc, so check
14831480
// for @nonobjc first.
14841481
if (VD->getAttrs().hasAttribute<NonObjCAttr>() ||
@@ -1487,10 +1484,23 @@ static std::optional<ObjCReason> shouldMarkAsObjC(const ValueDecl *VD,
14871484
.hasAttribute<NonObjCAttr>()))
14881485
return std::nullopt;
14891486

1490-
if (isMemberOfObjCClassExtension(VD) &&
1487+
if (isMemberOfObjCImplementationExtension(VD)) {
1488+
// A `final` member of an @objc @implementation extension is not @objc.
1489+
if (VD->isFinal())
1490+
return std::nullopt;
1491+
// Other members get a special reason.
1492+
if (canInferImplicitObjC(/*allowAnyAccess*/true)) {
1493+
auto ext = VD->getDeclContext()->getAsDecl();
1494+
auto attr = ext->getAttrs()
1495+
.getAttribute<ObjCImplementationAttr>(/*AllowInvalid=*/true);
1496+
return ObjCReason(ObjCReason::MemberOfObjCImplementationExtension, attr);
1497+
}
1498+
}
1499+
else if (isMemberOfObjCClassExtension(VD) &&
14911500
canInferImplicitObjC(/*allowAnyAccess*/true))
14921501
return ObjCReason(ObjCReason::MemberOfObjCExtension);
1493-
if (isMemberOfObjCMembersClass(VD) &&
1502+
1503+
if (isMemberOfObjCMembersClass(VD) &&
14941504
canInferImplicitObjC(/*allowAnyAccess*/false))
14951505
return ObjCReason(ObjCReason::MemberOfObjCMembersClass);
14961506

@@ -3849,9 +3859,13 @@ class ObjCImplementationChecker {
38493859
if (!isa<ExtensionDecl>(decl))
38503860
return;
38513861

3852-
diagnose(getAttr()->getLocation(),
3853-
diag::objc_implementation_early_spelling_deprecated)
3854-
.fixItReplace(getAttr()->getLocation(), "implementation");
3862+
auto diag = diagnose(getAttr()->getLocation(),
3863+
diag::objc_implementation_early_spelling_deprecated);
3864+
diag.fixItReplace(getAttr()->getRangeWithAt(), "@implementation");
3865+
3866+
ObjCSelector correctSelector(decl->getASTContext(), 0,
3867+
{getAttr()->CategoryName});
3868+
fixDeclarationObjCName(diag, decl, ObjCSelector(), correctSelector);
38553869
}
38563870
};
38573871
}

lib/Sema/TypeCheckObjC.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class ObjCReason {
7171
ExplicitlyGKInspectable,
7272
/// Is it a member of an @objc extension of a class.
7373
MemberOfObjCExtension,
74-
/// Is it a member of an \@\_objcImplementation extension.
74+
/// Is it a member of an \@objc \@implementation extension.
7575
MemberOfObjCImplementationExtension,
7676
/// Has an explicit '@objc' attribute added by an access note, rather than
7777
/// written in source code.

0 commit comments

Comments
 (0)