Skip to content

Commit 61daca9

Browse files
authored
Merge pull request #69257 from beccadax/convenience-implementation
Improve support for Swift-only initializers in @_objcImplementation checker
2 parents 7c6dacb + 1b7adbe commit 61daca9

File tree

6 files changed

+148
-17
lines changed

6 files changed

+148
-17
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1841,6 +1841,25 @@ NOTE(objc_implementation_requirement_here,none,
18411841
"%kind0 declared in header here",
18421842
(ValueDecl *))
18431843

1844+
ERROR(objc_implementation_init_must_be_convenience, none,
1845+
"%kind0 is not valid in an '@_objcImplementation' extension because "
1846+
"Objective-C subclasses must be able to override "
1847+
"%select{designated|required}1 initializers",
1848+
(const ValueDecl *, /*isRequired=*/bool))
1849+
NOTE(objc_implementation_init_turn_designated_to_convenience, none,
1850+
"add 'convenience' keyword to make this a convenience initializer",
1851+
())
1852+
NOTE(objc_implementation_init_turn_required_to_convenience, none,
1853+
"replace 'required' keyword with 'convenience' to make this a convenience "
1854+
"initializer",
1855+
())
1856+
1857+
// Fallback diagnostic; super-general by nature.
1858+
ERROR(objc_implementation_member_requires_vtable, none,
1859+
"%kind0 is not valid in an '@_objcImplementation' extension because it "
1860+
"is an overridable Swift-only %kindonly0",
1861+
(const ValueDecl *))
1862+
18441863
ERROR(objc_implementation_multiple_matching_requirements,none,
18451864
"%kind0 could match several different members declared in the header",
18461865
(ValueDecl *))
@@ -6100,7 +6119,7 @@ ERROR(objc_extension_not_class,none,
61006119
"'@objc' can only be applied to an extension of a class", ())
61016120

61026121
// If you change this, also change enum ObjCReason
6103-
#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)|marked @objc by an access note}"
6122+
#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}"
61046123

