Skip to content

Commit 576a4ba

Browse files
authored
Merge pull request #72596 from beccadax/objcimpl-real-name
Add @implementation and feature flags for objcImpl
2 parents 4a0f380 + 05e93da commit 576a4ba

26 files changed

+226
-61
lines changed

docs/ReferenceGuides/UnderscoredAttributes.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,11 @@ of a header as non-`Sendable` so that you can make spot exceptions with
779779

780780
## `@_objcImplementation(CategoryName)`
781781

782+
A pre-stable form of `@implementation`. The main difference between them is that
783+
many things that are errors with `@implementation` are warnings with
784+
`@_objcImplementation`, which permitted workarounds for compiler bugs and
785+
changes in compiler behavior.
786+
782787
Declares an extension that defines an implementation for the Objective-C
783788
category `CategoryName` on the class in question, or for the main `@interface`
784789
if the argument list is omitted.
@@ -841,12 +846,6 @@ Notes:
841846

842847
* We don't currently plan to support ObjC generics.
843848

844-
* Eventually, we want the main `@_objcImplementation` extension to be able to
845-
declare stored properties that aren't in the interface. We also want
846-
`final` stored properties to be allowed to be resilent Swift types, but
847-
it's not clear how to achieve that without boxing them in `__SwiftValue`
848-
(which we might do as a stopgap).
849-
850849
* We should think about ObjC "direct" members, but that would probably
851850
require a way to spell this in Swift.
852851

include/swift/AST/ASTBridging.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,10 +625,10 @@ BridgedObjCAttr BridgedObjCAttr_createParsedSelector(
625625
BridgedArrayRef cNameLocs, BridgedArrayRef cNames,
626626
BridgedSourceLoc cRParenLoc);
627627

628-
SWIFT_NAME("BridgedObjCImplementationAttr.createParsed(_:atLoc:range:name:)")
628+
SWIFT_NAME("BridgedObjCImplementationAttr.createParsed(_:atLoc:range:name:isEarlyAdopter:)")
629629
BridgedObjCImplementationAttr BridgedObjCImplementationAttr_createParsed(
630630
BridgedASTContext cContext, BridgedSourceLoc cAtLoc,
631-
BridgedSourceRange cRange, BridgedIdentifier cName);
631+
BridgedSourceRange cRange, BridgedIdentifier cName, bool isEarlyAdopter);
632632

