Skip to content

Commit 93188f8

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 2c335ee commit 93188f8

14 files changed

+143
-68
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
@@ -1980,10 +1980,16 @@ ERROR(objc_implementation_wrong_swift_name,none,
19801980
"you mean %1?",
19811981
(ObjCSelector, const ValueDecl *))
19821982

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

19881994
ERROR(objc_implementation_class_or_instance_mismatch,none,
19891995
"%kind0 does not match %kindonly1 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
@@ -4229,7 +4229,8 @@ void PrintAST::visitAccessorDecl(AccessorDecl *decl) {
42294229
Printer << getAccessorLabel(decl->getAccessorKind());
42304230

42314231
auto params = decl->getParameters();
4232-
if (params->size() != 0 && !params->get(0)->isImplicit()) {
4232+
if (params->size() != 0 && !params->get(0)->isImplicit()
4233+
&& Options.PrintExplicitAccessorParameters) {
42334234
auto Name = params->get(0)->getName();
42344235
if (!Name.empty()) {
42354236
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"
@@ -4038,18 +4039,38 @@ class ObjCImplementationChecker {
40384039

40394040
public:
40404041
void diagnoseUnmatchedRequirements() {
4042+
auto ext = dyn_cast<ExtensionDecl>(decl);
4043+
if (!ext)
4044+
return;
4045+
4046+
llvm::SmallString<128> stubs;
4047+
llvm::raw_svector_ostream stubStream(stubs);
4048+
4049+
unsigned numEmitted = 0;
4050+
40414051
for (auto req : unmatchedRequirements) {
40424052
// Ignore `@optional` protocol requirements.
40434053
if (isOptionalObjCProtocolRequirement(req))
40444054
continue;
40454055

4046-
auto ext = cast<IterableDeclContext>(req->getDeclContext()->getAsDecl())
4047-
->getImplementationContext();
4056+
if (numEmitted == 0) {
4057+
// Emit overall diagnostic for all the notes to attach to.
4058+
diagnose(ext, diag::objc_implementation_missing_impls,
4059+
getCategoryName(req->getDeclContext()));
4060+
}
4061+
4062+
numEmitted += 1;
4063+
diagnose(ext, diag::objc_implementation_missing_impl, req);
40484064

4049-
diagnose(ext->getDecl(), diag::objc_implementation_missing_impl,
4050-
getCategoryName(req->getDeclContext()), req);
4065+
// Append stub for this requirement into eventual fix-it.
4066+
swift::printRequirementStub(req, ext, ext->getSelfInterfaceType(),
4067+
ext->getStartLoc(), stubStream,
4068+
/*objCAttr=*/true);
4069+
}
40514070

4052-
// FIXME: Should give fix-it to add stub implementation
4071+
if (!stubs.empty()) {
4072+
diagnose(ext, diag::objc_implementation_missing_impls_fixit, numEmitted)
4073+
.fixItInsertAfter(ext->getBraces().Start, stubs);
40534074
}
40544075
}
40554076

lib/Sema/TypeCheckProtocol.cpp

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

37053705
bool swift::
37063706
printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
3707-
Type AdopterTy, SourceLoc TypeLoc, raw_ostream &OS) {
3707+
Type AdopterTy, SourceLoc TypeLoc, raw_ostream &OS,
3708+
bool withExplicitObjCAttr) {
37083709
if (isa<ConstructorDecl>(Requirement)) {
37093710
if (auto CD = Adopter->getSelfClassDecl()) {
37103711
if (!CD->isSemanticallyFinal() && isa<ExtensionDecl>(Adopter)) {
@@ -3731,15 +3732,35 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
37313732
ExtraIndentStreamPrinter Printer(OS, StubIndent);
37323733
Printer.printNewline();
37333734

3735+
PrintOptions Options = PrintOptions::printForDiagnostics(
3736+
AccessLevel::Private, Ctx.TypeCheckerOpts.PrintFullConvention);
3737+
Options.PrintDocumentationComments = false;
3738+
Options.PrintAccess = false;
3739+
Options.SkipAttributes = true;
3740+
Options.FunctionDefinitions = true;
3741+
Options.PrintAccessorBodiesInProtocols = true;
3742+
Options.PrintExplicitAccessorParameters = false;
3743+
Options.FullyQualifiedTypesIfAmbiguous = true;
3744+
3745+
if (withExplicitObjCAttr) {
3746+
if (auto runtimeName = Requirement->getObjCRuntimeName()) {
3747+
llvm::SmallString<32> scratch;
3748+
Printer.printAttrName("@objc");
3749+
Printer << "(" << runtimeName->getString(scratch) << ")";
3750+
Printer.printNewline();
3751+
Options.ExcludeAttrList.push_back(DeclAttrKind::ObjC);
3752+
}
3753+
}
3754+
37343755
AccessLevel Access =
37353756
std::min(
37363757
/* Access of the context */
37373758
Adopter->getSelfNominalTypeDecl()->getFormalAccess(),
37383759
/* Access of the protocol */
3739-
Requirement->getDeclContext()->getSelfProtocolDecl()->
3740-
getFormalAccess());
3741-
if (Access == AccessLevel::Public)
3742-
Printer << "public ";
3760+
Requirement->getDeclContext()->getSelfNominalTypeDecl()
3761+
->getFormalAccess());
3762+
if (Access > AccessLevel::Internal)
3763+
Printer.printKeyword(getAccessLevelSpelling(Access), Options, " ");
37433764

37443765
if (auto MissingTypeWitness = dyn_cast<AssociatedTypeDecl>(Requirement)) {
37453766
Printer << "typealias " << MissingTypeWitness->getName() << " = ";
@@ -3763,15 +3784,6 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
37633784
}
37643785
}
37653786

3766-
PrintOptions Options = PrintOptions::printForDiagnostics(
3767-
AccessLevel::Private, Ctx.TypeCheckerOpts.PrintFullConvention);
3768-
Options.PrintDocumentationComments = false;
3769-
Options.PrintAccess = false;
3770-
Options.SkipAttributes = true;
3771-
Options.FunctionDefinitions = true;
3772-
Options.PrintAccessorBodiesInProtocols = true;
3773-
Options.FullyQualifiedTypesIfAmbiguous = true;
3774-
37753787
bool AdopterIsClass = Adopter->getSelfClassDecl() != nullptr;
37763788
// Skip 'mutating' only inside classes: mutating methods usually
37773789
// 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) {}

test/decl/ext/objc_implementation_conflicts.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ import objc_implementation_private
108108
get { return 1 }
109109
}
110110

111+
@objc let readonlyPropertyFromHeader7: CInt
112+
111113
@objc fileprivate var propertyNotFromHeader2: CInt
112114
// OK, provides a nonpublic but ObjC-compatible stored property
113115

0 commit comments

Comments
 (0)