Skip to content

Commit bab82dd

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, the notes have a fix-it adding a stub. 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(propertyFromHeader7) { <#code#> } } @objc(propertyFromHeader8) open var propertyFromHeader8: Int32 { get { <#code#> } set(propertyFromHeader8) { <#code#> } } @objc(propertyFromHeader9) open var propertyFromHeader9: Int32 { get { <#code#> } set(propertyFromHeader9) { <#code#> } } @objc(extensionMethodFromHeader2:) open func extensionMethod(fromHeader2 param: Int32) { <#code#> } ``` Fixes rdar://130038221.
1 parent 3aa1ed1 commit bab82dd

9 files changed

+129
-65
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",

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: 25 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,34 @@ 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.FullyQualifiedTypesIfAmbiguous = true;
3733+
3734+
if (withExplicitObjCAttr) {
3735+
if (auto runtimeName = Requirement->getObjCRuntimeName()) {
3736+
llvm::SmallString<32> scratch;
3737+
Printer.printAttrName("@objc");
3738+
Printer << "(" << runtimeName->getString(scratch) << ")";
3739+
Printer.printNewline();
3740+
Options.ExcludeAttrList.push_back(DeclAttrKind::ObjC);
3741+
}
3742+
}
3743+
37243744
AccessLevel Access =
37253745
std::min(
37263746
/* Access of the context */
37273747
Adopter->getSelfNominalTypeDecl()->getFormalAccess(),
37283748
/* Access of the protocol */
3729-
Requirement->getDeclContext()->getSelfProtocolDecl()->
3730-
getFormalAccess());
3731-
if (Access == AccessLevel::Public)
3732-
Printer << "public ";
3749+
Requirement->getDeclContext()->getSelfNominalTypeDecl()
3750+
->getFormalAccess());
3751+
if (Access > AccessLevel::Internal)
3752+
Printer.printKeyword(getAccessLevelSpelling(Access), Options, " ");
37333753

37343754
if (auto MissingTypeWitness = dyn_cast<AssociatedTypeDecl>(Requirement)) {
37353755
Printer << "typealias " << MissingTypeWitness->getName() << " = ";
@@ -3753,15 +3773,6 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
37533773
}
37543774
}
37553775

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-
37653776
bool AdopterIsClass = Adopter->getSelfClassDecl() != nullptr;
37663777
// Skip 'mutating' only inside classes: mutating methods usually
37673778
// 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/objc_implementation.swift

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@
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 {{extension for main class interface does not provide all required implementations}}
11+
// expected-note@-2 {{missing instance method 'method(fromHeader4:)'}} {{none}}
12+
// expected-note@-3 {{missing property 'propertyFromHeader9'}} {{none}}
13+
// FIXME: give better diagnostic expected-note@-4 {{missing property 'propertyFromHeader8'}} {{none}}
14+
// FIXME: give better diagnostic expected-note@-5 {{missing property 'propertyFromHeader7'}} {{none}}
15+
// FIXME: give better diagnostic expected-note@-6 {{missing instance method 'method(fromHeader3:)'}} {{none}}
16+
// expected-note@-7 {{missing instance method 'extensionMethod(fromHeader2:)'}} {{none}}
17+
// expected-note@-8 {{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(propertyFromHeader7) {\n <#code#>\n \}\n \}\n\n @objc(propertyFromHeader8)\n open var propertyFromHeader8: Int32 {\n get {\n <#code#>\n \}\n set(propertyFromHeader8) {\n <#code#>\n \}\n \}\n\n @objc(propertyFromHeader9)\n open var propertyFromHeader9: Int32 {\n get {\n <#code#>\n \}\n set(propertyFromHeader9) {\n <#code#>\n \}\n \}\n\n @objc(extensionMethodFromHeader2:)\n open func extensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n}}
18+
// expected-error@-9 {{'@objc @implementation' extension cannot add conformance to 'EmptySwiftProto'; add this conformance with an ordinary extension}}
19+
// expected-error@-10 {{'@objc @implementation' extension cannot add conformance to 'EmptyObjCProto'; add this conformance in the Objective-C header}}
1820

1921
func method(fromHeader1: CInt) {
2022
// OK, provides an implementation for the header's method.
@@ -233,10 +235,12 @@ protocol EmptySwiftProto {}
233235
// expected-error@-1 {{property 'rdar122280735' of type '(() -> ()) -> Void' does not match type '(@escaping () -> Void) -> Void' declared by the header}}
234236
}
235237

238+
// expected-note@+1 {{'PresentAdditions' previously declared here}}
236239
@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:)'}}
240+
// expected-error@-1 {{extension for category 'PresentAdditions' does not provide all required implementations}}
241+
// expected-note@-2 {{missing instance method 'categoryMethod(fromHeader4:)'}} {{none}}
242+
// FIXME: give better diagnostic expected-note@-3 {{missing instance method 'categoryMethod(fromHeader3:)'}} {{none}}
243+
// 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}}
240244

241245
func method(fromHeader3: CInt) {
242246
// 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 +299,9 @@ protocol EmptySwiftProto {}
295299
}
296300

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

300306
func methodSwiftName1() {
301307
// expected-error@-1 {{selector 'methodSwiftName1' for instance method 'methodSwiftName1()' not found in header; did you mean 'methodObjCName1'?}} {{3-3=@objc(methodObjCName1) }}
@@ -406,7 +412,9 @@ protocol EmptySwiftProto {}
406412
}
407413

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

412420
func requiredMethod1() {}
@@ -437,7 +445,9 @@ protocol EmptySwiftProto {}
437445
}
438446

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

442452
func nonObjCMethod(_: EmptySwiftProto) {
443453
// 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(propertyFromHeader2) {\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(otherModuleExtensionPropertyFromHeader2) {\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(extensionPropertyFromHeader2) {\n <#code#>\n \}\n \}\n}}
1315

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

0 commit comments

Comments
 (0)