Skip to content

Commit 0f351e7

Browse files
committed
Stub fix-its for missing objcImpl requirements
Changes the diagnostics emitted when an `@objc @implementation` extension is missing some of the members required by the extension: • We now emit one error on the extension, plus a note for each missing member. • Where possible, we also emit a note with a fix-it adding stubs. For example: ``` 9 | @objc @implementation extension ObjCClass { | |- error: extension for main class interface does not provide all required implementations | |- note: missing instance method 'method(fromHeader3:)' | |- note: missing instance method 'method(fromHeader4:)' | |- note: missing property 'propertyFromHeader7' | |- note: missing property 'propertyFromHeader8' | |- note: missing property 'propertyFromHeader9' | |- note: missing instance method 'extensionMethod(fromHeader2:)' | `- note: add stubs for missing '@implementation' requirements ``` With a fix-it on the last note to insert the following after the open brace: ``` @objc(methodFromHeader3:) open func method(fromHeader3 param: Int32) { <#code#> } @objc(methodFromHeader4:) open func method(fromHeader4 param: Int32) { <#code#> } @objc(propertyFromHeader7) open var propertyFromHeader7: Int32 { get { <#code#> } set { <#code#> } } @objc(propertyFromHeader8) open var propertyFromHeader8: Int32 { get { <#code#> } set { <#code#> } } @objc(propertyFromHeader9) open var propertyFromHeader9: Int32 { get { <#code#> } set { <#code#> } } @objc(extensionMethodFromHeader2:) open func extensionMethod(fromHeader2 param: Int32) { <#code#> } ``` Fixes rdar://130038221.
1 parent 3aa1ed1 commit 0f351e7

12 files changed

+139
-66
lines changed

include/swift/AST/ASTPrinter.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,8 @@ class ExtraIndentStreamPrinter : public StreamPrinter {
413413
void printContext(raw_ostream &os, DeclContext *dc);
414414

415415
bool printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
416-
Type AdopterTy, SourceLoc TypeLoc, raw_ostream &OS);
416+
Type AdopterTy, SourceLoc TypeLoc, raw_ostream &OS,
417+
bool withExplicitObjCAttr = false);
417418

418419
/// Print a keyword or punctuator directly by its kind.
419420
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, tok keyword);

include/swift/AST/DiagnosticsSema.def

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1976,10 +1976,16 @@ ERROR(objc_implementation_wrong_swift_name,none,
19761976
"you mean %1?",
19771977
(ObjCSelector, const ValueDecl *))
19781978

1979-
ERROR(objc_implementation_missing_impl,none,
1980-
"extension for %select{main class interface|category %0}0 should "
1981-
"provide implementation for %kind1",
1982-
(Identifier, ValueDecl *))
1979+
ERROR(objc_implementation_missing_impls,none,
1980+
"extension for %select{main class interface|category %0}0 does not "
1981+
"provide all required implementations",
1982+
(Identifier))
1983+
NOTE(objc_implementation_missing_impls_fixit,none,
1984+
"add stub%s0 for missing '@implementation' requirement%s0",
1985+
(unsigned))
1986+
NOTE(objc_implementation_missing_impl,none,
1987+
"missing %kind0",
1988+
(ValueDecl *))
19831989

