Skip to content

Commit 9948a35

Browse files
authored
Merge pull request #4640 from DougGregor/infer-objc-extensions
[Type checker] Infer @objc for protocol conformances in other extensions
2 parents a7fef22 + 7e2efe5 commit 9948a35

File tree

7 files changed

+115
-28
lines changed

7 files changed

+115
-28
lines changed

lib/Sema/TypeCheckDecl.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,7 +2069,7 @@ static Optional<ObjCReason> shouldMarkAsObjC(TypeChecker &TC,
20692069
// A witness to an @objc protocol requirement is implicitly @objc.
20702070
else if (!TC.findWitnessedObjCRequirements(
20712071
VD,
2072-
/*onlyFirstRequirement=*/true).empty())
2072+
/*anySingleRequirement=*/true).empty())
20732073
return ObjCReason::WitnessToObjC;
20742074
else if (VD->isInvalid())
20752075
return None;
@@ -2280,8 +2280,7 @@ static void inferObjCName(TypeChecker &tc, ValueDecl *decl) {
22802280
// requirements for which this declaration is a witness.
22812281
Optional<ObjCSelector> requirementObjCName;
22822282
ValueDecl *firstReq = nullptr;
2283-
for (auto req : tc.findWitnessedObjCRequirements(decl,
2284-
/*onlyFirst=*/false)) {
2283+
for (auto req : tc.findWitnessedObjCRequirements(decl)) {
22852284
// If this is the first requirement, take its name.
22862285
if (!requirementObjCName) {
22872286
requirementObjCName = req->getObjCRuntimeName();

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4887,18 +4887,19 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
48874887

48884888
llvm::TinyPtrVector<ValueDecl *>
48894889
TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,
4890-
bool onlyFirstRequirement) {
4890+
bool anySingleRequirement) {
48914891
llvm::TinyPtrVector<ValueDecl *> result;
48924892

48934893
// Types don't infer @objc this way.
48944894
if (isa<TypeDecl>(witness)) return result;
48954895

48964896
auto dc = witness->getDeclContext();
48974897
auto name = witness->getFullName();
4898-
for (auto conformance : dc->getLocalConformances(ConformanceLookupKind::All,
4899-
nullptr, /*sorted=*/true)) {
4898+
auto nominal = dc->getAsNominalTypeOrNominalTypeExtensionContext();
4899+
if (!nominal) return result;
4900+
4901+
for (auto proto : nominal->getAllProtocols()) {
49004902
// We only care about Objective-C protocols.
4901-
auto proto = conformance->getProtocol();
49024903
if (!proto->isObjC()) continue;
49034904

49044905
for (auto req : proto->lookupDirect(name, true)) {
@@ -4907,16 +4908,41 @@ TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,
49074908

49084909
// Skip types.
49094910
if (isa<TypeDecl>(req)) continue;
4911+
4912+
// Dig out the conformance.
4913+
Optional<ProtocolConformance *> conformance;
4914+
if (!conformance.hasValue()) {
4915+
SmallVector<ProtocolConformance *, 2> conformances;
4916+
nominal->lookupConformance(dc->getParentModule(), proto,
4917+
conformances);
4918+
if (conformances.size() == 1)
4919+
conformance = conformances.front();
4920+
else
4921+
conformance = nullptr;
4922+
}
4923+
if (!*conformance) continue;
49104924

49114925
// Determine whether the witness for this conformance is in fact
49124926
// our witness.
4913-
if (conformance->getWitness(req, this).getDecl() == witness) {
4927+
if ((*conformance)->getWitness(req, this).getDecl() == witness) {
49144928
result.push_back(req);
4915-
if (onlyFirstRequirement) return result;
4929+
if (anySingleRequirement) return result;
49164930
}
49174931
}
49184932
}
49194933

4934+
// Sort the results.
4935+
if (result.size() > 2) {
4936+
std::stable_sort(result.begin(), result.end(),
4937+
[&](ValueDecl *lhs, ValueDecl *rhs) {
4938+
ProtocolDecl *lhsProto
4939+
= cast<ProtocolDecl>(lhs->getDeclContext());
4940+
ProtocolDecl *rhsProto
4941+
= cast<ProtocolDecl>(rhs->getDeclContext());
4942+
return ProtocolType::compareProtocols(&lhsProto,
4943+
&rhsProto) < 0;
4944+
});
4945+
}
49204946
return result;
49214947
}
49224948

lib/Sema/TypeCheckType.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2712,8 +2712,7 @@ static void describeObjCReason(TypeChecker &TC, const ValueDecl *VD,
27122712
TC.diagnose(overridden, diag::objc_overriding_objc_decl,
27132713
kind, VD->getOverriddenDecl()->getFullName());
27142714
} else if (Reason == ObjCReason::WitnessToObjC) {
2715-
auto requirement =
2716-
TC.findWitnessedObjCRequirements(VD, /*onlyFirst=*/true).front();
2715+
auto requirement = TC.findWitnessedObjCRequirements(VD).front();
27172716
TC.diagnose(requirement, diag::objc_witness_objc_requirement,
27182717
VD->getDescriptiveKind(), requirement->getFullName(),
27192718
cast<ProtocolDecl>(requirement->getDeclContext())

lib/Sema/TypeChecker.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,14 +1592,14 @@ class TypeChecker final : public LazyResolver {
15921592
/// Find the @objc requirement that are witnessed by the given
15931593
/// declaration.
15941594
///
1595-
/// \param onlyFirstRequirement If true, only returns the first such
1596-
/// requirement, rather than all of them.
1595+
/// \param anySingleRequirement If true, returns at most a single requirement,
1596+
/// which might be any of the requirements that match.
15971597
///
15981598
/// \returns the set of requirements to which the given witness is a
15991599
/// witness.
16001600
llvm::TinyPtrVector<ValueDecl *> findWitnessedObjCRequirements(
16011601
const ValueDecl *witness,
1602-
bool onlyFirstRequirement);
1602+
bool anySingleRequirement = false);
16031603

16041604
/// Mark any _ObjectiveCBridgeable conformances in the given type as "used".
16051605
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
@@ -5073,6 +5073,11 @@ var FooSubUnnamedEnumeratorA1: Int { get }
50735073
key.name: "init(rawValue:)",
50745074
key.usr: "s:FPs9OptionSetcFT8rawValuewx8RawValue_x"
50755075
},
5076+
{
5077+
key.kind: source.lang.swift.ref.function.constructor,
5078+
key.name: "init(rawValue:)",
5079+
key.usr: "s:FPs9OptionSetcFT8rawValuewx8RawValue_x"
5080+
},
50765081
{
50775082
key.kind: source.lang.swift.ref.function.constructor,
50785083
key.name: "init(rawValue:)",

test/attr/attr_objc.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,3 +2117,63 @@ func ==(lhs: ObjC_Class1, rhs: ObjC_Class1) -> Bool {
21172117
@objc protocol OperatorInProtocol {
21182118
static func +(lhs: Self, rhs: Self) -> Self // expected-error {{@objc protocols may not have operator requirements}}
21192119
}
2120+
2121+
//===--- @objc inference for witnesses
2122+
2123+
@objc protocol InferFromProtocol {
2124+
@objc(inferFromProtoMethod1:)
2125+
optional func method1(value: Int)
2126+
}
2127+
2128+
// Infer when in the same declaration context.
2129+
// CHECK-LABEL: ClassInfersFromProtocol1
2130+
class ClassInfersFromProtocol1 : InferFromProtocol{
2131+
// CHECK: {{^}} @objc func method1(value: Int)
2132+
func method1(value: Int) { }
2133+
}
2134+
2135+
// Infer when in a different declaration context of the same class.
2136+
// CHECK-LABEL: ClassInfersFromProtocol2a
2137+
class ClassInfersFromProtocol2a {
2138+
// CHECK: {{^}} @objc func method1(value: Int)
2139+
func method1(value: Int) { }
2140+
}
2141+
2142+
extension ClassInfersFromProtocol2a : InferFromProtocol { }
2143+
2144+
// Infer when in a different declaration context of the same class.
2145+
class ClassInfersFromProtocol2b : InferFromProtocol { }
2146+
2147+
// CHECK-LABEL: ClassInfersFromProtocol2b
2148+
extension ClassInfersFromProtocol2b {
2149+
// CHECK: {{^}} @objc dynamic func method1(value: Int)
2150+
func method1(value: Int) { }
2151+
}
2152+
2153+
// Don't infer when there is a signature mismatch.
2154+
// CHECK-LABEL: ClassInfersFromProtocol3
2155+
class ClassInfersFromProtocol3 : InferFromProtocol {
2156+
}
2157+
2158+
extension ClassInfersFromProtocol3 {
2159+
// CHECK: {{^}} func method1(value: String)
2160+
func method1(value: String) { }
2161+
}
2162+
2163+
class SuperclassImplementsProtocol : InferFromProtocol { }
2164+
2165+
// Note: no inference for subclasses
2166+
class SubclassInfersFromProtocol1 : SuperclassImplementsProtocol {
2167+
// CHECK: {{^}} func method1(value: Int)
2168+
func method1(value: Int) { }
2169+
}
2170+
2171+
// Note: no inference for subclasses
2172+
class SubclassInfersFromProtocol2 : SuperclassImplementsProtocol {
2173+
}
2174+
2175+
extension SubclassInfersFromProtocol2 {
2176+
// CHECK: {{^}} func method1(value: Int)
2177+
func method1(value: Int) { }
2178+
}
2179+

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)