61056124
WARNING(attribute_meaningless_when_nonobjc,none,
61066125
"'@%0' attribute is meaningless on a property that cannot be "

lib/AST/Decl.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1818,7 +1818,7 @@ Type ExtensionDecl::getExtendedType() const {
18181818
}
18191819

18201820
bool ExtensionDecl::isObjCImplementation() const {
1821-
return getAttrs().hasAttribute<ObjCImplementationAttr>();
1821+
return getAttrs().hasAttribute<ObjCImplementationAttr>(/*AllowInvalid=*/true);
18221822
}
18231823

18241824
llvm::Optional<Identifier>
@@ -4300,6 +4300,7 @@ isObjCMemberImplementation(const ValueDecl *VD,
43004300
? cast<AccessorDecl>(VD)->getStorage()
43014301
: VD;
43024302
return !attrDecl->isFinal()
4303+
&& !attrDecl->getAttrs().hasAttribute<NonObjCAttr>()
43034304
&& !attrDecl->getAttrs().hasAttribute<OverrideAttr>()
43044305
&& getAccessLevel() >= AccessLevel::Internal;
43054306
}

lib/Sema/TypeCheckDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ NeedsNewVTableEntryRequest::evaluate(Evaluator &evaluator,
11731173
AbstractFunctionDecl *decl) const {
11741174
auto *dc = decl->getDeclContext();
11751175

1176-
if (!isa<ClassDecl>(dc))
1176+
if (!isa<ClassDecl>(dc->getImplementedObjCContext()))
11771177
return false;
11781178

11791179
// Destructors always use a fixed vtable entry.

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ swift::behaviorLimitForObjCReason(ObjCReason reason, ASTContext &ctx) {
5151
case ObjCReason::WitnessToObjC:
5252
case ObjCReason::ImplicitlyObjC:
5353
case ObjCReason::MemberOfObjCExtension:
54+
case ObjCReason::MemberOfObjCImplementationExtension:
5455
return DiagnosticBehavior::Unspecified;
5556

5657
case ObjCReason::ExplicitlyIBInspectable:
@@ -88,6 +89,7 @@ unsigned swift::getObjCDiagnosticAttrKind(ObjCReason reason) {
8889
case ObjCReason::ExplicitlyIBInspectable:
8990
case ObjCReason::ExplicitlyGKInspectable:
9091
case ObjCReason::MemberOfObjCExtension:
92+
case ObjCReason::MemberOfObjCImplementationExtension:
9193
case ObjCReason::ExplicitlyObjCByAccessNote:
9294
return static_cast<unsigned>(reason);
9395

@@ -137,6 +139,7 @@ void ObjCReason::describe(const Decl *D) const {
137139
case ObjCReason::MemberOfObjCExtension:
138140
case ObjCReason::MemberOfObjCMembersClass:
139141
case ObjCReason::MemberOfObjCSubclass:
142+
case ObjCReason::MemberOfObjCImplementationExtension:
140143
case ObjCReason::ElementOfObjCEnum:
141144
case ObjCReason::Accessor:
142145
// No additional note required.
@@ -1463,9 +1466,12 @@ static llvm::Optional<ObjCReason> shouldMarkAsObjC(const ValueDecl *VD,
14631466
}
14641467

14651468
// A member implementation of an @objcImplementation extension is @objc.
1466-
if (VD->isObjCMemberImplementation())
1467-
// FIXME: New ObjCReason::Kind?
1468-
return ObjCReason(ObjCReason::ImplicitlyObjC);
1469+
if (VD->isObjCMemberImplementation()) {
1470+
auto ext = VD->getDeclContext()->getAsDecl();
1471+
auto attr = ext->getAttrs()
1472+
.getAttribute<ObjCImplementationAttr>(/*AllowInvalid=*/true);
1473+
return ObjCReason(ObjCReason::MemberOfObjCImplementationExtension, attr);
1474+
}
14691475

14701476
// A @nonobjc is not @objc, even if it is an override of an @objc, so check
14711477
// for @nonobjc first.
@@ -2952,6 +2958,58 @@ class ObjCImplementationChecker {
29522958
return nullptr;
29532959
}
29542960

2961+
void diagnoseVTableUse(ValueDecl *member) {
2962+
if (auto storage = dyn_cast<AbstractStorageDecl>(member)) {
2963+
for (auto accessor : storage->getAllAccessors()) {
2964+
diagnoseVTableUse(accessor);
2965+
}
2966+
return;
2967+
}
2968+
2969+
auto afd = dyn_cast_or_null<AbstractFunctionDecl>(member);
2970+
if (!afd || !afd->needsNewVTableEntry())
2971+
return;
2972+
2973+
// Don't diagnose if we already diagnosed an unrelated ObjC interop issue,
2974+
// like an un-representable type. If there's an `@objc` attribute on the
2975+
// member, this will be indicated by its `isInvalid()` bit; otherwise we'll
2976+
// use the enclosing extension's `@_objcImplementation` attribute.
2977+
DeclAttribute *attr = afd->getAttrs()
2978+
.getAttribute<ObjCAttr>(/*AllowInvalid=*/true);
2979+
if (!attr)
2980+
attr = member->getDeclContext()->getAsDecl()->getAttrs()
2981+
.getAttribute<ObjCImplementationAttr>(/*AllowInvalid=*/true);
2982+
assert(attr && "expected @_objcImplementation on context of member checked "
2983+
"by ObjCImplementationChecker");
2984+
if (attr->isInvalid())
2985+
return;
2986+
2987+
if (auto init = dyn_cast<ConstructorDecl>(afd)) {
2988+
if (!init->isObjC() && (init->isRequired() ||
2989+
!init->isConvenienceInit())) {
2990+
// Swift-only initializers have to be convenience.
2991+
diagnose(afd, diag::objc_implementation_init_must_be_convenience,
2992+
afd, init->isRequired());
2993+
2994+
// Add appropriate fix-it to 'convenience'.
2995+
if (auto requiredMod = init->getAttrs().getAttribute<RequiredAttr>())
2996+
diagnose(afd,
2997+
diag::objc_implementation_init_turn_required_to_convenience)
2998+
.fixItReplace(requiredMod->getRange(), "convenience");
2999+
else
3000+
diagnose(afd,
3001+
diag::objc_implementation_init_turn_designated_to_convenience)
3002+
.fixItInsert(afd->getAttributeInsertionLoc(/*forModifier=*/true),
3003+
"convenience ");
3004+
3005+
return;
3006+
}
3007+
}
3008+
3009+
// Emit a vague fallback diagnostic.
3010+
diagnose(afd, diag::objc_implementation_member_requires_vtable, afd);
3011+
}
3012+
29553013
void addRequirements(IterableDeclContext *idc) {
29563014
assert(idc->getDecl()->hasClangNode());
29573015
for (Decl *_member : idc->getMembers()) {
@@ -2992,8 +3050,12 @@ class ObjCImplementationChecker {
29923050

29933051
// Skip non-member implementations.
29943052
// FIXME: Should we consider them if they were only rejected for privacy?
2995-
if (!member->isObjCMemberImplementation())
3053+
if (!member->isObjCMemberImplementation()) {
3054+
// No member of an `@_objcImplementation` extension should need a vtable
3055+
// entry.
3056+
diagnoseVTableUse(member);
29963057
continue;
3058+
}
29973059

29983060
// `getExplicitObjCName()` is O(N) and would otherwise be used repeatedly
29993061
// in `matchRequirementsAtThreshold()`, so just precompute it.
@@ -3377,6 +3439,8 @@ class ObjCImplementationChecker {
33773439
case MatchOutcome::Match:
33783440
case MatchOutcome::MatchWithExplicitObjCName:
33793441
// Successful outcomes!
3442+
// If this member will require a vtable entry, diagnose that now.
3443+
diagnoseVTableUse(cand);
33803444
return;
33813445

33823446
case MatchOutcome::WrongImplicitObjCName:
@@ -3542,14 +3606,26 @@ class ObjCImplementationChecker {
35423606
diagnose(cand, diag::member_of_objc_implementation_not_objc_or_final,
35433607
cand, cand->getDeclContext()->getSelfClassDecl());
35443608

3545-
if (canBeRepresentedInObjC(cand))
3546-
diagnose(cand, diag::fixit_add_private_for_objc_implementation,
3547-
cand->getDescriptiveKind())
3548-
.fixItInsert(cand->getAttributeInsertionLoc(true), "private ");
3609+
if (canBeRepresentedInObjC(cand)) {
3610+
auto diagnostic =
3611+
diagnose(cand, diag::fixit_add_private_for_objc_implementation,
3612+
cand->getDescriptiveKind());
3613+
if (auto modifier = cand->getAttrs().getAttribute<AccessControlAttr>())
3614+
diagnostic.fixItReplace(modifier->getRange(), "private");
3615+
else
3616+
diagnostic.fixItInsert(cand->getAttributeInsertionLoc(true),
3617+
"private ");
3618+
}
35493619

3550-
diagnose(cand, diag::fixit_add_final_for_objc_implementation,
3551-
cand->getDescriptiveKind())
3552-
.fixItInsert(cand->getAttributeInsertionLoc(true), "final ");
3620+
// Initializers can't be 'final', but they can be '@nonobjc'
3621+
if (isa<ConstructorDecl>(cand))
3622+
diagnose(cand, diag::fixit_add_nonobjc_for_objc_implementation,
3623+
cand->getDescriptiveKind())
3624+
.fixItInsert(cand->getAttributeInsertionLoc(false), "@nonobjc ");
3625+
else
3626+
diagnose(cand, diag::fixit_add_final_for_objc_implementation,
3627+
cand->getDescriptiveKind())
3628+
.fixItInsert(cand->getAttributeInsertionLoc(true), "final ");
35533629
}
35543630
}
35553631
};

lib/Sema/TypeCheckObjC.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ 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.
75+
MemberOfObjCImplementationExtension,
7476
/// Has an explicit '@objc' attribute added by an access note, rather than
7577
/// written in source code.
7678
ExplicitlyObjCByAccessNote,
@@ -108,6 +110,7 @@ class ObjCReason {
108110
case ExplicitlyNSManaged:
109111
case ExplicitlyIBInspectable:
110112
case ExplicitlyGKInspectable:
113+
case MemberOfObjCImplementationExtension:
111114
case ExplicitlyObjCByAccessNote:
112115
return true;
113116

test/decl/ext/objc_implementation.swift

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ protocol EmptySwiftProto {}
114114

115115
internal var propertyNotFromHeader1: CInt
116116
// expected-warning@-1 {{property 'propertyNotFromHeader1' does not match any property declared in the headers for 'ObjCClass'; did you use the property's Swift name?; this will become an error before '@_objcImplementation' is stabilized}}
117-
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible property not declared in the header}} {{3-3=private }}
117+
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible property not declared in the header}} {{3-11=private}}
118118
// expected-note@-3 {{add 'final' to define a Swift property that cannot be overridden}} {{3-3=final }}
119119

120120
@objc private var propertyNotFromHeader2: CInt
@@ -183,6 +183,38 @@ protocol EmptySwiftProto {}
183183
class func instanceMethod2(_: CInt) {
184184
// expected-warning@-1 {{class method 'instanceMethod2' does not match instance method declared in header; this will become an error before '@_objcImplementation' is stabilized}} {{3-9=}}
185185
}
186+
187+
public init(notFromHeader1: CInt) {
188+
// expected-warning@-1 {{initializer 'init(notFromHeader1:)' does not match any initializer declared in the headers for 'ObjCClass'; did you use the initializer's Swift name?}}
189+
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible initializer not declared in the header}} {{3-9=private}}
190+
// expected-note@-3 {{add '@nonobjc' to define a Swift-only initializer}} {{3-3=@nonobjc }}
191+
}
192+
193+
public required init(notFromHeader2: CInt) {
194+
// expected-warning@-1 {{initializer 'init(notFromHeader2:)' does not match any initializer declared in the headers for 'ObjCClass'; did you use the initializer's Swift name?}}
195+
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible initializer not declared in the header}} {{3-9=private}}
196+
// expected-note@-3 {{add '@nonobjc' to define a Swift-only initializer}} {{3-3=@nonobjc }}
197+
}
198+
199+
public convenience init(notFromHeader3: CInt) {
200+
// expected-warning@-1 {{initializer 'init(notFromHeader3:)' does not match any initializer declared in the headers for 'ObjCClass'; did you use the initializer's Swift name?}}
201+
// expected-note@-2 {{add 'private' or 'fileprivate' to define an Objective-C-compatible initializer not declared in the header}} {{3-9=private}}
202+
// expected-note@-3 {{add '@nonobjc' to define a Swift-only initializer}} {{3-3=@nonobjc }}
203+
}
204+
205+
@nonobjc public init(notFromHeader4: CInt) {
206+
// expected-warning@-1 {{initializer 'init(notFromHeader4:)' is not valid in an '@_objcImplementation' extension because Objective-C subclasses must be able to override designated initializers}}
207+
// expected-note@-2 {{add 'convenience' keyword to make this a convenience initializer}} {{12-12=convenience }}
208+
}
209+
210+
@nonobjc public required init(notFromHeader5: CInt) {
211+
// expected-warning@-1 {{initializer 'init(notFromHeader5:)' is not valid in an '@_objcImplementation' extension because Objective-C subclasses must be able to override required initializers}}
212+
// expected-note@-2 {{replace 'required' keyword with 'convenience' to make this a convenience initializer}} {{19-27=convenience}}
213+
}
214+
215+
@nonobjc public convenience init(notFromHeader6: CInt) {
216+
// OK
217+
}
186218
}
187219

188220
@_objcImplementation(PresentAdditions) extension ObjCClass {
@@ -382,8 +414,8 @@ protocol EmptySwiftProto {}
382414
func nullableResult() -> Any { fatalError() } // expected-warning {{instance method 'nullableResult()' of type '() -> Any' does not match type '() -> Any?' declared by the header}}
383415
func nullableArgument(_: Any) {} // expected-warning {{instance method 'nullableArgument' of type '(Any) -> ()' does not match type '(Any?) -> Void' declared by the header}}
384416

385-
func nonPointerResult() -> CInt! { fatalError() } // expected-error{{method cannot be implicitly @objc because its result type cannot be represented in Objective-C}}
386-
func nonPointerArgument(_: CInt!) {} // expected-error {{method cannot be implicitly @objc because the type of the parameter cannot be represented in Objective-C}}
417+
func nonPointerResult() -> CInt! { fatalError() } // expected-error{{method cannot be in an @_objcImplementation extension of a class (without final or @nonobjc) because its result type cannot be represented in Objective-C}}
418+
func nonPointerArgument(_: CInt!) {} // expected-error {{method cannot be in an @_objcImplementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
387419
}
388420

389421
@_objcImplementation extension ObjCImplSubclass {

0 commit comments

Comments
 (0)