Skip to content

Commit 86e4a12

Browse files
authored
Merge pull request #4653 from DougGregor/objc-inference-3-0
[3.0] Extend inference of `@objc` to conformances in extensions and superclasses
2 parents 7c123de + 1c8e4ba commit 86e4a12

File tree

7 files changed

+136
-29
lines changed

7 files changed

+136
-29
lines changed

lib/Sema/TypeCheckDecl.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,7 +2058,7 @@ static Optional<ObjCReason> shouldMarkAsObjC(TypeChecker &TC,
20582058
// A witness to an @objc protocol requirement is implicitly @objc.
20592059
else if (!TC.findWitnessedObjCRequirements(
20602060
VD,
2061-
/*onlyFirstRequirement=*/true).empty())
2061+
/*anySingleRequirement=*/true).empty())
20622062
return ObjCReason::WitnessToObjC;
20632063
else if (VD->isInvalid())
20642064
return None;
@@ -2269,8 +2269,7 @@ static void inferObjCName(TypeChecker &tc, ValueDecl *decl) {
22692269
// requirements for which this declaration is a witness.
22702270
Optional<ObjCSelector> requirementObjCName;
22712271
ValueDecl *firstReq = nullptr;
2272-
for (auto req : tc.findWitnessedObjCRequirements(decl,
2273-
/*onlyFirst=*/false)) {
2272+
for (auto req : tc.findWitnessedObjCRequirements(decl)) {
22742273
// If this is the first requirement, take its name.
22752274
if (!requirementObjCName) {
22762275
requirementObjCName = req->getObjCRuntimeName();

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ matchWitness(TypeChecker &tc,
899899
static RequirementMatch
900900
matchWitness(TypeChecker &tc,
901901
ProtocolDecl *proto,
902-
NormalProtocolConformance *conformance,
902+
ProtocolConformance *conformance,
903903
DeclContext *dc, ValueDecl *req, ValueDecl *witness) {
904904
using namespace constraints;
905905

@@ -4838,18 +4838,19 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
48384838

48394839
llvm::TinyPtrVector<ValueDecl *>
48404840
TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,
4841-
bool onlyFirstRequirement) {
4841+
bool anySingleRequirement) {
48424842
llvm::TinyPtrVector<ValueDecl *> result;
48434843

48444844
// Types don't infer @objc this way.
48454845
if (isa<TypeDecl>(witness)) return result;
48464846

48474847
auto dc = witness->getDeclContext();
48484848
auto name = witness->getFullName();
4849-
for (auto conformance : dc->getLocalConformances(ConformanceLookupKind::All,
4850-
nullptr, /*sorted=*/true)) {
4849+
auto nominal = dc->getAsNominalTypeOrNominalTypeExtensionContext();
4850+
if (!nominal) return result;
4851+
4852+
for (auto proto : nominal->getAllProtocols()) {
48514853
// We only care about Objective-C protocols.
4852-
auto proto = conformance->getProtocol();
48534854
if (!proto->isObjC()) continue;
48544855

48554856
for (auto req : proto->lookupDirect(name, true)) {
@@ -4858,16 +4859,61 @@ TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,
48584859

48594860
// Skip types.
48604861
if (isa<TypeDecl>(req)) continue;
4862+
4863+
// Dig out the conformance.
4864+
Optional<ProtocolConformance *> conformance;
4865+
if (!conformance.hasValue()) {
4866+
SmallVector<ProtocolConformance *, 2> conformances;
4867+
nominal->lookupConformance(dc->getParentModule(), proto,
4868+
conformances);
4869+
if (conformances.size() == 1)
4870+
conformance = conformances.front();
4871+
else
4872+
conformance = nullptr;
4873+
}
4874+
if (!*conformance) continue;
48614875

48624876
// Determine whether the witness for this conformance is in fact
48634877
// our witness.
4864-
if (conformance->getWitness(req, this).getDecl() == witness) {
4878+
if ((*conformance)->getWitness(req, this).getDecl() == witness) {
48654879
result.push_back(req);
4866-
if (onlyFirstRequirement) return result;
4880+
if (anySingleRequirement) return result;
4881+
continue;
4882+
}
4883+
4884+
// If we have an inherited conformance, check whether the potential
4885+
// witness matches the requirement.
4886+
// FIXME: for now, don't even try this with generics involved. We
4887+
// should be tracking how subclasses implement optional requirements,
4888+
// in which case the getWitness() check above would suffice.
4889+
if (req->getAttrs().hasAttribute<OptionalAttr>() &&
4890+
isa<InheritedProtocolConformance>(*conformance)) {
4891+
auto normal = (*conformance)->getRootNormalConformance();
4892+
if (!(*conformance)->getDeclContext()->getGenericSignatureOfContext() &&
4893+
!normal->getDeclContext()->getGenericSignatureOfContext() &&
4894+
matchWitness(*this, proto, *conformance, witness->getDeclContext(),
4895+
req, const_cast<ValueDecl *>(witness)).Kind
4896+
== MatchKind::ExactMatch) {
4897+
result.push_back(req);
4898+
if (anySingleRequirement) return result;
4899+
continue;
4900+
}
48674901
}
48684902
}
48694903
}
48704904

4905+
// Sort the results.
4906+
if (result.size() > 2) {
4907+
std::stable_sort(result.begin(), result.end(),
4908+
[&](ValueDecl *lhs, ValueDecl *rhs) {
4909+
ProtocolDecl *lhsProto
4910+
= cast<ProtocolDecl>(lhs->getDeclContext());
4911+
ProtocolDecl *rhsProto
4912+
= cast<ProtocolDecl>(rhs->getDeclContext());
4913+
return ProtocolType::compareProtocols(&lhsProto,
4914+
&rhsProto) < 0;
4915+
});
4916+
}
48714917
return result;
48724918
}
48734919

lib/Sema/TypeCheckType.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2706,8 +2706,7 @@ static void describeObjCReason(TypeChecker &TC, const ValueDecl *VD,
27062706
TC.diagnose(overridden, diag::objc_overriding_objc_decl,
27072707
kind, VD->getOverriddenDecl()->getFullName());
27082708
} else if (Reason == ObjCReason::WitnessToObjC) {
2709-
auto requirement =
2710-
TC.findWitnessedObjCRequirements(VD, /*onlyFirst=*/true).front();
2709+
auto requirement = TC.findWitnessedObjCRequirements(VD).front();
27112710
TC.diagnose(requirement, diag::objc_witness_objc_requirement,
27122711
VD->getDescriptiveKind(), requirement->getFullName(),
27132712
cast<ProtocolDecl>(requirement->getDeclContext())

lib/Sema/TypeChecker.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,14 +1584,14 @@ class TypeChecker final : public LazyResolver {
15841584
/// Find the @objc requirement that are witnessed by the given
15851585
/// declaration.
15861586
///
1587-
/// \param onlyFirstRequirement If true, only returns the first such
1588-
/// requirement, rather than all of them.
1587+
/// \param anySingleRequirement If true, returns at most a single requirement,
1588+
/// which might be any of the requirements that match.
15891589
///
15901590
/// \returns the set of requirements to which the given witness is a
15911591
/// witness.
15921592
llvm::TinyPtrVector<ValueDecl *> findWitnessedObjCRequirements(
15931593
const ValueDecl *witness,
1594-
bool onlyFirstRequirement);
1594+
bool anySingleRequirement = false);
15951595

15961596
/// Mark any _ObjectiveCBridgeable conformances in the given type as "used".
15971597
void useObjectiveCBridgeableConformances(DeclContext *dc, Type type);

test/SourceKit/DocSupport/doc_clang_module.swift.response

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5068,6 +5068,11 @@ var FooSubUnnamedEnumeratorA1: Int { get }
50685068
key.name: "init(rawValue:)",
50695069
key.usr: "s:FPs9OptionSetcFT8rawValuewx8RawValue_x"
50705070
},
5071+
{
5072+
key.kind: source.lang.swift.ref.function.constructor,
5073+
key.name: "init(rawValue:)",
5074+
key.usr: "s:FPs9OptionSetcFT8rawValuewx8RawValue_x"
5075+
},
50715076
{
50725077
key.kind: source.lang.swift.ref.function.constructor,
50735078
key.name: "init(rawValue:)",

test/attr/attr_objc.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,3 +2084,63 @@ func ==(lhs: ObjC_Class1, rhs: ObjC_Class1) -> Bool {
20842084
@objc protocol OperatorInProtocol {
20852085
static func +(lhs: Self, rhs: Self) -> Self // expected-error {{@objc protocols may not have operator requirements}}
20862086
}
2087+
2088+
//===--- @objc inference for witnesses
2089+
2090+
@objc protocol InferFromProtocol {
2091+
@objc(inferFromProtoMethod1:)
2092+
optional func method1(value: Int)
2093+
}
2094+
2095+
// Infer when in the same declaration context.
2096+
// CHECK-LABEL: ClassInfersFromProtocol1
2097+
class ClassInfersFromProtocol1 : InferFromProtocol{
2098+
// CHECK: {{^}} @objc func method1(value: Int)
2099+
func method1(value: Int) { }
2100+
}
2101+
2102+
// Infer when in a different declaration context of the same class.
2103+
// CHECK-LABEL: ClassInfersFromProtocol2a
2104+
class ClassInfersFromProtocol2a {
2105+
// CHECK: {{^}} @objc func method1(value: Int)
2106+
func method1(value: Int) { }
2107+
}
2108+
2109+
extension ClassInfersFromProtocol2a : InferFromProtocol { }
2110+
2111+
// Infer when in a different declaration context of the same class.
2112+
class ClassInfersFromProtocol2b : InferFromProtocol { }
2113+
2114+
// CHECK-LABEL: ClassInfersFromProtocol2b
2115+
extension ClassInfersFromProtocol2b {
2116+
// CHECK: {{^}} @objc dynamic func method1(value: Int)
2117+
func method1(value: Int) { }
2118+
}
2119+
2120+
// Don't infer when there is a signature mismatch.
2121+
// CHECK-LABEL: ClassInfersFromProtocol3
2122+
class ClassInfersFromProtocol3 : InferFromProtocol {
2123+
}
2124+
2125+
extension ClassInfersFromProtocol3 {
2126+
// CHECK: {{^}} func method1(value: String)
2127+
func method1(value: String) { }
2128+
}
2129+
2130+
class SuperclassImplementsProtocol : InferFromProtocol { }
2131+
2132+
// Note: no inference for subclasses
2133+
class SubclassInfersFromProtocol1 : SuperclassImplementsProtocol {
2134+
// CHECK: {{^}} @objc func method1(value: Int)
2135+
func method1(value: Int) { }
2136+
}
2137+
2138+
// Note: no inference for subclasses
2139+
class SubclassInfersFromProtocol2 : SuperclassImplementsProtocol {
2140+
}
2141+
2142+
extension SubclassInfersFromProtocol2 {
2143+
// CHECK: {{^}} @objc dynamic func method1(value: Int)
2144+
func method1(value: Int) { }
2145+
}
2146+

test/decl/protocol/req/optional.swift

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
@objc class ObjCClass { }
77

88
@objc protocol P1 {
9-
@objc optional func method(_ x: Int) // expected-note 2{{requirement 'method' declared here}}
9+
@objc optional func method(_ x: Int) // expected-note {{requirement 'method' declared here}}
1010

11-
@objc optional var prop: Int { get } // expected-note{{requirement 'prop' declared here}}
11+
@objc optional var prop: Int { get }
1212

13-
@objc optional subscript (i: Int) -> ObjCClass? { get } // expected-note{{requirement 'subscript' declared here}}
13+
@objc optional subscript (i: Int) -> ObjCClass? { get }
1414
}
1515

1616
@objc protocol P2 {
@@ -39,10 +39,6 @@ class C2 : P1 {
3939
}
4040
}
4141

42-
// -----------------------------------------------------------------------
43-
// "Near" matches.
44-
// -----------------------------------------------------------------------
45-
4642
class C3 : P1 {
4743
func method(_ x: Int) { }
4844

@@ -71,28 +67,26 @@ extension C4 : P1 {
7167
}
7268
}
7369

70+
// -----------------------------------------------------------------------
71+
// Okay to match via extensions.
72+
// -----------------------------------------------------------------------
73+
7474
class C5 : P1 { }
7575

7676
extension C5 {
7777
func method(_ x: Int) { }
78-
// expected-warning@-1{{non-'@objc' method 'method' does not satisfy optional requirement of '@objc' protocol 'P1'}}{{3-3=@objc }}
79-
// expected-note@-2{{add '@nonobjc' to silence this warning}}{{3-3=@nonobjc }}
8078

8179
var prop: Int { return 5 }
82-
// expected-warning@-1{{non-'@objc' property 'prop' does not satisfy optional requirement of '@objc' protocol 'P1'}}{{3-3=@objc }}
83-
// expected-note@-2{{add '@nonobjc' to silence this warning}}{{3-3=@nonobjc }}
8480

8581
subscript (i: Int) -> ObjCClass? {
86-
// expected-warning@-1{{non-'@objc' subscript does not satisfy optional requirement of '@objc' protocol 'P1'}}{{3-3=@objc }}
87-
// expected-note@-2{{add '@nonobjc' to silence this warning}}{{3-3=@nonobjc }}
8882
get {
8983
return nil
9084
}
9185
set {}
9286
}
9387
}
9488

95-
// Note: @nonobjc suppresses warnings
89+
// Note: @nonobjc suppresses witness match.
9690
class C6 { }
9791

9892
extension C6 : P1 {
@@ -108,6 +102,10 @@ extension C6 : P1 {
108102
}
109103
}
110104

105+
// -----------------------------------------------------------------------
106+
// "Near" matches.
107+
// -----------------------------------------------------------------------
108+
111109
// Note: warn about selector matches where the Swift names didn't match.
112110
@objc class C7 : P1 { // expected-note{{class 'C7' declares conformance to protocol 'P1' here}}
113111
@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'}}

0 commit comments

Comments
 (0)