From 7e2efe559138ff73c341366eca8d608ddd66ab17 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 6 Sep 2016 11:04:12 -0700 Subject: [PATCH] [Type checker] Infer @objc for protocol conformances in other extensions. Expand the scope of @objc inference for witnesses to encompass witnesses that are in a different extension from that of the conformance, including cases where one or the other is in the nominal type declaration itself. Fixes rdar://problem/26892526. --- lib/Sema/TypeCheckDecl.cpp | 5 +- lib/Sema/TypeCheckProtocol.cpp | 38 ++++++++++-- lib/Sema/TypeCheckType.cpp | 3 +- lib/Sema/TypeChecker.h | 6 +- .../doc_clang_module.swift.response | 5 ++ test/attr/attr_objc.swift | 60 +++++++++++++++++++ test/decl/protocol/req/optional.swift | 26 ++++---- 7 files changed, 115 insertions(+), 28 deletions(-) diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index 3b7492bef1baa..55c077762177e 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -2069,7 +2069,7 @@ static Optional shouldMarkAsObjC(TypeChecker &TC, // A witness to an @objc protocol requirement is implicitly @objc. else if (!TC.findWitnessedObjCRequirements( VD, - /*onlyFirstRequirement=*/true).empty()) + /*anySingleRequirement=*/true).empty()) return ObjCReason::WitnessToObjC; else if (VD->isInvalid()) return None; @@ -2280,8 +2280,7 @@ static void inferObjCName(TypeChecker &tc, ValueDecl *decl) { // requirements for which this declaration is a witness. Optional requirementObjCName; ValueDecl *firstReq = nullptr; - for (auto req : tc.findWitnessedObjCRequirements(decl, - /*onlyFirst=*/false)) { + for (auto req : tc.findWitnessedObjCRequirements(decl)) { // If this is the first requirement, take its name. if (!requirementObjCName) { requirementObjCName = req->getObjCRuntimeName(); diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 044e35bd5e497..44a85169b6b87 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -4875,7 +4875,7 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc, llvm::TinyPtrVector TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness, - bool onlyFirstRequirement) { + bool anySingleRequirement) { llvm::TinyPtrVector result; // Types don't infer @objc this way. @@ -4883,10 +4883,11 @@ TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness, auto dc = witness->getDeclContext(); auto name = witness->getFullName(); - for (auto conformance : dc->getLocalConformances(ConformanceLookupKind::All, - nullptr, /*sorted=*/true)) { + auto nominal = dc->getAsNominalTypeOrNominalTypeExtensionContext(); + if (!nominal) return result; + + for (auto proto : nominal->getAllProtocols()) { // We only care about Objective-C protocols. - auto proto = conformance->getProtocol(); if (!proto->isObjC()) continue; for (auto req : proto->lookupDirect(name, true)) { @@ -4895,16 +4896,41 @@ TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness, // Skip types. if (isa(req)) continue; + + // Dig out the conformance. + Optional conformance; + if (!conformance.hasValue()) { + SmallVector conformances; + nominal->lookupConformance(dc->getParentModule(), proto, + conformances); + if (conformances.size() == 1) + conformance = conformances.front(); + else + conformance = nullptr; + } + if (!*conformance) continue; // Determine whether the witness for this conformance is in fact // our witness. - if (conformance->getWitness(req, this).getDecl() == witness) { + if ((*conformance)->getWitness(req, this).getDecl() == witness) { result.push_back(req); - if (onlyFirstRequirement) return result; + if (anySingleRequirement) return result; } } } + // Sort the results. + if (result.size() > 2) { + std::stable_sort(result.begin(), result.end(), + [&](ValueDecl *lhs, ValueDecl *rhs) { + ProtocolDecl *lhsProto + = cast(lhs->getDeclContext()); + ProtocolDecl *rhsProto + = cast(rhs->getDeclContext()); + return ProtocolType::compareProtocols(&lhsProto, + &rhsProto) < 0; + }); + } return result; } diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index af2d629882d86..71a5ccf16176b 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -2712,8 +2712,7 @@ static void describeObjCReason(TypeChecker &TC, const ValueDecl *VD, TC.diagnose(overridden, diag::objc_overriding_objc_decl, kind, VD->getOverriddenDecl()->getFullName()); } else if (Reason == ObjCReason::WitnessToObjC) { - auto requirement = - TC.findWitnessedObjCRequirements(VD, /*onlyFirst=*/true).front(); + auto requirement = TC.findWitnessedObjCRequirements(VD).front(); TC.diagnose(requirement, diag::objc_witness_objc_requirement, VD->getDescriptiveKind(), requirement->getFullName(), cast(requirement->getDeclContext()) diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index 05b146a52f626..8cc593474df1b 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -1592,14 +1592,14 @@ class TypeChecker final : public LazyResolver { /// Find the @objc requirement that are witnessed by the given /// declaration. /// - /// \param onlyFirstRequirement If true, only returns the first such - /// requirement, rather than all of them. + /// \param anySingleRequirement If true, returns at most a single requirement, + /// which might be any of the requirements that match. /// /// \returns the set of requirements to which the given witness is a /// witness. llvm::TinyPtrVector findWitnessedObjCRequirements( const ValueDecl *witness, - bool onlyFirstRequirement); + bool anySingleRequirement = false); /// Mark any _ObjectiveCBridgeable conformances in the given type as "used". void useObjectiveCBridgeableConformances(DeclContext *dc, Type type); diff --git a/test/SourceKit/DocSupport/doc_clang_module.swift.response b/test/SourceKit/DocSupport/doc_clang_module.swift.response index 1e793ffe5f01a..658a4fce05b26 100644 --- a/test/SourceKit/DocSupport/doc_clang_module.swift.response +++ b/test/SourceKit/DocSupport/doc_clang_module.swift.response @@ -5073,6 +5073,11 @@ var FooSubUnnamedEnumeratorA1: Int { get } key.name: "init(rawValue:)", key.usr: "s:FPs9OptionSetcFT8rawValuewx8RawValue_x" }, + { + key.kind: source.lang.swift.ref.function.constructor, + key.name: "init(rawValue:)", + key.usr: "s:FPs9OptionSetcFT8rawValuewx8RawValue_x" + }, { key.kind: source.lang.swift.ref.function.constructor, key.name: "init(rawValue:)", diff --git a/test/attr/attr_objc.swift b/test/attr/attr_objc.swift index 535cd5923f0be..2d531063aef84 100644 --- a/test/attr/attr_objc.swift +++ b/test/attr/attr_objc.swift @@ -2117,3 +2117,63 @@ func ==(lhs: ObjC_Class1, rhs: ObjC_Class1) -> Bool { @objc protocol OperatorInProtocol { static func +(lhs: Self, rhs: Self) -> Self // expected-error {{@objc protocols may not have operator requirements}} } + +//===--- @objc inference for witnesses + +@objc protocol InferFromProtocol { + @objc(inferFromProtoMethod1:) + optional func method1(value: Int) +} + +// Infer when in the same declaration context. +// CHECK-LABEL: ClassInfersFromProtocol1 +class ClassInfersFromProtocol1 : InferFromProtocol{ + // CHECK: {{^}} @objc func method1(value: Int) + func method1(value: Int) { } +} + +// Infer when in a different declaration context of the same class. +// CHECK-LABEL: ClassInfersFromProtocol2a +class ClassInfersFromProtocol2a { + // CHECK: {{^}} @objc func method1(value: Int) + func method1(value: Int) { } +} + +extension ClassInfersFromProtocol2a : InferFromProtocol { } + +// Infer when in a different declaration context of the same class. +class ClassInfersFromProtocol2b : InferFromProtocol { } + +// CHECK-LABEL: ClassInfersFromProtocol2b +extension ClassInfersFromProtocol2b { + // CHECK: {{^}} @objc dynamic func method1(value: Int) + func method1(value: Int) { } +} + +// Don't infer when there is a signature mismatch. +// CHECK-LABEL: ClassInfersFromProtocol3 +class ClassInfersFromProtocol3 : InferFromProtocol { +} + +extension ClassInfersFromProtocol3 { + // CHECK: {{^}} func method1(value: String) + func method1(value: String) { } +} + +class SuperclassImplementsProtocol : InferFromProtocol { } + +// Note: no inference for subclasses +class SubclassInfersFromProtocol1 : SuperclassImplementsProtocol { + // CHECK: {{^}} func method1(value: Int) + func method1(value: Int) { } +} + +// Note: no inference for subclasses +class SubclassInfersFromProtocol2 : SuperclassImplementsProtocol { +} + +extension SubclassInfersFromProtocol2 { + // CHECK: {{^}} func method1(value: Int) + func method1(value: Int) { } +} + diff --git a/test/decl/protocol/req/optional.swift b/test/decl/protocol/req/optional.swift index fffe12915bbb7..4ef8e3533b8a3 100644 --- a/test/decl/protocol/req/optional.swift +++ b/test/decl/protocol/req/optional.swift @@ -6,11 +6,11 @@ @objc class ObjCClass { } @objc protocol P1 { - @objc optional func method(_ x: Int) // expected-note 2{{requirement 'method' declared here}} + @objc optional func method(_ x: Int) // expected-note {{requirement 'method' declared here}} - @objc optional var prop: Int { get } // expected-note{{requirement 'prop' declared here}} + @objc optional var prop: Int { get } - @objc optional subscript (i: Int) -> ObjCClass? { get } // expected-note{{requirement 'subscript' declared here}} + @objc optional subscript (i: Int) -> ObjCClass? { get } } @objc protocol P2 { @@ -39,10 +39,6 @@ class C2 : P1 { } } -// ----------------------------------------------------------------------- -// "Near" matches. -// ----------------------------------------------------------------------- - class C3 : P1 { func method(_ x: Int) { } @@ -71,20 +67,18 @@ extension C4 : P1 { } } +// ----------------------------------------------------------------------- +// Okay to match via extensions. +// ----------------------------------------------------------------------- + class C5 : P1 { } extension C5 { func method(_ x: Int) { } - // expected-warning@-1{{non-'@objc' method 'method' does not satisfy optional requirement of '@objc' protocol 'P1'}}{{3-3=@objc }} - // expected-note@-2{{add '@nonobjc' to silence this warning}}{{3-3=@nonobjc }} var prop: Int { return 5 } - // expected-warning@-1{{non-'@objc' property 'prop' does not satisfy optional requirement of '@objc' protocol 'P1'}}{{3-3=@objc }} - // expected-note@-2{{add '@nonobjc' to silence this warning}}{{3-3=@nonobjc }} subscript (i: Int) -> ObjCClass? { - // expected-warning@-1{{non-'@objc' subscript does not satisfy optional requirement of '@objc' protocol 'P1'}}{{3-3=@objc }} - // expected-note@-2{{add '@nonobjc' to silence this warning}}{{3-3=@nonobjc }} get { return nil } @@ -92,7 +86,7 @@ extension C5 { } } -// Note: @nonobjc suppresses warnings +// Note: @nonobjc suppresses witness match. class C6 { } extension C6 : P1 { @@ -108,6 +102,10 @@ extension C6 : P1 { } } +// ----------------------------------------------------------------------- +// "Near" matches. +// ----------------------------------------------------------------------- + // Note: warn about selector matches where the Swift names didn't match. @objc class C7 : P1 { // expected-note{{class 'C7' declares conformance to protocol 'P1' here}} @objc(method:) func otherMethod(x: Int) { } // expected-error{{Objective-C method 'method:' provided by method 'otherMethod(x:)' conflicts with optional requirement method 'method' in protocol 'P1'}}