633633
SWIFT_NAME("BridgedObjCRuntimeNameAttr.createParsed(_:atLoc:range:name:)")
634634
BridgedObjCRuntimeNameAttr BridgedObjCRuntimeNameAttr_createParsed(

include/swift/AST/Attr.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,9 @@ class DeclAttribute : public AttributeBase {
184184
isUnchecked : 1
185185
);
186186

187-
SWIFT_INLINE_BITFIELD(ObjCImplementationAttr, DeclAttribute, 1,
188-
isCategoryNameInvalid : 1
187+
SWIFT_INLINE_BITFIELD(ObjCImplementationAttr, DeclAttribute, 2,
188+
isCategoryNameInvalid : 1,
189+
isEarlyAdopter : 1
189190
);
190191

191192
SWIFT_INLINE_BITFIELD(NonisolatedAttr, DeclAttribute, 1,
@@ -2424,11 +2425,19 @@ class ObjCImplementationAttr final : public DeclAttribute {
24242425
Identifier CategoryName;
24252426

24262427
ObjCImplementationAttr(Identifier CategoryName, SourceLoc AtLoc,
2427-
SourceRange Range, bool Implicit = false,
2428+
SourceRange Range, bool isEarlyAdopter = false,
2429+
bool Implicit = false,
24282430
bool isCategoryNameInvalid = false)
24292431
: DeclAttribute(DeclAttrKind::ObjCImplementation, AtLoc, Range, Implicit),
24302432
CategoryName(CategoryName) {
24312433
Bits.ObjCImplementationAttr.isCategoryNameInvalid = isCategoryNameInvalid;
2434+
Bits.ObjCImplementationAttr.isEarlyAdopter = isEarlyAdopter;
2435+
}
2436+
2437+
/// Early adopters use the \c \@_objcImplementation spelling. For backwards
2438+
/// compatibility, issues with them are diagnosed as warnings, not errors.
2439+
bool isEarlyAdopter() const {
2440+
return Bits.ObjCImplementationAttr.isEarlyAdopter;
24322441
}
24332442

24342443
bool isCategoryNameInvalid() const {

include/swift/AST/DeclAttr.def

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,10 @@ SIMPLE_DECL_ATTR(_staticInitializeObjCMetadata, StaticInitializeObjCMetadata,
188188
DECL_ATTR(_restatedObjCConformance, RestatedObjCConformance,
189189
OnProtocol | UserInaccessible | LongAttribute | RejectByParser | NotSerialized | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
190190
70)
191-
DECL_ATTR(_objcImplementation, ObjCImplementation,
191+
DECL_ATTR(implementation, ObjCImplementation,
192192
OnExtension | OnAbstractFunction | UserInaccessible | ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove,
193193
72)
194+
DECL_ATTR_ALIAS(_objcImplementation, ObjCImplementation)
194195
DECL_ATTR(_optimize, Optimize,
195196
OnAbstractFunction | OnSubscript | OnVar | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
196197
73)

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,6 +1739,9 @@ ERROR(swift_native_objc_runtime_base_not_on_root_class,none,
17391739
"@_swift_native_objc_runtime_base_not_on_root_class can only be applied "
17401740
"to root classes", ())
17411741

1742+
WARNING(objc_implementation_early_spelling_deprecated,none,
1743+
"'@_objcImplementation' is deprecated; use '@implementation' instead",
1744+
())
17421745
ERROR(attr_objc_implementation_must_be_unconditional,none,
17431746
"only unconditional extensions can implement an Objective-C '@interface'",
17441747
())
@@ -1917,8 +1920,7 @@ NOTE(objc_implementation_one_matched_requirement,none,
19171920
(ValueDecl *, ObjCSelector, bool, StringRef))
19181921

19191922
WARNING(wrap_objc_implementation_will_become_error,none,
1920-
"%0; this will become an error before '@_objcImplementation' is "
1921-
"stabilized",
1923+
"%0; this will become an error after adopting '@implementation'",
19221924
(DiagnosticInfo *))
19231925

19241926
ERROR(cdecl_not_at_top_level,none,

include/swift/Basic/Features.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,12 @@ EXPERIMENTAL_FEATURE(IsolatedAny2, true)
367367
// Enable usability improvements for global-actor-isolated types.
368368
EXPERIMENTAL_FEATURE(GlobalActorIsolatedTypesUsability, false)
369369

370+
// Enable @implementation on extensions of ObjC classes.
371+
EXPERIMENTAL_FEATURE(ObjCImplementation, true)
372+
373+
// Enable @implementation on @_cdecl functions.
374+
EXPERIMENTAL_FEATURE(CImplementation, true)
375+
370376
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
371377
#undef EXPERIMENTAL_FEATURE
372378
#undef UPCOMING_FEATURE

lib/AST/ASTBridging.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -662,9 +662,10 @@ BridgedObjCAttr BridgedObjCAttr_createParsedSelector(
662662

663663
BridgedObjCImplementationAttr BridgedObjCImplementationAttr_createParsed(
664664
BridgedASTContext cContext, BridgedSourceLoc cAtLoc,
665-
BridgedSourceRange cRange, BridgedIdentifier cName) {
665+
BridgedSourceRange cRange, BridgedIdentifier cName, bool isEarlyAdopter) {
666666
return new (cContext.unbridged()) ObjCImplementationAttr(
667-
cName.unbridged(), cAtLoc.unbridged(), cRange.unbridged());
667+
cName.unbridged(), cAtLoc.unbridged(), cRange.unbridged(),
668+
isEarlyAdopter);
668669
}
669670

670671
BridgedObjCRuntimeNameAttr BridgedObjCRuntimeNameAttr_createParsed(

lib/AST/ASTDumper.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,16 @@ namespace {
10841084

10851085
printFlag(D->isImplicit(), "implicit", DeclModifierColor);
10861086
printFlag(D->isHoisted(), "hoisted", DeclModifierColor);
1087+
1088+
if (auto implAttr = D->getAttrs().getAttribute<ObjCImplementationAttr>()) {
1089+
StringRef label =
1090+
implAttr->isEarlyAdopter() ? "objc_impl" : "clang_impl";
1091+
if (implAttr->CategoryName.empty())
1092+
printFlag(label);
1093+
else
1094+
printFieldQuoted(implAttr->CategoryName.str(), label);
1095+
}
1096+
10871097
printSourceRange(D->getSourceRange(), &D->getASTContext());
10881098
printFlag(D->TrailingSemiLoc.isValid(), "trailing_semi",
10891099
DeclModifierColor);

lib/AST/Attr.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1820,7 +1820,9 @@ StringRef DeclAttribute::getAttrName() const {
18201820
case DeclAttrKind::ObjCRuntimeName:
18211821
return "objc";
18221822
case DeclAttrKind::ObjCImplementation:
1823-
return "_objcImplementation";
1823+
if (cast<ObjCImplementationAttr>(this)->isEarlyAdopter())
1824+
return "_objcImplementation";
1825+
return "implementation";
18241826
case DeclAttrKind::MainType:
18251827
return "main";
18261828
case DeclAttrKind::DynamicReplacement:

lib/AST/FeatureSet.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,9 @@ static bool usesFeatureGlobalActorIsolatedTypesUsability(Decl *decl) {
687687
return false;
688688
}
689689

690+
UNINTERESTING_FEATURE(ObjCImplementation)
691+
UNINTERESTING_FEATURE(CImplementation)
692+
690693
// ----------------------------------------------------------------------------
691694
// MARK: - FeatureSet
692695
// ----------------------------------------------------------------------------

lib/ASTGen/Sources/ASTGen/DeclAttrs.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -732,19 +732,25 @@ extension ASTGenVisitor {
732732
}
733733

734734
func generateObjCImplementationAttr(attribute node: AttributeSyntax) -> BridgedObjCImplementationAttr? {
735-
let name: BridgedIdentifier? = self.generateSingleAttrOption(attribute: node) {
736-
self.generateIdentifier($0)
737-
}
735+
let name: BridgedIdentifier? = self.generateSingleAttrOption(
736+
attribute: node,
737+
self.generateIdentifier,
738+
valueIfOmitted: BridgedIdentifier()
739+
)
738740
guard let name else {
739-
// TODO: Diagnose.
741+
// Should be diagnosed by `generateSingleAttrOption`.
740742
return nil
741743
}
742744

745+
let attrName = node.attributeName.as(IdentifierTypeSyntax.self)?.name.text
746+
let isEarlyAdopter = attrName != "implementation"
747+
743748
return .createParsed(
744749
self.ctx,
745750
atLoc: self.generateSourceLoc(node.atSign),
746751
range: self.generateSourceRange(node),
747-
name: name
752+
name: name,
753+
isEarlyAdopter: isEarlyAdopter
748754
)
749755
}
750756

@@ -1060,6 +1066,11 @@ extension ASTGenVisitor {
10601066
return nil
10611067
}
10621068

1069+
if case .token(let tok) = arguments {
1070+
// Special case: was parsed as a token, not an an argument list
1071+
return valueGeneratorFunction(tok)
1072+
}
1073+
10631074
guard var arguments = arguments.as(LabeledExprListSyntax.self)?[...] else {
10641075
// TODO: Diagnose.
10651076
return nil

lib/Parse/ParseDecl.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3640,7 +3640,9 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
36403640
if (!name)
36413641
return makeParserSuccess();
36423642

3643-
Attributes.add(new (Context) ObjCImplementationAttr(*name, AtLoc, range));
3643+
bool isEarlyAdopter = (AttrName != "implementation");
3644+
Attributes.add(new (Context) ObjCImplementationAttr(*name, AtLoc, range,
3645+
isEarlyAdopter));
36443646
break;
36453647
}
36463648
case DeclAttrKind::ObjCRuntimeName: {

lib/Sema/TypeCheckAttr.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,9 +1516,31 @@ void AttributeChecker::visitNonObjCAttr(NonObjCAttr *attr) {
15161516
}
15171517
}
15181518

1519+
static bool hasObjCImplementationFeature(Decl *D, ObjCImplementationAttr *attr,
1520+
Feature requiredFeature) {
1521+
if (D->getASTContext().LangOpts.hasFeature(requiredFeature))
1522+
return true;
1523+
1524+
// Allow the use of @_objcImplementation *without* Feature::ObjCImplementation
1525+
// as long as you're using the early adopter syntax. (Avoids breaking existing
1526+
// adopters.)
1527+
if (requiredFeature == Feature::ObjCImplementation && attr->isEarlyAdopter())
1528+
return true;
1529+
1530+
// Either you're using Feature::ObjCImplementation without the early adopter
1531+
// syntax, or you're using Feature::CImplementation. Either way, no go.
1532+
swift::diagnoseAndRemoveAttr(D, attr, diag::requires_experimental_feature,
1533+
attr->getAttrName(), attr->isDeclModifier(),
1534+
getFeatureName(requiredFeature));
1535+
return false;
1536+
}
1537+
15191538
void AttributeChecker::
15201539
visitObjCImplementationAttr(ObjCImplementationAttr *attr) {
15211540
if (auto ED = dyn_cast<ExtensionDecl>(D)) {
1541+
if (!hasObjCImplementationFeature(D, attr, Feature::ObjCImplementation))
1542+
return;
1543+
15221544
if (ED->isConstrainedExtension())
15231545
diagnoseAndRemoveAttr(attr,
15241546
diag::attr_objc_implementation_must_be_unconditional);
@@ -1575,6 +1597,9 @@ visitObjCImplementationAttr(ObjCImplementationAttr *attr) {
15751597
}
15761598
}
15771599
else if (auto AFD = dyn_cast<AbstractFunctionDecl>(D)) {
1600+
if (!hasObjCImplementationFeature(D, attr, Feature::CImplementation))
1601+
return;
1602+
15781603
if (!attr->CategoryName.empty()) {
15791604
auto diagnostic =
15801605
diagnose(attr->getLocation(),

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2803,16 +2803,26 @@ fixDeclarationStaticSpelling(InFlightDiagnostic &diag, ValueDecl *VD,
28032803

28042804
namespace {
28052805
class ObjCImplementationChecker {
2806-
DiagnosticEngine &diags;
2806+
Decl *decl;
2807+
2808+
ObjCImplementationAttr *getAttr() const {
2809+
return decl->getAttrs()
2810+
.getAttribute<ObjCImplementationAttr>(/*AllowInvalid=*/true);
2811+
}
28072812

28082813
template<typename Loc, typename ...ArgTypes>
28092814
InFlightDiagnostic diagnose(Loc loc, Diag<ArgTypes...> diagID,
28102815
typename detail::PassArgument<ArgTypes>::type... Args) {
2816+
hasDiagnosed = true;
2817+
2818+
auto &diags = decl->getASTContext().Diags;
28112819
auto diag = diags.diagnose(loc, diagID, std::forward<ArgTypes>(Args)...);
28122820

2813-
// WORKAROUND (5.9): Soften newly-introduced errors to make things easier
2814-
// for early adopters.
2815-
if (diags.declaredDiagnosticKindFor(diagID.ID) == DiagnosticKind::Error)
2821+
// Early adopters using the '@_objcImplementation' syntax may have had the
2822+
// ObjCImplementationChecker evolve out from under them. Soften their errors
2823+
// to warnings so we don't break their projects.
2824+
if (getAttr()->isEarlyAdopter()
2825+
&& diags.declaredDiagnosticKindFor(diagID.ID) == DiagnosticKind::Error)
28162826
diag.wrapIn(diag::wrap_objc_implementation_will_become_error);
28172827

28182828
return diag;
@@ -2823,9 +2833,11 @@ class ObjCImplementationChecker {
28232833
/// Candidates with their explicit ObjC names, if any.
28242834
llvm::SmallDenseMap<ValueDecl *, ObjCSelector, 16> unmatchedCandidates;
28252835

2836+
bool hasDiagnosed = false;
2837+
28262838
public:
28272839
ObjCImplementationChecker(Decl *D)
2828-
: diags(D->getASTContext().Diags)
2840+
: decl(D), hasDiagnosed(getAttr()->isInvalid())
28292841
{
28302842
assert(!D->hasClangNode() && "passed interface, not impl, to checker");
28312843

@@ -3599,6 +3611,31 @@ class ObjCImplementationChecker {
35993611
.fixItInsert(cand->getAttributeInsertionLoc(true), "final ");
36003612
}
36013613
}
3614+
3615+
void diagnoseEarlyAdopterDeprecation() {
3616+
// Only encourage use of @implementation for early adopters, and only when
3617+
// there are no mismatches that they might be working around with it.
3618+
if (hasDiagnosed || !getAttr()->isEarlyAdopter())
3619+
return;
3620+
3621+
// Only encourage adoption if the corresponding language feature is enabled.
3622+
if (isa<ExtensionDecl>(decl) &&
3623+
!decl->getASTContext().LangOpts.hasFeature(Feature::ObjCImplementation))
3624+
return;
3625+
3626+
if (isa<AbstractFunctionDecl>(decl) &&
3627+
!decl->getASTContext().LangOpts.hasFeature(Feature::CImplementation))
3628+
return;
3629+
3630+
// Only encourage @_objcImplementation *extension* adopters to adopt
3631+
// @implementation; @_objcImplementation @_cdecl hasn't been stabilized yet.
3632+
if (!isa<ExtensionDecl>(decl))
3633+
return;
3634+
3635+
diagnose(getAttr()->getLocation(),
3636+
diag::objc_implementation_early_spelling_deprecated)
3637+
.fixItReplace(getAttr()->getLocation(), "implementation");
3638+
}
36023639
};
36033640
}
36043641

@@ -3618,6 +3655,7 @@ evaluate(Evaluator &evaluator, Decl *D) const {
36183655
checker.matchRequirements();
36193656
checker.diagnoseUnmatchedCandidates();
36203657
checker.diagnoseUnmatchedRequirements();
3658+
checker.diagnoseEarlyAdopterDeprecation();
36213659

36223660
return evaluator::SideEffect();
36233661
}

lib/Serialization/Deserialization.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6127,13 +6127,15 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
61276127
case decls_block::ObjCImplementation_DECL_ATTR: {
61286128
bool isImplicit;
61296129
bool isCategoryNameInvalid;
6130+
bool isEarlyAdopter;
61306131
uint64_t categoryNameID;
61316132
serialization::decls_block::ObjCImplementationDeclAttrLayout::
61326133
readRecord(scratch, isImplicit, isCategoryNameInvalid,
6133-
categoryNameID);
6134+
isEarlyAdopter, categoryNameID);
61346135
Identifier categoryName = MF.getIdentifier(categoryNameID);
61356136
Attr = new (ctx) ObjCImplementationAttr(categoryName, SourceLoc(),
6136-
SourceRange(), isImplicit,
6137+
SourceRange(), isEarlyAdopter,
6138+
isImplicit,
61376139
isCategoryNameInvalid);
61386140
break;
61396141
}

lib/Serialization/ModuleFormat.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5858
/// describe what change you made. The content of this comment isn't important;
5959
/// it just ensures a conflict if two people change the module format.
6060
/// Don't worry about adhering to the 80-column limit for this line.
61-
const uint16_t SWIFTMODULE_VERSION_MINOR = 868; // end_apply result
61+
const uint16_t SWIFTMODULE_VERSION_MINOR = 869; // @implementation
6262

6363
/// A standard hash seed used for all string hashes in a serialized module.
6464
///
@@ -2314,6 +2314,7 @@ namespace decls_block {
23142314
ObjCImplementation_DECL_ATTR,
23152315
BCFixed<1>, // implicit flag
23162316
BCFixed<1>, // category name invalid
2317+
BCFixed<1>, // is early adopter
23172318
IdentifierIDField // category name
23182319
>;
23192320

lib/Serialization/Serialization.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2947,7 +2947,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
29472947
S.DeclTypeAbbrCodes[ObjCImplementationDeclAttrLayout::Code];
29482948
ObjCImplementationDeclAttrLayout::emitRecord(S.Out, S.ScratchRecord,
29492949
abbrCode, theAttr->isImplicit(), theAttr->isCategoryNameInvalid(),
2950-
categoryNameID);
2950+
theAttr->isEarlyAdopter(), categoryNameID);
29512951
return;
29522952
}
29532953

test/ASTGen/attrs.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,8 @@ func testMutating(value: S3) {
5757
class C1 {}
5858
@_alignment(7) // expected-error {{alignment value must be a power of two}}
5959
struct S4 {}
60+
61+
@implementation extension ObjCClass1 {} // expected-error {{cannot find type 'ObjCClass1' in scope}}
62+
@implementation(Category) extension ObjCClass1 {} // expected-error {{cannot find type 'ObjCClass1' in scope}}
63+
@_objcImplementation extension ObjCClass2 {} // expected-error {{cannot find type 'ObjCClass2' in scope}}
64+
@_objcImplementation(Category) extension ObjCClass2 {} // expected-error {{cannot find type 'ObjCClass2' in scope}}

0 commit comments

Comments
 (0)