19841990
ERROR(objc_implementation_class_or_instance_mismatch,none,
19851991
"%kind0 does not match %1 declared in header",

include/swift/AST/PrintOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ struct PrintOptions {
172172
/// Whether to print the bodies of accessors in protocol context.
173173
bool PrintAccessorBodiesInProtocols = false;
174174

175+
/// Whether to print the parameter list of accessors like \c set . (Even when
176+
/// \c true , parameters marked implicit still won't be printed.)
177+
bool PrintExplicitAccessorParameters = true;
178+
175179
/// Whether to print type definitions.
176180
bool TypeDefinitions = false;
177181

lib/AST/ASTPrinter.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4228,7 +4228,8 @@ void PrintAST::visitAccessorDecl(AccessorDecl *decl) {
42284228
Printer << getAccessorLabel(decl->getAccessorKind());
42294229

42304230
auto params = decl->getParameters();
4231-
if (params->size() != 0 && !params->get(0)->isImplicit()) {
4231+
if (params->size() != 0 && !params->get(0)->isImplicit()
4232+
&& Options.PrintExplicitAccessorParameters) {
42324233
auto Name = params->get(0)->getName();
42334234
if (!Name.empty()) {
42344235
Printer << "(";

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "TypeCheckProtocol.h"
2020
#include "TypeChecker.h"
2121
#include "swift/AST/ASTContext.h"
22+
#include "swift/AST/ASTPrinter.h"
2223
#include "swift/AST/AvailabilityInference.h"
2324
#include "swift/AST/Decl.h"
2425
#include "swift/AST/ExistentialLayout.h"
@@ -4048,18 +4049,38 @@ class ObjCImplementationChecker {
40484049

40494050
public:
40504051
void diagnoseUnmatchedRequirements() {
4052+
auto ext = dyn_cast<ExtensionDecl>(decl);
4053+
if (!ext)
4054+
return;
4055+
4056+
llvm::SmallString<128> stubs;
4057+
llvm::raw_svector_ostream stubStream(stubs);
4058+
4059+
unsigned numEmitted = 0;
4060+
40514061
for (auto req : unmatchedRequirements) {
40524062
// Ignore `@optional` protocol requirements.
40534063
if (isOptionalObjCProtocolRequirement(req))
40544064
continue;
40554065

4056-
auto ext = cast<IterableDeclContext>(req->getDeclContext()->getAsDecl())
4057-
->getImplementationContext();
4066+
if (numEmitted == 0) {
4067+
// Emit overall diagnostic for all the notes to attach to.
4068+
diagnose(ext, diag::objc_implementation_missing_impls,
4069+
getCategoryName(req->getDeclContext()));
4070+
}
4071+
4072+
numEmitted += 1;
4073+
diagnose(ext, diag::objc_implementation_missing_impl, req);
40584074

4059-
diagnose(ext->getDecl(), diag::objc_implementation_missing_impl,
4060-
getCategoryName(req->getDeclContext()), req);
4075+
// Append stub for this requirement into eventual fix-it.
4076+
swift::printRequirementStub(req, ext, ext->getSelfInterfaceType(),
4077+
ext->getStartLoc(), stubStream,
4078+
/*objCAttr=*/true);
4079+
}
40614080

4062-
// FIXME: Should give fix-it to add stub implementation
4081+
if (!stubs.empty()) {
4082+
diagnose(ext, diag::objc_implementation_missing_impls_fixit, numEmitted)
4083+
.fixItInsertAfter(ext->getBraces().Start, stubs);
40634084
}
40644085
}
40654086

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3694,7 +3694,8 @@ static Type getTupleConformanceTypeWitness(DeclContext *dc,
36943694

36953695
bool swift::
36963696
printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
3697-
Type AdopterTy, SourceLoc TypeLoc, raw_ostream &OS) {
3697+
Type AdopterTy, SourceLoc TypeLoc, raw_ostream &OS,
3698+
bool withExplicitObjCAttr) {
36983699
if (isa<ConstructorDecl>(Requirement)) {
36993700
if (auto CD = Adopter->getSelfClassDecl()) {
37003701
if (!CD->isSemanticallyFinal() && isa<ExtensionDecl>(Adopter)) {
@@ -3721,15 +3722,35 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
37213722
ExtraIndentStreamPrinter Printer(OS, StubIndent);
37223723
Printer.printNewline();
37233724

3725+
PrintOptions Options = PrintOptions::printForDiagnostics(
3726+
AccessLevel::Private, Ctx.TypeCheckerOpts.PrintFullConvention);
3727+
Options.PrintDocumentationComments = false;
3728+
Options.PrintAccess = false;
3729+
Options.SkipAttributes = true;
3730+
Options.FunctionDefinitions = true;
3731+
Options.PrintAccessorBodiesInProtocols = true;
3732+
Options.PrintExplicitAccessorParameters = false;
3733+
Options.FullyQualifiedTypesIfAmbiguous = true;
3734+
3735+
if (withExplicitObjCAttr) {
3736+
if (auto runtimeName = Requirement->getObjCRuntimeName()) {
3737+
llvm::SmallString<32> scratch;
3738+
Printer.printAttrName("@objc");
3739+
Printer << "(" << runtimeName->getString(scratch) << ")";
3740+
Printer.printNewline();
3741+
Options.ExcludeAttrList.push_back(DeclAttrKind::ObjC);
3742+
}
3743+
}
3744+
37243745
AccessLevel Access =
37253746
std::min(
37263747
/* Access of the context */
37273748
Adopter->getSelfNominalTypeDecl()->getFormalAccess(),
37283749
/* Access of the protocol */
3729-
Requirement->getDeclContext()->getSelfProtocolDecl()->
3730-
getFormalAccess());
3731-
if (Access == AccessLevel::Public)
3732-
Printer << "public ";
3750+
Requirement->getDeclContext()->getSelfNominalTypeDecl()
3751+
->getFormalAccess());
3752+
if (Access > AccessLevel::Internal)
3753+
Printer.printKeyword(getAccessLevelSpelling(Access), Options, " ");
37333754

37343755
if (auto MissingTypeWitness = dyn_cast<AssociatedTypeDecl>(Requirement)) {
37353756
Printer << "typealias " << MissingTypeWitness->getName() << " = ";
@@ -3753,15 +3774,6 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
37533774
}
37543775
}
37553776

3756-
PrintOptions Options = PrintOptions::printForDiagnostics(
3757-
AccessLevel::Private, Ctx.TypeCheckerOpts.PrintFullConvention);
3758-
Options.PrintDocumentationComments = false;
3759-
Options.PrintAccess = false;
3760-
Options.SkipAttributes = true;
3761-
Options.FunctionDefinitions = true;
3762-
Options.PrintAccessorBodiesInProtocols = true;
3763-
Options.FullyQualifiedTypesIfAmbiguous = true;
3764-
37653777
bool AdopterIsClass = Adopter->getSelfClassDecl() != nullptr;
37663778
// Skip 'mutating' only inside classes: mutating methods usually
37673779
// don't have a sensible non-mutating implementation.

test/ClangImporter/rdar123543707.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import Module
77
import Module_Private.Sub4
88

99
@_objcImplementation extension Module {
10-
// expected-warning@-1 {{extension for main class interface should provide implementation for class method 'version()'}}
11-
// expected-warning@-2 {{extension for main class interface should provide implementation for class method 'alloc()'}}
10+
// expected-warning@-1 {{extension for main class interface does not provide all required implementations}}
11+
// expected-note@-2 {{missing class method 'version()'}}
12+
// expected-note@-3 {{missing class method 'alloc()'}}
13+
// expected-note@-4 {{add stubs for missing '@implementation' requirements}} {{40-40=\n @objc(version)\n open class func version() -> UnsafePointer<CChar>! {\n <#code#>\n \}\n\n @objc(alloc)\n open class func alloc() -> Self! {\n <#code#>\n \}\n}}
1214
}
1315

1416
extension Module: @retroactive ModuleProto {} // no-error

test/decl/ext/Inputs/objc_implementation.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
@property (readonly) int readonlyPropertyFromHeader4;
5454
@property (readonly) int readonlyPropertyFromHeader5;
5555
@property (readonly) int readonlyPropertyFromHeader6;
56+
@property (readonly) int readonlyPropertyFromHeader7;
5657

5758
+ (void)classMethod1:(int)param;
5859
+ (void)classMethod2:(int)param;

test/decl/ext/objc_implementation.swift

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55

66
protocol EmptySwiftProto {}
77

8+
// expected-note@+1 {{previously implemented here}}
89
@objc @implementation extension ObjCClass: EmptySwiftProto, EmptyObjCProto {
9-
// expected-note@-1 {{previously implemented here}}
10-
// expected-error@-2 {{extension for main class interface should provide implementation for instance method 'method(fromHeader4:)'}}
11-
// expected-error@-3 {{extension for main class interface should provide implementation for property 'propertyFromHeader9'}}
12-
// FIXME: give better diagnostic expected-error@-4 {{extension for main class interface should provide implementation for property 'propertyFromHeader8'}}
13-
// FIXME: give better diagnostic expected-error@-5 {{extension for main class interface should provide implementation for property 'propertyFromHeader7'}}
14-
// FIXME: give better diagnostic expected-error@-6 {{extension for main class interface should provide implementation for instance method 'method(fromHeader3:)'}}
15-
// expected-error@-7 {{'@objc @implementation' extension cannot add conformance to 'EmptySwiftProto'; add this conformance with an ordinary extension}}
16-
// expected-error@-8 {{'@objc @implementation' extension cannot add conformance to 'EmptyObjCProto'; add this conformance in the Objective-C header}}
17-
// expected-error@-9 {{extension for main class interface should provide implementation for instance method 'extensionMethod(fromHeader2:)'}}
10+
// expected-error@-1 {{'@objc @implementation' extension cannot add conformance to 'EmptySwiftProto'; add this conformance with an ordinary extension}}
11+
// expected-error@-2 {{'@objc @implementation' extension cannot add conformance to 'EmptyObjCProto'; add this conformance in the Objective-C header}}
12+
// expected-error@-3 {{extension for main class interface does not provide all required implementations}}
13+
// expected-note@-4 {{missing instance method 'method(fromHeader4:)'}} {{none}}
14+
// expected-note@-5 {{missing property 'propertyFromHeader9'}} {{none}}
15+
// FIXME: give better diagnostic expected-note@-6 {{missing property 'propertyFromHeader8'}} {{none}}
16+
// FIXME: give better diagnostic expected-note@-7 {{missing property 'propertyFromHeader7'}} {{none}}
17+
// FIXME: give better diagnostic expected-note@-8 {{missing instance method 'method(fromHeader3:)'}} {{none}}
18+
// expected-note@-9 {{missing instance method 'extensionMethod(fromHeader2:)'}} {{none}}
19+
// expected-note@-10 {{missing property 'readonlyPropertyFromHeader7'}}
20+
// expected-note@-11 {{add stubs for missing '@implementation' requirements}} {{77-77=\n @objc(methodFromHeader3:)\n open func method(fromHeader3 param: Int32) {\n <#code#>\n \}\n\n @objc(methodFromHeader4:)\n open func method(fromHeader4 param: Int32) {\n <#code#>\n \}\n\n @objc(propertyFromHeader7)\n open var propertyFromHeader7: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(propertyFromHeader8)\n open var propertyFromHeader8: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(propertyFromHeader9)\n open var propertyFromHeader9: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(readonlyPropertyFromHeader7)\n open var readonlyPropertyFromHeader7: Int32 {\n <#code#>\n \}\n\n @objc(extensionMethodFromHeader2:)\n open func extensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n}}
1821

1922
func method(fromHeader1: CInt) {
2023
// OK, provides an implementation for the header's method.
@@ -233,10 +236,12 @@ protocol EmptySwiftProto {}
233236
// expected-error@-1 {{property 'rdar122280735' of type '(() -> ()) -> Void' does not match type '(@escaping () -> Void) -> Void' declared by the header}}
234237
}
235238

239+
// expected-note@+1 {{'PresentAdditions' previously declared here}}
236240
@objc(PresentAdditions) @implementation extension ObjCClass {
237-
// expected-note@-1 {{'PresentAdditions' previously declared here}}
238-
// expected-error@-2 {{extension for category 'PresentAdditions' should provide implementation for instance method 'categoryMethod(fromHeader4:)'}}
239-
// FIXME: give better diagnostic expected-error@-3 {{extension for category 'PresentAdditions' should provide implementation for instance method 'categoryMethod(fromHeader3:)'}}
241+
// expected-error@-1 {{extension for category 'PresentAdditions' does not provide all required implementations}}
242+
// expected-note@-2 {{missing instance method 'categoryMethod(fromHeader4:)'}} {{none}}
243+
// FIXME: give better diagnostic expected-note@-3 {{missing instance method 'categoryMethod(fromHeader3:)'}} {{none}}
244+
// expected-note@-4 {{add stubs for missing '@implementation' requirements}} {{62-62=\n @objc(categoryMethodFromHeader3:)\n open func categoryMethod(fromHeader3 param: Int32) {\n <#code#>\n \}\n\n @objc(categoryMethodFromHeader4:)\n open func categoryMethod(fromHeader4 param: Int32) {\n <#code#>\n \}\n}}
240245

241246
func method(fromHeader3: CInt) {
242247
// FIXME: should emit expected-DISABLED-error@-1 {{instance method 'method(fromHeader3:)' should be implemented in extension for main class interface, not category 'PresentAdditions'}}
@@ -295,7 +300,9 @@ protocol EmptySwiftProto {}
295300
}
296301

297302
@objc(SwiftNameTests) @implementation extension ObjCClass {
298-
// expected-error@-1 {{extension for category 'SwiftNameTests' should provide implementation for instance method 'methodSwiftName6B()'}}
303+
// expected-error@-1 {{extension for category 'SwiftNameTests' does not provide all required implementations}}
304+
// expected-note@-2 {{missing instance method 'methodSwiftName6B()'}} {{none}}
305+
// expected-note@-3 {{add stub for missing '@implementation' requirement}} {{60-60=\n @objc(methodObjCName6B)\n open func methodSwiftName6B() {\n <#code#>\n \}\n}}
299306

300307
func methodSwiftName1() {
301308
// expected-error@-1 {{selector 'methodSwiftName1' for instance method 'methodSwiftName1()' not found in header; did you mean 'methodObjCName1'?}} {{3-3=@objc(methodObjCName1) }}
@@ -406,7 +413,9 @@ protocol EmptySwiftProto {}
406413
}
407414

408415
@objc(Conformance) @implementation extension ObjCClass {
409-
// expected-error@-1 {{extension for category 'Conformance' should provide implementation for instance method 'requiredMethod2()'}}
416+
// expected-error@-1 {{extension for category 'Conformance' does not provide all required implementations}}
417+
// expected-note@-2 {{missing instance method 'requiredMethod2()'}} {{none}}
418+
// expected-note@-3 {{add stub for missing '@implementation' requirement}} {{57-57=\n @objc(requiredMethod2)\n open func requiredMethod2() {\n <#code#>\n \}\n}}
410419
// no-error concerning 'optionalMethod2()'
411420

412421
func requiredMethod1() {}
@@ -437,7 +446,9 @@ protocol EmptySwiftProto {}
437446
}
438447

439448
@objc(InvalidMembers) @implementation extension ObjCClass {
440-
// expected-error@-1 {{extension for category 'InvalidMembers' should provide implementation for instance method 'unimplementedMember()'}}
449+
// expected-error@-1 {{extension for category 'InvalidMembers' does not provide all required implementations}}
450+
// expected-note@-2 {{missing instance method 'unimplementedMember()'}} {{none}}
451+
// expected-note@-3 {{add stub for missing '@implementation' requirement}} {{60-60=\n @objc(unimplementedMember)\n open func unimplementedMember() {\n <#code#>\n \}\n}}
441452

442453
func nonObjCMethod(_: EmptySwiftProto) {
443454
// expected-error@-1 {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}

test/decl/ext/objc_implementation_class_extension.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
@_implementationOnly import objc_implementation_class_extension_internal
55

66
@_objcImplementation extension ObjCClass {
7-
// expected-warning@-1 {{extension for main class interface should provide implementation for instance method 'method(fromHeader2:)'}}
8-
// expected-warning@-2 {{extension for main class interface should provide implementation for property 'propertyFromHeader2'}}
9-
// expected-warning@-3 {{extension for main class interface should provide implementation for instance method 'otherModuleExtensionMethod(fromHeader2:)'}}
10-
// expected-warning@-4 {{extension for main class interface should provide implementation for property 'otherModuleExtensionPropertyFromHeader2'}}
11-
// expected-warning@-5 {{extension for main class interface should provide implementation for instance method 'extensionMethod(fromHeader2:)'}}
12-
// expected-warning@-6 {{extension for main class interface should provide implementation for property 'extensionPropertyFromHeader2'}}
7+
// expected-warning@-1 {{extension for main class interface does not provide all required implementations}}
8+
// expected-note@-2 {{missing instance method 'method(fromHeader2:)'}} {{none}}
9+
// expected-note@-3 {{missing property 'propertyFromHeader2'}} {{none}}
10+
// expected-note@-4 {{missing instance method 'otherModuleExtensionMethod(fromHeader2:)'}} {{none}}
11+
// expected-note@-5 {{missing property 'otherModuleExtensionPropertyFromHeader2'}} {{none}}
12+
// expected-note@-6 {{missing instance method 'extensionMethod(fromHeader2:)'}} {{none}}
13+
// expected-note@-7 {{missing property 'extensionPropertyFromHeader2'}} {{none}}
14+
// expected-note@-8 {{add stubs for missing '@implementation' requirements}} {{43-43=\n @objc(methodFromHeader2:)\n open func method(fromHeader2 param: Int32) {\n <#code#>\n \}\n\n @objc(propertyFromHeader2)\n open var propertyFromHeader2: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(otherModuleExtensionMethodFromHeader2:)\n open func otherModuleExtensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n\n @objc(otherModuleExtensionPropertyFromHeader2)\n open var otherModuleExtensionPropertyFromHeader2: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(extensionMethodFromHeader2:)\n open func extensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n\n @objc(extensionPropertyFromHeader2)\n open var extensionPropertyFromHeader2: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n}}
1315

1416
@objc func method(fromHeader1: CInt) {}
1517
@objc private func method(fromHeader2: CInt) {}

0 commit comments

Comments
 (0)